fix and update

This commit is contained in:
lendry
2026-07-01 15:52:17 +03:00
parent de4310239c
commit f7c01a3963
8 changed files with 152 additions and 25 deletions

View File

@@ -246,4 +246,21 @@ export class FedcmController {
) {
return this.syncFedcmSessionFromAuthorization(authorization, res);
}
@Get('login-status')
@ApiOperation({
summary: 'FedCM Login Status (API origin)',
description:
'Минимальная HTML-страница на API-домене: Set-Login + navigator.login.setStatus для Chrome FedCM (origin login_url).'
})
async loginStatus(@Req() req: Request, @Res() res: Response) {
const session = await resolveFedcmSessionFromRequest(this.jwt, req.headers.cookie);
const loggedIn = Boolean(session);
applyFedcmLoginStatus(res, loggedIn);
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.setHeader('Cache-Control', 'no-store');
res.status(200).send(`<!DOCTYPE html><html lang="ru"><head><meta charset="utf-8"></head><body><script>
try{if(navigator.login&&navigator.login.setStatus){navigator.login.setStatus('${loggedIn ? 'logged-in' : 'logged-out'}');}}catch(e){}
</script></body></html>`);
}
}

View File

@@ -1,5 +1,6 @@
import type { Response } from 'express';
import type { CookieOptions, Response } from 'express';
import { JwtService } from '@nestjs/jwt';
import { resolveRegistrableDomain } from './public-url';
export const FEDCM_SESSION_COOKIE = 'lendry_fedcm_sess';
@@ -12,6 +13,10 @@ export interface FedcmSessionPayload {
function cookieSecureEnabled() {
if (process.env.FEDCM_COOKIE_SECURE === 'true') return true;
if (process.env.FEDCM_COOKIE_SECURE === 'false') return false;
const urls = [process.env.PUBLIC_API_URL, process.env.PUBLIC_FRONTEND_URL];
if (urls.some((url) => url?.trim().startsWith('https://'))) {
return true;
}
return process.env.NODE_ENV === 'production';
}
@@ -20,6 +25,52 @@ function cookieMaxAgeSeconds() {
return Number.isFinite(parsed) && parsed > 0 ? parsed : 2_592_000;
}
/** Общий домен cookie для api.* / sso.* / apex (FedCM accounts на api-домене). */
export function resolveFedcmCookieDomain(): string | undefined {
const explicit = process.env.FEDCM_COOKIE_DOMAIN?.trim();
if (explicit) {
if (['none', 'off', 'localhost'].includes(explicit.toLowerCase())) {
return undefined;
}
return explicit.startsWith('.') ? explicit : `.${explicit}`;
}
for (const raw of [process.env.PUBLIC_API_URL, process.env.PUBLIC_FRONTEND_URL]) {
const candidate = raw?.trim();
if (!candidate) continue;
try {
const hostname = new URL(candidate).hostname.toLowerCase();
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]') {
return undefined;
}
const apex = resolveRegistrableDomain(hostname);
if (apex.includes('.')) {
return `.${apex}`;
}
} catch {
continue;
}
}
return undefined;
}
function buildFedcmCookieOptions(maxAgeMs?: number): CookieOptions {
const secure = cookieSecureEnabled();
const options: CookieOptions = {
httpOnly: true,
secure,
sameSite: secure ? 'none' : 'lax',
path: '/',
maxAge: maxAgeMs ?? cookieMaxAgeSeconds() * 1000
};
const domain = resolveFedcmCookieDomain();
if (domain) {
options.domain = domain;
}
return options;
}
export async function signFedcmSessionPayload(jwt: JwtService, payload: FedcmSessionPayload) {
return jwt.signAsync(
{ sub: payload.sub, sessionId: payload.sessionId, pinVerified: payload.pinVerified, typ: 'fedcm_session' },
@@ -57,32 +108,19 @@ export async function setFedcmSessionCookie(res: Response, jwt: JwtService, payl
return;
}
const secure = cookieSecureEnabled();
const value = await signFedcmSessionPayload(jwt, payload);
if (res.headersSent) {
return;
}
res.cookie(FEDCM_SESSION_COOKIE, value, {
httpOnly: true,
secure,
sameSite: secure ? 'none' : 'lax',
path: '/',
maxAge: cookieMaxAgeSeconds() * 1000
});
res.cookie(FEDCM_SESSION_COOKIE, value, buildFedcmCookieOptions());
}
export function clearFedcmSessionCookie(res: Response) {
if (res.headersSent) {
return;
}
const secure = cookieSecureEnabled();
res.clearCookie(FEDCM_SESSION_COOKIE, {
httpOnly: true,
secure,
sameSite: secure ? 'none' : 'lax',
path: '/'
});
res.clearCookie(FEDCM_SESSION_COOKIE, buildFedcmCookieOptions(0));
}
export function readFedcmSessionCookie(cookieHeader?: string): string | null {