From f2de0e38928cacf0b42c0bf2e250b42fa02d6e97 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 24 Aug 2026 04:02:37 +0000 Subject: [PATCH] Fix missing OIDC button: stop static-prerendering /login /login was prerendered at Docker build time when AUTH_OIDC_* were unset, baking oidcEnabled=false into the page. Force dynamic render and also read /api/v1/auth/config on the client as a fallback. --- src/app/login/page.tsx | 4 ++++ src/components/LoginPageClient.tsx | 31 ++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index c467924..1f5b82b 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,6 +1,10 @@ import { isOidcConfigured } from "@/lib/auth/oidc"; import { LoginPageClient } from "@/components/LoginPageClient"; +// OIDC env is only available at runtime (Docker). Never prerender this page +// at build time or the SSO button stays permanently hidden. +export const dynamic = "force-dynamic"; + export default function LoginPage() { const authUrl = (process.env.AUTH_URL || "").replace(/\/$/, ""); const callbackUrlHint = authUrl diff --git a/src/components/LoginPageClient.tsx b/src/components/LoginPageClient.tsx index 8679d0f..68e28f4 100644 --- a/src/components/LoginPageClient.tsx +++ b/src/components/LoginPageClient.tsx @@ -1,6 +1,6 @@ "use client"; -import { FormEvent, useState } from "react"; +import { FormEvent, useEffect, useState } from "react"; import { signIn } from "next-auth/react"; import { useRouter, useSearchParams } from "next/navigation"; import { Suspense } from "react"; @@ -11,6 +11,12 @@ type Props = { callbackUrlHint: string; }; +type AuthConfig = { + oidcEnabled: boolean; + oidcName: string; + callbackUrl: string; +}; + function authErrorMessage(code: string | null): string | null { if (!code) return null; switch (code) { @@ -40,6 +46,23 @@ function LoginForm({ oidcEnabled, oidcName, callbackUrlHint }: Props) { authErrorMessage(params.get("error")) ); const [loading, setLoading] = useState(false); + const [runtimeOidc, setRuntimeOidc] = useState(null); + + // Belt-and-suspenders: ask the live API so a stale static shell can't hide SSO + useEffect(() => { + fetch("/api/v1/auth/config") + .then((r) => (r.ok ? r.json() : null)) + .then((data: AuthConfig | null) => { + if (data) setRuntimeOidc(data); + }) + .catch(() => { + /* ignore */ + }); + }, []); + + const showOidc = runtimeOidc?.oidcEnabled ?? oidcEnabled; + const displayName = runtimeOidc?.oidcName || oidcName; + const redirectHint = runtimeOidc?.callbackUrl || callbackUrlHint; async function onSubmit(e: FormEvent) { e.preventDefault(); @@ -97,19 +120,19 @@ function LoginForm({ oidcEnabled, oidcName, callbackUrlHint }: Props) { - {oidcEnabled && ( + {showOidc && ( <>

IdP redirect URI must be exactly:
- {callbackUrlHint} + {redirectHint}

)}