first commit
This commit is contained in:
commit
ff482ebe23
251 files changed
+19126
No files matched your search
@@ -0,0 +1,58 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {mkdtempSync,rmSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} from 'node:path';
|
||||
import {openStore,digest} from '../apps/server/store.mjs';
|
||||
import {createApp} from '../apps/server/app.mjs';
|
||||
import {verifyPolicy} from '../packages/core/policy.mjs';
|
||||
import {policyDiff} from '../apps/server/policy-history.mjs';
|
||||
async function fixture(t){
|
||||
const dir=mkdtempSync(join(tmpdir(),'safetynet-history-')),store=openStore(dir),credential='synthetic-history-admin-credential-123456789',origin='http://127.0.0.1:4310';
|
||||
const server=createApp(store,{adminToken:credential,publicUrl:origin,secure:false}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));
|
||||
t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});});
|
||||
async function api(path,body,role){
|
||||
const headers={'content-type':'application/json'};
|
||||
if(role){const key=`synthetic-history-${role}-credential-123456789`;store.db.prepare('INSERT OR REPLACE INTO sessions VALUES(?,?,?)').run(digest(key),Date.now()+60000,JSON.stringify({sub:role,role}));headers.cookie=`sn_session=${key}`;headers.origin=origin;}else headers.authorization=`Bearer ${credential}`;
|
||||
const response=await fetch(`http://127.0.0.1:${server.address().port}${path}`,{method:body?'POST':'GET',headers,body:body?JSON.stringify(body):undefined});return {status:response.status,body:await response.json()};
|
||||
}
|
||||
function save(policy){store.put('policy',policy);store.db.prepare('INSERT INTO policies VALUES(?,?,?,?)').run(policy.version,new Date().toISOString(),'test-publisher',JSON.stringify(policy));}
|
||||
return {store,api,save};
|
||||
}
|
||||
test('history cursor, schema snapshots and deterministic diffs cover immutable global versions',async t=>{
|
||||
const f=await fixture(t),p=f.store.get('policy');p.version=2;p.brand.name='Company Browser';f.save(p);
|
||||
const list=await f.api('/api/admin/policy/history?limit=1');assert.equal(list.status,200);assert.equal(list.body.versions[0].version,2);assert.equal(list.body.versions[0].current,true);assert.equal(list.body.nextBefore,2);
|
||||
const next=await f.api('/api/admin/policy/history?limit=1&before=2');assert.equal(next.body.versions[0].version,1);assert.equal(next.body.nextBefore,null);
|
||||
const item=await f.api('/api/admin/policy/history/2');assert.equal(item.body.policy.brand.name,'Company Browser');assert.equal(JSON.stringify(item.body).includes('privateKey'),false);
|
||||
const diff=await f.api('/api/admin/policy/diff?from=1&to=2');assert.equal(diff.status,200);assert.ok(diff.body.changes.some(c=>c.path==='/brand/name'&&c.after==='Company Browser'));assert.equal(diff.body.truncated,false);
|
||||
assert.equal((await f.api('/api/admin/policy/history/999')).status,404);assert.equal((await f.api('/api/admin/policy/diff?from=1&to=999')).status,404);assert.equal((await f.api('/api/admin/policy/history?limit=101')).status,400);
|
||||
});
|
||||
test('restore publishes new signed monotonic policy with refreshed expiry and preserves source and audit privacy',async t=>{
|
||||
const f=await fixture(t),expired=f.store.get('policy');expired.expiresAt='2020-01-01T00:00:00.000Z';f.store.db.prepare('UPDATE policies SET policy=? WHERE version=1').run(JSON.stringify(expired));
|
||||
const source=f.store.db.prepare('SELECT policy FROM policies WHERE version=1').get().policy,p=f.store.get('policy');p.version=2;p.brand.name='Changed';f.save(p);
|
||||
const result=await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:2,approve:true});assert.equal(result.status,200);assert.equal(result.body.version,3);assert.equal(result.body.sourceVersion,1);
|
||||
const signed=verifyPolicy(f.store.envelope(),f.store.publicKey());assert.equal(signed.version,3);assert.notEqual(signed.brand.name,'Changed');assert.ok(Date.parse(signed.expiresAt)>Date.now()+6*86400000);
|
||||
assert.equal(f.store.db.prepare('SELECT policy FROM policies WHERE version=1').get().policy,source);
|
||||
const audit=JSON.parse(f.store.db.prepare("SELECT metadata FROM events WHERE kind='policy.restored'").get().metadata);assert.deepEqual(audit,{actor:'bootstrap-admin',version:3,sourceVersion:1});
|
||||
assert.equal((await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:2,approve:true})).status,409);assert.equal(f.store.get('policy').version,3);
|
||||
assert.equal((await f.api('/api/admin/policy/restore',{sourceVersion:999,expectedVersion:3,approve:true})).status,404);assert.equal((await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:3,approve:false})).status,400);
|
||||
});
|
||||
test('invalid historic schemas cannot be restored and failure rolls back without new audit or version',async t=>{
|
||||
const f=await fixture(t),invalid=f.store.get('policy');invalid.proxy={mode:'unknown-mode'};f.store.db.prepare('UPDATE policies SET policy=? WHERE version=1').run(JSON.stringify(invalid));
|
||||
const result=await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:1,approve:true});assert.equal(result.status,400);assert.equal(f.store.get('policy').version,1);assert.equal(f.store.db.prepare("SELECT COUNT(*) AS count FROM events WHERE kind='policy.restored'").get().count,0);
|
||||
assert.equal((await f.api('/api/admin/policy/history')).status,200);
|
||||
});
|
||||
test('history and restoration enforce read/write roles and never expose legacy personal bookmarks',async t=>{
|
||||
const f=await fixture(t),p=f.store.get('policy');p.browser.bookmarks.personal={revision:2,items:[{id:'private',title:'Private bookmark',url:'https://private.example/',folder:''}]};f.store.db.prepare('UPDATE policies SET policy=? WHERE version=1').run(JSON.stringify(p));
|
||||
for(const role of ['operator','auditor']){assert.equal((await f.api('/api/admin/policy/history',null,role)).status,200);const item=await f.api('/api/admin/policy/history/1',null,role);assert.deepEqual(item.body.policy.browser.bookmarks.personal,{revision:0,items:[]});assert.equal((await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:1,approve:true},role)).status,403);}
|
||||
assert.equal(f.store.get('policy').version,1);
|
||||
});
|
||||
test('restore cannot collide historical company IDs with current device personal entries',async t=>{
|
||||
const f=await fixture(t),source=f.store.get('policy');source.browser.bookmarks.managed=[{id:'collision',title:'Company',url:'https://company.example/',folder:''}];f.store.db.prepare('UPDATE policies SET policy=? WHERE version=1').run(JSON.stringify(source));
|
||||
const p=f.store.get('policy');p.version=2;f.save(p);f.store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash) VALUES(?,?,?,?,?)').run('test-device','Test','win32','user','hash');f.store.db.prepare('INSERT INTO bookmarks VALUES(?,?,?)').run('test-device',1,JSON.stringify([{id:'collision',title:'Private',url:'https://private.example/',folder:''}]));
|
||||
const result=await f.api('/api/admin/policy/restore',{sourceVersion:1,expectedVersion:2,approve:true});assert.equal(result.status,409);assert.equal(f.store.get('policy').version,2);assert.equal(f.store.db.prepare('SELECT COUNT(*) AS count FROM policies').get().count,2);
|
||||
});
|
||||
test('diff limits size, preserves arrays and escapes JSON pointer paths',()=>{
|
||||
const result=policyDiff({'a/b':1,'x~y':1,list:[1]}, {'a/b':2,'x~y':2,list:[2]},2);assert.equal(result.truncated,true);assert.deepEqual(result.changes.map(c=>c.path),['/a~1b','/list']);assert.deepEqual(result.changes[1].after,[2]);
|
||||
assert.deepEqual(policyDiff({}, {new:1}).changes,[{path:'/new',kind:'added',after:1}]);
|
||||
});
|
||||
Reference in new issue
Block a user