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
text/typescript
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
}