import { Injectable, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { PrismaService } from '../prisma/prisma.service'; export type JwtPayload = { sub: string; organizationId: string; role: string; }; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor( config: ConfigService, private readonly prisma: PrismaService, ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: config.get('JWT_SECRET') ?? 'dev-secret', }); } async validate(payload: JwtPayload & { typ?: string; mobile?: string }) { if (payload.typ === 'portal' || payload.typ === 'org-select') { throw new UnauthorizedException(); } if (!payload.sub || !payload.organizationId) { throw new UnauthorizedException(); } const user = await this.prisma.user.findUnique({ where: { id: payload.sub }, select: { id: true, isActive: true }, }); if (!user || user.isActive === false) { throw new UnauthorizedException(); } const membership = await this.prisma.membership.findFirst({ where: { userId: user.id, organizationId: payload.organizationId, isActive: true, }, select: { organizationId: true, role: true }, }); if (!membership) { throw new UnauthorizedException(); } return { userId: user.id, organizationId: membership.organizationId, role: membership.role, }; } }