session.ts66 lines · main
| 1 | import { redirect } from 'next/navigation'; |
| 2 | |
| 3 | import { ApiError, apiFetch } from './api'; |
| 4 | |
| 5 | export interface SessionUser { |
| 6 | id: string; |
| 7 | email: string; |
| 8 | name: string | null; |
| 9 | emailVerified: boolean; |
| 10 | image: string | null; |
| 11 | isAdmin: boolean; |
| 12 | // Personal org id auto-created by migration 0010. Web uses this as the |
| 13 | // implicit org context for every billing + project route; Phase 3 adds |
| 14 | // an org switcher that overrides it. |
| 15 | defaultOrgId: string; |
| 16 | suspendedAt: string | null; |
| 17 | // EU KYC / billing profile (all optional until paid checkout). |
| 18 | legalName: string | null; |
| 19 | companyName: string | null; |
| 20 | companyRegistrationNumber: string | null; |
| 21 | vatId: string | null; |
| 22 | vatVerifiedAt: string | null; |
| 23 | addressLine1: string | null; |
| 24 | addressLine2: string | null; |
| 25 | addressCity: string | null; |
| 26 | addressPostalCode: string | null; |
| 27 | addressRegion: string | null; |
| 28 | addressCountry: string | null; |
| 29 | dateOfBirth: string | null; |
| 30 | countryOfBirth: string | null; |
| 31 | timezone: string | null; |
| 32 | createdAt: string; |
| 33 | lastSignIn: { |
| 34 | at: string; |
| 35 | ipAddress: string | null; |
| 36 | userAgent: string | null; |
| 37 | nearBy: { |
| 38 | city: string | null; |
| 39 | region: string | null; |
| 40 | country: string | null; |
| 41 | } | null; |
| 42 | } | null; |
| 43 | } |
| 44 | |
| 45 | /** |
| 46 | * Resolve the current session by calling apps/api's /v1/me. Returns null if |
| 47 | * the caller is unauthenticated. Use `requireUser()` below for pages that |
| 48 | * must redirect anonymous traffic to /signin. |
| 49 | */ |
| 50 | export async function getSessionUser(): Promise<SessionUser | null> { |
| 51 | try { |
| 52 | const res = await apiFetch('/v1/me'); |
| 53 | if (res.status === 401) return null; |
| 54 | if (!res.ok) throw new ApiError(res.status, await res.text()); |
| 55 | return (await res.json()) as SessionUser; |
| 56 | } catch (err) { |
| 57 | if (err instanceof ApiError && err.status === 401) return null; |
| 58 | throw err; |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | export async function requireUser(redirectTo = '/signin'): Promise<SessionUser> { |
| 63 | const user = await getSessionUser(); |
| 64 | if (!user) redirect(redirectTo); |
| 65 | return user; |
| 66 | } |