dsh-remote 0.4.0: edge auth – slaves log in to an auth proxy in front of the master
- lib/edge-auth.mjs: basic / bearer / oauth2 client_credentials (Authentik M2M) / custom headers (Cloudflare Access), proxy session cookie jar, retry with fresh token, clear edge-refusal vs DSH-refusal errors - link + enroll go through the edge; master.edgeAuth stored at pair time, secrets as DSH credential refs only; config edgeAuth override - /dsh-remote/edge-auth: show/change/probe; Settings UI section + pair form - pair.mjs / install-mac.sh: --edge … flags, secrets prompted via /dev/tty - tests: fake Authentik-like proxy for all modes + WebSocket through the edge
This commit is contained in:
1 parent
5dff41a3d0
commit
78fce8e1ce
12 files changed
+833
-40
No files matched your search
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env node
|
||||
// Screenshot Settings → Remote devices (headless Chromium via DevTools protocol).
|
||||
// node deploy/e2e-settings.mjs <chrome> <out.png> [port]
|
||||
import { spawn } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { cookieName, mintCookie } from '../lib/auth-cookie.mjs'
|
||||
|
||||
const [chrome, out, port = '3081'] = process.argv.slice(2)
|
||||
const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
||||
const secret = fs.readFileSync(path.join(home, '.credentials.yaml'), 'utf8').match(/browser-session:[\s\S]*?secret:\s*([A-Za-z0-9_-]{43})/)[1]
|
||||
const authority = `127.0.0.1:${port}`
|
||||
const prof = fs.mkdtempSync(path.join(os.tmpdir(), 'dshr-set-'))
|
||||
const proc = spawn(chrome, ['--headless=new', '--no-sandbox', '--disable-gpu', `--user-data-dir=${prof}`, '--remote-debugging-port=0', '--window-size=1400,1300', 'about:blank'], { stdio: ['ignore', 'ignore', 'pipe'] })
|
||||
const wsUrl = await new Promise((resolve, reject) => {
|
||||
let buf = ''
|
||||
proc.stderr.on('data', (d) => { buf += d; const m = buf.match(/DevTools listening on (ws:\/\/\S+)/); if (m) resolve(m[1]) })
|
||||
setTimeout(() => reject(new Error('chrome did not start')), 15000)
|
||||
})
|
||||
const page = (await (await fetch(wsUrl.replace(/^ws/, 'http').replace(/\/devtools\/browser\/.*/, '/json/list'))).json()).find((t) => t.type === 'page')
|
||||
const ws = new WebSocket(page.webSocketDebuggerUrl)
|
||||
await new Promise((r) => ws.addEventListener('open', r))
|
||||
let id = 0
|
||||
const pending = new Map()
|
||||
ws.addEventListener('message', (ev) => { const m = JSON.parse(ev.data); if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id) } })
|
||||
const send = (method, params = {}) => new Promise((resolve) => { const i = ++id; pending.set(i, resolve); ws.send(JSON.stringify({ id: i, method, params })) })
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
||||
const evalJs = async (expr) => (await send('Runtime.evaluate', { expression: expr, awaitPromise: true, returnByValue: true })).result?.result?.value
|
||||
await send('Page.enable'); await send('Runtime.enable'); await send('Network.enable')
|
||||
await send('Network.setCookie', { name: cookieName(authority), value: mintCookie(authority, secret, 1).split('=').slice(1).join('='), domain: '127.0.0.1', path: '/', httpOnly: true })
|
||||
await send('Page.navigate', { url: `http://${authority}/#/settings/dsh-remote` })
|
||||
await sleep(10000)
|
||||
// Open "Change" on the edge card to show the form, and switch it to Basic.
|
||||
console.log(await evalJs(`(() => {
|
||||
const root = document.querySelector('[data-dsh-plugin="dsh-remote"]'); if (!root) return 'no settings panel'
|
||||
const btn = [...root.querySelectorAll('button')].find((b) => b.textContent === 'Change'); if (btn) btn.click()
|
||||
return 'panel: ' + root.innerText.replace(/\\n+/g, ' | ').slice(0, 500)
|
||||
})()`))
|
||||
await sleep(800)
|
||||
await evalJs(`(() => { const sel = [...document.querySelectorAll('[data-dsh-plugin="dsh-remote"] select')].pop(); if (!sel) return; const set = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value').set; set.call(sel, 'basic'); sel.dispatchEvent(new Event('change', { bubbles: true })) })()`)
|
||||
await sleep(800)
|
||||
await evalJs(`document.querySelector('[data-dsh-plugin="dsh-remote"]')?.scrollIntoView()`)
|
||||
const r = await send('Page.captureScreenshot', { format: 'png' })
|
||||
fs.writeFileSync(out, Buffer.from(r.result.data, 'base64'))
|
||||
console.log('screenshot', out)
|
||||
ws.close(); proc.kill('SIGKILL')
|
||||
try { fs.rmSync(prof, { recursive: true, force: true }) } catch { /* chrome may still hold files */ }
|
||||
process.exit(0)
|
||||
Reference in new issue
Block a user