roles.ts230 lines · main
1/**
2 * briven-engine roles + permissions on Doltgres.
3 */
4
5import { getEnginePool } from './db.js';
6import { isAuthCoreInitialized } from './engine.js';
7import { projectIdToTenantId } from './project-map.js';
8
9async function ensureTenant(tenantId: string, projectId?: string): Promise<void> {
10 const pool = getEnginePool();
11 const existing = await pool.query(
12 `SELECT tenant_id FROM be_tenants WHERE tenant_id = $1 LIMIT 1`,
13 [tenantId],
14 );
15 if (!existing.rowCount) {
16 await pool.query(
17 `INSERT INTO be_tenants (tenant_id, project_id) VALUES ($1, $2)`,
18 [tenantId, projectId ?? tenantId],
19 );
20 }
21}
22
23export async function createBrivenEngineRole(
24 role: string,
25 permissions: string[] = [],
26 opts?: { projectId?: string; tenantId?: string },
27): Promise<{ ok: boolean; engine: 'briven-engine'; storage: 'doltgres'; message?: string }> {
28 if (!isAuthCoreInitialized()) {
29 return {
30 ok: false,
31 engine: 'briven-engine',
32 storage: 'doltgres',
33 message: 'engine not ready',
34 };
35 }
36 const name = role.trim().toLowerCase();
37 if (!name) {
38 return {
39 ok: false,
40 engine: 'briven-engine',
41 storage: 'doltgres',
42 message: 'role required',
43 };
44 }
45 const tenantId =
46 opts?.tenantId ??
47 (opts?.projectId ? projectIdToTenantId(opts.projectId) : 'public');
48 await ensureTenant(tenantId, opts?.projectId);
49 const pool = getEnginePool();
50 const existing = await pool.query(
51 `SELECT role_name FROM be_roles WHERE tenant_id = $1 AND role_name = $2 LIMIT 1`,
52 [tenantId, name],
53 );
54 if (existing.rowCount) {
55 await pool.query(
56 `UPDATE be_roles SET permissions_json = $3 WHERE tenant_id = $1 AND role_name = $2`,
57 [tenantId, name, JSON.stringify(permissions)],
58 );
59 return {
60 ok: true,
61 engine: 'briven-engine',
62 storage: 'doltgres',
63 message: 'updated',
64 };
65 }
66 await pool.query(
67 `INSERT INTO be_roles (tenant_id, role_name, permissions_json)
68 VALUES ($1, $2, $3)`,
69 [tenantId, name, JSON.stringify(permissions)],
70 );
71 return {
72 ok: true,
73 engine: 'briven-engine',
74 storage: 'doltgres',
75 message: 'created',
76 };
77}
78
79export async function assignBrivenEngineRole(
80 userId: string,
81 role: string,
82 opts?: { projectId?: string; tenantId?: string },
83): Promise<{ ok: boolean; engine: 'briven-engine'; storage: 'doltgres'; message?: string }> {
84 if (!isAuthCoreInitialized()) {
85 return {
86 ok: false,
87 engine: 'briven-engine',
88 storage: 'doltgres',
89 message: 'engine not ready',
90 };
91 }
92 const name = role.trim().toLowerCase();
93 const tenantId =
94 opts?.tenantId ??
95 (opts?.projectId ? projectIdToTenantId(opts.projectId) : 'public');
96 const pool = getEnginePool();
97 const roleRow = await pool.query(
98 `SELECT role_name FROM be_roles WHERE tenant_id = $1 AND role_name = $2 LIMIT 1`,
99 [tenantId, name],
100 );
101 if (!roleRow.rowCount) {
102 return {
103 ok: false,
104 engine: 'briven-engine',
105 storage: 'doltgres',
106 message: 'role does not exist',
107 };
108 }
109 const has = await pool.query(
110 `SELECT 1 FROM be_user_roles WHERE tenant_id = $1 AND user_id = $2 AND role_name = $3`,
111 [tenantId, userId, name],
112 );
113 if (!has.rowCount) {
114 await pool.query(
115 `INSERT INTO be_user_roles (tenant_id, user_id, role_name) VALUES ($1, $2, $3)`,
116 [tenantId, userId, name],
117 );
118 }
119 return { ok: true, engine: 'briven-engine', storage: 'doltgres', message: 'assigned' };
120}
121
122export async function getBrivenEngineUserRoles(
123 userId: string,
124 opts?: { projectId?: string; tenantId?: string },
125): Promise<{
126 roles: string[];
127 permissions: string[];
128 engine: 'briven-engine';
129 storage: 'doltgres';
130}> {
131 if (!isAuthCoreInitialized()) {
132 return {
133 roles: [],
134 permissions: [],
135 engine: 'briven-engine',
136 storage: 'doltgres',
137 };
138 }
139 const tenantId =
140 opts?.tenantId ??
141 (opts?.projectId ? projectIdToTenantId(opts.projectId) : 'public');
142 const pool = getEnginePool();
143 const res = await pool.query(
144 `SELECT ur.role_name, r.permissions_json
145 FROM be_user_roles ur
146 JOIN be_roles r ON r.tenant_id = ur.tenant_id AND r.role_name = ur.role_name
147 WHERE ur.tenant_id = $1 AND ur.user_id = $2`,
148 [tenantId, userId],
149 );
150 const roles: string[] = [];
151 const permSet = new Set<string>();
152 for (const row of res.rows as Array<{
153 role_name: string;
154 permissions_json: string;
155 }>) {
156 roles.push(row.role_name);
157 try {
158 const perms = JSON.parse(row.permissions_json) as string[];
159 for (const p of perms) permSet.add(p);
160 } catch {
161 /* ignore */
162 }
163 }
164 return {
165 roles,
166 permissions: [...permSet],
167 engine: 'briven-engine',
168 storage: 'doltgres',
169 };
170}
171
172export async function listBrivenEngineRoles(opts?: {
173 projectId?: string;
174 tenantId?: string;
175}): Promise<{
176 roles: Array<{ name: string; permissions: string[]; tenantId: string }>;
177 engine: 'briven-engine';
178 storage: 'doltgres';
179}> {
180 if (!isAuthCoreInitialized()) {
181 return { roles: [], engine: 'briven-engine', storage: 'doltgres' };
182 }
183 const pool = getEnginePool();
184 // Dashboard (no filter): all tenants. With project/tenant: that slice only.
185 const scoped =
186 opts?.tenantId ??
187 (opts?.projectId ? projectIdToTenantId(opts.projectId) : null);
188 const res = scoped
189 ? await pool.query(
190 `SELECT tenant_id, role_name, permissions_json FROM be_roles
191 WHERE tenant_id = $1 ORDER BY role_name`,
192 [scoped],
193 )
194 : await pool.query(
195 `SELECT tenant_id, role_name, permissions_json FROM be_roles
196 ORDER BY tenant_id, role_name`,
197 );
198 return {
199 engine: 'briven-engine',
200 storage: 'doltgres',
201 roles: (
202 res.rows as Array<{
203 tenant_id: string;
204 role_name: string;
205 permissions_json: string;
206 }>
207 ).map((r) => {
208 let permissions: string[] = [];
209 try {
210 permissions = JSON.parse(r.permissions_json) as string[];
211 } catch {
212 permissions = [];
213 }
214 return {
215 name: r.role_name,
216 permissions,
217 tenantId: r.tenant_id,
218 };
219 }),
220 };
221}
222
223export async function userHasPermission(
224 userId: string,
225 permission: string,
226 opts?: { projectId?: string; tenantId?: string },
227): Promise<boolean> {
228 const { permissions } = await getBrivenEngineUserRoles(userId, opts);
229 return permissions.includes(permission) || permissions.includes('*');
230}