// Run inside the disposable packaged container with only these synthetic keys. // Phases let the parent recreate the container against the same disposable data. import { finalizeEvent, getPublicKey } from 'nostr-tools'; const base = 'http://127.0.0.1:3000'; const key = new Uint8Array(32).fill(1); const other = new Uint8Array(32).fill(2); const body = '
Blossom qualification, synthetic data only.
'; const bytes = new TextEncoder().encode(body); const hash = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', bytes)), x => x.toString(16).padStart(2, '0')).join(''); const phase = Deno.env.get('BLOSSOM_FIXTURE_PHASE') ?? 'seed'; function auth(action: string, secret=key, server='127.0.0.1', expires=300, scopedHash=hash) { const now = Math.floor(Date.now()/1000); return 'Nostr ' + btoa(JSON.stringify(finalizeEvent({ kind:24242, created_at:now, content:'Local synthetic qualification only', tags:[['t',action],['x',scopedHash],['server',server],['expiration',String(now+expires)]] },secret))); } async function check(label: string, expected: number, path: string, init={}) { const r=await fetch(base+path,init); if(r.status!==expected) throw new Error(`${label}: expected ${expected}, got ${r.status}: ${await r.text()}`); console.log(`PASS ${label}: ${r.status}`);return r; } async function denied(label: string, path: string, init={}) { const r=await fetch(base+path,init); if(![401,403].includes(r.status)) throw new Error(`${label}: expected 401/403, got ${r.status}: ${await r.text()}`); console.log(`PASS ${label}: ${r.status}`);return r; } const upload=(token?:string)=>({method:'PUT',headers:{'content-type':'text/html',...(token?{authorization:token}:{})},body}); async function verifyStored() { const read=await check('read stored bytes',200,'/'+hash); if(await read.text()!==body) throw new Error('Stored bytes differ'); if(!read.headers.get('content-security-policy')?.includes('sandbox') || read.headers.get('content-disposition')!=='attachment') throw new Error('Active content not sandboxed'); console.log('PASS exact bytes and sandboxed attachment'); await check('anonymous list denied',401,'/list/'+getPublicKey(key)); await denied('other identity cannot list owner','/list/'+getPublicKey(key),{headers:{authorization:auth('list',other)}}); const listed=await (await check('owner list',200,'/list/'+getPublicKey(key),{headers:{authorization:auth('list')}})).json(); if(!Array.isArray(listed)||!listed.some(file=>file.sha256===hash&&file.size===bytes.length)) throw new Error('Owner list omitted preserved fixture'); } if(phase==='seed') { await check('unauthenticated upload denied',401,'/upload',upload()); await denied('unlisted identity denied','/upload',upload(auth('upload',other))); await denied('wrong host denied','/upload',upload(auth('upload',key,'wrong.invalid'))); await denied('expired token denied','/upload',upload(auth('upload',key,'127.0.0.1',-300))); const stored=await (await check('signed profile upload',201,'/upload',upload(auth('upload')))).json(); if(stored.sha256!==hash || stored.size!==bytes.length) throw new Error('Wrong descriptor'); await verifyStored(); await check('mirror disabled',403,'/mirror',{method:'PUT',headers:{authorization:auth('upload')}}); await check('canonical signer provider',200,'/nostr-provider.js'); await check('health',200,'/healthz'); console.log('PRESERVE_HASH '+hash); } else if(phase==='verify') { await verifyStored(); await denied('anonymous delete denied','/'+hash,{method:'DELETE'}); await denied('unlisted identity delete denied','/'+hash,{method:'DELETE',headers:{authorization:auth('delete',other)}}); await denied('wrong-host delete denied','/'+hash,{method:'DELETE',headers:{authorization:auth('delete',key,'wrong.invalid')}}); await denied('wrong-hash delete denied','/'+hash,{method:'DELETE',headers:{authorization:auth('delete',key,'127.0.0.1',300,'0'.repeat(64))}}); await check('fixture retained after delete negatives',200,'/'+hash); console.log('PRESERVED_AFTER_RESTART '+hash); } else if(phase==='cleanup') { await check('owner-authorized fixture cleanup',204,'/'+hash,{method:'DELETE',headers:{authorization:auth('delete')}}); await check('fixture absent after explicit cleanup',404,'/'+hash); } else throw new Error(`Unknown BLOSSOM_FIXTURE_PHASE: ${phase}`);