Add admin enhancements: impersonation, plan management, org status enforcement
Enhancement 1 - Block suspended/archived org access: - Add org status check in switchOrganization() (auth.service.ts) - Filter suspended/archived orgs from login response (generateTokenResponse) - Add org status guard with 60s cache in TenantMiddleware - Frontend: filter orgs in SelectOrgPage, add 403 handler in api.ts Enhancement 2 - Change tenant plan level: - Add updatePlanLevel() to organizations.service.ts - Add PUT /admin/organizations/:id/plan endpoint - Frontend: clickable plan dropdown in Organizations table + confirmation modal - Plan level Select in tenant detail drawer Enhancement 3 - User impersonation: - Add impersonateUser() to auth.service.ts with impersonatedBy JWT claim - Add POST /admin/impersonate/:userId endpoint - Frontend: Impersonate button in Users tab (disabled for admins) - Impersonation state management in authStore (start/stop/persist) - Orange impersonation banner in AppLayout header with stop button Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Controller, Get, Post, Put, Body, Param, UseGuards, Req, ForbiddenException, BadRequestException } from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from './guards/jwt-auth.guard';
|
||||
import { AuthService } from './auth.service';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { OrganizationsService } from '../organizations/organizations.service';
|
||||
import { AdminAnalyticsService } from './admin-analytics.service';
|
||||
@@ -12,6 +13,7 @@ import * as bcrypt from 'bcryptjs';
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class AdminController {
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private usersService: UsersService,
|
||||
private orgService: OrganizationsService,
|
||||
private analyticsService: AdminAnalyticsService,
|
||||
@@ -92,6 +94,23 @@ export class AdminController {
|
||||
return { success: true, organization: org };
|
||||
}
|
||||
|
||||
// ── Plan Level ──
|
||||
|
||||
@Put('organizations/:id/plan')
|
||||
async updateOrgPlan(
|
||||
@Req() req: any,
|
||||
@Param('id') id: string,
|
||||
@Body() body: { planLevel: string },
|
||||
) {
|
||||
await this.requireSuperadmin(req);
|
||||
const validPlans = ['standard', 'premium', 'enterprise'];
|
||||
if (!validPlans.includes(body.planLevel)) {
|
||||
throw new BadRequestException(`Invalid plan. Must be one of: ${validPlans.join(', ')}`);
|
||||
}
|
||||
const org = await this.orgService.updatePlanLevel(id, body.planLevel);
|
||||
return { success: true, organization: org };
|
||||
}
|
||||
|
||||
// ── Superadmin Toggle ──
|
||||
|
||||
@Post('users/:id/superadmin')
|
||||
@@ -101,6 +120,15 @@ export class AdminController {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// ── User Impersonation ──
|
||||
|
||||
@Post('impersonate/:userId')
|
||||
async impersonateUser(@Req() req: any, @Param('userId') userId: string) {
|
||||
await this.requireSuperadmin(req);
|
||||
const adminUserId = req.user.userId || req.user.sub;
|
||||
return this.authService.impersonateUser(adminUserId, userId);
|
||||
}
|
||||
|
||||
// ── Tenant Health ──
|
||||
|
||||
@Get('tenants-health')
|
||||
|
||||
Reference in New Issue
Block a user