UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

180 lines (149 loc) • 5.18 kB
import {type CliCommandContext, type CliPrompter} from '@sanity/cli' import logSymbols from 'log-symbols' import oneline from 'oneline' import url from 'url' const wildcardReplacement = 'a-wild-card-r3pl4c3m3n7-a' const portReplacement = ':7777777' interface AddCorsOriginFlags { credentials?: boolean } export async function addCorsOrigin( givenOrigin: string, flags: AddCorsOriginFlags, context: CliCommandContext, ): Promise<boolean> { const {apiClient, prompt, output} = context const origin = await (givenOrigin ? filterAndValidateOrigin(givenOrigin) : promptForOrigin(prompt)) const hasWildcard = origin.includes('*') if (hasWildcard && !(await promptForWildcardConfirmation(origin, context))) { return false } const allowCredentials = typeof flags.credentials === 'undefined' ? await promptForCredentials(hasWildcard, context) : Boolean(flags.credentials) if (givenOrigin !== origin) { output.print(`Normalized origin to ${origin}`) } const client = apiClient({ requireUser: true, requireProject: true, }) await client.request({ method: 'POST', url: '/cors', body: {origin, allowCredentials}, maxRedirects: 0, }) return true } function promptForCredentials(hasWildcard: boolean, context: CliCommandContext): Promise<string> { const {prompt, output, chalk} = context output.print('') if (hasWildcard) { output.print(oneline` ${chalk.yellow(`${logSymbols.warning} Warning:`)} We ${chalk.red(chalk.underline('HIGHLY'))} recommend NOT allowing credentials on origins containing wildcards. If you are logged in to a studio, people will be able to send requests ${chalk.underline('on your behalf')} to read and modify data, from any matching origin. Please tread carefully! `) } else { output.print(oneline` ${chalk.yellow(`${logSymbols.warning} Warning:`)} Should this origin be allowed to send requests using authentication tokens or session cookies? Be aware that any script on this origin will be able to send requests ${chalk.underline('on your behalf')} to read and modify data if you are logged in to a Sanity studio. If this origin hosts a studio, you will need this, otherwise you should probably answer "No" (n). `) } output.print('') return prompt.single({ type: 'confirm', message: oneline` Allow credentials to be sent from this origin? Please read the warning above. `, default: false, }) } function promptForWildcardConfirmation( origin: string, context: CliCommandContext, ): Promise<boolean> { const {prompt, output, chalk} = context output.print('') output.print(chalk.yellow(`${logSymbols.warning} Warning: Examples of allowed origins:`)) if (origin === '*') { output.print('- http://www.some-malicious.site') output.print('- https://not.what-you-were-expecting.com') output.print('- https://high-traffic-site.com') output.print('- http://192.168.1.1:8080') } else { output.print(`- ${origin.replace(/:\*/, ':1234').replace(/\*/g, 'foo')}`) output.print(`- ${origin.replace(/:\*/, ':3030').replace(/\*/g, 'foo.bar')}`) } output.print('') return prompt.single({ type: 'confirm', message: oneline` Using wildcards can be ${chalk.red('risky')}. Are you ${chalk.underline('absolutely sure')} you want to allow this origin?`, default: false, }) } function promptForOrigin(prompt: CliPrompter): Promise<string> { return prompt.single({ type: 'input', message: 'Origin (including protocol):', filter: filterOrigin, validate: (origin) => validateOrigin(origin, origin), }) } function filterOrigin(origin: string): string | null { if (origin === '*' || origin === 'file:///*' || origin === 'null') { return origin } try { const example = origin .replace(/([^:])\*/g, `$1${wildcardReplacement}`) .replace(/:\*/, portReplacement) const parsed = url.parse(example) let host = parsed.host || '' if (/^https?:$/.test(parsed.protocol || '')) { host = host.replace(/:(80|443)$/, '') } host = host.replace(portReplacement, ':*').replace(new RegExp(wildcardReplacement, 'g'), '*') return `${parsed.protocol}//${host}` } catch (err) { return null } } function validateOrigin(origin: string | null, givenOrigin: string): true | string { if (origin === '*' || origin === 'file:///*' || origin === 'null') { return true } try { url.parse(origin || (0 as any as string)) // Use 0 to trigger error for unset values return true } catch (err) { // Fall-through to error } if (/^file:\/\//.test(givenOrigin)) { return `Only a local file wildcard is currently allowed: file:///*` } return `Invalid origin "${givenOrigin}", must include protocol (https://some.host)` } function filterAndValidateOrigin(givenOrigin: string): string { const origin = filterOrigin(givenOrigin) const result = validateOrigin(origin, givenOrigin) if (result !== true) { throw new Error(result) } if (!origin) { throw new Error('Invalid origin') } return origin }