Archived
59 lines
3.8 KiB
JavaScript
59 lines
3.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {AUTO_LOCK} from '../vault-session.mjs';
|
|
import {VAULT_CAPABILITY} from '../connection-state.mjs';
|
|
|
|
test('worker keys stay private; trusted popup only; close, timer, auth loss and lock races fail closed', async () => {
|
|
let connect, valid = true;
|
|
const stored = {[VAULT_CAPABILITY]:{owner:'owner',capability:'A'.repeat(43)}}, timers = [];
|
|
globalThis.setInterval = () => 1; globalThis.clearInterval = () => {};
|
|
globalThis.setTimeout = callback => { timers.push(callback); return timers.length; };
|
|
globalThis.clearTimeout = () => {};
|
|
globalThis.chrome = {
|
|
runtime: {id:'fixture', getURL:path=>'chrome-extension://fixture/'+path,
|
|
getPlatformInfo:async()=>({}), onConnect:{addListener:fn=>connect=fn}},
|
|
storage:{local:{get:async key=>({[key]:stored[key]}),set:async data=>Object.assign(stored,data)}}
|
|
};
|
|
globalThis.fetch = async()=>new Response(JSON.stringify({owner:valid?'owner':'other',items:[]}),{headers:{'content-type':'application/json'}});
|
|
const {vaultUnlocked} = await import('../vault-worker.mjs');
|
|
function popup(url='chrome-extension://fixture/popup.html') {
|
|
let listener, close, disconnected = false, sequence = 0;
|
|
const replies = new Map();
|
|
connect({name:'human-vault',sender:{id:'fixture',url},disconnect:()=>disconnected=true,
|
|
onMessage:{addListener:fn=>listener=fn},onDisconnect:{addListener:fn=>close=fn},
|
|
postMessage:message=>{replies.get(message.id)?.(message);replies.delete(message.id)}});
|
|
return {get disconnected(){return disconnected}, close:()=>close(),
|
|
send:(op,data={})=>new Promise(resolve=>{const id=++sequence;replies.set(id,resolve);listener({id,op,owner:'owner',...data})})};
|
|
}
|
|
assert.equal(popup('https://persimmons.app/').disconnected,true);
|
|
assert.equal(popup('chrome-extension://fixture/other.html').disconnected,true);
|
|
let port = popup();
|
|
const unlock = () => port.send('unlock',{passphrase:'synthetic test passphrase'});
|
|
const firstUnlock = unlock();
|
|
await new Promise(resolve=>setImmediate(resolve));
|
|
assert.equal(vaultUnlocked('owner'),false,'status during unlock does not cancel it');
|
|
assert.equal((await firstUnlock).result,true);
|
|
assert.equal(vaultUnlocked('owner'),true);
|
|
assert.equal(vaultUnlocked('other'),false);
|
|
assert.equal(vaultUnlocked('owner'),true,'another owner status cannot lock the session');
|
|
const record=stored['human-vault:https://persimmons.app:owner'];
|
|
assert.ok(record.wrappedKey); assert.equal(Object.values(stored).some(x=>x instanceof CryptoKey),false);
|
|
assert.doesNotMatch(JSON.stringify(stored),/synthetic test passphrase/);
|
|
port.close(); port=popup();assert.equal((await port.send('status')).result,false);
|
|
stored[AUTO_LOCK]='5';assert.equal((await unlock()).result,true);
|
|
port.close();port=popup();assert.equal((await port.send('status')).result,true);
|
|
const encrypted=await port.send('encrypt',{origin:'https://wp.phly.cloud',account:{username:'fixture',password:'synthetic'}});
|
|
assert.equal(encrypted.ok,true);assert.ok(encrypted.result.ciphertext);
|
|
assert.deepEqual((await port.send('decrypt',{item:encrypted.result})).result,{username:'fixture',password:'synthetic'});
|
|
timers.at(-1)();assert.equal((await port.send('status')).result,false);
|
|
assert.equal((await unlock()).result,true);
|
|
valid=false;assert.equal((await port.send('status')).ok,false);valid=true;
|
|
assert.equal((await port.send('status')).result,false);
|
|
assert.equal((await port.send('unlock',{passphrase:'wrong'})).ok,false);
|
|
const pending=unlock();await port.send('lock');assert.equal((await pending).ok,false);
|
|
// Close invalidates any pending unlock and never installs its key.
|
|
unlock();port.close();port=popup();
|
|
await new Promise(resolve=>setImmediate(resolve));
|
|
assert.equal((await port.send('status')).result,false);
|
|
});
|