Files
accounted/lib/auth/claims.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

49 lines
2.0 KiB
TypeScript

import type { JwtPayload, User } from '@supabase/supabase-js'
/**
* Local JWT claims to a User: shared by the API route guard (require-auth),
* the dashboard request context and, once adopted, the auth proxy.
*
* getClaims() verifies signature and expiry locally against the cached JWKS
* (asymmetric signing keys; HS256 projects fall back to a server call inside
* getClaims itself). These helpers add the pinning and the User mapping.
*/
/**
* Defense-in-depth pinning on top of getClaims' signature/expiry verification:
* the token must come from THIS project's auth server (iss) and be an
* end-user access token (aud 'authenticated'; anonymous sign-ins share it).
* A mismatch is not treated as unauthenticated: callers fall back to the
* server-side getUser() check, which is authoritative.
*/
export function claimsPinned(claims: JwtPayload): boolean {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL?.replace(/\/+$/, '')
// Without a configured URL (unit tests) there is nothing to pin against.
const issOk = !supabaseUrl || claims.iss === `${supabaseUrl}/auth/v1`
const aud = claims.aud
const audOk = Array.isArray(aud) ? aud.includes('authenticated') : aud === 'authenticated'
return issOk && audOk
}
/**
* Maps verified JWT claims onto the User subset server code actually consumes
* (id, email, is_anonymous, app_metadata, user_metadata, role, phone).
*
* Server-only fields (identities, factors, created_at timestamps) are absent
* from the token and verified unused by any route (2026-07-23 audit);
* created_at is set to '' only to satisfy the type.
*/
export function userFromClaims(claims: JwtPayload): User {
return {
id: claims.sub,
aud: Array.isArray(claims.aud) ? (claims.aud[0] ?? 'authenticated') : (claims.aud ?? 'authenticated'),
role: claims.role,
email: claims.email,
phone: claims.phone,
app_metadata: claims.app_metadata ?? {},
user_metadata: claims.user_metadata ?? {},
is_anonymous: claims.is_anonymous ?? false,
created_at: '',
}
}