From e127df3d6df848910d07904744e2f74caa11a8bb Mon Sep 17 00:00:00 2001 From: lendry Date: Mon, 29 Jun 2026 15:08:26 +0300 Subject: [PATCH] fix and update --- .env.example | 10 +++---- apps/frontend/app/admin/oauth/page.tsx | 5 ++-- apps/frontend/lib/geocoding.ts | 2 +- apps/frontend/lib/oauth-url.ts | 16 +++++++++-- apps/frontend/lib/project-domains.ts | 28 +++++++++++++++++++ apps/frontend/lib/system-settings-catalog.ts | 6 ++-- apps/frontend/public/sso-widget.js | 2 +- .../src/domain/system-settings.seed.ts | 4 +-- 8 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 apps/frontend/lib/project-domains.ts diff --git a/.env.example b/.env.example index aa185fe..701e90b 100644 --- a/.env.example +++ b/.env.example @@ -20,17 +20,17 @@ SSL_CERT_FULLCHAIN= SSL_CERT_KEY= # Домены без протокола (install.sh подставит PUBLIC_* URL) -DOMAIN_API= -DOMAIN_FRONTEND= +DOMAIN_API=api.idpmvk.lpr +DOMAIN_FRONTEND=sso.idpmvk.lpr DOMAIN_DOCS= # Пусто = WebSocket на DOMAIN_API по пути /ws DOMAIN_WS= DOMAIN_MINIO= DOMAIN_MINIO_CONSOLE= -# Публичные URL (генерируются install.sh) -PUBLIC_API_URL=http://localhost:3002/idp-api -PUBLIC_FRONTEND_URL=http://localhost:3002 +# Публичные URL (генерируются install.sh; split-domain: API и SSO отдельно) +PUBLIC_API_URL=https://api.idpmvk.lpr +PUBLIC_FRONTEND_URL=https://sso.idpmvk.lpr PUBLIC_DOCS_URL=http://localhost:3003 PUBLIC_WS_URL=ws://localhost:8085/ws # Docker: http://api-gateway:3000 | локальный npm run dev: http://localhost:3000 diff --git a/apps/frontend/app/admin/oauth/page.tsx b/apps/frontend/app/admin/oauth/page.tsx index 78c9282..6504847 100644 --- a/apps/frontend/app/admin/oauth/page.tsx +++ b/apps/frontend/app/admin/oauth/page.tsx @@ -14,6 +14,7 @@ import { Input } from '@/components/ui/input'; import { OAuthClient, OAuthScope, apiFetch } from '@/lib/api'; import { getAdminLandingPath } from '@/lib/admin-access'; import { buildAuthorizeUrl, buildOAuthEndpoints, resolveFrontendBase, resolveOAuthApiBase } from '@/lib/oauth-url'; +import { DEFAULT_PUBLIC_API_URL, DEFAULT_PUBLIC_FRONTEND_URL } from '@/lib/project-domains'; export default function AdminOAuthPage() { const router = useRouter(); @@ -26,8 +27,8 @@ export default function AdminOAuthPage() { const [dialogOpen, setDialogOpen] = useState(false); const [selectedClient, setSelectedClient] = useState(null); const [secretDialog, setSecretDialog] = useState<{ clientId: string; clientSecret: string } | null>(null); - const [oauthApiBase, setOauthApiBase] = useState('http://localhost:3002/idp-api'); - const [frontendBase, setFrontendBase] = useState('http://localhost:3002'); + const [oauthApiBase, setOauthApiBase] = useState(DEFAULT_PUBLIC_API_URL); + const [frontendBase, setFrontendBase] = useState(DEFAULT_PUBLIC_FRONTEND_URL); const [projectName, setProjectName] = useState('MVK ID'); const [form, setForm] = useState({ name: '', redirectUris: '', type: 'CONFIDENTIAL', selectedScopes: ['openid', 'profile', 'email'] as string[] }); diff --git a/apps/frontend/lib/geocoding.ts b/apps/frontend/lib/geocoding.ts index 338fe06..88b3db6 100644 --- a/apps/frontend/lib/geocoding.ts +++ b/apps/frontend/lib/geocoding.ts @@ -6,7 +6,7 @@ export interface ParsedAddress { } const NOMINATIM_HEADERS = { - 'User-Agent': 'LendryID/1.0 (contact@lendry.ru)', + 'User-Agent': 'MVKID/1.0 (sso@idpmvk.lpr)', Accept: 'application/json' }; diff --git a/apps/frontend/lib/oauth-url.ts b/apps/frontend/lib/oauth-url.ts index 8303b16..fe22111 100644 --- a/apps/frontend/lib/oauth-url.ts +++ b/apps/frontend/lib/oauth-url.ts @@ -1,8 +1,14 @@ +import { + DEFAULT_PUBLIC_API_URL, + DEFAULT_PUBLIC_FRONTEND_URL, + inferApiBaseFromProjectDomain +} from './project-domains'; + export function normalizeBaseUrl(url: string) { return url.trim().replace(/\/+$/, ''); } -export function resolveOAuthApiBase(settings: Record, fallback = 'http://localhost:3002/idp-api') { +export function resolveOAuthApiBase(settings: Record, fallback = DEFAULT_PUBLIC_API_URL) { const publicApi = settings.PUBLIC_API_URL?.trim(); if (publicApi) { return normalizeBaseUrl(publicApi); @@ -10,16 +16,22 @@ export function resolveOAuthApiBase(settings: Record, fallback = const domain = settings.PROJECT_DOMAIN?.trim(); if (domain) { + const inferred = inferApiBaseFromProjectDomain(domain); + if (inferred) { + return inferred; + } + if (domain.startsWith('http://') || domain.startsWith('https://')) { return normalizeBaseUrl(domain); } + return `https://${domain.replace(/^\/+/, '')}/idp-api`; } return normalizeBaseUrl(fallback); } -export function resolveFrontendBase(settings: Record, fallback = 'http://localhost:3002') { +export function resolveFrontendBase(settings: Record, fallback = DEFAULT_PUBLIC_FRONTEND_URL) { const publicFrontend = settings.PUBLIC_FRONTEND_URL?.trim(); if (publicFrontend) { return normalizeBaseUrl(publicFrontend); diff --git a/apps/frontend/lib/project-domains.ts b/apps/frontend/lib/project-domains.ts new file mode 100644 index 0000000..ba523a9 --- /dev/null +++ b/apps/frontend/lib/project-domains.ts @@ -0,0 +1,28 @@ +/** Публичные домены IdP (split-domain: API и SSO на разных хостах). */ +export const PROJECT_API_HOST = 'api.idpmvk.lpr'; +export const PROJECT_SSO_HOST = 'sso.idpmvk.lpr'; + +export const DEFAULT_PUBLIC_API_URL = `https://${PROJECT_API_HOST}`; +export const DEFAULT_PUBLIC_FRONTEND_URL = `https://${PROJECT_SSO_HOST}`; + +export function inferApiBaseFromProjectDomain(domain: string): string | null { + const host = domain + .trim() + .replace(/^https?:\/\//i, '') + .split('/')[0] + .toLowerCase(); + + if (!host) { + return null; + } + + if (host.startsWith('sso.')) { + return `https://${host.replace(/^sso\./, 'api.')}`; + } + + if (host.startsWith('api.')) { + return `https://${host}`; + } + + return null; +} diff --git a/apps/frontend/lib/system-settings-catalog.ts b/apps/frontend/lib/system-settings-catalog.ts index cb3c526..4284855 100644 --- a/apps/frontend/lib/system-settings-catalog.ts +++ b/apps/frontend/lib/system-settings-catalog.ts @@ -25,20 +25,20 @@ export const SYSTEM_SETTING_GROUPS: Record = { export const SYSTEM_SETTING_CATALOG: SystemSettingMeta[] = [ { key: 'PROJECT_NAME', label: 'Название проекта', group: 'general', type: 'text', hint: 'Отображается в меню, заголовке и боковой панели' }, { key: 'PROJECT_TAGLINE', label: 'Слоган проекта', group: 'general', type: 'text' }, - { key: 'PROJECT_DOMAIN', label: 'Домен IdP', group: 'general', type: 'text', hint: 'Домен без пути, например sso.example.ru' }, + { key: 'PROJECT_DOMAIN', label: 'Домен IdP (SSO)', group: 'general', type: 'text', hint: 'Frontend-домен без пути, например sso.idpmvk.lpr' }, { key: 'PUBLIC_API_URL', label: 'URL API (OAuth issuer)', group: 'general', type: 'text', - hint: 'Публичный URL, доступный из браузера пользователя. Не используйте api-gateway или другие Docker-имена. Пример: https://id.lendry.ru/idp-api' + hint: 'Публичный URL, доступный из браузера. Пример: https://api.idpmvk.lpr (отдельный домен API) или https://sso.idpmvk.lpr/idp-api (same-origin)' }, { key: 'PUBLIC_FRONTEND_URL', label: 'URL Frontend IdP', group: 'general', type: 'text', - hint: 'Публичный адрес интерфейса IdP. Используется для login_url FedCM и popup OAuth' + hint: 'Публичный адрес UI IdP. Пример: https://sso.idpmvk.lpr' }, { key: 'ONE_TAP_ENABLED', diff --git a/apps/frontend/public/sso-widget.js b/apps/frontend/public/sso-widget.js index 29b9a9e..7570135 100644 --- a/apps/frontend/public/sso-widget.js +++ b/apps/frontend/public/sso-widget.js @@ -147,7 +147,7 @@ if (!resolved) { if (global.console && typeof global.console.warn === 'function') { global.console.warn( - '[MVK ID] FedCM: URL IdP недоступен из браузера. Укажите публичный data-idp-url (например https://id.lendry.ru/idp-api), не внутренний Docker-хост.' + '[MVK ID] FedCM: URL IdP недоступен из браузера. Укажите публичный data-idp-url (например https://api.idpmvk.lpr), не внутренний Docker-хост.' ); } return null; diff --git a/apps/sso-core/src/domain/system-settings.seed.ts b/apps/sso-core/src/domain/system-settings.seed.ts index fe1b171..ea08545 100644 --- a/apps/sso-core/src/domain/system-settings.seed.ts +++ b/apps/sso-core/src/domain/system-settings.seed.ts @@ -4,11 +4,11 @@ import { PrismaService } from '../infra/prisma.service'; export const DEFAULT_SYSTEM_SETTINGS = [ { key: 'PROJECT_NAME', value: 'MVK ID', description: 'Название проекта в интерфейсе и заголовке страницы' }, { key: 'PROJECT_TAGLINE', value: 'Единый аккаунт для сервисов Lendry', description: 'Краткий слоган под названием проекта' }, - { key: 'PROJECT_DOMAIN', value: 'id.lendry.ru', description: 'Основной домен IdP (без пути)' }, + { key: 'PROJECT_DOMAIN', value: 'sso.idpmvk.lpr', description: 'Основной домен IdP / frontend (без пути)' }, { key: 'PUBLIC_API_URL', value: 'http://localhost:3002/idp-api', - description: 'Публичный базовый URL API для OAuth и OpenID Connect (issuer). Пример: https://sso.example.ru/idp-api' + description: 'Публичный базовый URL API для OAuth и OpenID Connect (issuer). Пример: https://api.idpmvk.lpr' }, { key: 'PUBLIC_FRONTEND_URL',