initial: standalone repo from monorepo split
This commit is contained in:
23
src/impersonate/impersonate.controller.ts
Normal file
23
src/impersonate/impersonate.controller.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Controller, Post, Delete, Param, Req } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { ImpersonateService } from './impersonate.service';
|
||||
|
||||
@Controller('impersonate')
|
||||
export class ImpersonateController {
|
||||
constructor(private readonly service: ImpersonateService) {}
|
||||
|
||||
@Post(':tenantId')
|
||||
start(@Param('tenantId') tenantId: string, @Req() req: Request & { admin: any }) {
|
||||
const admin = req.admin;
|
||||
return this.service.startImpersonation(
|
||||
tenantId,
|
||||
admin.sub,
|
||||
`${admin.firstName || ''} ${admin.lastName || ''}`.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
end(@Req() req: Request & { admin: any }) {
|
||||
return this.service.endImpersonation(req.admin.sub);
|
||||
}
|
||||
}
|
||||
12
src/impersonate/impersonate.module.ts
Normal file
12
src/impersonate/impersonate.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ImpersonateController } from './impersonate.controller';
|
||||
import { ImpersonateService } from './impersonate.service';
|
||||
import { AuditModule } from '../audit/audit.module';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
|
||||
@Module({
|
||||
imports: [AuditModule, AuthModule],
|
||||
controllers: [ImpersonateController],
|
||||
providers: [ImpersonateService],
|
||||
})
|
||||
export class ImpersonateModule {}
|
||||
80
src/impersonate/impersonate.service.ts
Normal file
80
src/impersonate/impersonate.service.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
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<string>('JWT_SECRET', 'dev-jwt-secret-not-for-production');
|
||||
}
|
||||
|
||||
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' };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user