feat: ( passenger ) wire IAM global guard, org seeder, and backoffice auth

This commit is contained in:
Abubeker Yasin
2026-06-23 14:25:10 +03:00
parent 707e32c43c
commit 1eaffa7ba0
17 changed files with 658 additions and 99 deletions

View File

@@ -0,0 +1,14 @@
import { applyDecorators, UseGuards } from '@nestjs/common';
import { JwtGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard';
import { PassengerPermissionGuard } from './passenger-permission.guard';
import { PASSENGER_PERMS } from '../seed/passenger-permissions.registry';
export const PassengerStaff = (permission: string | string[]) =>
applyDecorators(
UseGuards(
JwtGuard,
PassengerPermissionGuard(Array.isArray(permission) ? permission : [permission]),
),
);
export const PassengerAdmin = () => PassengerStaff(PASSENGER_PERMS.admin);

View File

@@ -0,0 +1,30 @@
import {
CanActivate,
ExecutionContext,
ForbiddenException,
Injectable,
Type,
UnauthorizedException,
} from '@nestjs/common';
import { hasPassengerPermission } from './passenger-permission.util';
export function PassengerPermissionGuard(permissions: string[]): Type<CanActivate> {
@Injectable()
class PassengerPermissionsGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<{ user?: any }>();
const user = request.user;
if (!permissions?.length) return true;
if (!user) throw new UnauthorizedException('Authentication required');
if (permissions.some((p) => hasPassengerPermission(user, p))) return true;
throw new ForbiddenException(
`Missing permission. Required one of: ${permissions.join(', ')}`,
);
}
}
return PassengerPermissionsGuard;
}

View File

@@ -0,0 +1,74 @@
import { ForbiddenException } from '@nestjs/common';
const SUPER_ADMIN_ROLE = 'super_admin';
const ORGANIZATION_ADMIN_ROLE = 'organization_admin';
type PermissionLike = { key?: string };
type MeLikeUser = {
roles?: { key?: string }[];
permissions?: PermissionLike[];
employee?:
| { position?: { permissions?: PermissionLike[] }; delegatedPositions?: { permissions?: PermissionLike[] }[] }
| { positions?: { permissions?: PermissionLike[] }[] }[]
| null;
};
export function isSuperAdmin(user: MeLikeUser | null | undefined): boolean {
return user?.roles?.some((r) => r.key === SUPER_ADMIN_ROLE) ?? false;
}
export function isOrganizationAdmin(user: MeLikeUser | null | undefined): boolean {
return user?.roles?.some((r) => r.key === ORGANIZATION_ADMIN_ROLE) ?? false;
}
export function collectPermissionKeys(user: MeLikeUser | null | undefined): string[] {
if (!user) return [];
const keys = new Set<string>();
for (const p of user.permissions ?? []) {
if (p.key) keys.add(p.key);
}
const employee = user.employee;
if (!employee) return [...keys];
if (Array.isArray(employee)) {
for (const emp of employee) {
for (const pos of emp.positions ?? []) {
for (const p of pos.permissions ?? []) {
if (p.key) keys.add(p.key);
}
}
}
return [...keys];
}
for (const p of employee.position?.permissions ?? []) {
if (p.key) keys.add(p.key);
}
for (const delegated of employee.delegatedPositions ?? []) {
for (const p of delegated.permissions ?? []) {
if (p.key) keys.add(p.key);
}
}
return [...keys];
}
export function hasPassengerPermission(
user: MeLikeUser | null | undefined,
permissionKey: string,
): boolean {
if (!user) return false;
if (isSuperAdmin(user) || isOrganizationAdmin(user)) return true;
return collectPermissionKeys(user).includes(permissionKey);
}
export function assertPassengerPermission(
user: MeLikeUser | null | undefined,
permissionKey: string,
): void {
if (hasPassengerPermission(user, permissionKey)) return;
throw new ForbiddenException(`Missing permission: ${permissionKey}`);
}