audit
This commit is contained in:
@@ -4,6 +4,11 @@ services:
|
||||
container_name: broccolini-settings
|
||||
restart: unless-stopped
|
||||
env_file: ../.env
|
||||
environment:
|
||||
# Node must bind all-interfaces inside the container so Docker's DNAT
|
||||
# from the host-side Tailscale publish below can reach it. The Tailscale
|
||||
# restriction is enforced by the `ports:` binding, not by Node.
|
||||
SETTINGS_BIND_HOST: "0.0.0.0"
|
||||
ports:
|
||||
- "100.114.205.53:12752:12752"
|
||||
networks:
|
||||
|
||||
@@ -6,6 +6,14 @@ const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const { doubleCsrf } = require('csrf-csrf');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
// Mirror of safeEqual() in ../utils.js — duplicated here because the settings-site Docker build context excludes the parent dir.
|
||||
function safeEqual(a, b) {
|
||||
const ab = Buffer.from(String(a || ''), 'utf8');
|
||||
const bb = Buffer.from(String(b || ''), 'utf8');
|
||||
return ab.length === bb.length && crypto.timingSafeEqual(ab, bb);
|
||||
}
|
||||
|
||||
const app = express();
|
||||
const PORT = parseInt(process.env.SETTINGS_PORT) || 12752;
|
||||
@@ -57,7 +65,7 @@ app.use(express.urlencoded({ extended: true, limit: '64kb' }));
|
||||
app.use(session({
|
||||
secret: SESSION_SECRET,
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: IS_PROD,
|
||||
@@ -111,7 +119,12 @@ async function callBot(method, apiPath, body) {
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
});
|
||||
return res.json();
|
||||
const text = await res.text();
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return { error: 'bad_upstream', status: res.status, body: text.slice(0, 500) };
|
||||
}
|
||||
}
|
||||
|
||||
function proxy(method, botPath) {
|
||||
@@ -162,7 +175,7 @@ app.get('/login', (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/login', loginLimiter, (req, res) => {
|
||||
if (req.body.password === ADMIN_PASSWORD) {
|
||||
if (safeEqual(req.body.password, ADMIN_PASSWORD)) {
|
||||
req.session.authed = true;
|
||||
return res.json({ ok: true });
|
||||
}
|
||||
@@ -197,6 +210,14 @@ app.use((err, req, res, next) => {
|
||||
next(err);
|
||||
});
|
||||
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`[settings] running on port ${PORT}`);
|
||||
// Default bind is loopback. Production runs in Docker with
|
||||
// docker-compose.yml publishing `100.114.205.53:12752:12752` — that host-side
|
||||
// publish is what restricts ingress to the Tailscale IP, and the container
|
||||
// sets SETTINGS_BIND_HOST=0.0.0.0 so Docker's DNAT can reach Node. Caddy lives
|
||||
// on a separate host (rustdesk droplet) and reaches this box over Tailscale,
|
||||
// so 127.0.0.1 is not reachable from Caddy in prod; do not change that
|
||||
// default without updating docker-compose.yml in lockstep.
|
||||
const BIND_HOST = process.env.SETTINGS_BIND_HOST || '127.0.0.1';
|
||||
app.listen(PORT, BIND_HOST, () => {
|
||||
console.log(`[settings] running on ${BIND_HOST}:${PORT}`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user