From 2701c0e90bfaae52dbf751290a2cd3ec9bba505b Mon Sep 17 00:00:00 2001 From: lendry Date: Mon, 29 Jun 2026 17:28:52 +0300 Subject: [PATCH] fix and update --- apps/frontend/lib/api.ts | 45 ++++++++++++++++++------ install.sh | 75 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 107 insertions(+), 13 deletions(-) diff --git a/apps/frontend/lib/api.ts b/apps/frontend/lib/api.ts index 37bdd32..7275ecd 100644 --- a/apps/frontend/lib/api.ts +++ b/apps/frontend/lib/api.ts @@ -2,21 +2,42 @@ const API_ORIGIN_PROXY_PREFIX = '/idp-api'; const configuredApiUrl = (process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000').replace(/\/$/, ''); const configuredWsUrl = (process.env.NEXT_PUBLIC_WS_URL ?? 'ws://localhost:8085/ws').replace(/\/$/, ''); +function getServerApiUrl(): string { + return (process.env.INTERNAL_API_URL ?? configuredApiUrl).replace(/\/$/, ''); +} + +/** Same-origin /idp-api только если PUBLIC API URL явно задан как прокси-путь на фронтенде. */ +function usesSameOriginApiProxy(): boolean { + if (configuredApiUrl.startsWith('/')) { + return true; + } + return /\/idp-api\/?$/i.test(configuredApiUrl) || configuredApiUrl.includes('/idp-api/'); +} + +function usesSameOriginWsProxy(): boolean { + if (configuredWsUrl.startsWith('/')) { + return true; + } + return configuredWsUrl.includes('/idp-api/'); +} + function resolveBrowserApiBaseUrl(): string { - // На сервере (SSR) обращаемся к API напрямую по настроенному URL. + // SSR: внутренний URL Docker-сети (api-gateway), не публичный домен. if (typeof window === 'undefined') { - return configuredApiUrl; + return getServerApiUrl(); } - // Если URL уже относительный — используем как есть. if (configuredApiUrl.startsWith('/')) { return configuredApiUrl.replace(/\/+$/, '') || API_ORIGIN_PROXY_PREFIX; } - // В браузере ВСЕГДА ходим через same-origin прокси /idp-api (Next.js rewrites - // проксируют на внутренний api-gateway). Это исключает CORS и работу через - // отдельный домен API (например api.idpmvk.lpr) для запросов самого UI IdP. - return API_ORIGIN_PROXY_PREFIX; + // Split-domain (https://api.example.com): браузер ходит напрямую на API-домен. + // Same-origin (https://sso.example.com/idp-api): через /idp-api и host/nginx rewrites. + if (usesSameOriginApiProxy()) { + return API_ORIGIN_PROXY_PREFIX; + } + + return configuredApiUrl; } function resolveBrowserWsBaseUrl(): string { @@ -24,8 +45,12 @@ function resolveBrowserWsBaseUrl(): string { return configuredWsUrl; } - const scheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - return `${scheme}//${window.location.host}${API_ORIGIN_PROXY_PREFIX}/ws`; + if (usesSameOriginWsProxy()) { + const scheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + return `${scheme}//${window.location.host}${API_ORIGIN_PROXY_PREFIX}/ws`; + } + + return configuredWsUrl; } export function getApiUrl(): string { @@ -36,7 +61,7 @@ export function getWsUrl(): string { return resolveBrowserWsBaseUrl(); } -/** @deprecated Используйте getApiUrl() — URL может переключаться на same-origin /idp-api в браузере */ +/** @deprecated Используйте getApiUrl() — в браузере URL зависит от режима (split-domain или /idp-api) */ export const API_URL = configuredApiUrl; export interface PublicUser { diff --git a/install.sh b/install.sh index 03cbfec..25df569 100644 --- a/install.sh +++ b/install.sh @@ -830,7 +830,9 @@ derive_public_urls() { fi if [[ "$ssl_type" == "selfsigned" && -n "${DOMAIN_FRONTEND:-}" ]]; then - apply_same_origin_api_proxy_urls "$scheme" + if [[ -z "${DOMAIN_API:-}" || "${DOMAIN_API}" == "${DOMAIN_FRONTEND}" ]]; then + apply_same_origin_api_proxy_urls "$scheme" + fi fi SSL_TYPE="$ssl_type" @@ -859,6 +861,44 @@ apply_same_origin_api_proxy_urls() { fi } +# Split-domain (api.xxx + sso.xxx): PUBLIC_API_URL должен быть https://api.xxx, +# а не https://sso.xxx/idp-api — иначе фронтенд шлёт запросы не туда. +ensure_split_domain_public_urls() { + load_env + local api_domain frontend_domain public_api ssl_type scheme ws_url + api_domain="$(env_get DOMAIN_API "")" + frontend_domain="$(env_get DOMAIN_FRONTEND "")" + public_api="$(env_get PUBLIC_API_URL "")" + ssl_type="$(env_get SSL_TYPE none)" + + [[ -n "$api_domain" && -n "$frontend_domain" ]] || return 0 + [[ "$api_domain" != "$frontend_domain" ]] || return 0 + + if [[ "$public_api" != *"/idp-api"* ]]; then + return 0 + fi + + scheme="http" + [[ "$ssl_type" == "letsencrypt" || "$ssl_type" == "selfsigned" || "$ssl_type" == "custom" ]] && scheme="https" + + warn "PUBLIC_API_URL=${public_api} — некорректно для split-domain (API и SSO на разных доменах)" + PUBLIC_API_URL="${scheme}://${api_domain}" + env_set PUBLIC_API_URL "${PUBLIC_API_URL}" + + ws_url="$(env_get PUBLIC_WS_URL "")" + if [[ -z "$ws_url" || "$ws_url" == *"/idp-api"* ]]; then + if [[ "$scheme" == "https" ]]; then + PUBLIC_WS_URL="wss://${api_domain}/ws" + else + PUBLIC_WS_URL="ws://${api_domain}/ws" + fi + env_set PUBLIC_WS_URL "${PUBLIC_WS_URL}" + fi + + env_set INTERNAL_API_URL "http://api-gateway:3000" + ok "Исправлено: PUBLIC_API_URL=${PUBLIC_API_URL}, PUBLIC_WS_URL=$(env_get PUBLIC_WS_URL "")" +} + choose_ssl_type_interactive() { local default="${1:-none}" @@ -2597,7 +2637,6 @@ diagnose_common_issues() { if [[ "$(env_get NGINX_MODE docker)" == "host" ]] \ && [[ "$(env_get INSTALL_MODE local)" != "local" ]] \ && [[ -n "$(env_get DOMAIN_API "")" ]]; then - local port for port in 3000 3002 3003; do if ! port_in_use "$port"; then echo -e " ${RED}✘${NC} Порт 127.0.0.1:${port} не слушается — host Nginx получит 502" @@ -2626,6 +2665,23 @@ diagnose_common_issues() { echo -e " ${GREEN}✔${NC} api-gateway: контейнер запущен" fi + local api_domain frontend_domain public_api + api_domain="$(env_get DOMAIN_API "")" + frontend_domain="$(env_get DOMAIN_FRONTEND "")" + public_api="$(env_get PUBLIC_API_URL "")" + if [[ -n "$api_domain" && -n "$frontend_domain" && "$api_domain" != "$frontend_domain" ]]; then + if [[ "$public_api" == *"/idp-api"* ]]; then + echo -e " ${RED}✘${NC} Split-domain: PUBLIC_API_URL=${public_api} — должно быть https://${api_domain} (не /idp-api на SSO)" + echo " Решение: ./install.sh --fix-all" + issues=$((issues + 1)) + elif [[ "$public_api" == *"${frontend_domain}"* && "$public_api" != *"${api_domain}"* ]]; then + echo -e " ${RED}✘${NC} PUBLIC_API_URL указывает на домен SSO (${frontend_domain}), а не API (${api_domain})" + issues=$((issues + 1)) + else + echo -e " ${GREEN}✔${NC} Split-domain: PUBLIC_API_URL=${public_api}" + fi + fi + echo "" if [[ "$issues" -eq 0 ]]; then ok "Критических проблем не обнаружено" @@ -2668,6 +2724,8 @@ show_status() { echo " DOMAIN_FRONTEND: $(env_get DOMAIN_FRONTEND -)" echo " DOMAIN_DOCS: $(env_get DOMAIN_DOCS -)" echo " DOMAIN_WS: $(env_get DOMAIN_WS -)" + echo " PUBLIC_API_URL: $(env_get PUBLIC_API_URL -)" + echo " PUBLIC_WS_URL: $(env_get PUBLIC_WS_URL -)" echo "" docker_cmd compose ps 2>/dev/null || warn "Контейнеры не запущены" echo "" @@ -2833,6 +2891,8 @@ action_fix_host_proxy() { log "Шаг 2/5: compose override (LDAP / Nginx)..." write_compose_override + ensure_split_domain_public_urls + log "Шаг 3/5: пересоздание api-gateway, frontend, docs, media-ws..." compose_build_and_up "${HOST_EDGE_SERVICES[@]}" ensure_host_upstreams @@ -2869,6 +2929,7 @@ action_fix_all_errors() { resolve_nginx_mode_and_ports ensure_docker_nginx_not_conflicting write_compose_override + ensure_split_domain_public_urls log "Шаг 3/6: пересборка api-gateway и sso-core..." compose_build_and_up api-gateway sso-core @@ -2920,7 +2981,15 @@ action_fix_all_errors() { ok "Исправление завершено" echo "" - echo " Проверьте в браузере страницу после авторизации — запросы к /idp-api/* должны отвечать 200." + api_domain="$(env_get DOMAIN_API "")" + frontend_domain="$(env_get DOMAIN_FRONTEND "")" + if [[ -n "$api_domain" && -n "$frontend_domain" && "$api_domain" != "$frontend_domain" ]]; then + echo " Split-domain: в DevTools → Network запросы должны идти на https://${api_domain}/...," + echo " а не на https://${frontend_domain}/idp-api/..." + echo " После пересборки frontend обновите страницу с полным сбросом кеша (Ctrl+Shift+R)." + else + echo " Проверьте в браузере — запросы к /idp-api/* должны отвечать 200." + fi echo " Если ошибки остались: docker compose logs --tail=80 api-gateway" echo "" show_status