mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 09:35:39 +00:00
648a8d4f8a
Commit web-nodejs lockfile with tar override; CI uses npm ci and moderate npm audit. Add LOG_LEVEL filtering and log redaction in Node console and Go server. Validate WS tokens on bd-signal; require single-use token for remote-agent relay. Limit active relay sessions per IP and block open enrollment without TLS.
103 lines
3.2 KiB
JavaScript
103 lines
3.2 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Verify device WebSocket credentials (bd-signal, remote-agent).
|
|
* Accepts enrollment device_token (Go device_tokens table) or panel access tokens.
|
|
*/
|
|
|
|
const crypto = require('crypto');
|
|
const config = require('../config/config');
|
|
const { hashAccessToken } = require('./tokenHash');
|
|
|
|
function hashDeviceToken(token) {
|
|
return crypto.createHash('sha256').update(String(token), 'utf8').digest('hex');
|
|
}
|
|
|
|
function validateEnrollmentRow(row, deviceId) {
|
|
if (!row) return false;
|
|
if (row.status === 'revoked' || row.status === 'expired') return false;
|
|
if (row.max_uses > 0 && row.use_count >= row.max_uses) return false;
|
|
if (row.expires_at) {
|
|
const exp = new Date(row.expires_at);
|
|
if (!Number.isNaN(exp.getTime()) && exp < new Date()) return false;
|
|
}
|
|
if (row.peer_id && row.peer_id !== deviceId) return false;
|
|
return true;
|
|
}
|
|
|
|
async function lookupEnrollmentTokenSqlite(tokenHash) {
|
|
const Database = require('better-sqlite3');
|
|
const db = new Database(config.dbPath, { readonly: true, fileMustExist: false });
|
|
try {
|
|
return db.prepare(`
|
|
SELECT peer_id, status, max_uses, use_count, expires_at
|
|
FROM device_tokens WHERE token_hash = ?
|
|
`).get(tokenHash);
|
|
} catch (err) {
|
|
if (String(err.message || '').includes('no such table')) return null;
|
|
throw err;
|
|
} finally {
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
async function lookupEnrollmentTokenPostgres(tokenHash) {
|
|
const { Client } = require('pg');
|
|
const client = new Client({ connectionString: config.databaseUrl });
|
|
await client.connect();
|
|
try {
|
|
const res = await client.query(
|
|
`SELECT peer_id, status, max_uses, use_count, expires_at
|
|
FROM device_tokens WHERE token_hash = $1`,
|
|
[tokenHash]
|
|
);
|
|
return res.rows[0] || null;
|
|
} finally {
|
|
await client.end();
|
|
}
|
|
}
|
|
|
|
async function verifyEnrollmentToken(deviceId, plainToken) {
|
|
const tokenHash = hashDeviceToken(plainToken);
|
|
const row = config.dbType === 'postgres'
|
|
? await lookupEnrollmentTokenPostgres(tokenHash)
|
|
: await lookupEnrollmentTokenSqlite(tokenHash);
|
|
return validateEnrollmentRow(row, deviceId);
|
|
}
|
|
|
|
/**
|
|
* @param {string} deviceId
|
|
* @param {string} plainToken
|
|
* @param {{ getAccessToken?: function }} [db] optional database facade for access-token fallback
|
|
*/
|
|
async function verifyDeviceWsAuth(deviceId, plainToken, db) {
|
|
if (!deviceId || !plainToken || String(plainToken).length < 8) {
|
|
return false;
|
|
}
|
|
if (await verifyEnrollmentToken(deviceId, plainToken)) {
|
|
return true;
|
|
}
|
|
if (db && typeof db.getAccessToken === 'function') {
|
|
try {
|
|
const row = await db.getAccessToken(plainToken);
|
|
if (row && row.client_id === deviceId) {
|
|
return true;
|
|
}
|
|
const hashed = hashAccessToken(plainToken);
|
|
const rowHash = await db.getAccessToken(hashed);
|
|
if (rowHash && rowHash.client_id === deviceId) {
|
|
return true;
|
|
}
|
|
} catch (_) {
|
|
/* ignore */
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
module.exports = {
|
|
hashDeviceToken,
|
|
verifyDeviceWsAuth,
|
|
verifyEnrollmentToken,
|
|
};
|