diff --git a/frontend/src/lib/components/instanceSettings.ts b/frontend/src/lib/components/instanceSettings.ts index 3c60ac990d..d0528ec640 100644 --- a/frontend/src/lib/components/instanceSettings.ts +++ b/frontend/src/lib/components/instanceSettings.ts @@ -1,4 +1,5 @@ import type { ButtonType } from './common/button/model' +import { allowedOriginsError, parseAllowedOriginsSetting } from './triggers/http/utils' import { z } from 'zod' import { writable } from 'svelte/store' @@ -270,6 +271,12 @@ export const settings: Record = { fieldType: 'text', placeholder: 'https://app.example.com, https://admin.example.com', storage: 'setting', + error: + 'Each origin must be visible ASCII with no comma, and there can be at most 100 of them. null is not allowed, since every sandboxed iframe sends it.', + // The same check the API applies, so a value it would refuse cannot be + // saved here and then silently drop to no restriction at the next boot. + isValid: (value: string | undefined) => + allowedOriginsError(parseAllowedOriginsSetting(value)) === undefined, ee_only: '', hideInQuickSetup: true }, diff --git a/frontend/src/lib/components/triggers/http/utils.ts b/frontend/src/lib/components/triggers/http/utils.ts index 5fba39afb3..2c94abd333 100644 --- a/frontend/src/lib/components/triggers/http/utils.ts +++ b/frontend/src/lib/components/triggers/http/utils.ts @@ -66,12 +66,22 @@ export function allowedOriginsError(allowed_origins: string[] | undefined): stri export function allowedOriginWarning(origin: string): string | undefined { if (origin === '*' || allowedOriginRejection(origin) !== undefined) return undefined const separator = origin.indexOf('://') - if (separator < 0) return `'${origin}' has no scheme, such as https://` + if (separator <= 0) return `'${origin}' has no scheme, such as https://` const rest = origin.slice(separator + 3) if (rest === '') return `'${origin}' has no host` if (/[/?#]/.test(rest)) return `'${origin}' should be scheme://host[:port], with no path, query or fragment` if (rest.includes('@')) return `'${origin}' should not contain userinfo` + // Only the port is checked past this point. The host is left alone on + // purpose: browsers send origins this cannot anticipate, `chrome-extension` + // and IPv6 literals among them, and a warning that cries wolf on a working + // origin is worse than one that stays quiet. + const port = rest.startsWith('[') + ? rest.slice(rest.indexOf(']') + 1).replace(/^:/, '') + : rest.split(':')[1] + if (rest.startsWith(':')) return `'${origin}' has no host` + if (port !== undefined && port !== '' && !(/^[0-9]{1,5}$/.test(port) && Number(port) <= 65535)) + return `'${origin}' has a port no browser can send` return undefined }