Files
IdP/apps/api-gateway/src/session-auth.ts
2026-07-01 23:27:48 +03:00

68 lines
1.9 KiB
TypeScript

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<AccessTokenPayload> {
const token = extractBearerToken(authorization);
try {
return await jwt.verifyAsync<AccessTokenPayload>(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,
touchActivity = true
) {
if (!payload.sessionId) {
return;
}
const validation = (await firstValueFrom(
core.auth.ValidateSession({
userId: payload.sub,
sessionId: payload.sessionId,
touchActivity
})
)) 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,
touchActivity = true
): Promise<AccessTokenPayload> {
const payload = await verifyAccessToken(jwt, authorization);
await assertSessionUnlocked(core, payload, touchActivity);
return payload;
}