19 lines
6.7 KiB
JavaScript
19 lines
6.7 KiB
JavaScript
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 {randomUUID} from 'node:crypto';
|
||
|
|
import {openStore} from '../apps/server/store.mjs';
|
||
|
|
import {createApp} from '../apps/server/app.mjs';
|
||
|
|
import {BookmarkSchema,BookmarkItemsSchema,verifyBookmarkSnapshot} from '../packages/core/bookmarks.mjs';
|
||
|
|
import {verifyPolicy} from '../packages/core/policy.mjs';
|
||
|
|
async function fixture(t){const dir=mkdtempSync(join(tmpdir(),'bookmark-test-')),store=openStore(dir),adminToken='test-only-admin-token';const server=createApp(store,{adminToken,publicUrl:'http://127.0.0.1',secure:false}).listen(0,'127.0.0.1');await new Promise(r=>server.on('listening',r));t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});});async function api(route,{body,token=adminToken,method=body?'POST':'GET'}={}){const r=await fetch(`http://127.0.0.1:${server.address().port}${route}`,{method,headers:{authorization:`Bearer ${token}`,...(body?{'content-type':'application/json'}:{})},body:body?JSON.stringify(body):undefined});return {status:r.status,body:await r.json()};}async function enroll(){const code=(await api('/api/admin/enrollments',{body:{user:'public-fixture'}})).body.code;return (await api('/api/device/enroll',{body:{code,name:'Windows fixture',platform:'win32'},token:''})).body;}return {store,api,enroll};}
|
||
|
|
const entry={id:'intranet',title:'Company intranet',url:'https://intranet.example/',folder:'Work'};
|
||
|
|
test('bookmarks reject secrets, executable schemes and duplicate IDs',()=>{for(const url of ['javascript:alert(1)','file:///C:/secret','https://user:[email protected]','https://example.com/?token=secret','https://example.com/#secret'])assert.equal(BookmarkSchema.safeParse({...entry,url}).success,false);assert.equal(BookmarkSchema.parse(entry).url,entry.url);});
|
||
|
|
test('device bookmark changes are isolated, signed, revisioned and do not republish fleet policy',async t=>{const {store,api,enroll}=await fixture(t),a=await enroll(),b=await enroll();const result=await api('/api/device/bookmarks',{token:a.token,body:{action:'add',bookmark:entry,expectedRevision:0,policyVersion:1}});assert.equal(result.status,200);assert.equal(result.body.version,1);const envelope=(await api('/api/device/policy',{token:a.token})).body;assert.equal(verifyPolicy(envelope,a.publicKey).browser.bookmarks.personal.items.length,0);const payload=verifyBookmarkSnapshot(envelope.bookmarks,a.publicKey,{deviceId:a.id,policyVersion:1});assert.deepEqual(payload.items,[entry]);assert.equal(payload.revision,1);assert.equal((await api('/api/device/policy',{token:b.token})).body.bookmarks.payload.items.length,0);assert.throws(()=>verifyBookmarkSnapshot(envelope.bookmarks,a.publicKey,{deviceId:b.id,policyVersion:1}));assert.throws(()=>verifyBookmarkSnapshot(envelope.bookmarks,a.publicKey,{deviceId:a.id,policyVersion:1,minimumRevision:2}));assert.equal((await api('/api/device/bookmarks',{token:a.token,body:{action:'delete',id:entry.id,expectedRevision:0,policyVersion:1}})).status,409);const metadata=store.db.prepare("SELECT metadata FROM events WHERE kind='bookmarks.changed'").get().metadata;assert.ok(!metadata.includes(entry.url));assert.ok(!metadata.includes(entry.title));});
|
||
|
|
test('managed immutability, user permissions and admin read permission enforced at APIs',async t=>{const {api,enroll}=await fixture(t),a=await enroll();let p=(await api('/api/admin/overview')).body.policy;p.browser.bookmarks.managed=[entry];p.browser.bookmarks.allowUserAdd=false;p.version++;assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,200);assert.equal((await api('/api/device/bookmarks',{token:a.token,body:{action:'add',bookmark:{...entry,id:'personal'},expectedRevision:0,policyVersion:2}})).status,403);assert.equal((await api(`/api/admin/devices/${a.id}/bookmarks`,{method:'PUT',body:{expectedRevision:0,policyVersion:2,items:[{...entry,id:'personal'}]}})).status,200);assert.equal((await api('/api/device/bookmarks',{token:a.token,body:{action:'delete',id:entry.id,expectedRevision:1,policyVersion:2}})).status,403);p.version++;p.browser.bookmarks.adminReadUserBookmarks=false;assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,200);assert.equal((await api(`/api/admin/devices/${a.id}/bookmarks`)).status,403);assert.equal((await api(`/api/admin/devices/${a.id}/bookmarks`,{method:'PUT',body:{expectedRevision:1,policyVersion:3,items:[]}})).status,403);});
|
||
|
|
test('global policy refuses device personal data and managed ID collisions',async t=>{const {api,enroll}=await fixture(t),a=await enroll();await api('/api/device/bookmarks',{token:a.token,body:{action:'add',bookmark:entry,expectedRevision:0,policyVersion:1}});let p=(await api('/api/admin/overview')).body.policy;p.version++;p.browser.bookmarks.personal={revision:1,items:[entry]};assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,400);p.browser.bookmarks.personal={revision:0,items:[]};p.browser.bookmarks.managed=[entry];assert.equal((await api('/api/admin/policy',{method:'PUT',body:p})).status,400);});
|
||
|
|
test('bookmark canonicalization rejects Unicode URL expansion and oversized collections',()=>{assert.equal(BookmarkSchema.parse({...entry,url:'https://EXAMPLE.com'}).url,'https://example.com/');assert.equal(BookmarkSchema.safeParse({...entry,url:'https://example.com/'+ '界'.repeat(1000)}).success,false);const bulky=Array.from({length:100},(_,i)=>({...entry,id:'id'+i,url:'https://example.com/'+ 'a'.repeat(2000),title:'界'.repeat(120),folder:'界'.repeat(80)}));assert.equal(BookmarkItemsSchema.safeParse(bulky).success,false);});
|
||
|
|
|
||
|
|
test('snapshot signatures reject tampering and expired policy and revoked devices cannot mutate',async t=>{const {store,api,enroll}=await fixture(t),a=await enroll(),env=(await api('/api/device/policy',{token:a.token})).body;const modified=structuredClone(env.bookmarks);modified.payload.items=[entry];assert.throws(()=>verifyBookmarkSnapshot(modified,a.publicKey,{deviceId:a.id,policyVersion:1}));assert.throws(()=>verifyBookmarkSnapshot(env.bookmarks,a.publicKey,{deviceId:a.id,policyVersion:1,now:Date.parse(env.bookmarks.payload.expiresAt)+1}));const p=store.get('policy');p.expiresAt=new Date(Date.now()-1000).toISOString();store.put('policy',p);assert.equal((await api('/api/device/bookmarks',{token:a.token,body:{action:'add',bookmark:entry,expectedRevision:0,policyVersion:1}})).status,403);await api(`/api/admin/devices/${a.id}/revoke`,{body:{}});assert.equal((await api('/api/device/bookmarks',{token:a.token,body:{action:'add',bookmark:entry,expectedRevision:0,policyVersion:1}})).status,401);assert.equal((await api(`/api/admin/devices/${a.id}/bookmarks`)).status,404);});
|