DEV Community

Dr.A. Ali
Dr.A. Ali

Posted on

Building Multi-Role Authorization for a Property Platform with NestJS Guards

At Amlaki (amlakire.com), we have 6 user types on the same platform. How do we build flexible, precise authorization?

The Challenge

Each user type has different permissions. Agency manages everything. Independent owner manages only their properties. Linked owner is read-only. Tenant sees only their data.

The Solution: Custom Guards + Decorators

1. Permission Decorator

import { SetMetadata } from '@nestjs/common';export const RequirePermissions = (...permissions: string[]) =>
  SetMetadata('permissions', permissions);
Enter fullscreen mode Exit fullscreen mode

2. Permissions Guard

@Injectable()export class PermissionsGuard implements CanActivate {
  async canActivate(context: ExecutionContext) {
    const permissions = this.reflector.get(
      'permissions', context.getHandler()
    );
    const { user } = context.switchToHttp().getRequest();

    // Full access
    if (['SUPER_ADMIN', 'AGENCY_ADMIN'].includes(user.userType))
      return true;

    // Owner = full on own properties
    if (user.userType === 'OWNER') {
      const allowed = ['properties.*', 'units.*', 'tenants.*',
        'contracts.*', 'payments.*', 'reports.view'];
      return permissions.every(p =>
        allowed.some(a => this.matches(p, a)));
    }

    // Linked owner = read only
    if (user.userType === 'LINKED_OWNER')
      return permissions.every(p => p.endsWith('.view'));

    // Staff = role-based
    if (user.userType === 'AGENCY_STAFF')
      return this.staffService.hasPermission(user.sub, permissions);

    return false;
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Controller Usage

@Controller('properties')@UseGuards(JwtAuthGuard, PermissionsGuard)
export class PropertiesController {
  @Post()  @RequirePermissions('properties.create')  create() { /* Owner + Agency only */ }

  @Get()  @RequirePermissions('properties.view')  findAll() { /* Everyone except tenant */ }
}
Enter fullscreen mode Exit fullscreen mode

4. Data Isolation in Service

async findAll(user: UserPayload) {
  const where: any = {};
  if (user.userType === 'AGENCY_ADMIN')
    where.agencyId = user.agencyId;
  else if (user.userType === 'OWNER')
    where.owner = { userId: user.sub };
  return this.prisma.property.findMany({ where });
}
Enter fullscreen mode Exit fullscreen mode

Result

  • 6 user types on the same codebase
  • Each sees only what they're authorized to
  • Permissions checked at two layers: Guard (HTTP) + Service (Data)

Built at amlakire.com

Top comments (0)