Files
moeka-project/apps/ui-server-auth/src/modules/server-auth-context.ts
T
Lovehsigure_520 78ec4dd8ba fix(ui-server-auth): trust new Go backend origin (#2043)
## Summary
- trust the new Railway Go backend in the standalone Auth UI
- preserve exact-origin validation for untrusted redirects
- document the root cause, delivery plan, and production validation path

## Root cause
The Auth UI rejected
`api_server_url=https://airi-server-next.up.railway.app`, fell back to
the legacy API, and surfaced `Load failed` at the browser CORS boundary.

## Test plan
- focused bootstrap-context tests: 6/6
- complete Auth UI suite: 27/27
- `vue-tsc --noEmit`
- production Vite build
- emitted bundle contains the exact new backend origin
2026-07-10 15:03:10 +00:00

119 lines
3.2 KiB
TypeScript

import { SERVER_URL } from '@proj-airi/stage-ui/libs/server'
export interface ServerAuthBootstrapContext {
apiServerUrl: string
currentUrl: string
oidcCallback?: {
code: string
error: string
errorDescription: string
state: string
}
}
const SCRIPT_ID = 'airi-server-auth-context'
const API_SERVER_URL_QUERY_PARAM = 'api_server_url'
const TRUSTED_STANDALONE_API_SERVER_ORIGINS = [
'https://api.airi.build',
'https://airi-server-dev.up.railway.app',
'https://airi-server-next.up.railway.app',
]
const TRUSTED_HTTPS_API_SERVER_HOSTS = new Map(
TRUSTED_STANDALONE_API_SERVER_ORIGINS.map((origin) => {
const url = new URL(origin)
return [url.hostname, origin]
}),
)
const TRUSTED_LOCAL_API_SERVER_ORIGIN_PATTERNS = [
/^http:\/\/localhost(:\d+)?$/,
/^http:\/\/127\.0\.0\.1(:\d+)?$/,
/^https:\/\/localhost(:\d+)?$/,
/^https:\/\/127\.0\.0\.1(:\d+)?$/,
]
let cachedContext: ServerAuthBootstrapContext | null | undefined
export function getServerAuthBootstrapContext(): ServerAuthBootstrapContext | null {
if (cachedContext !== undefined)
return cachedContext
const element = document.getElementById(SCRIPT_ID)
if (!element) {
cachedContext = resolveStandaloneServerAuthContext(window.location.href, SERVER_URL)
return cachedContext
}
try {
const parsed = JSON.parse(element.textContent ?? '') as Partial<ServerAuthBootstrapContext>
cachedContext = {
apiServerUrl: parsed.apiServerUrl ?? SERVER_URL,
currentUrl: parsed.currentUrl ?? window.location.href,
oidcCallback: parsed.oidcCallback,
}
return cachedContext
}
catch {
cachedContext = resolveStandaloneServerAuthContext(window.location.href, SERVER_URL)
return cachedContext
}
}
/**
* Resolves API-server context carried by server redirects into static auth UI.
*
* Use when:
* - The standalone auth UI serves more than one AIRI environment from the same
* Pages deployment, such as production `accounts.airi.build` handling server-dev
* OIDC redirects.
*
* Expects:
* - The server-owned `/auth/*` redirect sets `api_server_url`.
* - Only known AIRI API origins and localhost development origins are accepted.
*
* Returns:
* - A bootstrap context using the trusted API origin, or null when no trusted
* override is present.
*/
export function resolveStandaloneServerAuthContext(currentUrl: string, fallbackApiServerUrl: string): ServerAuthBootstrapContext | null {
const url = new URL(currentUrl)
const apiServerUrl = normalizeTrustedApiServerUrl(
url.searchParams.get(API_SERVER_URL_QUERY_PARAM),
)
if (!apiServerUrl)
return null
return {
apiServerUrl: apiServerUrl ?? fallbackApiServerUrl,
currentUrl,
}
}
function normalizeTrustedApiServerUrl(value: string | null): string | null {
if (!value)
return null
try {
const url = new URL(value)
const normalizedHttpsOrigin = TRUSTED_HTTPS_API_SERVER_HOSTS.get(url.hostname)
if (normalizedHttpsOrigin)
return normalizedHttpsOrigin
const origin = url.origin
if (TRUSTED_STANDALONE_API_SERVER_ORIGINS.includes(origin))
return origin
if (TRUSTED_LOCAL_API_SERVER_ORIGIN_PATTERNS.some(pattern => pattern.test(origin)))
return origin
return null
}
catch {
return null
}
}