fix and update
This commit is contained in:
@@ -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>`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user