From 9253b9b22017cff33311f9e933dab7609622ed76 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Tue, 24 Feb 2026 19:54:51 -0600 Subject: [PATCH] fix(auth): prevent login page freeze on OAuth sign-in failure The login page froze after clicking "Continue with Authentik" because signIn.oauth2() resolves (not rejects) on server 500, leaving the button stuck in "Connecting..." state permanently. - Handle the .then() case to detect error/missing redirect URL and reset loading state with a user-facing error message - Log BetterAuth silent 500 responses that bypass NestJS error handling - Enable BetterAuth error-level logger for diagnostic visibility Co-Authored-By: Claude Opus 4.6 --- apps/api/src/auth/auth.config.ts | 4 ++++ apps/api/src/auth/auth.controller.ts | 8 +++++++ apps/web/src/app/(auth)/login/page.tsx | 31 +++++++++++++++++++++----- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/apps/api/src/auth/auth.config.ts b/apps/api/src/auth/auth.config.ts index d1c1554..ad6026f 100644 --- a/apps/api/src/auth/auth.config.ts +++ b/apps/api/src/auth/auth.config.ts @@ -254,6 +254,10 @@ export function createAuth(prisma: PrismaClient) { enabled: true, }, plugins: [...getOidcPlugins()], + logger: { + disabled: false, + level: "error", + }, session: { expiresIn: 60 * 60 * 24 * 7, // 7 days absolute max updateAge: 60 * 60 * 2, // 2 hours — minimum session age before BetterAuth refreshes the expiry on next request diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index 573dda5..b8802f7 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -123,6 +123,14 @@ export class AuthController { try { await handler(req, res); + + // BetterAuth writes responses directly — catch silent 500s that bypass NestJS error handling + if (res.statusCode >= 500) { + this.logger.error( + `BetterAuth returned ${String(res.statusCode)} for ${req.method} ${req.url} from ${clientIp}` + + ` — check container stdout for '# SERVER_ERROR' details` + ); + } } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error); const stack = error instanceof Error ? error.stack : undefined; diff --git a/apps/web/src/app/(auth)/login/page.tsx b/apps/web/src/app/(auth)/login/page.tsx index 388c5e1..d5c09e5 100644 --- a/apps/web/src/app/(auth)/login/page.tsx +++ b/apps/web/src/app/(auth)/login/page.tsx @@ -128,12 +128,31 @@ function LoginPageContent(): ReactElement { setError(null); const callbackURL = typeof window !== "undefined" ? new URL("/", window.location.origin).toString() : "/"; - signIn.oauth2({ providerId, callbackURL }).catch((err: unknown) => { - const message = err instanceof Error ? err.message : String(err); - console.error(`[Auth] OAuth sign-in initiation failed for ${providerId}:`, message); - setError("Unable to connect to the sign-in provider. Please try again in a moment."); - setOauthLoading(null); - }); + signIn + .oauth2({ providerId, callbackURL }) + .then((result) => { + // BetterAuth returns Data | Error union — check for error or missing redirect URL + const hasError = "error" in result && result.error; + const hasUrl = "data" in result && result.data?.url; + if (hasError || !hasUrl) { + const errObj = hasError ? result.error : null; + const message = + errObj && typeof errObj === "object" && "message" in errObj + ? String(errObj.message) + : "no redirect URL"; + console.error(`[Auth] OAuth sign-in failed for ${providerId}:`, message); + setError("Unable to connect to the sign-in provider. Please try again in a moment."); + setOauthLoading(null); + } + // If data.url exists, BetterAuth's client will redirect the browser automatically. + // No need to reset loading — the page is navigating away. + }) + .catch((err: unknown) => { + const message = err instanceof Error ? err.message : String(err); + console.error(`[Auth] OAuth sign-in initiation failed for ${providerId}:`, message); + setError("Unable to connect to the sign-in provider. Please try again in a moment."); + setOauthLoading(null); + }); }, []); const handleCredentialsLogin = useCallback(