fix(web): 完善统一认证恢复与重配交互
为处理中、失败和清理中的配对展示明确状态与恢复动作,支持放弃并清理、作用域内退役冲突连接,并在 Active 状态引导管理员先安全禁用再使用新接入码。同步修复运行时切换后的浏览器会话残留与确认弹窗焦点约束。 验证:pnpm test;pnpm lint;pnpm build。
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { OIDC_BROWSER_SESSION_CREDENTIAL } from '../api';
|
||||
import { restoreOIDCBrowserSession } from './oidc-browser-session';
|
||||
import {
|
||||
reconcileOIDCBrowserSessionAfterRuntimeChange,
|
||||
restoreOIDCBrowserSession,
|
||||
} from './oidc-browser-session';
|
||||
|
||||
describe('OIDC browser session lifecycle', () => {
|
||||
it('restores a shared cookie session in a fresh tab', async () => {
|
||||
@@ -21,4 +24,42 @@ describe('OIDC browser session lifecycle', () => {
|
||||
|
||||
await expect(restoreOIDCBrowserSession()).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it('resets App authentication when an OIDC sentinel session re-probe returns 401', async () => {
|
||||
const resetAuthenticatedSession = vi.fn();
|
||||
const onRestored = vi.fn();
|
||||
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
||||
error: { message: 'unauthorized', status: 401 },
|
||||
}), { status: 401, headers: { 'Content-Type': 'application/json' } }));
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
await expect(reconcileOIDCBrowserSessionAfterRuntimeChange({
|
||||
credential: OIDC_BROWSER_SESSION_CREDENTIAL,
|
||||
oidcEnabled: true,
|
||||
onReset: resetAuthenticatedSession,
|
||||
onRestored,
|
||||
})).resolves.toBe('reset');
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledOnce();
|
||||
expect(resetAuthenticatedSession).toHaveBeenCalledOnce();
|
||||
expect(onRestored).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('resets App authentication without probing /me when identity is disabled', async () => {
|
||||
const resetAuthenticatedSession = vi.fn();
|
||||
const onRestored = vi.fn();
|
||||
const fetchMock = vi.fn();
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
await expect(reconcileOIDCBrowserSessionAfterRuntimeChange({
|
||||
credential: OIDC_BROWSER_SESSION_CREDENTIAL,
|
||||
oidcEnabled: false,
|
||||
onReset: resetAuthenticatedSession,
|
||||
onRestored,
|
||||
})).resolves.toBe('reset');
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
expect(resetAuthenticatedSession).toHaveBeenCalledOnce();
|
||||
expect(onRestored).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,13 @@ export interface RestoredOIDCBrowserSession {
|
||||
user: CurrentUser;
|
||||
}
|
||||
|
||||
export interface ReconcileOIDCBrowserSessionOptions {
|
||||
credential: string;
|
||||
oidcEnabled: boolean;
|
||||
onReset: () => void;
|
||||
onRestored: (session: RestoredOIDCBrowserSession) => void;
|
||||
}
|
||||
|
||||
export async function restoreOIDCBrowserSession(): Promise<RestoredOIDCBrowserSession | null> {
|
||||
try {
|
||||
const user = await getCurrentUser(OIDC_BROWSER_SESSION_CREDENTIAL);
|
||||
@@ -20,3 +27,20 @@ export async function restoreOIDCBrowserSession(): Promise<RestoredOIDCBrowserSe
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function reconcileOIDCBrowserSessionAfterRuntimeChange(
|
||||
options: ReconcileOIDCBrowserSessionOptions,
|
||||
): Promise<'ignored' | 'reset' | 'restored'> {
|
||||
if (options.credential !== OIDC_BROWSER_SESSION_CREDENTIAL) return 'ignored';
|
||||
if (!options.oidcEnabled) {
|
||||
options.onReset();
|
||||
return 'reset';
|
||||
}
|
||||
const restored = await restoreOIDCBrowserSession();
|
||||
if (!restored) {
|
||||
options.onReset();
|
||||
return 'reset';
|
||||
}
|
||||
options.onRestored(restored);
|
||||
return 'restored';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user