fix: lazily instantiate Auth0Client to fix CI build failure (#2600)

## Summary
- Auth0Client was constructed at module import time, crashing during
`next build` static analysis of `/_not-found` when `AUTH0_DOMAIN` isn't
set in CI
- Wraps the client in a lazy Proxy that defers construction to first
method call
- Zero API change — all callers still do `auth0.getSession()`,
`auth0.handleAuth()`, etc.

## Context
This broke in #2598 when the layout restructure caused `/_not-found` to
evaluate the root layout's auth0 import during build. The `build` CI
check has been failing on all PRs since.

## Test plan
- [ ] `build` CI check passes (was failing on #2598, #2593, #2599)
- [ ] Auth flows still work at runtime (login, logout, callback)
This commit is contained in:
Kevin Turcios 2026-04-10 15:48:58 -05:00 committed by GitHub
parent 3b1398973e
commit f9d78e5cf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -16,104 +16,121 @@ function redirectTo(path: string): NextResponse {
// Auth0 v4 expects AUTH0_DOMAIN; derive from v3's AUTH0_ISSUER_BASE_URL if needed
const auth0Domain =
process.env.AUTH0_DOMAIN ??
process.env.AUTH0_ISSUER_BASE_URL?.replace(/^https?:\/\//, "")
process.env.AUTH0_DOMAIN ?? process.env.AUTH0_ISSUER_BASE_URL?.replace(/^https?:\/\//, "")
export const auth0 = new Auth0Client({
domain: auth0Domain,
authorizationParameters: {
scope: "openid profile email offline_access",
},
signInReturnToPath: APP_ROUTES.BASE,
async beforeSessionSaved(session, idToken) {
// Decode the ID token to get claims like nickname, name, picture
if (idToken) {
try {
const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString())
return {
...session,
user: {
...session.user,
nickname: payload.nickname ?? session.user.nickname,
name: payload.name ?? session.user.name,
picture: payload.picture ?? session.user.picture,
},
// Lazily instantiate Auth0Client so the module can be imported during
// `next build` static analysis (e.g. /_not-found) where env vars aren't set.
let _auth0: Auth0Client | undefined
function getAuth0Client(): Auth0Client {
if (!_auth0) {
_auth0 = new Auth0Client({
domain: auth0Domain,
authorizationParameters: {
scope: "openid profile email offline_access",
},
signInReturnToPath: APP_ROUTES.BASE,
async beforeSessionSaved(session, idToken) {
// Decode the ID token to get claims like nickname, name, picture
if (idToken) {
try {
const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString())
return {
...session,
user: {
...session.user,
nickname: payload.nickname ?? session.user.nickname,
name: payload.name ?? session.user.name,
picture: payload.picture ?? session.user.picture,
},
}
} catch {
// If decoding fails, return session as-is
}
}
} catch {
// If decoding fails, return session as-is
}
}
return session
},
async onCallback(error, context, session) {
if (error) {
console.error("[Auth] Error in callback:", error)
const errorMessage = error.message || ""
return session
},
async onCallback(error, context, session) {
if (error) {
console.error("[Auth] Error in callback:", error)
const errorMessage = error.message || ""
if (errorMessage.includes("allowlist-fail")) {
const re = /allowlist-fail\s(.*)\s(.*)\)/
const match = errorMessage.match(re)
if (match != null) {
const userId = match[1]
const userNickname = match[2]
return redirectTo(`/waitlist?username=${userNickname}&userid=${userId}`)
if (errorMessage.includes("allowlist-fail")) {
const re = /allowlist-fail\s(.*)\s(.*)\)/
const match = errorMessage.match(re)
if (match != null) {
const userId = match[1]
const userNickname = match[2]
return redirectTo(`/waitlist?username=${userNickname}&userid=${userId}`)
}
}
return redirectTo("/login?error=callback_failed")
}
}
return redirectTo("/login?error=callback_failed")
}
if (!session) {
return redirectTo("/waitlist")
}
if (!session) {
return redirectTo("/waitlist")
}
const user = session.user
console.log(`[Auth] Processing login for user: ${user.sub}`)
const user = session.user
console.log(`[Auth] Processing login for user: ${user.sub}`)
if (!user.sub || !user.nickname) {
console.error("[Auth] Missing required user fields")
return redirectTo(context.returnTo || APP_ROUTES.BASE)
}
if (!user.sub || !user.nickname) {
console.error("[Auth] Missing required user fields")
return redirectTo(context.returnTo || APP_ROUTES.BASE)
}
try {
// Save user to database
console.log("[Auth] Saving user to database...")
await createOrUpdateUser(user.sub, user.nickname, user.email ?? null, user.name ?? null)
console.log("[Auth] User saved successfully")
try {
// Save user to database
console.log("[Auth] Saving user to database...")
await createOrUpdateUser(user.sub, user.nickname, user.email ?? null, user.name ?? null)
console.log("[Auth] User saved successfully")
// Track login
await trackUserLogin({
userId: user.sub,
username: user.nickname,
email: user.email,
name: user.name,
})
// Track login
await trackUserLogin({
userId: user.sub,
username: user.nickname,
email: user.email,
name: user.name,
})
// Check onboarding
const completedOnboarding = await hasCompletedOnboarding(user.sub)
console.log(`[Auth] Onboarding completed: ${completedOnboarding}`)
// Check onboarding
const completedOnboarding = await hasCompletedOnboarding(user.sub)
console.log(`[Auth] Onboarding completed: ${completedOnboarding}`)
const intendedDestination = context.returnTo || APP_ROUTES.BASE
const intendedDestination = context.returnTo || APP_ROUTES.BASE
// Check if the path is codeflash/auth/[token]
const isAuthPath =
intendedDestination.startsWith("/codeflash/auth") ||
intendedDestination.includes("/codeflash/auth")
// Check if the path is codeflash/auth/[token]
const isAuthPath =
intendedDestination.startsWith("/codeflash/auth") ||
intendedDestination.includes("/codeflash/auth")
if (!completedOnboarding && !isAuthPath) {
return redirectTo("/onboarding")
}
if (!completedOnboarding && !isAuthPath) {
return redirectTo("/onboarding")
}
return redirectTo(intendedDestination)
} catch (err) {
console.error("[Auth] Error in onCallback:", err)
return redirectTo(context.returnTo || APP_ROUTES.BASE)
}
},
routes: {
login: "/auth/login",
logout: "/auth/logout",
callback: "/auth/callback",
},
appBaseUrl: process.env.APP_BASE_URL || process.env.AUTH0_BASE_URL,
})
}
return _auth0
}
return redirectTo(intendedDestination)
} catch (err) {
console.error("[Auth] Error in onCallback:", err)
return redirectTo(context.returnTo || APP_ROUTES.BASE)
}
// Proxy preserves the `auth0.getSession()` API while deferring construction
export const auth0: Auth0Client = new Proxy({} as Auth0Client, {
get(_, prop) {
const client = getAuth0Client()
const value = (client as any)[prop]
return typeof value === "function" ? value.bind(client) : value
},
routes: {
login: "/auth/login",
logout: "/auth/logout",
callback: "/auth/callback",
},
appBaseUrl: process.env.APP_BASE_URL || process.env.AUTH0_BASE_URL,
})