fix and update

This commit is contained in:
lendry
2026-07-01 14:34:55 +03:00
parent 55deb5c152
commit 0c9b8e2629
6 changed files with 137 additions and 19 deletions

View File

@@ -1,13 +1,18 @@
import type { Request } from 'express';
import { firstValueFrom } from 'rxjs';
import { CoreGrpcService } from '../core-grpc.service';
import { resolveFrontendUrl, resolveOAuthIssuer } from './oauth-issuer';
import {
resolveFrontendUrl,
resolveOAuthIssuer,
resolveProjectDomainApiUrl,
resolveProjectDomainFrontendUrl
} from './oauth-issuer';
import {
isLocalDevBaseUrl,
isValidFedcmEndpointUrl,
normalizePublicBaseUrl,
preferCanonicalBase,
resolveFedcmWebIdentityOrigin,
resolvePublicApiBaseFromRequest,
resolvePublicFrontendBaseFromRequest
pickBestPublicBase,
resolveFedcmWebIdentityOrigin
} from './public-url';
export interface FedcmEndpoints {
@@ -39,17 +44,44 @@ function buildFedcmEndpointUrls(issuer: string, frontendUrl: string) {
};
}
export async function resolveFedcmEndpoints(core: CoreGrpcService, req?: Request): Promise<FedcmEndpoints> {
const storedIssuer = await resolveOAuthIssuer(core);
const storedFrontend = await resolveFrontendUrl(core);
// ВАЖНО для FedCM: configUrl должен быть стабильным независимо от того, с какого
// хоста пришёл запрос. well-known запрашивается браузером всегда с eTLD+1
// (например https://idpmvk.lpr/.well-known/web-identity), а discover.json — с
// поддомена (https://sso.idpmvk.lpr/idp-api/...). Оба обязаны вернуть ОДИН и тот
// же configUrl, иначе браузер отвергнет провайдера. Поэтому берём канонический
// публичный URL из настроек, а не из текущего хоста запроса.
const issuer = preferCanonicalBase(storedIssuer, resolvePublicApiBaseFromRequest(req));
const frontendUrl = preferCanonicalBase(storedFrontend, resolvePublicFrontendBaseFromRequest(req));
async function resolveCanonicalFedcmIssuer(core: CoreGrpcService): Promise<string> {
const stored = await resolveOAuthIssuer(core);
const fromProject = await resolveProjectDomainApiUrl(core);
const issuer = pickBestPublicBase(
[stored, fromProject],
stored
);
if (isValidFedcmEndpointUrl(issuer)) {
return issuer;
}
if (fromProject && isValidFedcmEndpointUrl(fromProject)) {
return fromProject;
}
return issuer;
}
async function resolveCanonicalFedcmFrontend(core: CoreGrpcService): Promise<string> {
const stored = await resolveFrontendUrl(core);
const fromProject = await resolveProjectDomainFrontendUrl(core);
const frontend = pickBestPublicBase(
[stored, fromProject],
stored
);
if (isValidFedcmEndpointUrl(frontend) && !isLocalDevBaseUrl(frontend)) {
return frontend;
}
if (fromProject && isValidFedcmEndpointUrl(fromProject)) {
return fromProject;
}
return frontend;
}
export async function resolveFedcmEndpoints(core: CoreGrpcService, _req?: Request): Promise<FedcmEndpoints> {
// FedCM: web-identity (idpmvk.lpr) и config.json (api.idpmvk.lpr) ОБЯЗАНЫ
// возвращать одинаковые login_url / accounts_endpoint / configUrl.
// Хост запроса разный — никогда не выводим URL из req.headers.host.
const issuer = await resolveCanonicalFedcmIssuer(core);
const frontendUrl = await resolveCanonicalFedcmFrontend(core);
let projectName = 'MVK ID';
try {

View File

@@ -51,7 +51,8 @@ export async function resolveOAuthIssuer(core: CoreGrpcService, fallback = 'http
}
const fromDomain = await resolveProjectDomainUrl(core, true);
return pickBestPublicBase([fromDb, envIssuer, fromDomain], fallback);
// env первым: в Docker .env перекрывает seed localhost в БД после первого деплоя.
return pickBestPublicBase([envIssuer, fromDb, fromDomain], fallback);
}
export async function resolveFrontendUrl(core: CoreGrpcService, fallback = 'http://localhost:3002'): Promise<string> {
@@ -65,7 +66,15 @@ export async function resolveFrontendUrl(core: CoreGrpcService, fallback = 'http
}
const fromDomain = await resolveProjectDomainUrl(core, false);
return pickBestPublicBase([fromDb, envFrontend, fromDomain], fallback);
return pickBestPublicBase([envFrontend, fromDb, fromDomain], fallback);
}
export async function resolveProjectDomainFrontendUrl(core: CoreGrpcService): Promise<string | undefined> {
return resolveProjectDomainUrl(core, false);
}
export async function resolveProjectDomainApiUrl(core: CoreGrpcService): Promise<string | undefined> {
return resolveProjectDomainUrl(core, true);
}
export function buildOpenIdConfiguration(issuer: string) {

View File

@@ -47,6 +47,25 @@ export function isBrowserReachableBaseUrl(url: string) {
}
}
/** Абсолютный HTTPS (или localhost HTTP) URL для FedCM login_url / configUrl. */
export function isValidFedcmEndpointUrl(url: string) {
try {
const parsed = new URL(url);
if (!['http:', 'https:'].includes(parsed.protocol)) {
return false;
}
if (isInternalHostname(parsed.hostname)) {
return false;
}
if (isLocalDevBaseUrl(url)) {
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
}
return parsed.protocol === 'https:';
} catch {
return false;
}
}
export function readRequestHost(req?: Request): string | undefined {
if (!req) {
return undefined;