import { Body, Controller, Get, Headers, Param, Post, Req } from '@nestjs/common'; import type { Request } from 'express'; import { enrichAuthClientMeta } from '../client-request.util'; import { JwtService } from '@nestjs/jwt'; import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; import { CoreGrpcService } from '../core-grpc.service'; import { QrSessionDto, WebAuthnDto } from '../dto/identity.dto'; import { resolveAuthorizedPayload } from '../session-auth'; @ApiTags('Биометрия и QR-вход') @Controller('auth/advanced') export class AdvancedAuthController { constructor( private readonly core: CoreGrpcService, private readonly jwt: JwtService ) {} @Post('webauthn/register/challenge') @ApiOperation({ summary: 'Challenge регистрации WebAuthn', description: 'Создает challenge для регистрации лица/отпечатка. Endpoint готов для подключения настоящего WebAuthn attestation.' }) @ApiBody({ type: WebAuthnDto }) @ApiResponse({ status: 201, description: 'Challenge создан' }) webAuthnRegister(@Body() dto: WebAuthnDto) { return this.core.advancedAuth.CreateWebAuthnRegistrationChallenge(dto); } @Post('webauthn/login/challenge') @ApiOperation({ summary: 'Challenge входа WebAuthn', description: 'Создает challenge для входа по лицу или отпечатку.' }) @ApiBody({ type: WebAuthnDto }) @ApiResponse({ status: 201, description: 'Challenge создан' }) webAuthnLogin(@Body() dto: WebAuthnDto) { return this.core.advancedAuth.CreateWebAuthnLoginChallenge(dto); } @Post('qr/session') @ApiOperation({ summary: 'Создать QR-сессию', description: 'Создает временную QR-сессию для входа с другого устройства.' }) @ApiBody({ type: QrSessionDto }) @ApiResponse({ status: 201, description: 'QR-сессия создана' }) createQr(@Body() dto: QrSessionDto, @Req() req: Request) { return this.core.advancedAuth.CreateQrSession( enrichAuthClientMeta(req, { deviceName: dto.deviceName, fingerprint: dto.fingerprint, deviceType: dto.deviceType ?? 'WEB' }) ); } @Get('qr/session/:sessionId') @ApiOperation({ summary: 'Проверить QR-сессию', description: 'Возвращает текущий статус QR-сессии: PENDING/APPROVED/EXPIRED.' }) @ApiParam({ name: 'sessionId', description: 'ID QR-сессии' }) @ApiResponse({ status: 200, description: 'Статус QR-сессии получен' }) pollQr(@Param('sessionId') sessionId: string) { return this.core.advancedAuth.PollQrSession({ sessionId }); } @Post('qr/session/:sessionId/approve') @ApiBearerAuth() @ApiOperation({ summary: 'Подтвердить QR-вход', description: 'Подтверждает QR-сессию с мобильного приложения уже авторизованным пользователем.' }) @ApiParam({ name: 'sessionId', description: 'ID QR-сессии' }) @ApiResponse({ status: 201, description: 'QR-сессия подтверждена' }) async approveQr(@Param('sessionId') sessionId: string, @Headers('authorization') authorization?: string) { const payload = await resolveAuthorizedPayload(this.jwt, this.core, authorization); return this.core.advancedAuth.ApproveQrSession({ sessionId, userId: payload.sub }); } }