Files
TORCH/test/dashboard-auth.test.mjs
T
google-labs-jules[bot]andPR0M3TH3AN 456bc2df18 feat(security): add authentication and host binding to dashboard
This commit addresses the missing authentication vulnerability in the
dashboard server.

Changes:
- Added Basic Authentication support via TORCH_DASHBOARD_AUTH env var
  or dashboard.auth config field.
- Defaulted dashboard host binding to 127.0.0.1 for better security.
- Added --host CLI argument to allow overriding the bind address.
- Used crypto.timingSafeEqual for secure credential comparison.
- Fixed a potential crash when parsing malformed Authorization headers.
- Added comprehensive unit tests for dashboard authentication.
- Updated documentation in README.md and torch-config.example.json.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-17 17:24:33 +00:00

98 lines
2.8 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) => {
const testPort = 4174;
await t.test('returns 401 when auth is required but missing', async () => {
process.env.TORCH_DASHBOARD_AUTH = 'admin:password';
_resetTorchConfigCache();
const server = await cmdDashboard(testPort);
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 {
server.close();
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();
const server = await cmdDashboard(testPort);
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 {
server.close();
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();
const server = await cmdDashboard(testPort);
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 {
server.close();
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
}
});
await t.test('returns 200 when auth is disabled', async () => {
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
const server = await cmdDashboard(testPort);
try {
const res = await new Promise((resolve) => {
http.get(`http://127.0.0.1:${testPort}/dashboard/`, resolve);
});
assert.notStrictEqual(res.statusCode, 401);
} finally {
server.close();
_resetTorchConfigCache();
}
});
});