import { cookies } from "next/headers"; import { gatewayFetch } from "@/lib/api/gateway"; import { ACCESS_TOKEN_COOKIE } from "@/lib/auth/constants"; import { decodeJwt, isJwtExpired, type JwtClaims } from "@/lib/auth/jwt"; export interface Session { userId: string; email?: string; tenantId?: string; isAdmin: boolean; claims: JwtClaims; } /** Raw access token from the httpOnly cookie (for proxying to the gateway). */ export async function getAccessToken(): Promise { const store = await cookies(); return store.get(ACCESS_TOKEN_COOKIE)?.value ?? null; } /** * Decode the current session from the access-token cookie. Returns null when there is * no token, it is malformed, or it has expired. Use in server components / layouts to * guard rendering; the gateway is still the authority on every API call. */ export async function getSession(): Promise { const token = await getAccessToken(); if (!token) return null; const claims = decodeJwt(token); if (!claims || isJwtExpired(claims) || !claims.sub) return null; return { userId: String(claims.sub), email: claims.email ? String(claims.email) : undefined, tenantId: claims.tenant_id ? String(claims.tenant_id) : undefined, isAdmin: String(claims.is_admin) === "true", claims, }; } export interface IdentityUser { id: string; email?: string | null; full_name?: string | null; avatar_url?: string | null; is_admin?: boolean; [key: string]: unknown; } /** * Fetch the full current-user profile from Identity (`/v1/users/me`) using the access * cookie. Returns null when signed out or the token is rejected — use this as the * authoritative server-side guard (it validates the token against the service). */ export async function getCurrentUser(): Promise { const token = await getAccessToken(); if (!token) return null; const res = await gatewayFetch("/v1/users/me", { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) return null; return (await res.json().catch(() => null)) as IdentityUser | null; } /** Minimal, serializable user summary for the navbar/profile menu (passed from * server layouts into client components). Null when signed out. */ export interface NavUser { name: string; email: string; avatarUrl: string | null; isAdmin: boolean; } export async function getNavUser(): Promise { const user = await getCurrentUser(); if (!user) return null; const email = user.email ?? ""; const fullName = typeof user.full_name === "string" ? user.full_name.trim() : ""; return { name: fullName || (email ? email.split("@")[0] : "User"), email, avatarUrl: (user.avatar_url as string | null) ?? null, isAdmin: Boolean(user.is_admin) || Boolean((user as Record).is_tenant_admin), }; }