chore: initial commit for phase03
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
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<string, boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requesting user's membership in an organization.
|
||||
* Super admins bypass membership checks.
|
||||
*/
|
||||
export async function getOrgMembership(
|
||||
request: FastifyRequest,
|
||||
orgId: string,
|
||||
): Promise<OrgMember | 'super_admin'> {
|
||||
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<string, boolean>,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<void> {
|
||||
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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user