/** * Client-side auth helpers. * * Everything the SDK covers goes through an SDK method. Never hand-build a * REST path: the SDK owns the URL shape, and a string built here silently * rots the day a route moves. `getClient()` is configured with * `baseUrl: '/api/store'` + `proxyMode`, so each call still lands on the * same-origin BFF proxy, which adds the Authorization header from the * httpOnly cookie and strips the token out of the response. The SDK adds the * CSRF header the proxy requires on every non-GET request. * * Only the routes with no SDK equivalent are plain fetches: `/api/auth/me`, * `/api/auth/logout` and `/api/auth/reset-password` are this app's own Next * route handlers, not Brainerce endpoints. * * The token is managed server-side via httpOnly cookies, never exposed to JS. */ import { getClient } from '@/core/lib/brainerce'; const CSRF_HEADERS: Record = { 'Content-Type': 'application/json', 'X-Requested-With': 'brainerce', }; interface LoginResult { customer: { id: string; email: string; firstName?: string; lastName?: string; emailVerified: boolean; }; expiresAt: string; requiresVerification?: boolean; } interface RegisterResult { customer: { id: string; email: string; firstName?: string; lastName?: string; emailVerified: boolean; }; expiresAt: string; requiresVerification?: boolean; } interface AuthStatus { isLoggedIn: boolean; customer?: { id: string; email: string; firstName?: string; lastName?: string; phone?: string; emailVerified: boolean; }; error?: string; } interface VerifyEmailResult { verified: boolean; message?: string; } async function handleResponse(response: Response): Promise { const data = await response.json(); if (!response.ok) { throw new Error(data.message || data.error || `Request failed (${response.status})`); } return data as T; } /** * Login via the SDK, routed through the BFF proxy, which sets the httpOnly * cookie on success and strips the token from the response. The narrow * `LoginResult` return type is deliberate: the SDK's own response type * declares a `token`, but the proxy removes it before it reaches the browser, * so nothing here should read one. */ export async function proxyLogin(email: string, password: string): Promise { return getClient().loginCustomer(email, password); } /** * Register via the SDK, routed through the BFF proxy, which sets the httpOnly * cookie on success. */ export async function proxyRegister(data: { firstName: string; lastName: string; email: string; password: string; acceptsMarketing?: boolean; /** * Birthday month (1-12) and day (1-31), never a year. Powers the loyalty * birthday gift. Send both or neither: one without the other is rejected * with HTTP 400, and so is a day the month does not have. Required only when * `getStoreInfo().requireBirthday` is true for this sales channel. */ birthMonth?: number; birthDay?: number; /** * Loyalty referral share code, captured from a `?ref=` link on the way in and * read back out of the cookie by the register form. Validated asynchronously * after registration: a stale or unknown code never fails the registration * itself, so it is always safe to send whatever was captured. */ referralCode?: string; }): Promise { return getClient().registerCustomer(data); } /** * Check auth status. Reads httpOnly cookie server-side and validates with backend. */ export async function checkAuthStatus(): Promise { const response = await fetch('/api/auth/me'); return response.json(); } /** * Logout. Clears httpOnly auth cookies server-side. */ export async function proxyLogout(): Promise { await fetch('/api/auth/logout', { method: 'POST', headers: { 'X-Requested-With': 'brainerce' }, }); } /** * Verify email via the SDK. No token argument is passed: the auth token lives * in the httpOnly cookie (set during login/register) and the proxy attaches * the Authorization header. The SDK skips its own token check in proxy mode. */ export async function proxyVerifyEmail(code: string): Promise { return getClient().verifyEmail(code); } /** * Resend the verification email via the SDK. Uses the auth token from the * httpOnly cookie, added by the proxy. Rate limited to 3 requests per hour. */ export async function proxyResendVerification(): Promise<{ message: string }> { return getClient().resendVerificationEmail(); } /** * Reset password via BFF proxy. * The reset token is in an httpOnly cookie (set by /api/auth/reset-callback when the user * clicked the email link). The proxy reads it server-side — the token never reaches client JS. */ export async function proxyResetPassword(newPassword: string): Promise<{ message: string }> { const response = await fetch('/api/auth/reset-password', { method: 'POST', headers: CSRF_HEADERS, body: JSON.stringify({ newPassword }), }); return handleResponse<{ message: string }>(response); }