feat(web): 增加统一认证接入与运维页面

系统设置新增统一认证配对、验证、激活、重验证、回滚与禁用流程,安全事件改为统一能力状态。前端登录入口运行时读取公开配置,不再依赖 VITE_OIDC 构建变量。\n\n验证:web 31 项测试;web typecheck;pnpm lint;web production build
This commit is contained in:
2026-07-17 12:18:26 +08:00
parent b175d545ff
commit e0a356ee0c
11 changed files with 675 additions and 191 deletions
+66 -4
View File
@@ -20,6 +20,11 @@ import type {
GatewayAuditLog,
GatewayRunnerPolicy,
GatewayRunnerPolicyUpsertRequest,
IdentityConfigurationRevision,
IdentityConfigurationView,
IdentityPairingExchange,
IdentityPairingInput,
IdentityRevisionPolicyUpdate,
GatewayTenant,
GatewayTenantUpsertRequest,
GatewayNetworkProxyConfig,
@@ -52,7 +57,6 @@ import type {
WalletSummaryResponse,
} from '@easyai-ai-gateway/contracts';
import type { PlatformCreateInput, PlatformModelBindingInput, WorkspaceTaskQuery } from './types';
import { oidcBrowserSessionEnabled } from './lib/oidc';
const API_BASE = import.meta.env.VITE_GATEWAY_API_BASE_URL ?? 'http://localhost:8088';
export const OIDC_BROWSER_SESSION_CREDENTIAL = '__easyai_gateway_oidc_browser_session__';
@@ -644,7 +648,7 @@ export async function* streamChatCompletionText(
...authorizationHeader(token),
'Content-Type': 'application/json',
},
credentials: oidcBrowserSessionEnabled() ? 'include' : 'same-origin',
credentials: 'include',
method: 'POST',
signal,
});
@@ -845,7 +849,7 @@ export async function uploadFileToStorage(
const response = await fetch(`${API_BASE}/v1/files/upload`, {
body: form,
credentials: oidcBrowserSessionEnabled() ? 'include' : 'same-origin',
credentials: 'include',
headers: authorizationHeader(token),
method: 'POST',
});
@@ -1001,6 +1005,64 @@ export async function updateClientCustomizationSettings(
});
}
const identityConfigurationPath = '/api/admin/system/identity';
export async function getIdentityConfiguration(token: string): Promise<IdentityConfigurationView> {
return request<IdentityConfigurationView>(`${identityConfigurationPath}/configuration`, { token });
}
export async function startIdentityPairing(token: string, input: IdentityPairingInput): Promise<IdentityPairingExchange> {
return identityConfigurationWrite<IdentityPairingExchange>(token, `${identityConfigurationPath}/pairings`, 'POST', 0, input, 'pair');
}
export async function getIdentityPairing(token: string, pairingId: string): Promise<IdentityPairingExchange> {
return request<IdentityPairingExchange>(`${identityConfigurationPath}/pairings/${pairingId}`, { token });
}
export async function updateIdentityRevisionPolicy(
token: string,
revisionId: string,
version: number,
input: IdentityRevisionPolicyUpdate,
): Promise<IdentityConfigurationRevision> {
return identityConfigurationWrite<IdentityConfigurationRevision>(token, `${identityConfigurationPath}/revisions/${revisionId}`, 'PATCH', version, input, 'policy');
}
export async function validateIdentityRevision(token: string, revisionId: string, version: number): Promise<IdentityConfigurationRevision> {
return identityConfigurationWrite<IdentityConfigurationRevision>(token, `${identityConfigurationPath}/revisions/${revisionId}/validate`, 'POST', version, undefined, 'validate');
}
export async function activateIdentityRevision(token: string, revisionId: string, version: number): Promise<IdentityConfigurationRevision> {
return identityConfigurationWrite<IdentityConfigurationRevision>(token, `${identityConfigurationPath}/revisions/${revisionId}/activate`, 'POST', version, undefined, 'activate');
}
export async function rollbackIdentityRevision(token: string, revisionId: string, version: number): Promise<IdentityConfigurationRevision> {
return identityConfigurationWrite<IdentityConfigurationRevision>(token, `${identityConfigurationPath}/revisions/${revisionId}/rollback`, 'POST', version, undefined, 'rollback');
}
export async function disableIdentityConfiguration(token: string, version: number): Promise<IdentityConfigurationRevision> {
return identityConfigurationWrite<IdentityConfigurationRevision>(token, `${identityConfigurationPath}/disable`, 'POST', version, undefined, 'disable');
}
function identityConfigurationWrite<T>(
token: string,
path: string,
method: string,
version: number,
body: unknown,
action: string,
): Promise<T> {
return request<T>(path, {
body,
method,
token,
headers: {
'Idempotency-Key': `identity-${action}-${crypto.randomUUID()}`,
'If-Match': `W/"${version}"`,
},
});
}
const securityEventConnectionPath = '/api/admin/system/identity/security-events/connection';
export async function getSecurityEventConnection(token: string): Promise<SecurityEventConnectionResponse> {
@@ -1096,7 +1158,7 @@ async function request<T>(
method: options.method ?? 'GET',
headers,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
credentials: oidcBrowserSessionEnabled() ? 'include' : 'same-origin',
credentials: 'include',
});
if (!response.ok) {
const body = await response.text();