121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
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<AdminAuthRequest>();
|
|
|
|
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<string>('JWT_SECRET');
|
|
|
|
// Verify token with the secret
|
|
const payload = await this.jwtService.verifyAsync<IAdminTokenPayload>(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<string[]>(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;
|
|
}
|
|
}
|