2026-06-30 14:29:36 -04:00
|
|
|
import { test } from 'node:test';
|
|
|
|
|
import assert from 'node:assert/strict';
|
|
|
|
|
import { setFallbackSession, removeFallbackSession, getFallbackSession } from './sessions';
|
|
|
|
|
|
|
|
|
|
// The fallback-session helpers read/write specific `cinny_*` keys directly on
|
|
|
|
|
// `localStorage`. node has none, so install a controllable in-memory mock per
|
|
|
|
|
// case backed by a Map.
|
|
|
|
|
const installStorage = (): Map<string, string> => {
|
|
|
|
|
const store = new Map<string, string>();
|
|
|
|
|
(globalThis as { localStorage?: unknown }).localStorage = {
|
|
|
|
|
getItem: (key: string) => (store.has(key) ? store.get(key) : null),
|
|
|
|
|
setItem: (key: string, value: string) => {
|
|
|
|
|
store.set(key, String(value));
|
|
|
|
|
},
|
|
|
|
|
removeItem: (key: string) => {
|
|
|
|
|
store.delete(key);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
return store;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
test('setFallbackSession writes the cinny_* keys', () => {
|
|
|
|
|
const store = installStorage();
|
|
|
|
|
setFallbackSession('token-1', 'DEVICE1', '@alice:example.org', 'https://hs.example.org');
|
|
|
|
|
|
|
|
|
|
assert.equal(store.get('cinny_access_token'), 'token-1');
|
|
|
|
|
assert.equal(store.get('cinny_device_id'), 'DEVICE1');
|
|
|
|
|
assert.equal(store.get('cinny_user_id'), '@alice:example.org');
|
|
|
|
|
assert.equal(store.get('cinny_hs_base_url'), 'https://hs.example.org');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('getFallbackSession round-trips a full session and flags fallback stores', () => {
|
|
|
|
|
installStorage();
|
|
|
|
|
setFallbackSession('token-1', 'DEVICE1', '@alice:example.org', 'https://hs.example.org');
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(getFallbackSession(), {
|
|
|
|
|
baseUrl: 'https://hs.example.org',
|
|
|
|
|
userId: '@alice:example.org',
|
|
|
|
|
deviceId: 'DEVICE1',
|
|
|
|
|
accessToken: 'token-1',
|
|
|
|
|
fallbackSdkStores: true,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('getFallbackSession returns undefined when nothing is stored', () => {
|
|
|
|
|
installStorage();
|
|
|
|
|
assert.equal(getFallbackSession(), undefined);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('getFallbackSession returns undefined when a single key is missing', () => {
|
|
|
|
|
// Every one of the four keys is required; missing any one yields undefined.
|
|
|
|
|
const keys = [
|
|
|
|
|
'cinny_access_token',
|
|
|
|
|
'cinny_device_id',
|
|
|
|
|
'cinny_user_id',
|
|
|
|
|
'cinny_hs_base_url',
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
keys.forEach((missing) => {
|
|
|
|
|
installStorage();
|
|
|
|
|
setFallbackSession('token-1', 'DEVICE1', '@alice:example.org', 'https://hs.example.org');
|
|
|
|
|
localStorage.removeItem(missing);
|
|
|
|
|
assert.equal(getFallbackSession(), undefined, `missing ${missing} should yield undefined`);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('removeFallbackSession clears all keys', () => {
|
|
|
|
|
const store = installStorage();
|
|
|
|
|
setFallbackSession('token-1', 'DEVICE1', '@alice:example.org', 'https://hs.example.org');
|
|
|
|
|
removeFallbackSession();
|
|
|
|
|
|
|
|
|
|
assert.equal(store.size, 0);
|
|
|
|
|
assert.equal(getFallbackSession(), undefined);
|
|
|
|
|
});
|
2026-06-30 15:55:30 -04:00
|
|
|
|
|
|
|
|
test('round-trips an OIDC session (refresh token, expiry, oidc metadata)', () => {
|
|
|
|
|
installStorage();
|
|
|
|
|
setFallbackSession('tok', 'DEV', '@bob:mozilla.org', 'https://matrix-client.mozilla.org', {
|
|
|
|
|
refreshToken: 'refresh-xyz',
|
|
|
|
|
expiresInMs: 3_600_000,
|
|
|
|
|
oidc: {
|
|
|
|
|
issuer: 'https://chat.mozilla.org/',
|
|
|
|
|
clientId: 'client-123',
|
|
|
|
|
redirectUri: 'https://chat.lotusguild.org/auth/oidc/callback',
|
|
|
|
|
idTokenClaims: { sub: '@bob:mozilla.org', aud: 'client-123' },
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const s = getFallbackSession();
|
|
|
|
|
assert.ok(s);
|
|
|
|
|
assert.equal(s.refreshToken, 'refresh-xyz');
|
|
|
|
|
// stored as absolute expiry, read back as remaining lifetime
|
|
|
|
|
assert.ok(s.expiresInMs! > 0 && s.expiresInMs! <= 3_600_000);
|
|
|
|
|
assert.deepEqual(s.oidc, {
|
|
|
|
|
issuer: 'https://chat.mozilla.org/',
|
|
|
|
|
clientId: 'client-123',
|
|
|
|
|
redirectUri: 'https://chat.lotusguild.org/auth/oidc/callback',
|
|
|
|
|
idTokenClaims: { sub: '@bob:mozilla.org', aud: 'client-123' },
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('a password session carries no OIDC fields, and re-saving clears stale OIDC keys', () => {
|
|
|
|
|
installStorage();
|
|
|
|
|
// first an OIDC session...
|
|
|
|
|
setFallbackSession('tok', 'DEV', '@bob:mozilla.org', 'https://hs', {
|
|
|
|
|
refreshToken: 'r',
|
|
|
|
|
oidc: { issuer: 'https://i', clientId: 'c', redirectUri: 'https://cb' },
|
|
|
|
|
});
|
|
|
|
|
assert.ok(getFallbackSession()?.oidc);
|
|
|
|
|
// ...overwritten by a plain password session must drop the OIDC state
|
|
|
|
|
setFallbackSession('tok2', 'DEV', '@alice:example.org', 'https://hs');
|
|
|
|
|
const s = getFallbackSession();
|
|
|
|
|
assert.ok(s);
|
|
|
|
|
assert.equal(s.oidc, undefined);
|
|
|
|
|
assert.equal(s.refreshToken, undefined);
|
|
|
|
|
});
|