import type { Request } from 'express'; import { firstValueFrom } from 'rxjs'; import { CoreGrpcService } from '../core-grpc.service'; import { resolveFrontendUrl, resolveOAuthIssuer } from './oauth-issuer'; import { hostsMatch, normalizePublicBaseUrl, preferBrowserReachableBase, readRequestHost, readRequestProto, resolvePublicApiBaseFromRequest, resolvePublicFrontendBaseFromRequest } from './public-url'; export interface FedcmEndpoints { issuer: string; frontendUrl: string; projectName: string; configUrl: string; accountsEndpoint: string; clientMetadataEndpoint: string; idAssertionEndpoint: string; loginUrl: string; signupUrl: string; webIdentityUrl: string; } function buildFedcmEndpointUrls(issuer: string, frontendUrl: string) { const base = normalizePublicBaseUrl(issuer); const front = normalizePublicBaseUrl(frontendUrl); let webIdentityOrigin = front; try { webIdentityOrigin = new URL(base).origin; } catch { // keep frontend origin } return { configUrl: `${base}/fedcm/config.json`, accountsEndpoint: `${base}/fedcm/accounts`, clientMetadataEndpoint: `${base}/fedcm/client_metadata`, idAssertionEndpoint: `${base}/fedcm/id_assertion`, loginUrl: `${front}/auth/login`, signupUrl: `${front}/auth/register`, webIdentityUrl: `${webIdentityOrigin}/.well-known/web-identity` }; } function resolveSameOriginFrontend(storedFrontend: string, req?: Request): string { const fromRequest = resolvePublicFrontendBaseFromRequest(req); const preferred = preferBrowserReachableBase(storedFrontend, fromRequest); if (preferred !== storedFrontend) { return preferred; } const host = readRequestHost(req); if (!host) { return storedFrontend; } const requestOrigin = normalizePublicBaseUrl(`${readRequestProto(req)}://${host}`); try { const frontendUrl = new URL(storedFrontend); const requestUrl = new URL(requestOrigin); if (hostsMatch(frontendUrl.hostname, requestUrl.hostname)) { return requestOrigin; } } catch { return storedFrontend; } return storedFrontend; } export function resolveSameOriginIssuer(storedIssuer: string, storedFrontend: string, req?: Request): string { const fromRequest = resolvePublicApiBaseFromRequest(req); const preferred = preferBrowserReachableBase(storedIssuer, fromRequest); if (preferred !== storedIssuer) { return preferred; } const host = readRequestHost(req); if (!host) { return storedIssuer; } const requestOrigin = normalizePublicBaseUrl(`${readRequestProto(req)}://${host}`); const sameOriginApi = `${requestOrigin}/idp-api`; try { const issuerUrl = new URL(storedIssuer); const frontendUrl = new URL(storedFrontend); const requestUrl = new URL(requestOrigin); const hostMatches = hostsMatch(issuerUrl.hostname, requestUrl.hostname) || hostsMatch(frontendUrl.hostname, requestUrl.hostname); if (!hostMatches) { return storedIssuer; } if (normalizePublicBaseUrl(storedIssuer) !== sameOriginApi) { return sameOriginApi; } } catch { return fromRequest ?? storedIssuer; } return storedIssuer; } export async function resolveFedcmEndpoints(core: CoreGrpcService, req?: Request): Promise { const storedIssuer = await resolveOAuthIssuer(core); const storedFrontend = await resolveFrontendUrl(core); const frontendUrl = resolveSameOriginFrontend(storedFrontend, req); const issuer = resolveSameOriginIssuer(storedIssuer, storedFrontend, req); let projectName = 'MVK ID'; try { const setting = (await firstValueFrom(core.settings.GetSetting({ key: 'PROJECT_NAME' }))) as { value?: string }; if (setting.value?.trim()) { projectName = setting.value.trim(); } } catch { // fallback } return { issuer, frontendUrl, projectName, ...buildFedcmEndpointUrls(issuer, frontendUrl) }; } export function buildFedcmProviderConfig(endpoints: FedcmEndpoints) { return { accounts_endpoint: endpoints.accountsEndpoint, client_metadata_endpoint: endpoints.clientMetadataEndpoint, id_assertion_endpoint: endpoints.idAssertionEndpoint, login_url: endpoints.loginUrl, branding: { background_color: '#ffffff', color: '#3390ec', icons: [{ url: `${endpoints.frontendUrl}/favicon.ico`, size: 32 }] } }; } export function buildFedcmWebIdentityManifest(endpoints: FedcmEndpoints) { return { provider_urls: [endpoints.configUrl], accounts_endpoint: endpoints.accountsEndpoint, login_url: endpoints.loginUrl }; } export function buildFedcmDiscoverPayload(endpoints: FedcmEndpoints, enabled = true) { return { enabled, apiBase: endpoints.issuer, frontendUrl: endpoints.frontendUrl, projectName: endpoints.projectName, configUrl: endpoints.configUrl, webIdentityUrl: endpoints.webIdentityUrl, accountsEndpoint: endpoints.accountsEndpoint, loginUrl: endpoints.loginUrl }; } async function readOneTapEnabled(core: CoreGrpcService): Promise { try { const setting = (await firstValueFrom(core.settings.GetSetting({ key: 'ONE_TAP_ENABLED' }))) as { value?: string }; const raw = setting.value?.trim().toLowerCase(); if (!raw) return true; return ['true', '1', 'yes'].includes(raw); } catch { return true; } } export { readOneTapEnabled };