fix and update
This commit is contained in:
128
apps/api-gateway/src/lib/fedcm-cookie.ts
Normal file
128
apps/api-gateway/src/lib/fedcm-cookie.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import type { Response } from 'express';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
export const FEDCM_SESSION_COOKIE = 'lendry_fedcm_sess';
|
||||
|
||||
export interface FedcmSessionPayload {
|
||||
sub: string;
|
||||
sessionId: string;
|
||||
pinVerified: boolean;
|
||||
}
|
||||
|
||||
function cookieSecureEnabled() {
|
||||
if (process.env.FEDCM_COOKIE_SECURE === 'true') return true;
|
||||
if (process.env.FEDCM_COOKIE_SECURE === 'false') return false;
|
||||
return process.env.NODE_ENV === 'production';
|
||||
}
|
||||
|
||||
function cookieMaxAgeSeconds() {
|
||||
const parsed = Number(process.env.FEDCM_COOKIE_MAX_AGE ?? 2_592_000);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : 2_592_000;
|
||||
}
|
||||
|
||||
export async function signFedcmSessionPayload(jwt: JwtService, payload: FedcmSessionPayload) {
|
||||
return jwt.signAsync(
|
||||
{ sub: payload.sub, sessionId: payload.sessionId, pinVerified: payload.pinVerified, typ: 'fedcm_session' },
|
||||
{
|
||||
secret: process.env.JWT_ACCESS_SECRET ?? 'docker-access-secret',
|
||||
expiresIn: cookieMaxAgeSeconds(),
|
||||
issuer: 'id.lendry.ru'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function verifyFedcmSessionToken(jwt: JwtService, token: string): Promise<FedcmSessionPayload | null> {
|
||||
try {
|
||||
const payload = await jwt.verifyAsync<{ sub: string; sessionId: string; pinVerified?: boolean; typ?: string }>(token, {
|
||||
secret: process.env.JWT_ACCESS_SECRET ?? 'docker-access-secret',
|
||||
issuer: 'id.lendry.ru'
|
||||
});
|
||||
if (payload.typ !== 'fedcm_session' || !payload.sub || !payload.sessionId) {
|
||||
return null;
|
||||
}
|
||||
if (payload.pinVerified === false) {
|
||||
return null;
|
||||
}
|
||||
return { sub: payload.sub, sessionId: payload.sessionId, pinVerified: true };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function setFedcmSessionCookie(res: Response, jwt: JwtService, payload: FedcmSessionPayload) {
|
||||
if (!payload.sub || !payload.sessionId || payload.pinVerified === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const secure = cookieSecureEnabled();
|
||||
const value = await signFedcmSessionPayload(jwt, payload);
|
||||
|
||||
res.cookie(FEDCM_SESSION_COOKIE, value, {
|
||||
httpOnly: true,
|
||||
secure,
|
||||
sameSite: secure ? 'none' : 'lax',
|
||||
path: '/',
|
||||
maxAge: cookieMaxAgeSeconds() * 1000
|
||||
});
|
||||
}
|
||||
|
||||
export function clearFedcmSessionCookie(res: Response) {
|
||||
const secure = cookieSecureEnabled();
|
||||
res.clearCookie(FEDCM_SESSION_COOKIE, {
|
||||
httpOnly: true,
|
||||
secure,
|
||||
sameSite: secure ? 'none' : 'lax',
|
||||
path: '/'
|
||||
});
|
||||
}
|
||||
|
||||
export function readFedcmSessionCookie(cookieHeader?: string): string | null {
|
||||
if (!cookieHeader) return null;
|
||||
const parts = cookieHeader.split(';');
|
||||
for (const part of parts) {
|
||||
const [rawKey, ...rawValue] = part.trim().split('=');
|
||||
if (rawKey === FEDCM_SESSION_COOKIE) {
|
||||
const value = rawValue.join('=').trim();
|
||||
return value || null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function resolveFedcmSessionFromRequest(
|
||||
jwt: JwtService,
|
||||
cookieHeader?: string
|
||||
): Promise<FedcmSessionPayload | null> {
|
||||
const token = readFedcmSessionCookie(cookieHeader);
|
||||
if (!token) return null;
|
||||
return verifyFedcmSessionToken(jwt, token);
|
||||
}
|
||||
|
||||
export function attachFedcmCookieFromAuthResult(
|
||||
res: Response,
|
||||
jwt: JwtService,
|
||||
result: unknown
|
||||
): void {
|
||||
if (!result || typeof result !== 'object') return;
|
||||
const data = result as {
|
||||
accessToken?: string;
|
||||
sessionId?: string;
|
||||
pinVerified?: boolean;
|
||||
requiresPin?: boolean;
|
||||
user?: { id?: string };
|
||||
};
|
||||
|
||||
if (data.requiresPin || data.pinVerified === false || !data.sessionId) {
|
||||
clearFedcmSessionCookie(res);
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = data.user?.id;
|
||||
if (!userId) return;
|
||||
|
||||
void setFedcmSessionCookie(res, jwt, {
|
||||
sub: userId,
|
||||
sessionId: data.sessionId,
|
||||
pinVerified: true
|
||||
});
|
||||
}
|
||||
16
apps/api-gateway/src/lib/fedcm-cors.ts
Normal file
16
apps/api-gateway/src/lib/fedcm-cors.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { Response } from 'express';
|
||||
|
||||
export function applyFedcmCorsHeaders(res: Response, origin?: string) {
|
||||
if (origin) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
res.setHeader('Vary', 'Origin');
|
||||
}
|
||||
}
|
||||
|
||||
export function applyFedcmPreflightHeaders(res: Response, origin?: string) {
|
||||
applyFedcmCorsHeaders(res, origin);
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site');
|
||||
res.setHeader('Access-Control-Max-Age', '86400');
|
||||
}
|
||||
Reference in New Issue
Block a user