Files
claude-code-conduit/server/auth.mjs
mikael-lovqvists-claude-agent dd7743501a Add runtime policy overrides and TUI policy management view
- server/policy_overrides.mjs: persistent group/action override store
- Group toggle: disabling a group forces all its actions to auto-deny without
  touching individual overrides; re-enabling restores them intact
- Action overrides: cycle auto-accept/queue/auto-deny/clear per action
- Effective policy resolution in index.mjs: group > action override > hardcoded default
- auth.mjs: add check_can_manage_policies (requires canApprove.length > 0)
- Three new endpoints: GET /policies, POST /policies/group/:group, POST /policies/action/:action
- client/conduit.mjs: get_policies, set_group_policy, set_action_policy
- ccc-queue.mjs: policy view (Tab to switch); group/action panel with space/e/←/→
- actions.mjs: group fields on desktop/email/calendar actions; list-actions includes group
- config.example.json: add policy_overrides key
- Bump version to 1.3.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:01:45 +00:00

55 lines
1.8 KiB
JavaScript

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);
}
// A user can manage policy overrides if they have canApprove authority over
// at least one other user. This ensures only trusted humans (never agents)
// can change what agents are allowed to do. canApprove itself is never
// runtime-writable — it is loaded from secrets.json at startup.
export function check_can_manage_policies(users, caller) {
return (users[caller]?.canApprove?.length ?? 0) > 0;
}