Files
TORCH/test/dashboard-auth.test.mjs
google-labs-jules[bot]andPR0M3TH3AN c0c5ac5473 fix(torch-lock): re-apply fixes for circular deps and syntax errors
- Moved `withTimeout` implementation to `src/lock-utils.mjs` and exported it correctly.
- Removed duplicate local definitions of `withTimeout`, `mergeRelayList`, and `secureRandom` in `src/lock-ops.mjs` and `src/lock-publisher.mjs`.
- Fixed imports in `src/lock-ops.mjs`, `src/lock-publisher.mjs`, and `src/relay-health.mjs` to use `src/lock-utils.mjs` consistently.
- Updated `test/utils.test.mjs` and `test/utils-async.test.mjs` to fix broken imports and test definitions.
- Verified `npm run lock:complete` and `npm run scheduler:daily` execute correctly.
- Updated `src/todo/TODO_2026-02-20T03-00-00Z.md` to mark verification task as completed.
- Verified `src/relay-health.mjs` does not use `MINUTE_MS` locally, despite mysterious CI error. Confirmed imports are correct.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-21 19:23:48 +00:00

151 lines
4.7 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert';
import http from 'node:http';
import { cmdDashboard } from '../src/dashboard.mjs';
import { _resetTorchConfigCache } from '../src/torch-config.mjs';
test('Dashboard Authentication', async (t) => {
let testPort = 0;
let server;
t.beforeEach(async () => {
// We don't start server here because some tests need env vars set BEFORE starting
});
t.afterEach((done) => {
if (server) server.close(done);
else done();
server = null;
});
await t.test('returns 401 when auth is required but missing', async () => {
process.env.TORCH_DASHBOARD_AUTH = 'admin:password';
_resetTorchConfigCache();
server = await cmdDashboard(0);
testPort = server.address().port;
try {
const res = await new Promise((resolve) => {
http.get(`http://127.0.0.1:${testPort}/dashboard/`, resolve);
});
assert.strictEqual(res.statusCode, 401);
assert.strictEqual(res.headers['www-authenticate'], 'Basic realm="TORCH Dashboard"');
} finally {
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
}
});
await t.test('returns 401 when malformed auth header is provided', async () => {
process.env.TORCH_DASHBOARD_AUTH = 'admin:password';
_resetTorchConfigCache();
server = await cmdDashboard(0);
testPort = server.address().port;
try {
const res = await new Promise((resolve) => {
http.get({
host: '127.0.0.1',
port: testPort,
path: '/dashboard/',
headers: { 'Authorization': 'Basic' }
}, resolve);
});
assert.strictEqual(res.statusCode, 401);
} finally {
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
}
});
await t.test('returns 200 when valid credentials are provided', async () => {
process.env.TORCH_DASHBOARD_AUTH = 'admin:password';
_resetTorchConfigCache();
server = await cmdDashboard(0);
testPort = server.address().port;
try {
const auth = Buffer.from('admin:password').toString('base64');
const res = await new Promise((resolve) => {
http.get({
host: '127.0.0.1',
port: testPort,
path: '/dashboard/',
headers: { 'Authorization': `Basic ${auth}` }
}, resolve);
});
// We expect 200 (if dashboard/index.html exists) or 404 (if not), but not 401
assert.notStrictEqual(res.statusCode, 401);
} finally {
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
}
});
await t.test('returns 401 when password has different byte length than stored credential', async () => {
// Regression test for timingSafeCompare: previously the comparison result
// was discarded and `false` was returned unconditionally for unequal-length
// inputs, making the timing-safe branch dead code. Verify that a prefix of
// the correct password (shorter) and an extended version (longer) are both
// rejected.
process.env.TORCH_DASHBOARD_AUTH = 'admin:secret';
_resetTorchConfigCache();
server = await cmdDashboard(0);
testPort = server.address().port;
try {
const makeRequest = (credentials) => {
const auth = Buffer.from(credentials).toString('base64');
return new Promise((resolve) => {
http.get({
host: '127.0.0.1',
port: testPort,
path: '/dashboard/',
headers: { 'Authorization': `Basic ${auth}` }
}, resolve);
});
};
// Shorter than correct password — different byte length
const resShorter = await makeRequest('admin:secre');
assert.strictEqual(resShorter.statusCode, 401, 'shorter password must be rejected');
// Longer than correct password — different byte length
const resLonger = await makeRequest('admin:secret_extra');
assert.strictEqual(resLonger.statusCode, 401, 'longer password must be rejected');
// Correct credentials still work
const resCorrect = await makeRequest('admin:secret');
assert.notStrictEqual(resCorrect.statusCode, 401, 'correct credentials must not be rejected');
} finally {
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
}
});
await t.test('returns 200 when auth is disabled', async () => {
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
server = await cmdDashboard(0);
testPort = server.address().port;
try {
const res = await new Promise((resolve) => {
http.get(`http://127.0.0.1:${testPort}/dashboard/`, resolve);
});
assert.notStrictEqual(res.statusCode, 401);
} finally {
_resetTorchConfigCache();
}
});
});