fix and update

This commit is contained in:
lendry
2026-06-29 17:28:52 +03:00
parent e3c418d921
commit 2701c0e90b
2 changed files with 107 additions and 13 deletions

View File

@@ -2,32 +2,57 @@ const API_ORIGIN_PROXY_PREFIX = '/idp-api';
const configuredApiUrl = (process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000').replace(/\/$/, ''); 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(/\/$/, ''); const configuredWsUrl = (process.env.NEXT_PUBLIC_WS_URL ?? 'ws://localhost:8085/ws').replace(/\/$/, '');
function resolveBrowserApiBaseUrl(): string { function getServerApiUrl(): string {
// На сервере (SSR) обращаемся к API напрямую по настроенному URL. return (process.env.INTERNAL_API_URL ?? configuredApiUrl).replace(/\/$/, '');
if (typeof window === 'undefined') { }
return configuredApiUrl;
/** 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: внутренний URL Docker-сети (api-gateway), не публичный домен.
if (typeof window === 'undefined') {
return getServerApiUrl();
} }
// Если URL уже относительный — используем как есть.
if (configuredApiUrl.startsWith('/')) { if (configuredApiUrl.startsWith('/')) {
return configuredApiUrl.replace(/\/+$/, '') || API_ORIGIN_PROXY_PREFIX; return configuredApiUrl.replace(/\/+$/, '') || API_ORIGIN_PROXY_PREFIX;
} }
// В браузере ВСЕГДА ходим через same-origin прокси /idp-api (Next.js rewrites // Split-domain (https://api.example.com): браузер ходит напрямую на API-домен.
// проксируют на внутренний api-gateway). Это исключает CORS и работу через // Same-origin (https://sso.example.com/idp-api): через /idp-api и host/nginx rewrites.
// отдельный домен API (например api.idpmvk.lpr) для запросов самого UI IdP. if (usesSameOriginApiProxy()) {
return API_ORIGIN_PROXY_PREFIX; return API_ORIGIN_PROXY_PREFIX;
} }
return configuredApiUrl;
}
function resolveBrowserWsBaseUrl(): string { function resolveBrowserWsBaseUrl(): string {
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return configuredWsUrl; return configuredWsUrl;
} }
if (usesSameOriginWsProxy()) {
const scheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const scheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
return `${scheme}//${window.location.host}${API_ORIGIN_PROXY_PREFIX}/ws`; return `${scheme}//${window.location.host}${API_ORIGIN_PROXY_PREFIX}/ws`;
} }
return configuredWsUrl;
}
export function getApiUrl(): string { export function getApiUrl(): string {
return resolveBrowserApiBaseUrl(); return resolveBrowserApiBaseUrl();
} }
@@ -36,7 +61,7 @@ export function getWsUrl(): string {
return resolveBrowserWsBaseUrl(); return resolveBrowserWsBaseUrl();
} }
/** @deprecated Используйте getApiUrl() — URL может переключаться на same-origin /idp-api в браузере */ /** @deprecated Используйте getApiUrl() — в браузере URL зависит от режима (split-domain или /idp-api) */
export const API_URL = configuredApiUrl; export const API_URL = configuredApiUrl;
export interface PublicUser { export interface PublicUser {

View File

@@ -830,8 +830,10 @@ derive_public_urls() {
fi fi
if [[ "$ssl_type" == "selfsigned" && -n "${DOMAIN_FRONTEND:-}" ]]; then if [[ "$ssl_type" == "selfsigned" && -n "${DOMAIN_FRONTEND:-}" ]]; then
if [[ -z "${DOMAIN_API:-}" || "${DOMAIN_API}" == "${DOMAIN_FRONTEND}" ]]; then
apply_same_origin_api_proxy_urls "$scheme" apply_same_origin_api_proxy_urls "$scheme"
fi fi
fi
SSL_TYPE="$ssl_type" SSL_TYPE="$ssl_type"
USE_NGINX_SSL=$([[ "$ssl_type" == "none" ]] && echo false || echo true) USE_NGINX_SSL=$([[ "$ssl_type" == "none" ]] && echo false || echo true)
@@ -859,6 +861,44 @@ apply_same_origin_api_proxy_urls() {
fi 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() { choose_ssl_type_interactive() {
local default="${1:-none}" local default="${1:-none}"
@@ -2597,7 +2637,6 @@ diagnose_common_issues() {
if [[ "$(env_get NGINX_MODE docker)" == "host" ]] \ if [[ "$(env_get NGINX_MODE docker)" == "host" ]] \
&& [[ "$(env_get INSTALL_MODE local)" != "local" ]] \ && [[ "$(env_get INSTALL_MODE local)" != "local" ]] \
&& [[ -n "$(env_get DOMAIN_API "")" ]]; then && [[ -n "$(env_get DOMAIN_API "")" ]]; then
local port
for port in 3000 3002 3003; do for port in 3000 3002 3003; do
if ! port_in_use "$port"; then if ! port_in_use "$port"; then
echo -e " ${RED}${NC} Порт 127.0.0.1:${port} не слушается — host Nginx получит 502" 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: контейнер запущен" echo -e " ${GREEN}${NC} api-gateway: контейнер запущен"
fi 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 "" echo ""
if [[ "$issues" -eq 0 ]]; then if [[ "$issues" -eq 0 ]]; then
ok "Критических проблем не обнаружено" ok "Критических проблем не обнаружено"
@@ -2668,6 +2724,8 @@ show_status() {
echo " DOMAIN_FRONTEND: $(env_get DOMAIN_FRONTEND -)" echo " DOMAIN_FRONTEND: $(env_get DOMAIN_FRONTEND -)"
echo " DOMAIN_DOCS: $(env_get DOMAIN_DOCS -)" echo " DOMAIN_DOCS: $(env_get DOMAIN_DOCS -)"
echo " DOMAIN_WS: $(env_get DOMAIN_WS -)" 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 "" echo ""
docker_cmd compose ps 2>/dev/null || warn "Контейнеры не запущены" docker_cmd compose ps 2>/dev/null || warn "Контейнеры не запущены"
echo "" echo ""
@@ -2833,6 +2891,8 @@ action_fix_host_proxy() {
log "Шаг 2/5: compose override (LDAP / Nginx)..." log "Шаг 2/5: compose override (LDAP / Nginx)..."
write_compose_override write_compose_override
ensure_split_domain_public_urls
log "Шаг 3/5: пересоздание api-gateway, frontend, docs, media-ws..." log "Шаг 3/5: пересоздание api-gateway, frontend, docs, media-ws..."
compose_build_and_up "${HOST_EDGE_SERVICES[@]}" compose_build_and_up "${HOST_EDGE_SERVICES[@]}"
ensure_host_upstreams ensure_host_upstreams
@@ -2869,6 +2929,7 @@ action_fix_all_errors() {
resolve_nginx_mode_and_ports resolve_nginx_mode_and_ports
ensure_docker_nginx_not_conflicting ensure_docker_nginx_not_conflicting
write_compose_override write_compose_override
ensure_split_domain_public_urls
log "Шаг 3/6: пересборка api-gateway и sso-core..." log "Шаг 3/6: пересборка api-gateway и sso-core..."
compose_build_and_up api-gateway sso-core compose_build_and_up api-gateway sso-core
@@ -2920,7 +2981,15 @@ action_fix_all_errors() {
ok "Исправление завершено" ok "Исправление завершено"
echo "" 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 " Если ошибки остались: docker compose logs --tail=80 api-gateway"
echo "" echo ""
show_status show_status