fix and update
This commit is contained in:
10
.env.example
10
.env.example
@@ -20,17 +20,17 @@ SSL_CERT_FULLCHAIN=
|
|||||||
SSL_CERT_KEY=
|
SSL_CERT_KEY=
|
||||||
|
|
||||||
# Домены без протокола (install.sh подставит PUBLIC_* URL)
|
# Домены без протокола (install.sh подставит PUBLIC_* URL)
|
||||||
DOMAIN_API=
|
DOMAIN_API=api.idpmvk.lpr
|
||||||
DOMAIN_FRONTEND=
|
DOMAIN_FRONTEND=sso.idpmvk.lpr
|
||||||
DOMAIN_DOCS=
|
DOMAIN_DOCS=
|
||||||
# Пусто = WebSocket на DOMAIN_API по пути /ws
|
# Пусто = WebSocket на DOMAIN_API по пути /ws
|
||||||
DOMAIN_WS=
|
DOMAIN_WS=
|
||||||
DOMAIN_MINIO=
|
DOMAIN_MINIO=
|
||||||
DOMAIN_MINIO_CONSOLE=
|
DOMAIN_MINIO_CONSOLE=
|
||||||
|
|
||||||
# Публичные URL (генерируются install.sh)
|
# Публичные URL (генерируются install.sh; split-domain: API и SSO отдельно)
|
||||||
PUBLIC_API_URL=http://localhost:3002/idp-api
|
PUBLIC_API_URL=https://api.idpmvk.lpr
|
||||||
PUBLIC_FRONTEND_URL=http://localhost:3002
|
PUBLIC_FRONTEND_URL=https://sso.idpmvk.lpr
|
||||||
PUBLIC_DOCS_URL=http://localhost:3003
|
PUBLIC_DOCS_URL=http://localhost:3003
|
||||||
PUBLIC_WS_URL=ws://localhost:8085/ws
|
PUBLIC_WS_URL=ws://localhost:8085/ws
|
||||||
# Docker: http://api-gateway:3000 | локальный npm run dev: http://localhost:3000
|
# Docker: http://api-gateway:3000 | локальный npm run dev: http://localhost:3000
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { OAuthClient, OAuthScope, apiFetch } from '@/lib/api';
|
import { OAuthClient, OAuthScope, apiFetch } from '@/lib/api';
|
||||||
import { getAdminLandingPath } from '@/lib/admin-access';
|
import { getAdminLandingPath } from '@/lib/admin-access';
|
||||||
import { buildAuthorizeUrl, buildOAuthEndpoints, resolveFrontendBase, resolveOAuthApiBase } from '@/lib/oauth-url';
|
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() {
|
export default function AdminOAuthPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -26,8 +27,8 @@ export default function AdminOAuthPage() {
|
|||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [selectedClient, setSelectedClient] = useState<OAuthClient | null>(null);
|
const [selectedClient, setSelectedClient] = useState<OAuthClient | null>(null);
|
||||||
const [secretDialog, setSecretDialog] = useState<{ clientId: string; clientSecret: string } | null>(null);
|
const [secretDialog, setSecretDialog] = useState<{ clientId: string; clientSecret: string } | null>(null);
|
||||||
const [oauthApiBase, setOauthApiBase] = useState('http://localhost:3002/idp-api');
|
const [oauthApiBase, setOauthApiBase] = useState(DEFAULT_PUBLIC_API_URL);
|
||||||
const [frontendBase, setFrontendBase] = useState('http://localhost:3002');
|
const [frontendBase, setFrontendBase] = useState(DEFAULT_PUBLIC_FRONTEND_URL);
|
||||||
const [projectName, setProjectName] = useState('MVK ID');
|
const [projectName, setProjectName] = useState('MVK ID');
|
||||||
const [form, setForm] = useState({ name: '', redirectUris: '', type: 'CONFIDENTIAL', selectedScopes: ['openid', 'profile', 'email'] as string[] });
|
const [form, setForm] = useState({ name: '', redirectUris: '', type: 'CONFIDENTIAL', selectedScopes: ['openid', 'profile', 'email'] as string[] });
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export interface ParsedAddress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NOMINATIM_HEADERS = {
|
const NOMINATIM_HEADERS = {
|
||||||
'User-Agent': 'LendryID/1.0 (contact@lendry.ru)',
|
'User-Agent': 'MVKID/1.0 (sso@idpmvk.lpr)',
|
||||||
Accept: 'application/json'
|
Accept: 'application/json'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
import {
|
||||||
|
DEFAULT_PUBLIC_API_URL,
|
||||||
|
DEFAULT_PUBLIC_FRONTEND_URL,
|
||||||
|
inferApiBaseFromProjectDomain
|
||||||
|
} from './project-domains';
|
||||||
|
|
||||||
export function normalizeBaseUrl(url: string) {
|
export function normalizeBaseUrl(url: string) {
|
||||||
return url.trim().replace(/\/+$/, '');
|
return url.trim().replace(/\/+$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveOAuthApiBase(settings: Record<string, string>, fallback = 'http://localhost:3002/idp-api') {
|
export function resolveOAuthApiBase(settings: Record<string, string>, fallback = DEFAULT_PUBLIC_API_URL) {
|
||||||
const publicApi = settings.PUBLIC_API_URL?.trim();
|
const publicApi = settings.PUBLIC_API_URL?.trim();
|
||||||
if (publicApi) {
|
if (publicApi) {
|
||||||
return normalizeBaseUrl(publicApi);
|
return normalizeBaseUrl(publicApi);
|
||||||
@@ -10,16 +16,22 @@ export function resolveOAuthApiBase(settings: Record<string, string>, fallback =
|
|||||||
|
|
||||||
const domain = settings.PROJECT_DOMAIN?.trim();
|
const domain = settings.PROJECT_DOMAIN?.trim();
|
||||||
if (domain) {
|
if (domain) {
|
||||||
|
const inferred = inferApiBaseFromProjectDomain(domain);
|
||||||
|
if (inferred) {
|
||||||
|
return inferred;
|
||||||
|
}
|
||||||
|
|
||||||
if (domain.startsWith('http://') || domain.startsWith('https://')) {
|
if (domain.startsWith('http://') || domain.startsWith('https://')) {
|
||||||
return normalizeBaseUrl(domain);
|
return normalizeBaseUrl(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `https://${domain.replace(/^\/+/, '')}/idp-api`;
|
return `https://${domain.replace(/^\/+/, '')}/idp-api`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalizeBaseUrl(fallback);
|
return normalizeBaseUrl(fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveFrontendBase(settings: Record<string, string>, fallback = 'http://localhost:3002') {
|
export function resolveFrontendBase(settings: Record<string, string>, fallback = DEFAULT_PUBLIC_FRONTEND_URL) {
|
||||||
const publicFrontend = settings.PUBLIC_FRONTEND_URL?.trim();
|
const publicFrontend = settings.PUBLIC_FRONTEND_URL?.trim();
|
||||||
if (publicFrontend) {
|
if (publicFrontend) {
|
||||||
return normalizeBaseUrl(publicFrontend);
|
return normalizeBaseUrl(publicFrontend);
|
||||||
|
|||||||
28
apps/frontend/lib/project-domains.ts
Normal file
28
apps/frontend/lib/project-domains.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -25,20 +25,20 @@ export const SYSTEM_SETTING_GROUPS: Record<string, string> = {
|
|||||||
export const SYSTEM_SETTING_CATALOG: SystemSettingMeta[] = [
|
export const SYSTEM_SETTING_CATALOG: SystemSettingMeta[] = [
|
||||||
{ key: 'PROJECT_NAME', label: 'Название проекта', group: 'general', type: 'text', hint: 'Отображается в меню, заголовке и боковой панели' },
|
{ key: 'PROJECT_NAME', label: 'Название проекта', group: 'general', type: 'text', hint: 'Отображается в меню, заголовке и боковой панели' },
|
||||||
{ key: 'PROJECT_TAGLINE', label: 'Слоган проекта', group: 'general', type: 'text' },
|
{ 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',
|
key: 'PUBLIC_API_URL',
|
||||||
label: 'URL API (OAuth issuer)',
|
label: 'URL API (OAuth issuer)',
|
||||||
group: 'general',
|
group: 'general',
|
||||||
type: 'text',
|
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',
|
key: 'PUBLIC_FRONTEND_URL',
|
||||||
label: 'URL Frontend IdP',
|
label: 'URL Frontend IdP',
|
||||||
group: 'general',
|
group: 'general',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
hint: 'Публичный адрес интерфейса IdP. Используется для login_url FedCM и popup OAuth'
|
hint: 'Публичный адрес UI IdP. Пример: https://sso.idpmvk.lpr'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'ONE_TAP_ENABLED',
|
key: 'ONE_TAP_ENABLED',
|
||||||
|
|||||||
@@ -147,7 +147,7 @@
|
|||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
if (global.console && typeof global.console.warn === 'function') {
|
if (global.console && typeof global.console.warn === 'function') {
|
||||||
global.console.warn(
|
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;
|
return null;
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { PrismaService } from '../infra/prisma.service';
|
|||||||
export const DEFAULT_SYSTEM_SETTINGS = [
|
export const DEFAULT_SYSTEM_SETTINGS = [
|
||||||
{ key: 'PROJECT_NAME', value: 'MVK ID', description: 'Название проекта в интерфейсе и заголовке страницы' },
|
{ key: 'PROJECT_NAME', value: 'MVK ID', description: 'Название проекта в интерфейсе и заголовке страницы' },
|
||||||
{ key: 'PROJECT_TAGLINE', value: 'Единый аккаунт для сервисов Lendry', 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',
|
key: 'PUBLIC_API_URL',
|
||||||
value: 'http://localhost:3002/idp-api',
|
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',
|
key: 'PUBLIC_FRONTEND_URL',
|
||||||
|
|||||||
Reference in New Issue
Block a user