import { ForbiddenException, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { firstValueFrom } from 'rxjs'; import { extractBearerToken } from './auth-token'; import { CoreGrpcService } from './core-grpc.service'; export interface AccessTokenPayload { sub: string; sessionId?: string; pinVerified?: boolean; isSuperAdmin?: boolean; } export async function verifyAccessToken(jwt: JwtService, authorization?: string): Promise { const token = extractBearerToken(authorization); try { return await jwt.verifyAsync(token, { secret: process.env.JWT_ACCESS_SECRET ?? 'docker-access-secret', issuer: 'id.lendry.ru' }); } catch { throw new UnauthorizedException({ statusCode: 401, message: 'Токен доступа недействителен или истёк', code: 'TOKEN_EXPIRED' }); } } export async function assertSessionUnlocked(core: CoreGrpcService, payload: AccessTokenPayload) { if (!payload.sessionId) { return; } const validation = (await firstValueFrom( core.auth.ValidateSession({ userId: payload.sub, sessionId: payload.sessionId, touchActivity: true }) )) as { requiresPin: boolean; sessionId: string }; if (validation.requiresPin) { throw new ForbiddenException({ statusCode: 403, message: 'Требуется подтверждение PIN-кода', code: 'PIN_REQUIRED', sessionId: validation.sessionId }); } } export async function resolveAuthorizedPayload( jwt: JwtService, core: CoreGrpcService, authorization?: string ): Promise { const payload = await verifyAccessToken(jwt, authorization); await assertSessionUnlocked(core, payload); return payload; }