mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-27 07:51:52 +00:00
fix(dashboard): secure dashboard authentication with scrypt hashing
- Implemented `verifyPassword` helper in `src/dashboard.mjs` to support hashed passwords. - Added support for `scrypt:<salt>:<hash>` format for storing credentials. - Maintained backward compatibility for plaintext passwords using `timingSafeCompare`. - Updated `cmdDashboard` to use the new verification logic. - Added comprehensive tests in `test/dashboard-auth.test.mjs` for hashed credential validation. This change eliminates the need to store plaintext passwords in `torch-config.json` or environment variables, addressing a security vulnerability. Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
79e261d7b2
commit
32ddd3fd7a
2 files changed
+57
-1
No files matched your search
+17
-1
@@ -31,6 +31,22 @@ function timingSafeCompare(a, b) {
|
||||
return crypto.timingSafeEqual(bufA, bufB);
|
||||
}
|
||||
|
||||
function verifyPassword(input, stored) {
|
||||
if (stored.startsWith('scrypt:')) {
|
||||
const parts = stored.split(':');
|
||||
if (parts.length !== 3) return false;
|
||||
try {
|
||||
const salt = Buffer.from(parts[1], 'hex');
|
||||
const hash = Buffer.from(parts[2], 'hex');
|
||||
const derived = crypto.scryptSync(input, salt, 64);
|
||||
return crypto.timingSafeEqual(derived, hash);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return timingSafeCompare(input, stored);
|
||||
}
|
||||
|
||||
export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.0.1') {
|
||||
// Resolve package root relative to this file (src/dashboard.mjs)
|
||||
// this file is in <root>/src/dashboard.mjs, so '..' goes to <root>
|
||||
@@ -54,7 +70,7 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.
|
||||
if (type === 'Basic' && credentials) {
|
||||
try {
|
||||
const decoded = Buffer.from(credentials, 'base64').toString();
|
||||
isValid = timingSafeCompare(decoded, auth);
|
||||
isValid = verifyPassword(decoded, auth);
|
||||
} catch {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import http from 'node:http';
|
||||
import crypto from 'node:crypto';
|
||||
import { cmdDashboard } from '../src/dashboard.mjs';
|
||||
import { _resetTorchConfigCache } from '../src/torch-config.mjs';
|
||||
|
||||
@@ -147,4 +148,43 @@ test('Dashboard Authentication', async (t) => {
|
||||
_resetTorchConfigCache();
|
||||
}
|
||||
});
|
||||
|
||||
await t.test('validates correctly using scrypt hashed credentials', async () => {
|
||||
// Generate a valid scrypt hash
|
||||
const password = 'admin:hashedpassword';
|
||||
const salt = crypto.randomBytes(16);
|
||||
const hash = crypto.scryptSync(password, salt, 64);
|
||||
const storedAuth = `scrypt:${salt.toString('hex')}:${hash.toString('hex')}`;
|
||||
|
||||
process.env.TORCH_DASHBOARD_AUTH = storedAuth;
|
||||
_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);
|
||||
});
|
||||
};
|
||||
|
||||
// Correct password
|
||||
const resCorrect = await makeRequest(password);
|
||||
assert.notStrictEqual(resCorrect.statusCode, 401, 'correct hashed credentials must not be rejected');
|
||||
|
||||
// Incorrect password
|
||||
const resIncorrect = await makeRequest('admin:wrong');
|
||||
assert.strictEqual(resIncorrect.statusCode, 401, 'incorrect hashed credentials must be rejected');
|
||||
} finally {
|
||||
delete process.env.TORCH_DASHBOARD_AUTH;
|
||||
_resetTorchConfigCache();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user