import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { ConfigService } from '@nestjs/config'; import { TenantPrismaService } from '../prisma/tenant-prisma.service'; import { AuditService } from '../audit/audit.service'; @Injectable() export class ImpersonateService { private readonly mainJwtSecret: string; constructor( private readonly tenantDb: TenantPrismaService, private readonly jwt: JwtService, private readonly config: ConfigService, private readonly audit: AuditService, ) { // Use the MAIN API's JWT secret so the token works with the tenant API this.mainJwtSecret = this.config.get('MAIN_API_JWT_SECRET', 'your-secret-key'); } async startImpersonation(tenantId: string, adminId: string, adminName: string) { const tenant = await this.tenantDb.tenant.findUnique({ where: { id: tenantId, deletedAt: null, isActive: true }, }); if (!tenant) throw new NotFoundException('Tenant not found or inactive'); // Find the tenant's first admin user const adminUser = await this.tenantDb.user.findFirst({ where: { tenantId, deletedAt: null, isActive: true, roles: { some: { role: 'tenant_admin' } }, }, }); if (!adminUser) throw new NotFoundException('No tenant admin found for this tenant'); // Generate a JWT compatible with the main API const token = this.jwt.sign( { sub: adminUser.id, tenantId, roles: ['tenant_admin'], permissions: 'all', impersonatedBy: adminId, }, { secret: this.mainJwtSecret, expiresIn: '1h', }, ); // Audit the impersonation await this.audit.log(adminId, 'impersonate.start', tenantId, { tenantName: tenant.name, impersonatedUserId: adminUser.id, }); return { accessToken: token, impersonatedUser: { id: adminUser.id, email: adminUser.email, firstName: adminUser.firstName, lastName: adminUser.lastName, }, tenant: { id: tenant.id, name: tenant.name, slug: tenant.slug, }, expiresAt: new Date(Date.now() + 3600000).toISOString(), }; } async endImpersonation(adminId: string) { await this.audit.log(adminId, 'impersonate.end'); return { message: 'Impersonation ended' }; } }