import { BadRequestException } from '@nestjs/common'; import type { Request, Response } from 'express'; export function normalizeFedcmOrigin(origin?: string) { return origin?.trim().replace(/\/+$/, '') || undefined; } export function applyFedcmCorsHeaders(res: Response, origin?: string) { const normalized = normalizeFedcmOrigin(origin); if (!normalized) { return; } res.setHeader('Access-Control-Allow-Origin', normalized); res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader('Vary', 'Origin'); } export function requireFedcmCorsOrigin(origin?: string) { const normalized = normalizeFedcmOrigin(origin); if (!normalized) { throw new BadRequestException('FedCM требует заголовок Origin'); } return normalized; } 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, Authorization, Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site' ); res.setHeader('Access-Control-Max-Age', '86400'); } /** * FedCM-запросы должны иметь Sec-Fetch-Dest: webidentity (или empty, если прокси * не пробрасывает заголовок). Блокируем только явно «не-FedCM» dest (document, * script, image…) — иначе well-known/config отдают 400 и One Tap ломается. */ const BLOCKED_FEDCM_FETCH_DEST = new Set([ 'document', 'embed', 'frame', 'iframe', 'object', 'script', 'style', 'image', 'font', 'audio', 'video', 'track', 'worker', 'serviceworker', 'sharedworker', 'manifest', 'xslt' ]); export function assertFedcmWebIdentityRequest(req: Request) { const dest = String(req.headers['sec-fetch-dest'] ?? '').toLowerCase(); if (dest && BLOCKED_FEDCM_FETCH_DEST.has(dest)) { throw new BadRequestException('Недопустимый Sec-Fetch-Dest для FedCM'); } } export function applyFedcmLoginStatus(res: Response, loggedIn: boolean) { res.setHeader('Set-Login', loggedIn ? 'logged-in' : 'logged-out'); }