DEV Community

Dr.A. Ali
Dr.A. Ali

Posted on

بناء نظام صلاحيات متعدد الأدوار لمنصة عقارية بـ NestJS Guards

في أملاكي (amlakire.com)، عندنا ٦ أنواع مستخدمين على نفس المنصة. كيف نبني نظام صلاحيات مرن ودقيق؟

التحدي

كل نوع مستخدم له صلاحيات مختلفة. المكتب يدير كل شيء. المالك المستقل يدير عقاراته فقط. المالك التابع يقرأ فقط. المستأجر يرى بياناته فقط.

الحل: Custom Guards + Decorators

1. Permission Decorator

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

2. Permissions Guard

// guards/permissions.guard.ts
@Injectable()export class PermissionsGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const permissions = this.reflector.get(
      'permissions', context.getHandler()
    );
    if (!permissions) return true;

    const { user } = context.switchToHttp().getRequest();

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

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

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

    // AGENCY_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() {}

  @Get()  @RequirePermissions('properties.view')  findAll() {}

  @Delete(':id')  @RequirePermissions('properties.delete')  delete() {}
}
Enter fullscreen mode Exit fullscreen mode

4. Data Isolation in Service

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

النتيجة

  • ٦ أنواع مستخدمين على نفس الكود
  • كل واحد يرى فقط ما يحق له
  • الصلاحيات تُفحص في طبقتين: Guard (HTTP) + Service (Data)

مبني في amlakire.com

Top comments (0)