← back to Labj Cre Banks
server.js
63 lines
#!/usr/bin/env node
'use strict';
/*
* LABJ CRE + Banking — RENTV Prospect Database viewer.
* Zero-dependency static server (Node built-in http) for public/,
* behind Basic Auth admin / DW2024! (override via VIEWER_USER/PASS).
* Serves index.html + labj_master.json — no DB, no framework.
*/
const http = require('http');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const PORT = Number(process.env.PORT || 0);
const HOST = process.env.HOST || '127.0.0.1';
const USER = process.env.VIEWER_USER || 'admin';
const PASS = process.env.VIEWER_PASS || 'DW2024!';
const ROOT = path.join(__dirname, 'public');
const TYPES = {
'.html': 'text/html; charset=utf-8', '.json': 'application/json; charset=utf-8',
'.js': 'text/javascript', '.css': 'text/css', '.csv': 'text/csv',
'.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml',
};
function safeEq(a, b) {
const ab = Buffer.from(a), bb = Buffer.from(b);
return ab.length === bb.length && crypto.timingSafeEqual(ab, bb);
}
function authed(req) {
const h = req.headers.authorization || '';
if (!h.startsWith('Basic ')) return false;
const [u, p] = Buffer.from(h.slice(6), 'base64').toString('utf8').split(':');
return safeEq(u || '', USER) && safeEq(p || '', PASS);
}
const server = http.createServer((req, res) => {
if (!authed(req)) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="labj-viewer"' });
return res.end('Auth required');
}
let rel;
try {
rel = decodeURIComponent(new URL(req.url, 'http://x').pathname);
} catch (_) {
res.writeHead(400);
return res.end('Bad request');
}
if (rel === '/' || rel === '') rel = '/index.html';
if (rel === '/healthz') { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end('{"ok":true}'); }
const full = path.normalize(path.join(ROOT, rel));
if (!full.startsWith(ROOT + path.sep) && full !== ROOT) { res.writeHead(403); return res.end('Forbidden'); }
fs.readFile(full, (err, buf) => {
if (err) { res.writeHead(404); return res.end('Not found'); }
res.writeHead(200, { 'Content-Type': TYPES[path.extname(full)] || 'application/octet-stream', 'Cache-Control': 'no-store' });
res.end(buf);
});
});
server.listen(PORT, HOST, () => {
console.log(`LABJ viewer live: http://${HOST}:${server.address().port} (login ${USER})`);
});