chore(types): typed secret storage key cache; drop the cryptoCallbacks cast (#210)
CI / Build & Quality Checks (push) Skipped
CI / Secret scan (gitleaks) (push) Skipped
CI / Playwright smoke (e2e) (push) Skipped
CI / Build & Quality Checks (pull_request) Successful in 3m32s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 16s
CI / Playwright smoke (e2e) (pull_request) Successful in 24m32s
CI / Build & Quality Checks (push) Skipped
CI / Secret scan (gitleaks) (push) Skipped
CI / Playwright smoke (e2e) (push) Skipped
CI / Build & Quality Checks (pull_request) Successful in 3m32s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 16s
CI / Playwright smoke (e2e) (pull_request) Successful in 24m32s
secretStorageKeys was untyped JS, passed to createClient behind `as any`, which hid that getSecretStorageKey returned undefined where the SDK type says null. Now TypeScript against the SDK's CryptoCallbacks, returns null, and storePrivateKey keeps its own copy of the key. Unit test added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3b3bf8abac
commit
b944456ab5
@@ -87,7 +87,7 @@ export const initClient = async (session: Session): Promise<MatrixClient> => {
|
||||
// the user (KeyUploadBanner) instead of retrying silently forever.
|
||||
fetchFn: watchKeyUploads((input, init) => globalThis.fetch(input, init)),
|
||||
timelineSupport: true,
|
||||
cryptoCallbacks: cryptoCallbacks as any,
|
||||
cryptoCallbacks,
|
||||
// SAS (emoji) + QR-code verification (show/scan/reciprocate).
|
||||
verificationMethods: ['m.sas.v1', 'm.qr_code.show.v1', 'm.qr_code.scan.v1', 'm.reciprocate.v1'],
|
||||
oauthClientId: oidcIdentity?.oidc.clientId,
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
const secretStorageKeys = new Map();
|
||||
|
||||
export function storePrivateKey(keyId, privateKey) {
|
||||
if (privateKey instanceof Uint8Array === false) {
|
||||
throw new Error('Unable to store, privateKey is invalid.');
|
||||
}
|
||||
secretStorageKeys.set(keyId, privateKey);
|
||||
}
|
||||
|
||||
function hasPrivateKey(keyId) {
|
||||
return secretStorageKeys.get(keyId) instanceof Uint8Array;
|
||||
}
|
||||
|
||||
function getPrivateKey(keyId) {
|
||||
return secretStorageKeys.get(keyId);
|
||||
}
|
||||
|
||||
export function clearSecretStorageKeys() {
|
||||
secretStorageKeys.clear();
|
||||
}
|
||||
|
||||
async function getSecretStorageKey({ keys }) {
|
||||
const keyIds = Object.keys(keys);
|
||||
const keyId = keyIds.find(hasPrivateKey);
|
||||
if (!keyId) return undefined;
|
||||
const privateKey = getPrivateKey(keyId);
|
||||
return [keyId, privateKey];
|
||||
}
|
||||
|
||||
function cacheSecretStorageKey(keyId, keyInfo, privateKey) {
|
||||
secretStorageKeys.set(keyId, privateKey);
|
||||
}
|
||||
|
||||
export const cryptoCallbacks = {
|
||||
getSecretStorageKey,
|
||||
cacheSecretStorageKey,
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { clearSecretStorageKeys, cryptoCallbacks, storePrivateKey } from './secretStorageKeys';
|
||||
|
||||
const ask = (...ids: string[]) =>
|
||||
cryptoCallbacks.getSecretStorageKey!(
|
||||
{ keys: Object.fromEntries(ids.map((id) => [id, {} as never])) },
|
||||
'm.cross_signing.master',
|
||||
);
|
||||
|
||||
test('[#210] secret storage key cache: null when unknown, key when stored, copies input', async () => {
|
||||
clearSecretStorageKeys();
|
||||
assert.equal(await ask('k1'), null);
|
||||
|
||||
const key = new Uint8Array([1, 2, 3]);
|
||||
storePrivateKey('k1', key);
|
||||
key[0] = 9; // the cache keeps its own copy
|
||||
assert.deepEqual(await ask('k0', 'k1'), ['k1', new Uint8Array([1, 2, 3])]);
|
||||
|
||||
cryptoCallbacks.cacheSecretStorageKey!('k2', {} as never, new Uint8Array([7]));
|
||||
assert.deepEqual(await ask('k2'), ['k2', new Uint8Array([7])]);
|
||||
|
||||
clearSecretStorageKeys();
|
||||
assert.equal(await ask('k1', 'k2'), null);
|
||||
assert.throws(() => storePrivateKey('k3', 'nope' as never), /invalid/);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { CryptoCallbacks } from 'matrix-js-sdk/lib/crypto-api';
|
||||
|
||||
const secretStorageKeys = new Map<string, Uint8Array<ArrayBuffer>>();
|
||||
|
||||
export function storePrivateKey(keyId: string, privateKey: Uint8Array) {
|
||||
if (privateKey instanceof Uint8Array === false) {
|
||||
throw new Error('Unable to store, privateKey is invalid.');
|
||||
}
|
||||
// Our own copy, on a plain ArrayBuffer (what the SDK's callback type wants).
|
||||
secretStorageKeys.set(keyId, new Uint8Array(privateKey));
|
||||
}
|
||||
|
||||
export function clearSecretStorageKeys() {
|
||||
secretStorageKeys.clear();
|
||||
}
|
||||
|
||||
export const cryptoCallbacks: CryptoCallbacks = {
|
||||
async getSecretStorageKey({ keys }) {
|
||||
const keyId = Object.keys(keys).find((id) => secretStorageKeys.get(id) instanceof Uint8Array);
|
||||
const privateKey = keyId ? secretStorageKeys.get(keyId) : undefined;
|
||||
// The SDK expects null (not undefined) when no key is cached.
|
||||
return keyId && privateKey ? [keyId, privateKey] : null;
|
||||
},
|
||||
cacheSecretStorageKey(keyId, keyInfo, privateKey) {
|
||||
secretStorageKeys.set(keyId, privateKey);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user