doxa-maps-cdn/server/utils/rbac.ts
2026-04-21 12:12:54 +01:00

73 lines
2.6 KiB
TypeScript

import type { H3Event } from 'h3'
import { db } from './database'
import { requireAuth } from './auth'
import { getStaticRole, ROLES, STATIC_ROLE_NAMES } from '~~/app/utils/role-definitions'
import type { Permission } from '~~/app/utils/permissions'
export async function getUserRoles(userId: string): Promise<string[]> {
const row = await db
.selectFrom('users')
.select('roles')
.where('id', '=', userId)
.executeTakeFirst()
return row?.roles ?? []
}
// Resolves a list of role names to the union of permissions they grant.
// Baseline: static definitions only. The optional `custom-roles` block ships
// a replacement of this file that also falls back to the `custom_roles` table
// for names that aren't statically defined.
export async function getRolePermissions(roleNames: readonly string[]): Promise<Set<Permission>> {
const set = new Set<Permission>()
for (const roleName of roleNames) {
const staticRole = getStaticRole(roleName)
if (!staticRole) continue
for (const perm of staticRole.permissions) set.add(perm)
}
return set
}
export async function getUserPermissions(userId: string): Promise<Set<Permission>> {
const roles = await getUserRoles(userId)
return getRolePermissions(roles)
}
export async function userHasRole(userId: string, roleName: string): Promise<boolean> {
const roles = await getUserRoles(userId)
return roles.includes(roleName)
}
export async function userHasPermission(userId: string, permission: Permission): Promise<boolean> {
const perms = await getUserPermissions(userId)
return perms.has(permission)
}
export async function requireRole(event: H3Event, roleName: string) {
const authUser = requireAuth(event)
const ok = await userHasRole(authUser.userId, roleName)
if (!ok) {
throw createError({ statusCode: 403, statusMessage: `Role required: ${roleName}` })
}
return authUser
}
export async function requirePermission(event: H3Event, permission: Permission) {
const authUser = requireAuth(event)
const ok = await userHasPermission(authUser.userId, permission)
if (!ok) {
throw createError({ statusCode: 403, statusMessage: `Permission required: ${permission}` })
}
return authUser
}
// Validates that every entry in `roles` is a known role name.
// v1: only static roles are known. The optional custom-roles block extends
// this to also consult the `custom_roles` table.
export async function validateRoleNames(roles: string[]): Promise<{ valid: boolean; unknown: string[] }> {
const known = new Set<string>(STATIC_ROLE_NAMES)
const unknown = roles.filter(r => !known.has(r))
return { valid: unknown.length === 0, unknown }
}
export { ROLES, STATIC_ROLE_NAMES }