117 lines
4.1 KiB
TypeScript
117 lines
4.1 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
|
|
import {
|
|
KEYCLOAK_LOGIN_STATE_KEY,
|
|
KEYCLOAK_LOGOUT_IN_PROGRESS_KEY,
|
|
buildKeycloakAuthorizeUrl,
|
|
buildKeycloakLogoutUrl,
|
|
clearKeycloakLogoutInProgress,
|
|
createCodeChallenge,
|
|
isKeycloakLogoutInProgress,
|
|
markKeycloakLogoutInProgress,
|
|
parseAuthCallbackUrl,
|
|
} from './keycloak-oidc';
|
|
|
|
function createStorage() {
|
|
const data = new Map<string, string>();
|
|
return {
|
|
getItem: (key: string) => data.get(key) ?? null,
|
|
setItem: (key: string, value: string) => data.set(key, value),
|
|
removeItem: (key: string) => data.delete(key),
|
|
clear: () => data.clear(),
|
|
};
|
|
}
|
|
|
|
const testSessionStorage = createStorage();
|
|
const testWindow = {
|
|
location: new URL('http://172.19.0.245:18080/login'),
|
|
sessionStorage: testSessionStorage,
|
|
crypto: globalThis.crypto,
|
|
};
|
|
|
|
Object.defineProperty(globalThis, 'window', {
|
|
configurable: true,
|
|
value: testWindow,
|
|
});
|
|
Object.defineProperty(globalThis, 'sessionStorage', {
|
|
configurable: true,
|
|
value: testSessionStorage,
|
|
});
|
|
|
|
function setWindowLocation(url: string) {
|
|
testWindow.location = new URL(url);
|
|
}
|
|
|
|
describe('keycloak oidc helpers', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date('2026-06-15T08:00:00.000Z'));
|
|
testSessionStorage.clear();
|
|
setWindowLocation('http://172.19.0.245:18080/login');
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
testSessionStorage.clear();
|
|
});
|
|
|
|
test('builds a Keycloak authorization URL using code flow with PKCE and nonce', () => {
|
|
const url = new URL(buildKeycloakAuthorizeUrl({
|
|
state: 'state-1',
|
|
nonce: 'nonce-1',
|
|
codeChallenge: 'challenge-1',
|
|
nextPath: '/files',
|
|
}));
|
|
|
|
expect(url.origin + url.pathname).toBe('https://keycloak.bwgdi.com/realms/beaver/protocol/openid-connect/auth');
|
|
expect(url.searchParams.get('client_id')).toBe('beaver-agnet');
|
|
expect(url.searchParams.get('response_type')).toBe('code');
|
|
expect(url.searchParams.get('scope')).toBe('openid profile email');
|
|
expect(url.searchParams.get('redirect_uri')).toBe('http://172.19.0.245:18080/auth/callback');
|
|
expect(url.searchParams.get('state')).toBe('state-1');
|
|
expect(url.searchParams.get('nonce')).toBe('nonce-1');
|
|
expect(url.searchParams.get('code_challenge')).toBe('challenge-1');
|
|
expect(url.searchParams.get('code_challenge_method')).toBe('S256');
|
|
});
|
|
|
|
test('creates the RFC 7636 S256 challenge without requiring https WebCrypto', async () => {
|
|
const challenge = await createCodeChallenge('dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk');
|
|
|
|
expect(challenge).toBe('E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM');
|
|
});
|
|
|
|
test('builds a logout URL with post_logout_redirect_uri and id_token_hint', () => {
|
|
const url = new URL(buildKeycloakLogoutUrl('id-token-1'));
|
|
|
|
expect(url.origin + url.pathname).toBe('https://keycloak.bwgdi.com/realms/beaver/protocol/openid-connect/logout');
|
|
expect(url.searchParams.get('client_id')).toBe('beaver-agnet');
|
|
expect(url.searchParams.get('id_token_hint')).toBe('id-token-1');
|
|
expect(url.searchParams.get('post_logout_redirect_uri')).toBe('http://172.19.0.245:18080/logout/callback');
|
|
});
|
|
|
|
test('tracks logout progress briefly to prevent immediate login restart', () => {
|
|
expect(isKeycloakLogoutInProgress()).toBe(false);
|
|
|
|
markKeycloakLogoutInProgress();
|
|
|
|
expect(testSessionStorage.getItem(KEYCLOAK_LOGOUT_IN_PROGRESS_KEY)).toBe('1781510400000');
|
|
expect(isKeycloakLogoutInProgress()).toBe(true);
|
|
|
|
vi.advanceTimersByTime(121_000);
|
|
|
|
expect(isKeycloakLogoutInProgress()).toBe(false);
|
|
});
|
|
|
|
test('parses callback code and state from the current URL', () => {
|
|
setWindowLocation('http://172.19.0.245:18080/auth/callback?code=abc&state=xyz');
|
|
|
|
expect(parseAuthCallbackUrl()).toEqual({ code: 'abc', state: 'xyz', error: '', errorDescription: '' });
|
|
});
|
|
|
|
test('exports stable session storage keys', () => {
|
|
expect(KEYCLOAK_LOGIN_STATE_KEY).toBe('beaver_keycloak_login_state');
|
|
clearKeycloakLogoutInProgress();
|
|
expect(testSessionStorage.getItem(KEYCLOAK_LOGOUT_IN_PROGRESS_KEY)).toBeNull();
|
|
});
|
|
});
|