import { Injectable, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import type { AuthUser, JwtAccessPayload, } from '../../../common/auth/auth-user'; import { UsersService } from '../../users/users.service'; import { RevokedAccessTokensRepository } from '../revoked-access-tokens.repository'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { constructor( config: ConfigService, private readonly usersService: UsersService, private readonly revokedAccessTokensRepository: RevokedAccessTokensRepository, ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: config.getOrThrow('JWT_ACCESS_SECRET'), algorithms: ['HS256'], }); } async validate(payload: JwtAccessPayload): Promise { if (payload.typ !== 'access' || !payload.sub || !payload.jti) { throw new UnauthorizedException('Invalid access token'); } const revoked = await this.revokedAccessTokensRepository.exists( payload.jti, ); if (revoked) { throw new UnauthorizedException('Access token revoked'); } const user = await this.usersService.findById(payload.sub); if (!user) { throw new UnauthorizedException('User not found'); } this.usersService.assertCanAuthenticate(user); return { id: user.id, username: user.username, jti: payload.jti, isSuperadmin: user.isSuperadmin, }; } }