feat(proxy): desktop lockout notification + 'proxy-bridge reauth' command
Notification path (bridge.js): entering auth lockout fires ONE desktop notification per episode (notify-send, critical urgency), only when a session is detectable (DBUS_SESSION_BUS_ADDRESS or DISPLAY) — headless stays silent. Message explains the 407 cause and points at 'proxy-bridge reauth'. Recovery path (proxyctl.js): 'proxy-bridge reauth' prompts masked, verifies the new password with a SINGLE upstream CONNECT (407 -> NOT stored), stores via secret-tool on success, then writes a reauth-stamp.json the bridge watches (1s poll): clears in-memory authState and reloads keyring creds — no restart. Shared one-shot verifier extracted to verify-creds.js (install.js + proxyctl.js use the same code; also fixes proxyctl 'setup' shelling to the retired /opt/setup.js — 'reauth' is its replacement for the password-expiry flow). Tests: +3 bridge (notify once-per-episode incl. remediation hint; headless silent; stamp clears lockout without restart) +2 proxyctl (verified stored + stamped; rejected never stored/stamped). Suite: 28/28.
This commit is contained in:
1 parent
e49b4af14a
commit
be0e2d84cc
6 files changed
+409
-31
No files matched your search
@@ -0,0 +1,31 @@
|
||||
// One single CONNECT attempt with candidate creds against the upstream proxy.
|
||||
// Used by install.js (credential setup) and proxyctl.js (reauth). NEVER retries,
|
||||
// NEVER logs the password, resolves: 'ok' | 'authfail' | 'unreachable'
|
||||
const net = require('net');
|
||||
|
||||
function verifyProxyCredentials(host, port, username, password, timeoutMs = 10000) {
|
||||
return new Promise((resolve) => {
|
||||
const socket = net.connect(Number(port), host);
|
||||
let buf = Buffer.alloc(0);
|
||||
const finish = (r) => { try { socket.destroy(); } catch {} resolve(r); };
|
||||
socket.setTimeout(timeoutMs, () => finish('unreachable'));
|
||||
socket.once('connect', () => {
|
||||
const auth = 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64');
|
||||
socket.write(`CONNECT 127.0.0.1:443 HTTP/1.1\r\nHost: 127.0.0.1:443\r\nProxy-Authorization: ${auth}\r\n\r\n`);
|
||||
});
|
||||
socket.on('data', (d) => {
|
||||
buf = Buffer.concat([buf, d]);
|
||||
const head = buf.toString('latin1').split('\r\n')[0] || '';
|
||||
const m = head.match(/^HTTP\/\d\.\d\s+(\d{3})/i);
|
||||
if (m || buf.includes('\r\n\r\n')) {
|
||||
const code = m ? Number(m[1]) : 0;
|
||||
// 200/4xx non-407 (e.g. 403 for the probe target) = creds accepted; 407 = rejected.
|
||||
finish(code === 407 ? 'authfail' : (code > 0 ? 'ok' : 'unreachable'));
|
||||
}
|
||||
});
|
||||
socket.once('error', () => finish('unreachable'));
|
||||
socket.once('end', () => { if (buf.length === 0) finish('unreachable'); });
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { verifyProxyCredentials };
|
||||
Reference in new issue
Block a user