Files
IdP/apps/api-gateway/src/controllers/security.controller.ts
2026-06-29 22:51:25 +03:00

152 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Body, Controller, Get, Headers, Param, Post } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { CoreGrpcService } from '../core-grpc.service';
import { OptionalPinDto, PinDto, TotpCodeDto, VerifySecurityPinDto } from '../dto/security.dto';
import { resolveAuthorizedPayload } from '../session-auth';
@ApiTags('Безопасность')
@ApiBearerAuth()
@Controller('security')
export class SecurityController {
constructor(
private readonly core: CoreGrpcService,
private readonly jwt: JwtService
) {}
@Get('users/:userId/devices')
@ApiOperation({ summary: 'Активные устройства', description: 'Показывает устройства пользователя и связанные активные сессии. Текущее устройство не отображается.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiResponse({ status: 200, description: 'Список устройств получен' })
async listDevices(@Param('userId') userId: string, @Headers('authorization') authorization?: string) {
const payload = await resolveAuthorizedPayload(this.jwt, this.core, authorization);
return this.core.security.ListActiveDevices({ userId, exceptSessionId: payload.sessionId });
}
@Get('users/:userId/sessions')
@ApiOperation({ summary: 'Активные сессии', description: 'Возвращает ACTIVE и LOCKED сессии пользователя для управления устройствами.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiResponse({ status: 200, description: 'Список активных сессий получен' })
async listSessions(@Param('userId') userId: string, @Headers('authorization') authorization?: string) {
await resolveAuthorizedPayload(this.jwt, this.core, authorization);
return this.core.security.ListActiveSessions({ userId });
}
@Get('users/:userId/sign-in-history')
@ApiOperation({ summary: 'История входов', description: 'Показывает последние попытки входа и причины отказов.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiResponse({ status: 200, description: 'История входов получена' })
listHistory(@Param('userId') userId: string, @Headers('authorization') authorization?: string) {
return resolveAuthorizedPayload(this.jwt, this.core, authorization).then(() =>
this.core.security.ListSignInHistory({ userId })
);
}
@Get('users/:userId/totp/status')
@ApiOperation({ summary: 'Статус TOTP', description: 'Показывает, включена ли двухфакторная аутентификация через приложение.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
getTotpStatus(@Param('userId') userId: string) {
return this.core.security.GetTotpStatus({ userId });
}
@Post('users/:userId/totp/setup')
@ApiOperation({ summary: 'Настроить TOTP', description: 'Генерирует секрет и otpauth URL для Google Authenticator и аналогов.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
setupTotp(@Param('userId') userId: string) {
return this.core.security.SetupTotp({ userId });
}
@Post('users/:userId/totp/enable')
@ApiOperation({ summary: 'Включить TOTP', description: 'Подтверждает код из приложения и включает двухфакторную аутентификацию.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: TotpCodeDto })
enableTotp(@Param('userId') userId: string, @Body() dto: TotpCodeDto) {
return this.core.security.EnableTotp({ userId, code: dto.code });
}
@Post('users/:userId/totp/disable')
@ApiOperation({ summary: 'Отключить TOTP', description: 'Отключает двухфакторную аутентификацию после проверки кода.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: TotpCodeDto })
disableTotp(@Param('userId') userId: string, @Body() dto: TotpCodeDto) {
return this.core.security.DisableTotp({ userId, code: dto.code });
}
@Post('users/:userId/pin/setup')
@ApiOperation({ summary: 'Настроить PIN-код', description: 'Создает или включает PIN-код пользователя. PIN хранится только в виде bcrypt hash.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: PinDto })
@ApiResponse({ status: 201, description: 'PIN-код настроен' })
setupPin(@Param('userId') userId: string, @Body() dto: PinDto) {
return this.core.security.SetupPin({ userId, pin: dto.pin });
}
@Post('users/:userId/pin/update')
@ApiOperation({ summary: 'Обновить PIN-код', description: 'Меняет PIN-код и переводит активные сессии пользователя в LOCKED до повторной проверки.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: PinDto })
@ApiResponse({ status: 201, description: 'PIN-код обновлен' })
updatePin(@Param('userId') userId: string, @Body() dto: PinDto) {
return this.core.security.UpdatePin({ userId, pin: dto.pin });
}
@Post('users/:userId/pin/delete-request')
@ApiOperation({ summary: 'Запросить удаление PIN-кода', description: 'Запускает период ожидания перед отключением PIN-кода согласно SystemSetting.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: OptionalPinDto })
requestPinDeletion(@Param('userId') userId: string, @Body() dto: OptionalPinDto) {
return this.core.security.RequestPinDeletion({ userId, pin: dto.pin ?? '' });
}
@Post('users/:userId/pin/delete-cancel')
@ApiOperation({ summary: 'Отменить удаление PIN-кода', description: 'Отменяет запланированное удаление PIN-кода.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
cancelPinDeletion(@Param('userId') userId: string) {
return this.core.security.CancelPinDeletion({ userId });
}
@Post('pin/verify')
@ApiOperation({ summary: 'Проверить PIN-код', description: 'Проверяет PIN-код и возвращает access token с pinVerified=true для указанной сессии.' })
@ApiBody({ type: VerifySecurityPinDto })
@ApiResponse({ status: 201, description: 'PIN-код проверен' })
verifyPin(@Body() dto: VerifySecurityPinDto) {
return this.core.security.VerifyPinCode(dto);
}
@Post('users/:userId/sessions/:sessionId/lock')
@ApiOperation({ summary: 'Заблокировать сессию', description: 'Переводит активную сессию в LOCKED и сбрасывает pinVerified=false.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiParam({ name: 'sessionId', description: 'ID сессии' })
@ApiResponse({ status: 201, description: 'Сессия заблокирована' })
lockSession(@Param('userId') userId: string, @Param('sessionId') sessionId: string) {
return this.core.security.LockSession({ userId, sessionId });
}
@Post('users/:userId/sessions/:sessionId/revoke')
@ApiOperation({ summary: 'Выйти с устройства', description: 'Отзывает конкретную сессию пользователя и завершает доступ с выбранного устройства.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiParam({ name: 'sessionId', description: 'ID сессии' })
@ApiResponse({ status: 201, description: 'Сессия отозвана' })
revokeSession(@Param('userId') userId: string, @Param('sessionId') sessionId: string) {
return this.core.security.RevokeSession({ userId, sessionId });
}
@Post('users/:userId/devices/:deviceId/revoke')
@ApiOperation({ summary: 'Завершить сессии устройства', description: 'Отзывает все активные сессии выбранного устройства пользователя.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiParam({ name: 'deviceId', description: 'ID устройства' })
@ApiResponse({ status: 201, description: 'Сессии устройства завершены' })
revokeDevice(@Param('userId') userId: string, @Param('deviceId') deviceId: string) {
return this.core.security.RevokeDevice({ userId, deviceId });
}
@Post('users/:userId/revoke-all-sessions')
@ApiOperation({ summary: 'Выйти везде', description: 'Отзывает все активные и PIN-заблокированные сессии пользователя, кроме текущей.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiResponse({ status: 201, description: 'Остальные сессии отозваны' })
async revokeAll(@Param('userId') userId: string, @Headers('authorization') authorization?: string) {
const payload = await resolveAuthorizedPayload(this.jwt, this.core, authorization);
return this.core.security.RevokeAllSessions({ userId, exceptSessionId: payload.sessionId });
}
}