import type { FastifyRequest } from 'fastify'; import { eq, and } from 'drizzle-orm'; import { organizationMembers } from '@source/database'; import { ROLES } from '@source/shared'; import type { Permission, Role } from '@source/shared'; import { AppError } from './errors.js'; interface OrgMember { role: Role; customPermissions: Record; } /** * Get the requesting user's membership in an organization. * Super admins bypass membership checks. */ export async function getOrgMembership( request: FastifyRequest, orgId: string, ): Promise { const user = request.user; if (user.isSuperAdmin) { return 'super_admin'; } const member = await (request.server as any).db.query.organizationMembers.findFirst({ where: and( eq(organizationMembers.organizationId, orgId), eq(organizationMembers.userId, user.sub), ), }); if (!member) { throw AppError.forbidden('You are not a member of this organization'); } return { role: member.role as Role, customPermissions: (member.customPermissions ?? {}) as Record, }; } /** * Check if the user has a specific permission in the organization. * Super admins always have all permissions. */ export function hasPermission(membership: OrgMember | 'super_admin', permission: Permission): boolean { if (membership === 'super_admin') return true; // Check custom permission overrides first if (permission in membership.customPermissions) { return membership.customPermissions[permission]!; } // Fall back to role defaults const rolePerms = ROLES[membership.role]?.permissions ?? []; return (rolePerms as readonly string[]).includes(permission); } /** * Require a specific permission, throw 403 if not allowed. */ export async function requirePermission( request: FastifyRequest, orgId: string, permission: Permission, ): Promise { const membership = await getOrgMembership(request, orgId); if (!hasPermission(membership, permission)) { throw AppError.forbidden(`Missing permission: ${permission}`); } } /** * Require super admin role. */ export function requireSuperAdmin(request: FastifyRequest): void { if (!request.user.isSuperAdmin) { throw AppError.forbidden('Super admin access required'); } }