import { Injectable, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import type { DataVisibility } from '@mento/shared'; import { BillingService } from '../billing/billing.service'; import { normalizeDataVisibility } from '../common/record-scope'; import { PrismaService } from '../prisma/prisma.service'; import { canViewAllRecords } from '@mento/shared'; export type JwtPayload = { sub: string; organizationId: string; role: string; }; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor( config: ConfigService, private readonly prisma: PrismaService, private readonly billing: BillingService, ) { 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' || payload.typ === 'platform-admin' ) { 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, organization: { isActive: true }, }, select: { organizationId: true, role: true, dataVisibility: true }, }); if (!membership) { throw new UnauthorizedException(); } await this.billing.requireActiveForOrganization(membership.organizationId); const dataVisibility = normalizeDataVisibility(membership.dataVisibility); const visibleUserIds = await this.resolveVisibleUserIds( membership.organizationId, user.id, membership.role, dataVisibility, ); return { userId: user.id, organizationId: membership.organizationId, role: membership.role, dataVisibility, visibleUserIds, }; } private async resolveVisibleUserIds( organizationId: string, userId: string, role: string, dataVisibility: DataVisibility, ): Promise { if (canViewAllRecords(role) || dataVisibility === 'org') { return null; } if (dataVisibility === 'own') { return [userId]; } const memberships = await this.prisma.teamMember.findMany({ where: { userId, team: { organizationId }, }, select: { teamId: true }, }); if (!memberships.length) return [userId]; const peers = await this.prisma.teamMember.findMany({ where: { teamId: { in: memberships.map((m) => m.teamId) }, }, select: { userId: true }, }); return [...new Set([userId, ...peers.map((p) => p.userId)])]; } }