import { CanActivate, ExecutionContext, ForbiddenException, Injectable, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import type { UserRole } from '@mento/shared'; import type { Actor } from '../common/record-scope'; import { ROLES_KEY } from './roles.decorator'; const VIEW_AS_MESSAGE = 'در حالت مشاهده به‌جای کاربر دیگر امکان تغییر وجود ندارد.'; /** خواندن‌های صفحهٔ تیم وقتی مالک در حالت مشاهده است. */ function isTeamSettingsRead(method: string, url: string): boolean { if (method.toUpperCase() !== 'GET') return false; const path = (url.split('?')[0] || '').replace(/\/+$/, ''); return ( /\/users(\/|$)/.test(path) || /\/org-roles(\/|$)/.test(path) || /\/teams(\/|$)/.test(path) || /\/organizations\/current/.test(path) ); } function isViewAsAllowedMutation(method: string, url: string): boolean { const path = (url.split('?')[0] || '').replace(/\/+$/, '') || '/'; const m = method.toUpperCase(); if (m === 'POST' && (path.endsWith('/auth/view-as') || path.endsWith('/auth/view-as/stop'))) { return true; } if (m === 'PATCH' && path.endsWith('/auth/me/prior-archive')) { return true; } return false; } @Injectable() export class RolesGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest<{ user?: Actor; method?: string; originalUrl?: string; url?: string; }>(); const method = (request.method || 'GET').toUpperCase(); const url = request.originalUrl || request.url || ''; const actor = request.user; if ( actor?.viewAsReadOnly && method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && !isViewAsAllowedMutation(method, url) ) { throw new ForbiddenException(VIEW_AS_MESSAGE); } const roles = this.reflector.getAllAndOverride(ROLES_KEY, [ context.getHandler(), context.getClass(), ]); if (!roles?.length) return true; const role = actor?.role as UserRole | undefined; if (role && roles.includes(role)) return true; if ( actor?.viewAsReadOnly && actor.impersonatorRole === 'owner' && roles.includes('owner') && isTeamSettingsRead(method, url) ) { return true; } throw new ForbiddenException('دسترسی به این بخش برای نقش شما مجاز نیست'); } }