auth-api.ts232 lines · main
1/**
2 * Server helpers for yellow Authentication pages → briven-engine API.
3 * Forwards session cookies via apiFetch.
4 */
5
6import { apiFetch } from '@/lib/api';
7
8export type AuthCoreInfo = {
9 ok?: boolean;
10 engine?: string;
11 engineVersion?: string;
12 storage?: string;
13 database?: string;
14 message?: string;
15 hello?: string | null;
16 schemaReady?: boolean;
17 poolReady?: boolean;
18 appLoginReady?: boolean;
19 loginMethods?: string[];
20 productStatus?: string;
21 notice?: string;
22 buildSha?: string;
23 buildAt?: string;
24};
25
26export type AuthDashboard = {
27 engine: string;
28 storage: string;
29 database: string;
30 ok: boolean;
31 message: string;
32 counts: {
33 users: number;
34 sessions: number;
35 tenants: number;
36 thirdPartyLinks: number;
37 passwordlessCodesActive: number;
38 };
39 methods: {
40 emailPassword: boolean;
41 passwordlessEmail: boolean;
42 passwordlessSms: boolean;
43 google: boolean;
44 github: boolean;
45 webauthn: boolean;
46 mfa: boolean;
47 };
48 recentUsers: Array<{
49 id: string;
50 emails: string[];
51 phoneNumbers: string[];
52 timeJoined: number;
53 }>;
54 recipesLoaded: string[];
55};
56
57export async function fetchAuthCoreInfo(): Promise<AuthCoreInfo | null> {
58 try {
59 const res = await apiFetch('/v1/auth-core/info');
60 if (!res.ok) return null;
61 return (await res.json()) as AuthCoreInfo;
62 } catch {
63 return null;
64 }
65}
66
67export async function fetchAuthDashboard(
68 projectId?: string,
69): Promise<
70 | { ok: true; data: AuthDashboard }
71 | { ok: false; status: number; message: string }
72> {
73 try {
74 const q = projectId
75 ? `?projectId=${encodeURIComponent(projectId)}`
76 : '';
77 const res = await apiFetch(`/v1/auth-core/dashboard${q}`);
78 if (res.status === 401) {
79 return { ok: false, status: 401, message: 'sign in to briven.tech required' };
80 }
81 if (!res.ok) {
82 const t = await res.text().catch(() => '');
83 return { ok: false, status: res.status, message: t || res.statusText };
84 }
85 return { ok: true, data: (await res.json()) as AuthDashboard };
86 } catch (err) {
87 return {
88 ok: false,
89 status: 0,
90 message: err instanceof Error ? err.message : String(err),
91 };
92 }
93}
94
95export type AuthUserStatus = 'active' | 'held' | 'archived';
96
97export type AuthUserSummary = {
98 id: string;
99 emails: string[];
100 phoneNumbers: string[];
101 tenantId?: string;
102 timeJoined: number;
103 status?: AuthUserStatus;
104 heldAt?: string | null;
105 heldReason?: string | null;
106 archivedAt?: string | null;
107 archivedReason?: string | null;
108 storage?: string;
109};
110
111export type AuthUserDetail = AuthUserSummary & {
112 emailVerified?: boolean;
113 metadata?: Record<string, unknown>;
114 roles?: string[];
115 linkedLogins?: Array<{
116 id: string;
117 provider: string;
118 providerUserId: string;
119 createdAt: string;
120 }>;
121 sessions?: Array<{
122 handle: string;
123 expiresAt: string;
124 createdAt: string;
125 }>;
126 passkeyCount?: number;
127 totpCount?: number;
128};
129
130export async function fetchAuthUsers(
131 limit = 50,
132 projectId?: string,
133): Promise<
134 | {
135 ok: true;
136 users: AuthUserSummary[];
137 storage?: string;
138 }
139 | { ok: false; status: number; message: string }
140> {
141 try {
142 const params = new URLSearchParams({ limit: String(limit) });
143 if (projectId) params.set('projectId', projectId);
144 const res = await apiFetch(`/v1/auth-core/users?${params}`);
145 if (res.status === 401) {
146 return { ok: false, status: 401, message: 'sign in to briven.tech required' };
147 }
148 if (!res.ok) {
149 const t = await res.text().catch(() => '');
150 return { ok: false, status: res.status, message: t || res.statusText };
151 }
152 const body = (await res.json()) as {
153 users?: AuthUserSummary[];
154 storage?: string;
155 };
156 return {
157 ok: true,
158 users: body.users ?? [],
159 storage: body.storage ?? 'doltgres',
160 };
161 } catch (err) {
162 return {
163 ok: false,
164 status: 0,
165 message: err instanceof Error ? err.message : String(err),
166 };
167 }
168}
169
170export async function fetchAuthUserDetail(
171 userId: string,
172 projectId: string,
173): Promise<
174 | { ok: true; user: AuthUserDetail }
175 | { ok: false; status: number; message: string }
176> {
177 try {
178 const params = new URLSearchParams({ projectId });
179 const res = await apiFetch(
180 `/v1/auth-core/users/${encodeURIComponent(userId)}?${params}`,
181 );
182 if (res.status === 401) {
183 return { ok: false, status: 401, message: 'sign in to briven.tech required' };
184 }
185 if (!res.ok) {
186 const t = await res.text().catch(() => '');
187 return { ok: false, status: res.status, message: t || res.statusText };
188 }
189 const body = (await res.json()) as { user?: AuthUserDetail };
190 if (!body.user) {
191 return { ok: false, status: 404, message: 'user not found' };
192 }
193 return { ok: true, user: body.user };
194 } catch (err) {
195 return {
196 ok: false,
197 status: 0,
198 message: err instanceof Error ? err.message : String(err),
199 };
200 }
201}
202
203export async function fetchAuthRecipes(): Promise<{
204 loaded: string[];
205 catalog: Array<{
206 id: string;
207 title: string;
208 phase: number;
209 loaded: boolean;
210 sms: boolean;
211 }>;
212 smsIncluded?: boolean;
213 storage?: string;
214} | null> {
215 try {
216 const res = await apiFetch('/v1/auth-core/recipes');
217 if (!res.ok) return null;
218 return (await res.json()) as {
219 loaded: string[];
220 catalog: Array<{
221 id: string;
222 title: string;
223 phase: number;
224 loaded: boolean;
225 sms: boolean;
226 }>;
227 smsIncluded?: boolean;
228 };
229 } catch {
230 return null;
231 }
232}