load-workspace.ts102 lines · main
1import { apiFetch, apiJson } from '../../../../../lib/api';
2
3import type { AuthV2ProjectRow } from './auth-v2-types';
4
5/**
6 * Server-side load of Auth workspace (all projects + enable flags).
7 * Prefers briven-engine `/v1/auth-core/workspace`, then merges tenants list
8 * so "Auth on" never lags behind be_tenants.
9 */
10export async function loadAuthV2Workspace(): Promise<AuthV2ProjectRow[]> {
11 let projects: AuthV2ProjectRow[] = [];
12
13 try {
14 const data = await apiJson<{
15 projects?: AuthV2ProjectRow[];
16 }>(`/v1/auth-core/workspace?_=${Date.now()}`);
17 if (data.projects?.length) projects = data.projects;
18 } catch {
19 /* fall through */
20 }
21
22 if (projects.length === 0) {
23 try {
24 const data = await apiJson<{
25 projects?: AuthV2ProjectRow[];
26 }>('/v1/auth-v2/workspace');
27 if (data.projects?.length) projects = data.projects;
28 } catch {
29 /* fall through */
30 }
31 }
32
33 if (projects.length === 0) {
34 try {
35 const data = await apiJson<{
36 projects: Array<{ id: string; slug: string; name: string }>;
37 }>('/v1/projects');
38 projects = (data.projects ?? []).map((p) => ({
39 id: p.id,
40 slug: p.slug,
41 name: p.name,
42 authEnabled: false,
43 tenantId: null,
44 providers: null,
45 }));
46 } catch {
47 return [];
48 }
49 }
50
51 // Merge live tenants — fixes false "Auth off" when workspace flag is stale.
52 try {
53 const res = await apiFetch('/v1/auth-core/tenants');
54 if (res.ok) {
55 const body = (await res.json()) as {
56 tenants?: Array<{ projectId?: string; tenantId?: string }>;
57 tenantIds?: string[];
58 };
59 const byProject = new Map<string, string>();
60 const tenantSet = new Set<string>();
61 for (const t of body.tenants ?? []) {
62 if (t.tenantId) tenantSet.add(t.tenantId);
63 if (t.projectId) {
64 byProject.set(t.projectId, t.tenantId ?? '');
65 byProject.set(t.projectId.toLowerCase(), t.tenantId ?? '');
66 }
67 }
68 for (const tid of body.tenantIds ?? []) tenantSet.add(tid);
69
70 projects = projects.map((p) => {
71 const tid =
72 byProject.get(p.id) ||
73 byProject.get(p.id.toLowerCase()) ||
74 p.tenantId ||
75 null;
76 // Map rule: tenant id is always proj-{normalized project id}
77 const mapped = `proj-${p.id
78 .trim()
79 .toLowerCase()
80 .replace(/_/g, '-')
81 .replace(/[^a-z0-9-]/g, '-')
82 .replace(/-+/g, '-')
83 .replace(/^-|-$/g, '')}`.slice(0, 64);
84 const on =
85 p.authEnabled === true ||
86 byProject.has(p.id) ||
87 byProject.has(p.id.toLowerCase()) ||
88 (mapped ? tenantSet.has(mapped) : false) ||
89 (p.tenantId ? tenantSet.has(p.tenantId) : false);
90 return {
91 ...p,
92 authEnabled: on,
93 tenantId: on ? tid || mapped || p.tenantId || null : null,
94 };
95 });
96 }
97 } catch {
98 /* keep projects as-is */
99 }
100
101 return projects;
102}