fix(frontend): align NextAuth authorize() with flat backend LoginResponse
Login reached the backend (HTTP 200) but NextAuth returned CredentialsSignin.
Cause: authorize() read data.member.id/email/clubName/clubId, but the backend
LoginResponse is flat — { accessToken, refreshToken, expiresIn, role } with no
member object. Accessing data.member.id on undefined threw, so authorize()
returned null.
Decode the JWT payload to recover identity claims (sub=userId, email,
tenant_id=clubId) and use the flat top-level role. Adds a small decodeJwtPayload
helper (claims only, no signature verification needed here).
This commit is contained in:
@@ -2,6 +2,20 @@ import NextAuth from "next-auth"
|
||||
|
||||
import Credentials from "next-auth/providers/credentials"
|
||||
|
||||
/**
|
||||
* Decode a JWT payload (no signature verification — we only need the claims for
|
||||
* populating the session). Returns {} on any parse failure.
|
||||
*/
|
||||
function decodeJwtPayload(token: string): Record<string, unknown> {
|
||||
try {
|
||||
const payload = token.split(".")[1]
|
||||
const json = Buffer.from(payload, "base64url").toString("utf8")
|
||||
return JSON.parse(json) as Record<string, unknown>
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper: fetch with an AbortController timeout (default 5s) */
|
||||
async function fetchWithTimeout(
|
||||
url: string,
|
||||
@@ -42,14 +56,17 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||
|
||||
if (!res.ok) return null
|
||||
|
||||
// Backend LoginResponse is flat: { accessToken, refreshToken, expiresIn, role }.
|
||||
// Identity claims (sub=userId, email, tenant_id=clubId) live inside the JWT.
|
||||
const data = await res.json()
|
||||
const claims = decodeJwtPayload(data.accessToken)
|
||||
|
||||
return {
|
||||
id: data.member.id,
|
||||
email: data.member.email,
|
||||
name: data.member.clubName,
|
||||
role: data.member.role,
|
||||
clubId: data.member.clubId,
|
||||
id: (claims.sub as string) ?? data.accessToken,
|
||||
email: (claims.email as string) ?? credentials.email,
|
||||
name: (claims.email as string) ?? credentials.email,
|
||||
role: data.role ?? (claims.role as string),
|
||||
clubId: (claims.tenant_id as string) ?? "",
|
||||
accessToken: data.accessToken,
|
||||
refreshToken: data.refreshToken,
|
||||
expiresAt: Date.now() + data.expiresIn * 1000,
|
||||
|
||||
Reference in New Issue
Block a user