// Real, operator-authorized local IndeeHub login. Only NIP-98 session signatures are allowed. // Uses isolated browser contexts, does not publish events or perform wallet operations. const fs=require('fs');const {chromium,expect}=require(process.env.PLAYWRIGHT_MODULE || '@playwright/test'); (async()=>{ if(process.env.ALLOW_REAL_AUTH_SIGNING!=='1')throw new Error('Set ALLOW_REAL_AUTH_SIGNING=1 only for an authorized real IndeeHub login check'); const origin=process.env.QUALIFICATION_ORIGIN,cookieFile=process.env.QUALIFICATION_COOKIES,node=process.env.QUALIFICATION_LABEL||'node'; if(!origin||!cookieFile)throw new Error('Set private node origin and cookie file'); const u=new URL(origin),octets=u.hostname.split('.').map(Number); const privateHost=u.hostname==='localhost'||(octets.length===4&&octets.every(n=>Number.isInteger(n)&&n>=0&&n<=255)&&(octets[0]===127||octets[0]===10||(octets[0]===192&&octets[1]===168)||(octets[0]===172&&octets[1]>=16&&octets[1]<=31)||(octets[0]===100&&octets[1]>=64&&octets[1]<=127))); if(!privateHost||!['http:','https:'].includes(u.protocol)||u.username||u.password||u.pathname!=='/'||u.search||u.hash)throw new Error('Refusing non-private node origin'); u.port='7778';const appOrigin=u.origin; const b=await chromium.connectOverCDP(process.env.BROWSER_CDP||'http://127.0.0.1:32911'); for(const width of [390,1440]){ const c=await b.newContext({viewport:{width,height:900},serviceWorkers:'block'}); let page; const proof={node,width,stage:'setup',realLogin:true,sessionResponses:[],profileResponses:[],signedAuthRequests:0,blockedUnexpectedRequest:false}; try{ const cookies=JSON.parse(fs.readFileSync(cookieFile,'utf8'));await c.addCookies(Object.entries(cookies).map(([name,value])=>({name,value,url:origin,httpOnly:name!=='csrf_token'}))); await c.addInitScript(()=>{localStorage.setItem('neode-auth','true');localStorage.setItem('lnd-seed-backup-prompt-snooze-until',String(Date.now()+3600000));sessionStorage.setItem('indeedhub_splash_shown','true');}); await c.route('**/rpc/v1',async route=>{ let r;try{r=route.request().postDataJSON()}catch{return route.continue()} if(['identity.nostr-sign','node.nostr-sign'].includes(r?.method)){ const e=r.params?.event;let safe=false;try{const u=new URL(e.tags.find(t=>t[0]==='u')[1]);safe=e.kind===27235&&u.origin===appOrigin&&u.pathname==='/api/auth/nostr/session'&&e.tags.some(t=>t[0]==='method'&&t[1]==='POST')}catch{} if(!safe){proof.blockedUnexpectedRequest=true;return route.abort()} proof.signedAuthRequests++; } else if(r?.method==='identity.sign') { if(typeof r.params?.message!=='string'||!/^archipelago-identity:\d+$/.test(r.params.message)){proof.blockedUnexpectedRequest=true;return route.abort()} } else if(/nostr-(encrypt|decrypt)|\.(pay|send|spend|melt|withdraw)(-|$)/.test(r?.method||'')){ proof.blockedUnexpectedRequest=true;return route.abort(); } return route.continue(); }); const p=await c.newPage();page=p;p.on('response',r=>{const u=new URL(r.url());if(u.origin!==appOrigin)return;if(u.pathname.endsWith('/auth/nostr/session'))proof.sessionResponses.push(r.status());if(u.pathname.endsWith('/auth/me'))proof.profileResponses.push(r.status())}); await p.goto(origin+'/dashboard/app-session/indeedhub',{waitUntil:'domcontentloaded'}); proof.stage='identity selection'; const authenticate=p.getByRole('button',{name:'Authenticate',exact:true});await expect(authenticate).toBeEnabled({timeout:30000});await authenticate.click(); proof.stage='open app sign-in'; const frame=p.frameLocator('iframe[src*="7778"]'); const signIn=frame.getByRole('button',{name:'Sign In',exact:true}); proof.stage='approve native login'; const extension=frame.getByRole('button',{name:'Extension',exact:true}); const deadline=Date.now()+45000;let approved=0,extensionChosen=false,signInOpened=false; while(Date.now()s===200||s===201)&&proof.profileResponses.includes(200))break; const approve=p.getByRole('button',{name:'Approve',exact:true}); if(await approve.isVisible()){ if(++approved>6)throw new Error('Unexpected repeated consent'); await approve.click();await p.waitForTimeout(800); }else if(!signInOpened&&await signIn.isVisible()){ try{await signIn.click({timeout:1000});signInOpened=true}catch{await p.waitForTimeout(200)} }else if(!extensionChosen&&await extension.isVisible()){ // The app can automatically request native login after identity selection. // Do not click through the parent's consent overlay to choose it again. try{await extension.click({timeout:1000});extensionChosen=true} catch{await p.waitForTimeout(200)} }else await p.waitForTimeout(250); } expect(proof.signedAuthRequests).toBeGreaterThan(0);expect(proof.sessionResponses.some(s=>s===200||s===201)).toBe(true);expect(proof.profileResponses).toContain(200); await expect(signIn).toHaveCount(0,{timeout:10000}); proof.stage='reload authenticated app'; const profilesBeforeReload=proof.profileResponses.filter(s=>s===200).length; await p.reload({waitUntil:'domcontentloaded'}); await expect.poll(()=>proof.profileResponses.filter(s=>s===200).length,{timeout:30000}).toBeGreaterThan(profilesBeforeReload); await expect(frame.getByRole('button',{name:'Sign In',exact:true})).toHaveCount(0,{timeout:10000}); proof.reloadRetainedLogin=true;proof.result='PASS';console.log(JSON.stringify(proof)); }catch(e){console.log(JSON.stringify({...proof,result:'FAIL',path:page?new URL(page.url()).pathname:null,buttons:page?await page.getByRole('button').allTextContents():[]}));if(page)await page.screenshot({path:'/tmp/archy-indeehub-login-'+node+'-'+width+'-failure.png'});throw e}finally{await c.close()} } process.exit(0) })().catch(e=>{console.error(String(e.message).split('Call log:')[0]);process.exit(1)});