index.ts196 lines · main
1/**
2 * @briven/auth/engine — Briven Auth client for **briven-engine** (Path A).
3 *
4 * First-party proxy rule (required):
5 * Browser → https://YOUR_APP/api/auth/* → https://api.briven.tech/v1/auth-core/fdi/*
6 * so session cookies sit on the app domain.
7 *
8 * import { createBrivenEngineClient } from '@briven/auth/engine';
9 *
10 * const auth = createBrivenEngineClient({
11 * projectId: 'p_abc',
12 * // Prefer same-origin proxy path in the browser:
13 * apiBasePath: '/api/auth',
14 * });
15 *
16 * Product brand: briven-engine only.
17 */
18
19export const BRIVEN_ENGINE_ID = 'briven-engine' as const;
20
21export type BrivenEngineClientOptions = {
22 readonly projectId: string;
23 /**
24 * Base path for FDI calls. Default `/api/auth` (first-party proxy on app).
25 * For direct API (server-side only / local): full origin + `/v1/auth-core/fdi`.
26 */
27 readonly apiBasePath?: string;
28 /** Absolute API origin when not using same-origin proxy. */
29 readonly apiOrigin?: string;
30 readonly fetch?: typeof globalThis.fetch;
31};
32
33export type BrivenEngineSession = {
34 readonly userId: string;
35 readonly sessionHandle?: string;
36 readonly accessTokenPayload?: Record<string, unknown>;
37};
38
39export type BrivenEngineResult<T> =
40 | { ok: true; data: T }
41 | { ok: false; status: number; message: string };
42
43export type BrivenEngineClient = {
44 readonly engine: typeof BRIVEN_ENGINE_ID;
45 readonly projectId: string;
46 /** Session recipe: refresh */
47 refreshSession: () => Promise<BrivenEngineResult<unknown>>;
48 /** Session recipe: sign out */
49 signOut: () => Promise<BrivenEngineResult<unknown>>;
50 /** EmailPassword sign up */
51 signUpEmailPassword: (input: {
52 email: string;
53 password: string;
54 }) => Promise<BrivenEngineResult<unknown>>;
55 /** EmailPassword sign in */
56 signInEmailPassword: (input: {
57 email: string;
58 password: string;
59 }) => Promise<BrivenEngineResult<unknown>>;
60 /** Passwordless: create code (email or phone — SMS included) */
61 createPasswordlessCode: (input: {
62 email?: string;
63 phoneNumber?: string;
64 }) => Promise<BrivenEngineResult<unknown>>;
65 /** Passwordless: consume user input code */
66 consumePasswordlessCode: (input: {
67 preAuthSessionId: string;
68 userInputCode: string;
69 deviceId: string;
70 }) => Promise<BrivenEngineResult<unknown>>;
71 /** Raw FDI helper */
72 fdi: (path: string, init?: RequestInit) => Promise<Response>;
73};
74
75function joinBase(apiOrigin: string | undefined, apiBasePath: string): string {
76 const path = apiBasePath.replace(/\/$/, '') || '/api/auth';
77 if (!apiOrigin) return path;
78 return `${apiOrigin.replace(/\/$/, '')}${path.startsWith('/') ? path : `/${path}`}`;
79}
80
81export function createBrivenEngineClient(
82 opts: BrivenEngineClientOptions,
83): BrivenEngineClient {
84 const fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
85 const base = joinBase(
86 opts.apiOrigin,
87 opts.apiBasePath ?? '/api/auth',
88 );
89
90 async function fdi(path: string, init?: RequestInit): Promise<Response> {
91 const p = path.startsWith('/') ? path : `/${path}`;
92 const headers = new Headers(init?.headers);
93 headers.set('x-briven-project-id', opts.projectId);
94 headers.set('x-briven-engine', BRIVEN_ENGINE_ID);
95 if (!headers.has('content-type') && init?.body) {
96 headers.set('content-type', 'application/json');
97 }
98 return fetchFn(`${base}${p}`, {
99 ...init,
100 headers,
101 credentials: 'include',
102 });
103 }
104
105 async function jsonResult(res: Response): Promise<BrivenEngineResult<unknown>> {
106 let data: unknown = null;
107 try {
108 data = await res.json();
109 } catch {
110 data = null;
111 }
112 if (!res.ok) {
113 const msg =
114 data && typeof data === 'object' && data !== null && 'message' in data
115 ? String((data as { message: unknown }).message)
116 : res.statusText;
117 return { ok: false, status: res.status, message: msg };
118 }
119 return { ok: true, data };
120 }
121
122 return {
123 engine: BRIVEN_ENGINE_ID,
124 projectId: opts.projectId,
125 fdi,
126 refreshSession: async () => {
127 const res = await fdi('/session/refresh', { method: 'POST', body: '{}' });
128 return jsonResult(res);
129 },
130 signOut: async () => {
131 const res = await fdi('/signout', { method: 'POST', body: '{}' });
132 return jsonResult(res);
133 },
134 signUpEmailPassword: async ({ email, password }) => {
135 const res = await fdi('/signup', {
136 method: 'POST',
137 body: JSON.stringify({
138 formFields: [
139 { id: 'email', value: email },
140 { id: 'password', value: password },
141 ],
142 }),
143 headers: { rid: 'emailpassword' },
144 });
145 return jsonResult(res);
146 },
147 signInEmailPassword: async ({ email, password }) => {
148 const res = await fdi('/signin', {
149 method: 'POST',
150 body: JSON.stringify({
151 formFields: [
152 { id: 'email', value: email },
153 { id: 'password', value: password },
154 ],
155 }),
156 headers: { rid: 'emailpassword' },
157 });
158 return jsonResult(res);
159 },
160 createPasswordlessCode: async (input) => {
161 const body: Record<string, string> = {};
162 if (input.email) body.email = input.email;
163 if (input.phoneNumber) body.phoneNumber = input.phoneNumber;
164 const res = await fdi('/signinup/code', {
165 method: 'POST',
166 body: JSON.stringify(body),
167 headers: { rid: 'passwordless' },
168 });
169 return jsonResult(res);
170 },
171 consumePasswordlessCode: async (input) => {
172 const res = await fdi('/signinup/code/consume', {
173 method: 'POST',
174 body: JSON.stringify(input),
175 headers: { rid: 'passwordless' },
176 });
177 return jsonResult(res);
178 },
179 };
180}
181
182/** Next.js (or any) first-party proxy target for briven-engine FDI. */
183export function brivenEngineProxyTarget(apiOrigin?: string): string {
184 const origin = (apiOrigin ?? 'https://api.briven.tech').replace(/\/$/, '');
185 return `${origin}/v1/auth-core/fdi`;
186}
187
188export { BRIVEN_ENGINE_SCAFFOLDS, listBrivenEngineScaffolds } from './scaffolds';
189export {
190 proxyBrivenEngineAuth,
191 brivenEngineNextHandler,
192 appAuthPathToFdiSuffix,
193 resolveFdiTarget,
194} from './proxy';
195export type { BrivenEngineProxyOptions } from './proxy';
196// Server session helper: import from '@briven/auth/engine/server'