63 lines
1.8 KiB
TypeScript
63 lines
1.8 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) {
|
|
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<AccessTokenPayload> {
|
|
const payload = await verifyAccessToken(jwt, authorization);
|
|
await assertSessionUnlocked(core, payload);
|
|
return payload;
|
|
}
|