diff --git a/apps/api-gateway/src/lib/oauth-issuer.ts b/apps/api-gateway/src/lib/oauth-issuer.ts index cb9a128..6a89d6a 100644 --- a/apps/api-gateway/src/lib/oauth-issuer.ts +++ b/apps/api-gateway/src/lib/oauth-issuer.ts @@ -1,6 +1,6 @@ import { firstValueFrom } from 'rxjs'; import { CoreGrpcService } from '../core-grpc.service'; -import { normalizePublicBaseUrl, pickBestPublicBase, isInternalHostname } from './public-url'; +import { normalizePublicBaseUrl, pickBestPublicBase, isInternalHostname, normalizeDedicatedApiIssuer } from './public-url'; function normalizeBaseUrl(url: string) { return normalizePublicBaseUrl(url); @@ -52,7 +52,8 @@ export async function resolveOAuthIssuer(core: CoreGrpcService, fallback = 'http const fromDomain = await resolveProjectDomainUrl(core, true); // env первым: в Docker .env перекрывает seed localhost в БД после первого деплоя. - return pickBestPublicBase([envIssuer, fromDb, fromDomain], fallback); + const picked = pickBestPublicBase([envIssuer, fromDb, fromDomain], fallback); + return normalizeDedicatedApiIssuer(picked); } export async function resolveFrontendUrl(core: CoreGrpcService, fallback = 'http://localhost:3002'): Promise { diff --git a/apps/api-gateway/src/lib/public-url.ts b/apps/api-gateway/src/lib/public-url.ts index 586de6b..9cbe8c3 100644 --- a/apps/api-gateway/src/lib/public-url.ts +++ b/apps/api-gateway/src/lib/public-url.ts @@ -17,6 +17,25 @@ export function normalizePublicBaseUrl(url: string) { return url.trim().replace(/\/+$/, ''); } +/** + * На выделенном API-домене (api.example.com) gateway слушает в корне (/fedcm, /oauth). + * Префикс /idp-api нужен только на SSO-домене. Убираем лишний суффикс из PUBLIC_API_URL. + */ +export function normalizeDedicatedApiIssuer(url: string) { + const normalized = normalizePublicBaseUrl(url); + try { + const parsed = new URL(normalized); + const host = parsed.hostname.toLowerCase(); + const path = parsed.pathname.replace(/\/+$/, '') || '/'; + if (host.startsWith('api.') && (path === '/idp-api' || path.endsWith('/idp-api'))) { + return `${parsed.protocol}//${parsed.host}`; + } + } catch { + return normalized; + } + return normalized; +} + export function isInternalHostname(hostname: string) { const host = hostname.trim().toLowerCase(); if (!host) { diff --git a/install.sh b/install.sh index 540e8a8..46966a9 100644 --- a/install.sh +++ b/install.sh @@ -2312,9 +2312,32 @@ EOF build_nginx_api_location_block() { if [[ "$NGINX_MODE" == "docker" ]]; then - local proxy + local proxy proxy_idp proxy_ws proxy="$(render_proxy_root api)" + proxy_idp="$(render_proxy_idp_api)" + proxy_ws="$(render_proxy_idp_ws)" cat </dev/null \ + | jq -r '.login_url // empty' 2>/dev/null || true)" + config_login_url_alias="$(curl ${curl_extra} -fsS --max-time 8 \ + -H "Sec-Fetch-Dest: webidentity" \ + "${scheme}://${api_domain}/idp-api/fedcm/config.json" 2>/dev/null \ + | jq -r '.login_url // empty' 2>/dev/null || true)" + if [[ -n "$config_login_url" ]]; then + echo -e " ${GREEN}✔${NC} FedCM config.json login_url=${config_login_url}" + elif [[ -n "$config_login_url_alias" ]]; then + echo -e " ${YELLOW}!${NC} FedCM config.json: используйте ${scheme}://${api_domain}/fedcm/config.json (не /idp-api/ на API-домене)" + echo -e " ${GREEN}✔${NC} FedCM config.json (alias /idp-api) login_url=${config_login_url_alias}" + else + echo -e " ${RED}✘${NC} FedCM config.json без login_url (${api_domain}) — проверьте PUBLIC_FRONTEND_URL и api-gateway" + return 1 + fi + apex_domain="$(registrable_domain "${frontend_domain:-$api_domain}")" if [[ -z "$apex_domain" \ || "$apex_domain" == "$frontend_domain" \ @@ -3043,6 +3085,13 @@ verify_fedcm_endpoints() { body="$(curl ${curl_extra} -fsS --max-time 8 "${scheme}://${apex_domain}/.well-known/web-identity" 2>/dev/null || true)" if [[ "$code" == "200" && "$body" == *provider_urls* ]]; then echo -e " ${GREEN}✔${NC} FedCM web-identity → 200 (${apex_domain})" + local well_known_login config_login + well_known_login="$(printf '%s' "$body" | jq -r '.login_url // empty' 2>/dev/null || true)" + config_login="${config_login_url:-$config_login_url_alias}" + if [[ -n "$well_known_login" && -n "$config_login" && "$well_known_login" != "$config_login" ]]; then + echo -e " ${RED}✘${NC} FedCM login_url не совпадает: web-identity=${well_known_login}, config.json=${config_login}" + return 1 + fi return 0 elif [[ "$code" == "000" ]]; then echo -e " ${RED}✘${NC} FedCM web-identity недоступен (${apex_domain}) — ERR_CERT или DNS"