66 lines
3.4 KiB
TypeScript
66 lines
3.4 KiB
TypeScript
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 });
|
||
}
|
||
}
|