48 lines
4.1 KiB
JavaScript
48 lines
4.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import grants from '../apps/browser/agent-grants.cjs';
|
|
const now=Date.now(),expiry=new Date(now+60000).toISOString();
|
|
const policy=()=>({expiresAt:expiry,agents:{enabled:true,identityMode:'individual',scopes:['read','navigate'],allowedHosts:['corp.example'],grants:[{id:'worker.a',name:'Reader',enabled:true,expiresAt:expiry,scopes:['read'],allowedHosts:['corp.example'],generation:2}]}});
|
|
const authorize=changes=>grants.authorizeGrant({policy:policy(),ready:true,principalId:'worker.a',generation:2,action:'read',url:'https://corp.example/',hostMatches:(a,b)=>a===b,now,...changes});
|
|
test('active grant discovery excludes expired and disabled grants and exposes no mutable policy reference',()=>{
|
|
const p=policy();p.agents.grants.push({...p.agents.grants[0],id:'disabled',enabled:false},{...p.agents.grants[0],id:'expired',expiresAt:new Date(now-1).toISOString()});
|
|
const active=grants.activeGrants(p,now);assert.deepEqual(active.map(g=>g.id),['worker.a']);active[0].scopes.push('navigate');assert.deepEqual(p.agents.grants[0].scopes,['read']);
|
|
assert.deepEqual(grants.activeGrants(p,now+60001),[]);
|
|
});
|
|
test('individual authorization intersects global and principal rights and destinations',()=>{
|
|
assert.equal(authorize().allowed,true);
|
|
for(const changes of [{principalId:'worker.b'},{generation:1},{action:'navigate'},{ready:false},{url:'https://evil.example/'},{url:'file:///secret'},{now:now+60001}])assert.equal(authorize(changes).allowed,false);
|
|
const p=policy();p.agents.scopes=[];assert.equal(authorize({policy:p}).allowed,false);
|
|
p.agents.scopes=['read'];p.agents.grants[0].scopes=[];assert.equal(authorize({policy:p}).allowed,false);
|
|
p.agents.grants[0].scopes=['read'];p.agents.grants[0].allowedHosts=['other.example'];assert.equal(authorize({policy:p}).allowed,false);
|
|
});
|
|
test('legacy mode never accepts individual credentials and individual mode has no shared bypass',()=>{
|
|
assert.equal(authorize({principalId:undefined,generation:undefined}).allowed,false);
|
|
const p=policy();p.agents.identityMode='legacy';assert.equal(authorize({policy:p}).allowed,false);
|
|
assert.equal(authorize({policy:p,principalId:undefined,generation:undefined}).allowed,true);
|
|
delete p.agents.identityMode;assert.equal(authorize({policy:p,principalId:undefined,generation:undefined}).allowed,false);
|
|
});
|
|
test('generation floor survives removal and restart and disables restored older credentials',()=>{
|
|
const first=grants.reconcileGenerationFloor(policy().agents.grants,{});
|
|
const removed=grants.reconcileGenerationFloor([],JSON.parse(JSON.stringify(first.floor)));
|
|
const restored=grants.reconcileGenerationFloor([{...policy().agents.grants[0],generation:1}],removed.floor);
|
|
assert.equal(restored.grants[0].enabled,false);assert.equal(restored.floor['worker.a'],2);
|
|
assert.equal(grants.reconcileGenerationFloor([{...policy().agents.grants[0],generation:3}],restored.floor).grants[0].enabled,true);
|
|
});
|
|
test('protected credential publication is atomic, bounded to hashed names and stale-safe',async()=>{
|
|
const dir=fs.mkdtempSync(path.join(os.tmpdir(),'agent-grants-'));
|
|
try{
|
|
const filePath=grants.descriptorPath(dir,'worker.a');assert.match(path.basename(filePath),/^agent-[a-f0-9]{64}\.json$/);
|
|
await grants.persistConnection({filePath,connection:{token:'synthetic-secret'},protect:async()=> 'opaque-dpapi',isCurrent:()=>true});
|
|
const prior=fs.readFileSync(filePath,'utf8');assert.equal(prior.includes('synthetic-secret'),false);
|
|
let current=true;
|
|
await assert.rejects(grants.persistConnection({filePath,connection:{token:'other'},protect:async()=>{current=false;return 'new';},isCurrent:()=>current}),/context changed/);
|
|
assert.equal(fs.readFileSync(filePath,'utf8'),prior);assert.equal(fs.readdirSync(dir).length,1);
|
|
const linked=path.join(dir,'linked.json');fs.linkSync(filePath,linked);
|
|
await assert.rejects(grants.persistConnection({filePath,connection:{},protect:async()=>'',isCurrent:()=>true}),/Unsafe credential/);
|
|
}finally{fs.rmSync(dir,{recursive:true,force:true});}
|
|
});
|