roles.ts230 lines · main
| 1 | /** |
| 2 | * briven-engine roles + permissions on Doltgres. |
| 3 | */ |
| 4 | |
| 5 | import { getEnginePool } from './db.js'; |
| 6 | import { isAuthCoreInitialized } from './engine.js'; |
| 7 | import { projectIdToTenantId } from './project-map.js'; |
| 8 | |
| 9 | async 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 | |
| 23 | export 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 | |
| 79 | export 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 | |
| 122 | export 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 | |
| 172 | export 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 | |
| 223 | export 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 | } |