import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, Inject, Logger, ForbiddenException, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Request } from 'express'; import { JwtService } from '@nestjs/jwt'; import { ConfigService } from '@nestjs/config'; import { IAdminTokenPayload } from '../interfaces/IToken-payload'; import { PERMISSIONS_KEY } from '../../../../common/decorators/permissions.decorator'; import { PermissionsService } from 'src/modules/roles/providers/permissions.service'; export interface AdminAuthRequest extends Request { adminId: string; restId: string; } @Injectable() export class AdminAuthGuard implements CanActivate { private readonly logger = new Logger(AdminAuthGuard.name); constructor( @Inject(JwtService) private readonly jwtService: JwtService, @Inject(ConfigService) private readonly configService: ConfigService, private readonly permissionsService: PermissionsService, private readonly reflector: Reflector, ) { } async canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request); if (!token) { this.logger.warn('No token provided', { hasAuthHeader: !!request.headers.authorization, authHeader: request.headers.authorization ? 'present' : 'missing', headers: Object.keys(request.headers), }); throw new UnauthorizedException('No token provided'); } try { // Get the JWT secret from config const secret = this.configService.getOrThrow('JWT_SECRET'); // Verify token with the secret const payload = await this.jwtService.verifyAsync(token, { secret, }); if (!payload.adminId || !payload.restId) { this.logger.error('Invalid token payload structure', payload); throw new UnauthorizedException('Invalid token payload'); } request['adminId'] = payload.adminId; request['restId'] = payload.restId; // check if the user has the required permissions const requiredPermissions = this.reflector.getAllAndOverride(PERMISSIONS_KEY, [context.getHandler(), context.getClass()]) ?? []; if (!requiredPermissions || requiredPermissions.length === 0) { return true; } const adminPermission = await this.permissionsService.getAdminPermissions(payload.adminId, payload.restId); if (!adminPermission || !Array.isArray(adminPermission)) { this.logger.error('No permissions found', { adminId: payload.adminId, restId: payload.restId }); throw new ForbiddenException('No permissions found'); } const hasPermission = requiredPermissions.every(p => adminPermission.includes(p)); if (!hasPermission) { this.logger.warn('Insufficient permissions', { adminId: payload.adminId, restId: payload.restId, required: requiredPermissions, has: adminPermission, }); throw new ForbiddenException('You are not authorized to access this resource'); } return true; } catch (err) { if (err instanceof ForbiddenException || err instanceof UnauthorizedException) { throw err; } const errorMessage = err instanceof Error ? err.message : 'Unknown error'; const errorStack = err instanceof Error ? err.stack : undefined; this.logger.error('Token verification error in AdminAuthGuard', { error: errorMessage, stack: errorStack, }); throw new UnauthorizedException('Invalid or expired token'); } } private extractTokenFromHeader(request: Request): string | undefined { const authHeader = request.headers.authorization || request.headers['authorization']; if (!authHeader) { return undefined; } const [type, token] = authHeader.split(' '); if (type?.toLowerCase() !== 'bearer' || !token) { return undefined; } return token; } }