Files
RayLab-Core/src/core/auth/guards/jwt-auth.guard.ts
T
Rayyan 7ce0de4e91 feat(identity): redesign identity module and introduce RBAC foundation
- Redesign the Identity module with a richer domain model.
- Extend the User entity to support username, Authentik integration, activity tracking, and storage information.
- Add Role and Permission domain models with many-to-many relationships.
- Implement RBAC foundation using UserRole, RolePermission, and UserPermission mappings.
- Add user storage quota and usage fields with default values.
- Introduce Authentik identifiers and synchronization metadata.
- Refactor user domain logic for role and permission management.
- Update Prisma schema to support the new identity architecture.
- Improve JWT authentication and permission guard integration.
- Update repositories, handlers, controllers, mappers, DTOs, and Swagger configuration.
- Refresh environment configuration and project dependencies.
2026-08-02 00:25:36 +07:00

85 lines
2.5 KiB
TypeScript

import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { Request } from 'express';
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { IdentityData } from '../interfaces/identity-data';
import * as jwt from 'jsonwebtoken';
/**
* JwtAuthGuard verifies JWTs issued by the external Identity Provider (Authentik)
* using JWKS (RS256). It also accepts internal RayLab JWTs signed with a local secret.
*/
@Injectable()
export class JwtAuthGuard implements CanActivate {
private jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
constructor() {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request & { identity?: IdentityData }>();
const authHeader = request.headers.authorization;
if (!authHeader) {
throw new UnauthorizedException('Authorization header is missing.');
}
const [type, token] = authHeader.split(' ');
if (type !== 'Bearer' || !token) {
throw new UnauthorizedException('Invalid authorization header.');
}
const jwksUri = process.env.AUTHENTIK_JWKS_URI;
// First try verifying with external JWKS (Authentik)
if (jwksUri) {
try {
if (!this.jwks) this.jwks = createRemoteJWKSet(new URL(jwksUri));
const { payload } = await jwtVerify(token, this.jwks, {
issuer: process.env.AUTHENTIK_ISSUER,
audience: process.env.AUTHENTIK_AUDIENCE,
});
const identity = new IdentityData(
payload.sub as string,
(payload as any).preferred_username as string | undefined,
(payload as any).email as string | undefined,
payload as Record<string, any>,
);
request.identity = identity;
return true;
} catch (err) {
// ignore and try internal verification
}
}
// Fallback: verify with internal symmetric secret
const secret = process.env.RAYLAB_JWT_SECRET;
if (!secret) {
throw new UnauthorizedException('Invalid or expired token.');
}
try {
const payload = jwt.verify(token, secret) as any;
const identity = new IdentityData(
payload.sub as string,
payload.preferred_username as string | undefined,
payload.email as string | undefined,
payload as Record<string, any>,
);
request.identity = identity;
return true;
} catch (err: any) {
throw new UnauthorizedException('Invalid or expired token.');
}
}
}