Files
dsh-remote/deploy/fake-edge.mjs
T
lyuksovannyy 78fce8e1ce 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
2026-10-11 12:19:36 +00:00

66 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
// Test helper: an Authentik-like auth proxy in front of a DSH master.
// node deploy/fake-edge.mjs <listenPort> <upstream http://host:port> <user> <password>
// Unauthenticated → 302 to a fake login page; Basic user:password → session cookie +
// pass-through (HTTP and WebSocket upgrades); session cookie → pass-through.
import crypto from 'node:crypto'
import http from 'node:http'
import net from 'node:net'
const [port, upstream, user, pass] = process.argv.slice(2)
const up = new URL(upstream)
const sessions = new Set()
const want = `Basic ${Buffer.from(`${user}:${pass}`).toString('base64')}`
const stats = { redirects: 0, credentialLogins: 0, cookieHits: 0, upgrades: 0 }
function auth(req) {
const sid = /edge_session=([a-f0-9]+)/.exec(req.headers.cookie || '')?.[1]
if (sid && sessions.has(sid)) { stats.cookieHits++; return { ok: true } }
if (req.headers.authorization === want) {
stats.credentialLogins++
const id = crypto.randomBytes(12).toString('hex')
sessions.add(id)
return { ok: true, setCookie: `edge_session=${id}; Path=/; HttpOnly; Max-Age=86400` }
}
if (req.headers.authorization) return { ok: false, status: 401 }
return { ok: false, status: 302 }
}
const strip = (h) => { const o = { ...h }; delete o.authorization; return o }
const srv = http.createServer((req, res) => {
if (req.url === '/__stats') { res.end(JSON.stringify(stats)); return }
const a = auth(req)
if (!a.ok) {
if (a.status === 302) stats.redirects++
res.writeHead(a.status, a.status === 302 ? { location: 'https://auth.example/application/o/authorize/?client_id=x' } : {})
return res.end()
}
const p = http.request({ host: up.hostname, port: up.port, method: req.method, path: req.url, headers: { ...strip(req.headers), host: up.host } }, (r) => {
const h = { ...r.headers }
if (a.setCookie) h['set-cookie'] = [...(h['set-cookie'] || []), a.setCookie]
res.writeHead(r.statusCode, h)
r.pipe(res)
})
p.on('error', () => { res.writeHead(502); res.end() })
req.pipe(p)
})
srv.on('upgrade', (req, socket, head) => {
const a = auth(req)
if (!a.ok) {
if (a.status === 302) stats.redirects++
socket.end(a.status === 302 ? 'HTTP/1.1 302 Found\r\nLocation: https://auth.example/login\r\nContent-Length: 0\r\n\r\n' : 'HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n')
return
}
stats.upgrades++
const s = net.connect(Number(up.port), up.hostname, () => {
const lines = [`${req.method} ${req.url} HTTP/1.1`]
for (const [k, v] of Object.entries({ ...strip(req.headers), host: up.host })) for (const one of [].concat(v)) lines.push(`${k}: ${one}`)
s.write(lines.join('\r\n') + '\r\n\r\n')
if (head?.length) s.write(head)
s.pipe(socket); socket.pipe(s)
})
s.on('error', () => socket.destroy())
socket.on('error', () => s.destroy())
})
srv.listen(Number(port), '127.0.0.1', () => console.log(`fake edge on 127.0.0.1:${port} → ${upstream}`))