import { Injectable, UnauthorizedException } from "@nestjs/common"; import { PassportStrategy } from "@nestjs/passport"; import { ExtractJwt, Strategy } from "passport-jwt"; import { ConfigService } from "@nestjs/config"; import type { AuthenticatedUser } from "@customer-portal/domain/auth"; import { UsersService } from "@bff/modules/users/users.service"; import { mapPrismaUserToDomain } from "@bff/infra/mappers"; import type { Request } from "express"; const cookieExtractor = (req: Request): string | null => { const cookieSource: unknown = Reflect.get(req, "cookies"); if (!cookieSource || typeof cookieSource !== "object") { return null; } const token = Reflect.get(cookieSource, "access_token") as unknown; return typeof token === "string" && token.length > 0 ? token : null; }; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor( private configService: ConfigService, private readonly usersService: UsersService ) { const jwtSecret = configService.get("JWT_SECRET"); if (!jwtSecret) { throw new Error("JWT_SECRET is required in environment variables"); } const options = { jwtFromRequest: ExtractJwt.fromExtractors([ ExtractJwt.fromAuthHeaderAsBearerToken(), cookieExtractor, ]), ignoreExpiration: false, secretOrKey: jwtSecret, }; super(options); } async validate(payload: { sub: string; email: string; role: string; iat?: number; exp?: number; }): Promise { // Validate payload structure if (!payload.sub || !payload.email) { throw new Error("Invalid JWT payload"); } const prismaUser = await this.usersService.findByIdInternal(payload.sub); if (!prismaUser) { throw new UnauthorizedException("User not found"); } if (prismaUser.email !== payload.email) { throw new UnauthorizedException("Token subject does not match user record"); } const profile = mapPrismaUserToDomain(prismaUser); return profile; } }