import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { ALLOW_VIEWER_KEY } from '../decorators/allow-viewer.decorator'; @Injectable() export class WriteAccessGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); const method = request.method; // Allow all read methods if (['GET', 'HEAD', 'OPTIONS'].includes(method)) return true; // Determine role from either req.userRole (set by TenantMiddleware which runs // before guards) or req.user.role (set by JwtAuthGuard Passport strategy). const role = request.userRole || request.user?.role; if (!role) return true; // unauthenticated endpoints like login/register // Check for @AllowViewer() exemption on handler or class const allowViewer = this.reflector.getAllAndOverride(ALLOW_VIEWER_KEY, [ context.getHandler(), context.getClass(), ]); if (allowViewer) return true; // Block viewer role from write operations if (role === 'viewer') { throw new ForbiddenException('Read-only users cannot modify data'); } // Block writes for past_due organizations (grace period: read-only access) if (request.orgPastDue) { throw new ForbiddenException( 'Your subscription is past due. Please update your payment method to continue making changes.', ); } return true; } }