Add HMAC auth, user permissions, snake_case rename
Each request is signed with HMAC-SHA256 over timestamp+body using a per-user secret loaded from a --secrets file (never env vars or git). Users have a canApprove list controlling who may approve queued actions. Queue entries track submitted_by for permission checks on approve/deny. Also renames all identifiers to snake_case throughout the codebase. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
46
server/auth.mjs
Normal file
46
server/auth.mjs
Normal file
@@ -0,0 +1,46 @@
|
||||
import { createHmac, timingSafeEqual } from "crypto";
|
||||
|
||||
export function create_auth_middleware(users) {
|
||||
return function hmac_auth(req, res, next) {
|
||||
const username = req.headers["x-conduit-user"];
|
||||
const timestamp = req.headers["x-conduit-timestamp"];
|
||||
const signature = req.headers["x-conduit-signature"];
|
||||
|
||||
if (!username || !timestamp || !signature) {
|
||||
return res.status(401).json({ error: "Missing auth headers" });
|
||||
}
|
||||
|
||||
const ts = parseInt(timestamp, 10);
|
||||
if (!Number.isFinite(ts) || Date.now() - ts > 30_000) {
|
||||
return res.status(401).json({ error: "Request expired" });
|
||||
}
|
||||
|
||||
const user = users[username];
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: "Unknown user" });
|
||||
}
|
||||
|
||||
const raw_body = req.raw_body ?? "";
|
||||
const expected = createHmac("sha256", user.secret)
|
||||
.update(timestamp + "." + raw_body)
|
||||
.digest("hex");
|
||||
|
||||
const sig_buf = Buffer.from(signature, "hex");
|
||||
const expected_buf = Buffer.from(expected, "hex");
|
||||
|
||||
if (sig_buf.length !== expected_buf.length || !timingSafeEqual(sig_buf, expected_buf)) {
|
||||
return res.status(401).json({ error: "Invalid signature" });
|
||||
}
|
||||
|
||||
req.conduit_user = username;
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
export function check_can_approve(users, requester_name, submitter_name) {
|
||||
const requester = users[requester_name];
|
||||
if (!requester) {
|
||||
return false;
|
||||
}
|
||||
return requester.canApprove.includes(submitter_name);
|
||||
}
|
||||
Reference in New Issue
Block a user