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
+47
View File
@@ -6,6 +6,8 @@ import {
gatewayErrorMessage,
getCurrentUser,
OIDC_BROWSER_SESSION_CREDENTIAL,
startIdentityPairing,
validateIdentityRevision,
} from './api';
describe('Gateway provisioning errors', () => {
@@ -35,6 +37,51 @@ describe('Gateway provisioning errors', () => {
});
});
describe('identity configuration transport', () => {
afterEach(() => {
vi.unstubAllGlobals();
});
it('keeps the one-time onboarding code in the request body and enforces write headers', async () => {
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ id: 'pairing', version: 1 }), {
status: 202,
headers: { 'Content-Type': 'application/json' },
}));
vi.stubGlobal('fetch', fetchMock);
vi.stubGlobal('crypto', { randomUUID: () => '00000000-0000-4000-8000-000000000000' });
await startIdentityPairing('manager-token', {
authCenterUrl: 'https://auth.example.com',
onboardingCode: 'one-time-code-must-stay-in-body',
publicBaseUrl: 'https://api.gateway.example.com',
webBaseUrl: 'https://gateway.example.com',
localTenantKey: 'default',
legacyJwtEnabled: false,
});
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
expect(url).not.toContain('one-time-code-must-stay-in-body');
expect(JSON.parse(String(init.body)).onboardingCode).toBe('one-time-code-must-stay-in-body');
expect(new Headers(init.headers).get('If-Match')).toBe('W/"0"');
expect(new Headers(init.headers).get('Idempotency-Key')).toContain('identity-pair-');
});
it('sends the revision version when validating a draft or active runtime', async () => {
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ id: 'revision', version: 8 }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}));
vi.stubGlobal('fetch', fetchMock);
vi.stubGlobal('crypto', { randomUUID: () => '00000000-0000-4000-8000-000000000000' });
await validateIdentityRevision('manager-token', 'revision', 7);
const [, init] = fetchMock.mock.calls[0] as [string, RequestInit];
expect(new Headers(init.headers).get('If-Match')).toBe('W/"7"');
expect(init.credentials).toBe('include');
});
});
describe('security event connection transport', () => {
afterEach(() => {
vi.unstubAllGlobals();