From 085c8b9cfa03340288379b32fae44202f442dac5 Mon Sep 17 00:00:00 2001 From: Paperclip Date: Sat, 11 Apr 2026 18:01:59 +0000 Subject: [PATCH 1/4] fix(GRO-545): switch OAuth state to cookie storage and add login error display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OAuth callback was failing with "please_restart_the_process" because Better-Auth's default DB-backed state (verification table) was unreliable — the UAT hourly reset wipes all tables including verification records. Switch to cookie-based state storage so the encrypted state survives in the browser cookie across the redirect flow. Also removes explicit redirectURI from socialProviders (Better-Auth derives it from baseURL) and adds visible error feedback on the login page when OAuth callbacks fail. Co-Authored-By: Paperclip --- apps/api/src/lib/auth.ts | 7 +++---- apps/web/src/App.tsx | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/api/src/lib/auth.ts b/apps/api/src/lib/auth.ts index f77dcbf..fc9f2e2 100644 --- a/apps/api/src/lib/auth.ts +++ b/apps/api/src/lib/auth.ts @@ -170,8 +170,6 @@ export async function initAuth(): Promise { const hasGoogle = !!(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET); const hasGitHub = !!(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET); - const callbackBase = `${BETTER_AUTH_URL}/api/auth/callback`; - // Build Better-Auth instance using resolved config authInstance = betterAuth({ database: drizzleAdapter(db, { @@ -179,6 +177,9 @@ export async function initAuth(): Promise { }), secret: BETTER_AUTH_SECRET, baseURL: BETTER_AUTH_URL, + account: { + storeStateStrategy: "cookie" as const, + }, plugins: [ genericOAuth({ config: [ @@ -205,14 +206,12 @@ export async function initAuth(): Promise { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, - redirectURI: `${callbackBase}/google`, }, } : {}), ...(hasGitHub ? { github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, - redirectURI: `${callbackBase}/github`, }, } : {}), }, diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index efaefd3..bf34c03 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -23,17 +23,26 @@ import { useSession, signIn } from "./lib/auth-client.js"; function LoginPage() { const [isLoading, setIsLoading] = useState(false); const [providers, setProviders] = useState([]); + const [error, setError] = useState(null); useEffect(() => { fetch("/api/auth/providers") .then((r) => r.json()) .then((data) => setProviders(data.providers ?? [])) .catch(() => setProviders([])); + const params = new URLSearchParams(window.location.search); + const authError = params.get("error"); + if (authError) setError(authError.replace(/_/g, " ")); }, []); const handleSocialLogin = async (provider: string) => { setIsLoading(true); - await signIn.social({ provider, callbackURL: window.location.origin }); + setError(null); + const result = await signIn.social({ provider, callbackURL: window.location.origin }); + if (result?.error) { + setError(result.error.message ?? "Sign-in failed"); + setIsLoading(false); + } }; const isGoogle = providers.includes("google"); @@ -65,6 +74,11 @@ function LoginPage() {

Sign in to continue

+ {error && ( +
+ {error} +
+ )} {isGoogle && (