138 lines
3.7 KiB
TypeScript
138 lines
3.7 KiB
TypeScript
|
|
import { Controller, Get, Post, Body, Param, UseGuards, Query } from '@nestjs/common';
|
||
|
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||
|
|
import { JwtAuthGuard } from './guards/jwt-auth.guard';
|
||
|
|
import { AdminGuard } from './guards/admin.guard';
|
||
|
|
import { AuditService, AuditAction } from '../common/audit/audit.service';
|
||
|
|
import { UsersService } from '../users/users.service';
|
||
|
|
|
||
|
|
@ApiTags('auth-admin')
|
||
|
|
@ApiBearerAuth()
|
||
|
|
@UseGuards(JwtAuthGuard, 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;
|
||
|
|
|
||
|
|
const where: any = {};
|
||
|
|
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 Error('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,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|