diff --git a/server/routers/external.ts b/server/routers/external.ts index 62ca15316..1dd156b58 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -1397,6 +1397,12 @@ authenticated.put( // Auth routes export const authRouter = Router(); unauthenticated.use("/auth", authRouter); + +// Register setup-check BEFORE the global auth rate limiter. +// This endpoint is called on every dashboard root page load (pure boolean +// read, no secrets) and must not consume the auth rate-limit budget. +authRouter.get("/initial-setup-complete", auth.initialSetupComplete); + authRouter.use( rateLimit({ windowMs: @@ -1701,7 +1707,6 @@ authRouter.post("/idp/:idpId/oidc/generate-url", idp.generateOidcUrl); authRouter.post("/idp/:idpId/oidc/validate-callback", idp.validateOidcCallback); authRouter.put("/set-server-admin", auth.setServerAdmin); -authRouter.get("/initial-setup-complete", auth.initialSetupComplete); authRouter.post("/validate-setup-token", auth.validateSetupToken); // Security Key routes diff --git a/src/app/page.tsx b/src/app/page.tsx index 3b3a45a04..7b10bf57d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -29,14 +29,19 @@ export default async function Page(props: { const user = await verifySession({ skipCheckVerifyEmail: true }); - let complete = false; + let complete: boolean | null = null; // null means "unknown" (request errored) try { const setupRes = await internal.get< AxiosResponse >(`/auth/initial-setup-complete`, await authCookieHeader()); complete = setupRes.data.data.complete; - } catch (e) {} - if (!complete) { + } catch (e) { + // Swallow errors (e.g. 429 rate limit, 500, network failure). + // Only redirect to initial-setup when the server *confirms* setup + // is incomplete (complete === false). If the request itself failed we + // cannot tell, so fall through to the login redirect instead. + } + if (complete === false) { redirect("/auth/initial-setup"); }