import { getPublicAppOrigin } from '@/lib/public-app-url' type HeaderAccessor = { get?: (name: string) => string | null | undefined } type RequestLike = { headers?: HeaderAccessor nextUrl?: { origin?: string protocol?: string } url?: string } function getFirstHeaderValue(headersLike: HeaderAccessor | undefined, name: string): string { const raw = headersLike?.get?.(name) || '' return raw.split(',')[0].trim() } function normalizeProtocol(value: string | null | undefined): 'http' | 'https' | null { const normalized = String(value || '').trim().replace(/:$/, '').toLowerCase() if (normalized === 'http' || normalized === 'https') { return normalized } return null } function resolveForwardedProtocol(headersLike: HeaderAccessor | undefined): 'http' | 'https' | null { const forwardedProto = normalizeProtocol(getFirstHeaderValue(headersLike, 'x-forwarded-proto')) if (forwardedProto) { return forwardedProto } const forwardedSsl = getFirstHeaderValue(headersLike, 'x-forwarded-ssl').toLowerCase() if (forwardedSsl === 'on') { return 'https' } const forwardedPort = getFirstHeaderValue(headersLike, 'x-forwarded-port') if (forwardedPort === '443') { return 'https' } return null } export function normalizeHttpOrigin(value: unknown): string | null { const raw = String(value ?? '').trim() if (!raw) return null try { const parsed = new URL(raw) if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { return null } if (parsed.username || parsed.password || parsed.pathname !== '/' || parsed.search || parsed.hash) { return null } return parsed.origin } catch { return null } } export function getRequestHostOrigin(request: RequestLike): string | null { const host = getFirstHeaderValue(request.headers, 'host') || getFirstHeaderValue(request.headers, 'x-forwarded-host') if (!host) { return null } const protocol = resolveForwardedProtocol(request.headers) || normalizeProtocol(request.nextUrl?.protocol) || normalizeProtocol(request.url ? new URL(request.url).protocol : null) || 'https' return normalizeHttpOrigin(`${protocol}://${host}`) } export function getAllowedCorsOrigins(request: RequestLike): Set { const configuredOrigins = String(process.env.CORS_ALLOWED_ORIGINS || '') .split(',') .map((item) => normalizeHttpOrigin(item)) .filter((item): item is string => Boolean(item)) const values = [ getPublicAppOrigin(), getRequestHostOrigin(request), normalizeHttpOrigin(request.nextUrl?.origin), normalizeHttpOrigin(process.env.APP_PUBLIC_URL), normalizeHttpOrigin(process.env.NEXTAUTH_URL), ...configuredOrigins, ] return new Set(values.filter((item): item is string => Boolean(item))) } export function isAllowedCorsOrigin(request: RequestLike, requestOrigin: string | null): boolean { if (!requestOrigin) return true const normalizedRequestOrigin = normalizeHttpOrigin(requestOrigin) if (!normalizedRequestOrigin) return false return getAllowedCorsOrigins(request).has(normalizedRequestOrigin) }