diff --git a/server.ts b/server.ts index 546c0df..b93e574 100644 --- a/server.ts +++ b/server.ts @@ -36,10 +36,6 @@ type Handler = { default: { fetch: (req: Request) => Promise } }; const { default: handler } = (await import(SERVER_ENTRY.href)) as Handler; -if (!process.env.ADMIN_PANEL_METRICS_SECRET) { - console.warn('[metrics] ADMIN_PANEL_METRICS_SECRET is not set — /metrics will return 401 for all requests'); -} - async function buildStaticRoutes(): Promise Response>> { const routes: Record Response> = {}; for await (const path of new Glob('**/*').scan(CLIENT_DIR)) { @@ -51,7 +47,7 @@ async function buildStaticRoutes(): Promise Response>> { return routes; } -Bun.serve({ +const server = Bun.serve({ port: Number(process.env.PORT ?? 3000), routes: { ...(await buildStaticRoutes()), @@ -72,4 +68,8 @@ Bun.serve({ }, }); -console.log(`Admin panel listening on http://localhost:${process.env.PORT ?? 3000}`); +console.log(`Admin panel listening on http://localhost:${server.port}`); + +if (!process.env.ADMIN_PANEL_METRICS_SECRET) { + console.warn('[metrics] ADMIN_PANEL_METRICS_SECRET is not set — /metrics will return 401 for all requests'); +} diff --git a/src/server/auth.ts b/src/server/auth.ts index c05ae44..9c66005 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -21,6 +21,26 @@ function extractCookieValue(response: Response, name: string): string | undefine return undefined; } +function getRequestOrigin(): string | undefined { + const origin = getRequestHeader('origin'); + if (origin) return origin; + + const referer = getRequestHeader('referer'); + if (referer) { + try { + return new URL(referer).origin; + } catch { + return undefined; + } + } + + const host = getRequestHeader('host'); + if (!host) return undefined; + + const proto = getRequestHeader('x-forwarded-proto') ?? 'http'; + return `${proto}://${host}`; +} + export const adminLoginFn = createServerFn({ method: 'POST' }) .inputValidator( z.object({ @@ -303,12 +323,17 @@ export const openIdCheckOptions = queryOptions({ }); export const checkOpenIdFn = createServerFn({ method: 'GET' }).handler(async () => { + const checkUrl = `${getServerApiUrl()}/api/admin/oauth/openid/check`; try { - const response = await fetch(`${getServerApiUrl()}/api/admin/oauth/openid/check`); - if (!response.ok) return { available: false, ssoOnly: false }; + const response = await fetch(checkUrl); + if (!response.ok) { + console.warn('[checkOpenIdFn] OpenID check failed:', response.status, checkUrl); + return { available: false, ssoOnly: false }; + } const ssoOnly = process.env.ADMIN_SSO_ONLY === 'true'; return { available: true, ssoOnly }; - } catch { + } catch (error) { + console.warn('[checkOpenIdFn] OpenID check request failed:', checkUrl, error); return { available: false, ssoOnly: false }; } }); @@ -317,11 +342,12 @@ export const openidLoginFn = createServerFn({ method: 'GET' }).handler(async () try { const baseUrl = getApiBaseUrl(); const authUrl = new URL(`${baseUrl}/api/admin/oauth/openid`); + const requestOrigin = getRequestOrigin(); - /** Generate PKCE code_verifier and store in session */ const codeVerifier = crypto.randomBytes(32).toString('hex'); const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('hex'); authUrl.searchParams.set('code_challenge', codeChallenge); + if (requestOrigin) authUrl.searchParams.set('redirect_uri', `${requestOrigin}/auth/openid/callback`); const session = await useAppSession(); await session.update({ codeVerifier }); @@ -340,16 +366,9 @@ export const oauthExchangeFn = createServerFn({ method: 'POST' }) .handler(async ({ data }) => { try { const headers: Record = { 'Content-Type': 'application/json' }; - const rawOrigin = getRequestHeader('origin') || getRequestHeader('referer'); - if (rawOrigin) { - try { - headers['Origin'] = new URL(rawOrigin).origin; - } catch { - // malformed URL – skip forwarding - } - } + const requestOrigin = getRequestOrigin(); + if (requestOrigin) headers['Origin'] = requestOrigin; - /** Read PKCE code_verifier from session (stored during openidLoginFn) */ const session = await useAppSession(); const { codeVerifier } = session.data;