Assist_Design/apps/bff/src/modules/auth/auth-admin.controller.ts
T. Narantuya a22b84f128 Refactor and clean up BFF and portal components for improved maintainability
- Removed deprecated files and components from the BFF application, including various auth and catalog services, enhancing code clarity.
- Updated package.json scripts for better organization and streamlined development processes.
- Refactored portal components to improve structure and maintainability, including the removal of unused files and components.
- Enhanced type definitions and imports across the application for consistency and clarity.
2025-09-18 14:52:26 +09:00

145 lines
3.9 KiB
TypeScript

import {
Controller,
Get,
Post,
Param,
UseGuards,
Query,
BadRequestException,
} from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from "@nestjs/swagger";
import { AdminGuard } from "./guards/admin.guard";
import { AuditService, AuditAction } from "@bff/infra/audit/audit.service";
import { UsersService } from "@bff/modules/users/users.service";
@ApiTags("auth-admin")
@ApiBearerAuth()
@UseGuards(AdminGuard)
@Controller("auth/admin")
export class AuthAdminController {
constructor(
private auditService: AuditService,
private usersService: UsersService
) {}
@Get("audit-logs")
@ApiOperation({ summary: "Get audit logs (admin only)" })
@ApiResponse({ status: 200, description: "Audit logs retrieved" })
async getAuditLogs(
@Query("page") page: string = "1",
@Query("limit") limit: string = "50",
@Query("action") action?: AuditAction,
@Query("userId") userId?: string
) {
const pageNum = parseInt(page, 10);
const limitNum = parseInt(limit, 10);
const skip = (pageNum - 1) * limitNum;
if (Number.isNaN(pageNum) || Number.isNaN(limitNum) || pageNum < 1 || limitNum < 1) {
throw new BadRequestException("Invalid pagination parameters");
}
const where: { action?: AuditAction; userId?: string } = {};
if (action) where.action = action;
if (userId) where.userId = userId;
const [logs, total] = await Promise.all([
this.auditService.prismaClient.auditLog.findMany({
where,
include: {
user: {
select: {
id: true,
email: true,
firstName: true,
lastName: true,
},
},
},
orderBy: { createdAt: "desc" },
skip,
take: limitNum,
}),
this.auditService.prismaClient.auditLog.count({ where }),
]);
return {
logs,
pagination: {
page: pageNum,
limit: limitNum,
total,
totalPages: Math.ceil(total / limitNum),
},
};
}
@Post("unlock-account/:userId")
@ApiOperation({ summary: "Unlock user account (admin only)" })
@ApiResponse({ status: 200, description: "Account unlocked" })
async unlockAccount(@Param("userId") userId: string) {
const user = await this.usersService.findById(userId);
if (!user) {
throw new BadRequestException("User not found");
}
await this.usersService.update(userId, {
failedLoginAttempts: 0,
lockedUntil: null,
});
await this.auditService.log({
userId,
action: AuditAction.ACCOUNT_UNLOCKED,
resource: "auth",
details: { adminAction: true, email: user.email },
success: true,
});
return { message: "Account unlocked successfully" };
}
@Get("security-stats")
@ApiOperation({ summary: "Get security statistics (admin only)" })
@ApiResponse({ status: 200, description: "Security stats retrieved" })
async getSecurityStats() {
const today = new Date(new Date().setHours(0, 0, 0, 0));
const [totalUsers, lockedAccounts, failedLoginsToday, successfulLoginsToday] =
await Promise.all([
this.auditService.prismaClient.user.count(),
this.auditService.prismaClient.user.count({
where: {
lockedUntil: {
gt: new Date(),
},
},
}),
this.auditService.prismaClient.auditLog.count({
where: {
action: AuditAction.LOGIN_FAILED,
createdAt: {
gte: today,
},
},
}),
this.auditService.prismaClient.auditLog.count({
where: {
action: AuditAction.LOGIN_SUCCESS,
createdAt: {
gte: today,
},
},
}),
]);
return {
totalUsers,
lockedAccounts,
failedLoginsToday,
successfulLoginsToday,
securityEventsToday: failedLoginsToday + successfulLoginsToday,
};
}
}