Files
safetynet/scripts/credential-native-facts-smoke.cjs
T

10 lines
5.5 KiB
JavaScript
Raw Normal View History

'use strict';
const {app,BrowserWindow,session,webContents}=require('electron'),fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),{X509Certificate}=require('node:crypto');
app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardwareAcceleration();
const facts=[],origin=process.env.SAFETYNET_SMOKE_ORIGIN,base=process.env.SAFETYNET_SMOKE_SERVER;
const route=url=>url===origin+'/login'?'login':url===origin+'/submit'?'submit':url===origin+'/final'?'final':url?'other':'empty';
const frame=f=>{try{return f?{processId:f.processId,routingId:f.routingId,urlKind:route(f.url)}:null;}catch{return {destroyed:true};}};
app.whenReady().then(()=>{const ses=session.fromPartition('persist:safetynet-managed');for(const name of ['onBeforeRequest','onBeforeSendHeaders']){const original=ses.webRequest[name].bind(ses.webRequest);ses.webRequest[name]=function(filter,listener){if(typeof filter==='function'){listener=filter;filter=undefined;}return original(filter,(details,callback)=>{const wc=details.webContents??(details.webContentsId?webContents.fromId(details.webContentsId):null);if(details.method==='POST'&&details.resourceType==='mainFrame')facts.push({phase:name,time:performance.now(),requestId:details.id,urlKind:route(details.url),webContentsId:details.webContentsId??null,requestFrame:frame(details.frame),mainFrame:frame(wc?.mainFrame),currentUrlKind:route(wc?.getURL())});return listener(details,response=>{if(details.method==='POST'&&details.resourceType==='mainFrame')facts.push({phase:name+'.release',time:performance.now(),requestId:details.id,urlKind:route(details.url),cancel:response?.cancel===true});callback(response);});});};} });
require('../apps/browser/main.cjs');
const wait=ms=>new Promise(r=>setTimeout(r,ms));async function until(fn){for(let i=0;i<200;i++){const value=await fn();if(value)return value;await wait(100);}throw Error('Native credential facts timeout');}
app.whenReady().then(async()=>{let page,stage='startup';try{const win=await until(()=>{const w=BrowserWindow.getAllWindows()[0];return w&&!w.webContents.isLoading()&&w.webContents.getURL().endsWith('index.html')&&w;});const fingerprint=new X509Certificate(fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem'))).fingerprint256;session.fromPartition('persist:safetynet-managed').setCertificateVerifyProc((req,callback)=>callback(req.hostname==='127.0.0.1'&&new X509Certificate(req.certificate.data).fingerprint256===fingerprint?0:-2));const ui=async(method,value)=>{const result=await win.webContents.executeJavaScript(`window.safetynet.${method}(${value===undefined?'':JSON.stringify(value)})`);assert.equal(result.ok,true,method);return result.data;};stage='enrollment';await ui('enroll',{server:base,code:process.env.SAFETYNET_SMOKE_CODE});page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;await until(()=>!page.isLoading()&&page.getURL()===origin+'/login');for(const name of ['will-navigate','did-start-navigation','will-redirect','did-redirect-navigation','did-frame-navigate','did-navigate'])page.on(name,(event,...args)=>{const info=typeof args[0]==='object'?args[0]:{};facts.push({phase:name,time:performance.now(),urlKind:route(info.url??args[0]??event.url),mainFrame:frame(page.mainFrame),currentUrlKind:route(page.getURL())});});stage='public form submit';await page.executeJavaScript(`document.querySelector('[name=username]').value='fixture-public-user';document.querySelector('[name=password]').value='fixture-public-value';document.querySelector('form').requestSubmit();`);await until(()=>!page.isLoading()&&page.getURL()===origin+'/final');assert.equal(facts.filter(f=>f.phase==='onBeforeRequest').length,2);assert.equal(facts.filter(f=>f.phase==='onBeforeSendHeaders').length,2);assert.equal(facts.some(f=>f.cancel),false);await wait(1000);const headers={authorization:'Bearer '+process.env.SAFETYNET_SMOKE_ADMIN};const overview=await(await fetch(base+'/api/admin/overview',{headers})).json();const event=overview.events.find(e=>e.kind==='upload.authorized'&&e.metadata.traceId);assert.ok(event);const trace=await(await fetch('http://127.0.0.1:4318/api/trace/'+event.metadata.traceId)).json();const requestSpan=trace.spans.find(s=>s.name==='browser.request');assert.ok(requestSpan);assert.ok(trace.spans.some(s=>s.parentSpanId===requestSpan.spanId&&s.name==='browser.security'));const summary=await(await fetch('http://127.0.0.1:4318/api/summary')).json();const health=await(await fetch('http://127.0.0.1:4318/health')).json();assert.equal(health.rejectedWrites,0);const proof={actualMainGuardsPreserved:true,exactPinnedSyntheticHTTPS:true,publicSyntheticBodyOnly:true,electron:process.versions.electron,chromium:process.versions.chrome,facts,summary,traceId:event.metadata.traceId,spans:trace.spans.map(({name,operation,spanId,parentSpanId,durationMs})=>({name,operation,spanId,parentSpanId,durationMs})),health:{storedBytes:health.storedBytes,rejectedWrites:health.rejectedWrites}};fs.writeFileSync(path.resolve('artifacts/credential-native-facts-proof.json'),JSON.stringify(proof,null,2));console.log('Native credential lifecycle facts passed');require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(0);}catch(error){fs.writeFileSync(path.resolve('artifacts/credential-native-facts-error.json'),JSON.stringify({stage,name:error.name,message:error.message,facts},null,2));console.error('Credential lifecycle facts failed at '+stage+': '+error.name);if(page&&!page.isDestroyed())require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(1);}});