All files / src/modules/auth jwt.strategy.ts

100% Statements 29/29
90.9% Branches 20/22
100% Functions 2/2
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 7413x 13x 13x 13x 13x       13x   29x 29x   30x 30x 30x 30x   30x 1x     29x                       147x   147x 4x   143x 143x   141x                       140x 2x       138x                   1x 1x      
import { Injectable }          from '@nestjs/common';
import { PassportStrategy }    from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { PrismaService } from '@app/modules/prisma/prisma.service';
import { Algorithm } from 'jsonwebtoken';
 
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    private prisma: PrismaService,
    private config: ConfigService
  ) {
    const publicKey = config.get<string>('JWT_PUBLIC_KEY')?.replace(/\\n/g, '\n');
    const algorithm = (config.get<string>('JWT_ALGORITHM') || 'RS256') as Algorithm;
    const issuer = config.get<string>('JWT_ISSUER');
    const audience = config.get<string>('JWT_AUDIENCE');
 
    if (!publicKey) {
      throw new Error('JWT_PUBLIC_KEY is required for JWT strategy');
    }
 
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      algorithms: [algorithm],
      secretOrKey: publicKey,
      issuer,
      audience,
    });
  }
 
  /** The payload becomes req.user */
  async validate(payload: any) {
    try {
      // Validate payload structure
      if (!payload || !payload.sub || (typeof payload.sub !== 'number' && typeof payload.sub !== 'string')) {
        return null;
      }
      const userId = typeof payload.sub === 'string' ? parseInt(payload.sub, 10) : payload.sub;
      if (!Number.isFinite(userId)) return null;
 
      const user = await this.prisma.client.user.findUnique({
        where: { id: userId },
        select: {
          id: true,
          email: true,
          role: true,
          tokenVersion: true,
          agencyId: true,
          developerCompanyId: true,
        }
      });
 
      if (!user || user.tokenVersion !== payload.tokenVersion) {
        return null;
      }
 
      // Return complete user context for authorization
      return {
        id: user.id,
        userId: user.id, // Backward compatibility
        email: user.email,
        role: user.role,
        agencyId: user.agencyId,
        developerCompanyId: user.developerCompanyId,
      };
    } catch (error) {
      // Log error but return null to trigger 401 instead of 500
      console.error('JWT strategy validation error:', error);
      return null;
    }
  }
}