Hierarchy organizer! Architect Vitruvius has noticed that although all legionaries now have access to the camp, not everyone should have access to all rooms. It's time to learn Role-based Authorization - a system that ensures each legionary has access only to what their rank in the Roman army entitles them to!
Imagine you are a prefect responsible for order in the legionary camp. Every legionary has already passed through the gate (authentication), but now you must decide which parts of the camp they can access:
Authorization is the process of determining what an authenticated user can access.
1// enums/roles.enum.ts
2export enum Role {
3 TIRO = 'TIRO', // Recruit
4 MILES = 'MILES', // Soldier
5 DECANUS = 'DECANUS', // Leader of ten
6 OPTIO = 'OPTIO', // Deputy centurion
7 CENTURION = 'CENTURION', // Century commander
8 LEGATUS = 'LEGATUS', // Legion commander
9 ADMIN = 'ADMIN', // Super admin of the Empire
10}
11
12// Role hierarchy (higher ranks inherit permissions of lower ones)
13export const ROLE_HIERARCHY = {
14 [Role.ADMIN]: 100,
15 [Role.LEGATUS]: 90,
16 [Role.CENTURION]: 80,
17 [Role.OPTIO]: 70,
18 [Role.DECANUS]: 60,
19 [Role.MILES]: 50,
20 [Role.TIRO]: 40,
21};
22
23export function hasPermission(userRole: Role, requiredRole: Role): boolean {
24 return ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole];
25}1// auth/decorators/roles.decorator.ts
2import { SetMetadata } from '@nestjs/common';
3import { Role } from '../enums/roles.enum';
4
5export const ROLES_KEY = 'roles';
6export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
7
8// Aliases for convenience - Roman army ranks
9export const RequireLegatus = () => Roles(Role.LEGATUS);
10export const RequireCenturion = () => Roles(Role.CENTURION, Role.LEGATUS);
11export const RequireOptio = () => Roles(Role.OPTIO, Role.CENTURION, Role.LEGATUS);
12export const RequireDecanus = () => Roles(Role.DECANUS, Role.OPTIO, Role.CENTURION, Role.LEGATUS);
13export const RequireMiles = () => Roles(Role.MILES, Role.DECANUS, Role.OPTIO, Role.CENTURION, Role.LEGATUS);1// auth/guards/roles.guard.ts
2import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
3import { Reflector } from '@nestjs/core';
4import { Role, hasPermission } from '../enums/roles.enum';
5import { ROLES_KEY } from '../decorators/roles.decorator';
6import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
7
8@Injectable()
9export class RolesGuard implements CanActivate {
10 constructor(private reflector: Reflector) {}
11
12 canActivate(context: ExecutionContext): boolean {
13 // Check if the endpoint is public
14 const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
15 context.getHandler(),
16 context.getClass(),
17 ]);
18
19 if (isPublic) {
20 return true;
21 }
22
23 // Get the required roles
24 const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
25 context.getHandler(),
26 context.getClass(),
27 ]);
28
29 if (!requiredRoles) {
30 return true; // No role restrictions
31 }
32
33 const { user } = context.switchToHttp().getRequest();
34
35 if (!user) {
36 throw new ForbiddenException('No user information available!');
37 }
38
39 // Check if the user has one of the required roles
40 const hasRole = requiredRoles.some(role =>
41 hasPermission(user.role as Role, role)
42 );
43
44 if (!hasRole) {
45 throw new ForbiddenException(
46 `Insufficient permissions! Required role: ${requiredRoles.join(' or ')}. Your role: ${user.role}`
47 );
48 }
49
50 return true;
51 }
52}1// enums/permissions.enum.ts
2export enum Permission {
3 // User management
4 USER_CREATE = 'user:create',
5 USER_READ = 'user:read',
6 USER_UPDATE = 'user:update',
7 USER_DELETE = 'user:delete',
8
9 // Legion resource management
10 RESOURCE_CREATE = 'resource:create',
11 RESOURCE_READ = 'resource:read',
12 RESOURCE_UPDATE = 'resource:update',
13 RESOURCE_DELETE = 'resource:delete',
14 RESOURCE_TRANSFER = 'resource:transfer',
15
16 // Cohort management
17 COHORT_PATROL = 'cohort:patrol',
18 COHORT_COMMAND = 'cohort:command',
19 COHORT_MAINTENANCE = 'cohort:maintenance',
20
21 // Armory management
22 ARMORY_ACCESS = 'armory:access',
23 ARMORY_MAINTAIN = 'armory:maintain',
24
25 // Special permissions
26 ACCESS_PRAETORIUM = 'access:praetorium',
27 ACCESS_TREASURY = 'access:treasury',
28 ACCESS_ARMORY = 'access:armory',
29 ACCESS_STRATEGIC_ROOM = 'access:strategic_room',
30}
31
32// Mapping roles to permissions - Roman army hierarchy
33export const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
34 [Role.TIRO]: [
35 Permission.RESOURCE_READ,
36 ],
37
38 [Role.MILES]: [
39 Permission.RESOURCE_READ,
40 Permission.COHORT_MAINTENANCE,
41 ],
42
43 [Role.DECANUS]: [
44 Permission.RESOURCE_READ,
45 Permission.COHORT_MAINTENANCE,
46 Permission.ARMORY_ACCESS,
47 Permission.ARMORY_MAINTAIN,
48 Permission.ACCESS_ARMORY,
49 ],
50
51 [Role.OPTIO]: [
52 Permission.RESOURCE_READ,
53 Permission.RESOURCE_UPDATE,
54 Permission.RESOURCE_TRANSFER,
55 Permission.COHORT_MAINTENANCE,
56 Permission.ARMORY_ACCESS,
57 Permission.ACCESS_ARMORY,
58 Permission.ACCESS_TREASURY,
59 ],
60
61 [Role.CENTURION]: [
62 Permission.USER_READ,
63 Permission.USER_UPDATE,
64 Permission.RESOURCE_READ,
65 Permission.RESOURCE_UPDATE,
66 Permission.RESOURCE_TRANSFER,
67 Permission.COHORT_PATROL,
68 Permission.COHORT_COMMAND,
69 Permission.COHORT_MAINTENANCE,
70 Permission.ARMORY_ACCESS,
71 Permission.ARMORY_MAINTAIN,
72 Permission.ACCESS_ARMORY,
73 Permission.ACCESS_TREASURY,
74 Permission.ACCESS_STRATEGIC_ROOM,
75 ],
76
77 [Role.LEGATUS]: Object.values(Permission), // All permissions
78
79 [Role.ADMIN]: Object.values(Permission), // All permissions
80};
81
82export function hasPermission(userRole: Role, permission: Permission): boolean {
83 return ROLE_PERMISSIONS[userRole]?.includes(permission) || false;
84}
85
86export function getUserPermissions(userRole: Role): Permission[] {
87 return ROLE_PERMISSIONS[userRole] || [];
88}1// auth/decorators/permissions.decorator.ts
2import { SetMetadata } from '@nestjs/common';
3import { Permission } from '../enums/permissions.enum';
4
5export const PERMISSIONS_KEY = 'permissions';
6export const RequirePermissions = (...permissions: Permission[]) =>
7 SetMetadata(PERMISSIONS_KEY, permissions);
8
9// Aliases for common legion permissions
10export const CanReadResources = () => RequirePermissions(Permission.RESOURCE_READ);
11export const CanManageResources = () => RequirePermissions(
12 Permission.RESOURCE_CREATE,
13 Permission.RESOURCE_UPDATE,
14 Permission.RESOURCE_DELETE
15);
16export const CanAccessArmory = () => RequirePermissions(Permission.ARMORY_ACCESS);
17export const CanCommandCohort = () => RequirePermissions(Permission.COHORT_COMMAND);1// auth/guards/permissions.guard.ts
2import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
3import { Reflector } from '@nestjs/core';
4import { Permission, hasPermission } from '../enums/permissions.enum';
5import { Role } from '../enums/roles.enum';
6import { PERMISSIONS_KEY } from '../decorators/permissions.decorator';
7import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
8
9@Injectable()
10export class PermissionsGuard implements CanActivate {
11 constructor(private reflector: Reflector) {}
12
13 canActivate(context: ExecutionContext): boolean {
14 const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
15 context.getHandler(),
16 context.getClass(),
17 ]);
18
19 if (isPublic) {
20 return true;
21 }
22
23 const requiredPermissions = this.reflector.getAllAndOverride<Permission[]>(PERMISSIONS_KEY, [
24 context.getHandler(),
25 context.getClass(),
26 ]);
27
28 if (!requiredPermissions || requiredPermissions.length === 0) {
29 return true;
30 }
31
32 const { user } = context.switchToHttp().getRequest();
33
34 if (!user) {
35 throw new ForbiddenException('No user information available!');
36 }
37
38 // Check if the user has all required permissions
39 const hasAllPermissions = requiredPermissions.every(permission =>
40 hasPermission(user.role as Role, permission)
41 );
42
43 if (!hasAllPermissions) {
44 const missingPermissions = requiredPermissions.filter(permission =>
45 !hasPermission(user.role as Role, permission)
46 );
47
48 throw new ForbiddenException(
49 `Missing permissions: ${missingPermissions.join(', ')}. Contact the centurion!`
50 );
51 }
52
53 return true;
54 }
55}1// auth/guards/resource-owner.guard.ts
2import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
3import { Reflector } from '@nestjs/core';
4import { Role } from '../enums/roles.enum';
5
6@Injectable()
7export class ResourceOwnerGuard implements CanActivate {
8 constructor(private reflector: Reflector) {}
9
10 canActivate(context: ExecutionContext): boolean {
11 const request = context.switchToHttp().getRequest();
12 const { user, params } = request;
13
14 if (!user) {
15 throw new ForbiddenException('No user information available!');
16 }
17
18 // Legati and admins have access to everything
19 if (user.role === Role.LEGATUS || user.role === Role.ADMIN) {
20 return true;
21 }
22
23 // Check if the user is trying to access their own resources
24 const resourceUserId = params.userId || params.id;
25
26 if (resourceUserId && user.id.toString() === resourceUserId.toString()) {
27 return true;
28 }
29
30 throw new ForbiddenException('You can only manage your own resources!');
31 }
32}1// auth/guards/context-aware.guard.ts
2import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
3import { ResourceService } from '../../resource/resource.service';
4import { Role } from '../enums/roles.enum';
5
6@Injectable()
7export class ResourceOwnerContextGuard implements CanActivate {
8 constructor(private resourceService: ResourceService) {}
9
10 async canActivate(context: ExecutionContext): Promise<boolean> {
11 const request = context.switchToHttp().getRequest();
12 const { user, params } = request;
13
14 if (!user) {
15 throw new ForbiddenException('No user information available!');
16 }
17
18 // Legati have access to all resources in the legion
19 if (user.role === Role.LEGATUS || user.role === Role.ADMIN) {
20 return true;
21 }
22
23 const resourceId = params.resourceId || params.id;
24
25 if (!resourceId) {
26 return true; // If there is no resource ID, allow to continue
27 }
28
29 try {
30 const resource = await this.resourceService.findById(resourceId);
31
32 if (!resource) {
33 throw new ForbiddenException('Resource not found!');
34 }
35
36 // Check if the user is the resource owner
37 if (resource.ownerId === user.id) {
38 return true;
39 }
40
41 // Check if the user is in the same cohort as the resource
42 if (user.cohortId === resource.cohortId &&
43 [Role.CENTURION, Role.OPTIO].includes(user.role as Role)) {
44 return true;
45 }
46
47 throw new ForbiddenException('You do not have access to this resource!');
48 } catch (error) {
49 throw new ForbiddenException('Error while checking resource permissions');
50 }
51 }
52}1// controllers/resource.controller.ts
2import {
3 Controller,
4 Get,
5 Post,
6 Put,
7 Delete,
8 Body,
9 Param,
10 UseGuards
11} from '@nestjs/common';
12import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
13import { RolesGuard } from '../auth/guards/roles.guard';
14import { PermissionsGuard } from '../auth/guards/permissions.guard';
15import { ResourceOwnerContextGuard } from '../auth/guards/context-aware.guard';
16import { Roles, RequireOptio } from '../auth/decorators/roles.decorator';
17import { RequirePermissions, CanReadResources } from '../auth/decorators/permissions.decorator';
18import { CurrentUser } from '../auth/decorators/current-user.decorator';
19import { Role } from '../auth/enums/roles.enum';
20import { Permission } from '../auth/enums/permissions.enum';
21
22@Controller('resources')
23@UseGuards(JwtAuthGuard, RolesGuard, PermissionsGuard)
24export class ResourceController {
25 constructor(private resourceService: ResourceService) {}
26
27 // All legionaries can browse resources
28 @Get()
29 @CanReadResources()
30 async getAllResources(@CurrentUser() user: any) {
31 return await this.resourceService.findAllForUser(user);
32 }
33
34 // Only optio and above can add resources
35 @Post()
36 @RequireOptio()
37 @RequirePermissions(Permission.RESOURCE_CREATE)
38 async createResource(@Body() createResourceDto: any, @CurrentUser() user: any) {
39 return await this.resourceService.create(createResourceDto, user);
40 }
41
42 // Only the resource owner or higher ranks can edit
43 @Put(':id')
44 @UseGuards(ResourceOwnerContextGuard)
45 @RequirePermissions(Permission.RESOURCE_UPDATE)
46 async updateResource(
47 @Param('id') id: string,
48 @Body() updateResourceDto: any,
49 @CurrentUser() user: any,
50 ) {
51 return await this.resourceService.update(+id, updateResourceDto, user);
52 }
53
54 // Only legatus can delete resources
55 @Delete(':id')
56 @Roles(Role.LEGATUS)
57 @RequirePermissions(Permission.RESOURCE_DELETE)
58 async deleteResource(@Param('id') id: string) {
59 return await this.resourceService.remove(+id);
60 }
61
62 // Resource transfer between cohorts - special permissions
63 @Post(':id/transfer')
64 @UseGuards(ResourceOwnerContextGuard)
65 @RequirePermissions(Permission.RESOURCE_TRANSFER)
66 async transferResource(
67 @Param('id') id: string,
68 @Body('targetUserId') targetUserId: number,
69 @CurrentUser() user: any,
70 ) {
71 return await this.resourceService.transfer(+id, targetUserId, user);
72 }
73}1// app.module.ts
2import { Module } from '@nestjs/common';
3import { APP_GUARD } from '@nestjs/core';
4import { JwtAuthGuard } from './auth/guards/jwt-auth.guard';
5import { RolesGuard } from './auth/guards/roles.guard';
6
7@Module({
8 // ... other imports
9 providers: [
10 // Global guards - will be applied to all endpoints
11 {
12 provide: APP_GUARD,
13 useClass: JwtAuthGuard,
14 },
15 {
16 provide: APP_GUARD,
17 useClass: RolesGuard,
18 },
19 // ... other providers
20 ],
21})
22export class AppModule {}1// auth/policies/resource.policy.ts
2import { Injectable } from '@nestjs/common';
3import { Role } from '../enums/roles.enum';
4
5export interface PolicyContext {
6 user: any;
7 resource?: any;
8 action: string;
9}
10
11@Injectable()
12export class ResourcePolicy {
13 canRead(context: PolicyContext): boolean {
14 const { user, resource } = context;
15
16 // Everyone can read basic resource information
17 if (!resource) return true;
18
19 // Owner can read their resources
20 if (resource.ownerId === user.id) return true;
21
22 // Higher ranks can read resources in their cohort
23 if (user.cohortId === resource.cohortId &&
24 [Role.OPTIO, Role.CENTURION, Role.LEGATUS].includes(user.role)) {
25 return true;
26 }
27
28 return false;
29 }
30
31 canUpdate(context: PolicyContext): boolean {
32 const { user, resource } = context;
33
34 // Only the owner or centurion+ can edit
35 if (resource.ownerId === user.id) return true;
36
37 if (user.cohortId === resource.cohortId &&
38 [Role.OPTIO, Role.CENTURION, Role.LEGATUS].includes(user.role)) {
39 return true;
40 }
41
42 return false;
43 }
44
45 canDelete(context: PolicyContext): boolean {
46 const { user, resource } = context;
47
48 // Only legatus can delete legion resources
49 return user.role === Role.LEGATUS && user.cohortId === resource.cohortId;
50 }
51
52 canTransfer(context: PolicyContext): boolean {
53 const { user, resource } = context;
54
55 // Owner can transfer their resources
56 if (resource.ownerId === user.id) return true;
57
58 // Optio can transfer resources in their cohort
59 if (user.cohortId === resource.cohortId &&
60 [Role.OPTIO, Role.CENTURION, Role.LEGATUS].includes(user.role)) {
61 return true;
62 }
63
64 return false;
65 }
66}Implement a permissions system for the Empire's military campaigns:
1// Your code here
2// 1. Define roles for the campaign system (Tiro, Miles, Decanus, Optio, Centurion, Legatus)
3// 2. Create permissions for campaign management (planning, approving, canceling)
4// 3. Implement a context-aware guard checking cohort membership
5// 4. Create a policy for different actions on campaigns
6// 5. Add decorators for convenient use in controllersWell done! You have been appointed chief legion prefect! Now you can:
✓ Implement RBAC with Roman army rank hierarchy ✓ Manage permissions at the individual action level ✓ Create Guards for different authorization scenarios ✓ Implement resource-based and context-aware authorization ✓ Use decorators for readable and maintainable code ✓ Design policy-based systems for complex business rules
Now every legionary knows their place in the hierarchy and has access only to what their rank entitles them to!