#!/usr/bin/env node // E2E for the "auth middleware" flow, run on a slave host against a real master: // node deploy/e2e-signin.mjs // 1. start fake-edge (Authentik-like, user svc / password s3cret) in front of the master // 2. start a throw-away slave DSH (own DSH_HOME, port 3085) and pair it THROUGH the edge // using the pasted-cookie mode // 3. kill the edge session (edge restarts → cookie invalid) and check the slave reports // authRequired with authMiddleware on, and nothing with it off // 4. open the slave UI in headless Chromium, trigger the popup flow by calling the // hand-over (postMessage) the master's /dsh-remote/signin page would send // 5. verify the link is back up import { spawn, execFileSync } from 'node:child_process' import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { fileURLToPath } from 'node:url' const [master, code, chrome] = process.argv.slice(2) const here = path.dirname(fileURLToPath(import.meta.url)) const root = path.resolve(here, '..') const H = fs.mkdtempSync(path.join(os.tmpdir(), 'dshr-signin-')) const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) const log = (...a) => console.log(...a) const procs = [] const stop = () => { for (const p of procs) { try { p.kill('SIGKILL') } catch { /* gone */ } } } process.on('exit', stop) const startEdge = () => { const p = spawn('node', [path.join(here, 'fake-edge.mjs'), '3098', master, 'svc', 's3cret'], { stdio: 'ignore' }) procs.push(p) return p } const cli = (args, env = {}) => execFileSync('node', [path.join(here, 'dshr-cli.mjs'), ...args], { env: { ...process.env, DSH_HOME: H, DSH_PORT: '3085', ...env }, encoding: 'utf8', timeout: 30000 }) let edge = startEdge() await sleep(800) // A browser that already signed in would own this cookie: get one from the fake edge. const r = await fetch('http://127.0.0.1:3098/dsh-remote/enroll', { method: 'POST', headers: { authorization: `Basic ${Buffer.from('svc:s3cret').toString('base64')}` } }) const edgeCookieOf = (resp) => (resp.headers.getSetCookie?.() || []).map((c) => c.split(';')[0]).find((c) => c.startsWith('edge_session=')) || '' const cookie = edgeCookieOf(r) log('browser-style edge cookie:', cookie.replace(/=.*/, '=…')) // Throw-away slave profile. const env = { ...process.env, DSH_HOME: H, PATH: process.env.PATH } execFileSync('dsh', ['web', '--dump-config'], { env, stdio: 'ignore', timeout: 40000 }) fs.mkdirSync(path.join(H, 'plugins'), { recursive: true }) fs.cpSync(root, path.join(H, 'plugins', 'dsh-remote'), { recursive: true }) execFileSync('node', [path.join(H, 'plugins', 'dsh-remote', 'deploy', 'install-local.mjs'), '--role', 'slave', '--name', 'signin-test'], { env }) log('pair through the edge with the pasted cookie:') process.env.DSH_REMOTE_EDGE_COOKIE = cookie log(execFileSync('node', [path.join(here, 'pair.mjs'), '--master', 'http://127.0.0.1:3098', '--code', code, '--name', 'signin-test', '--edge', 'cookie'], { env: { ...env, DSH_REMOTE_EDGE_COOKIE: cookie }, encoding: 'utf8' }).trim()) const dsh = spawn('dsh', ['web', '--host', '127.0.0.1', '--port', '3085', '--no-open'], { env, stdio: 'ignore' }) procs.push(dsh) for (let i = 0; i < 60; i++) { try { if ((await fetch('http://127.0.0.1:3085/')).status) break } catch { /* booting */ } await sleep(1000) } await sleep(6000) const state = () => JSON.parse(cli(['GET', 'state']).split('\n').slice(1).join('\n')) let s = state() log('linked with pasted cookie:', s.master.linked, '| cookies:', s.master.edgeAuth.cookies.join(',')) log('--- edge forgets the session (restart) → authRequired when authMiddleware is on') cli(['POST', 'node', '{"authMiddleware":true}']) edge.kill('SIGKILL'); await sleep(500); edge = startEdge(); await sleep(800) cli(['POST', 'retry-link', '{}']) await sleep(500) s = state() log('linked:', s.master.linked, '| authRequired:', !!s.master.authRequired, '|', s.master.authRequired?.reason?.slice(0, 90)) log('--- same with authMiddleware off → no authRequired') cli(['POST', 'node', '{"authMiddleware":false}']) cli(['POST', 'retry-link', '{}']) s = state() log('authRequired:', !!s.master.authRequired, '| error:', s.master.error?.slice(0, 80)) log('--- popup hand-over: the master /dsh-remote/signin page postMessages the cookie') cli(['POST', 'node', '{"authMiddleware":true}']) const fresh = await fetch('http://127.0.0.1:3098/dsh-remote/enroll', { method: 'POST', headers: { authorization: `Basic ${Buffer.from('svc:s3cret').toString('base64')}` } }) const freshCookie = edgeCookieOf(fresh) const cookieText = `${freshCookie}; dsh-auth-xyz=ignored` const sign = await fetch('http://127.0.0.1:3098/dsh-remote/signin?origin=' + encodeURIComponent('http://127.0.0.1:3085'), { headers: { cookie: freshCookie } }) const page = await sign.text() log('signin page served through the edge:', sign.status, '| offers hand-over:', /Send session/.test(page), '| cookie embedded:', page.includes(freshCookie.split('=')[0])) const untrusted = await (await fetch('http://127.0.0.1:3098/dsh-remote/signin?origin=' + encodeURIComponent('https://evil.example'), { headers: { cookie: freshCookie } })).text() log('untrusted origin gets nothing:', /not on this machine's trusted list/.test(untrusted) && !untrusted.includes(freshCookie.split('=')[1])) // What the UI does when the postMessage arrives: cli(['POST', 'edge-auth', JSON.stringify({ edgeAuth: { mode: 'cookie', cookie: cookieText } })]) const retry = JSON.parse(cli(['POST', 'retry-link', '{}']).split('\n').slice(1).join('\n')) await sleep(1500) s = state() log('after hand-over → linked:', s.master.linked, '| retry-link:', JSON.stringify(retry).slice(0, 100)) process.exit(s.master.linked ? 0 : 1)