diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index c81aa7ac..823696ac 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -1,23 +1,10 @@ import { Module } from '@nestjs/common'; import { PassportModule } from '@nestjs/passport'; import { JwtStrategy } from '@/common/guards/keycloak.strategy'; -import { RbacJwtStrategy } from '@/common/guards/rbac.strategy'; -/** - * Auth module — mirrors user-microservice auth pattern. - * - * Auth flow: - * 1. User authenticates via user-microservice (Keycloak login → /auth/login) - * 2. User gets RBAC token from user-microservice (/auth/rbac/token) - * 3. Survey service validates both tokens: - * - Authorization: Bearer → validated by JwtStrategy (RSA public key) - * - rbac_token: → validated by RbacJwtStrategy (RBAC_JWT_SECRET) - * - * No login/logout endpoints here — those live in user-microservice. - */ @Module({ imports: [PassportModule], - providers: [JwtStrategy, RbacJwtStrategy], - exports: [JwtStrategy, RbacJwtStrategy], + providers: [JwtStrategy], + exports: [JwtStrategy], }) export class AuthModule {} diff --git a/src/common/guards/keycloak.strategy.ts b/src/common/guards/keycloak.strategy.ts index 465ab2f0..2b699e02 100644 --- a/src/common/guards/keycloak.strategy.ts +++ b/src/common/guards/keycloak.strategy.ts @@ -1,15 +1,19 @@ import { ExtractJwt, Strategy } from 'passport-jwt'; import { PassportStrategy } from '@nestjs/passport'; -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, 'jwt-keycloak') { constructor(configService: ConfigService) { + const publicKey = configService.get('KEYCLOAK_REALM_RSA_PUBLIC_KEY'); + new Logger('JwtStrategy').log( + `KEYCLOAK_REALM_RSA_PUBLIC_KEY loaded: ${publicKey ? publicKey.substring(0, 80) + '...' : 'NOT SET / EMPTY'}`, + ); super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, - secretOrKey: configService.get('KEYCLOAK_REALM_RSA_PUBLIC_KEY'), + secretOrKey: publicKey, }); } diff --git a/src/common/guards/rbac.guard.ts b/src/common/guards/rbac.guard.ts deleted file mode 100644 index f20def7f..00000000 --- a/src/common/guards/rbac.guard.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ExecutionContext, Injectable } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { AuthGuard } from '@nestjs/passport'; -import { Observable } from 'rxjs'; - -@Injectable() -export class RbacAuthGuard extends AuthGuard('jwt-rbac') { - constructor(private readonly reflector: Reflector) { - super(); - } - - canActivate( - context: ExecutionContext, - ): boolean | Promise | Observable { - // Required permissions come from @Permissions() decorator for each API endpoint - const requiredPermissions = this.reflector.get( - 'permissions', - context.getHandler(), - ); - - const payload = super.getRequest(context).user; - - if (!requiredPermissions) { - return true; // No permissions required, allow access - } - - payload.requiredPermissions = requiredPermissions; - return super.canActivate(context); - } -} diff --git a/src/common/guards/rbac.strategy.ts b/src/common/guards/rbac.strategy.ts deleted file mode 100644 index 2ebd7b84..00000000 --- a/src/common/guards/rbac.strategy.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ExtractJwt, Strategy } from 'passport-jwt'; -import { PassportStrategy } from '@nestjs/passport'; -import { Injectable, UnauthorizedException } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; - -@Injectable() -export class RbacJwtStrategy extends PassportStrategy(Strategy, 'jwt-rbac') { - constructor(private configService: ConfigService) { - super({ - jwtFromRequest: ExtractJwt.fromHeader('rbac_token'), - ignoreExpiration: false, - secretOrKey: configService.get('RBAC_JWT_SECRET'), - passReqToCallback: true, - }); - } - - async validate(request: any, payload: any) { - const requiredPermissions = request.user.requiredPermissions; - - const userPermissions = payload.userData.privileges; - const roles = payload.userData.roles; - - // Admin role bypasses permission checks - if (roles.includes('admin')) { - return payload; - } - - if ( - !( - payload.iss === this.configService.get('ISSUER') && - payload.aud === this.configService.get('AUDIENCE') && - userPermissions.length > 0 - ) - ) { - throw new UnauthorizedException(); - } - - const isAuthorized = requiredPermissions.every((permission: string) => - userPermissions.includes(permission), - ); - - if (isAuthorized) { - return payload; - } - - throw new UnauthorizedException(); - } -} diff --git a/src/config/auth.config.ts b/src/config/auth.config.ts index 3e454577..23701126 100644 --- a/src/config/auth.config.ts +++ b/src/config/auth.config.ts @@ -4,11 +4,6 @@ export default registerAs('auth', () => ({ // Keycloak JWT validation keycloakRsaPublicKey: process.env.KEYCLOAK_REALM_RSA_PUBLIC_KEY || '', - // RBAC JWT validation - rbacJwtSecret: process.env.RBAC_JWT_SECRET || '', - rbacIssuer: process.env.RBAC_JWT_ISSUER || 'survey-service', - rbacAudience: process.env.RBAC_JWT_AUDIENCE || 'survey-service', - // Legacy (kept for backward compatibility) jwtSecret: process.env.JWT_SECRET || 'default-secret-change-me', jwtExpiration: parseInt(process.env.JWT_EXPIRATION || '3600', 10), diff --git a/src/main.ts b/src/main.ts index c818b2dc..4041a7c4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -31,7 +31,6 @@ async function bootstrap() { 'Authorization', 'tenantid', 'academicyearid', - 'rbac_token', ], });