feat: zero-config auth — keys optional, localhost is admin

Multi mode now allows unauthenticated requests as "anonymous".
Keys are optional: provide one for per-key usage tracking, or
skip it for zero-config access. Invalid keys are still rejected.

Localhost requests are always admin-level (can manage keys,
view dashboard data, etc.) without any token.

This makes OCP much friendlier for home LAN setups — family
members can use it immediately without any key configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 10:11:08 +10:00
co-authored by Claude Opus 4.6
parent 3eecca35ce
commit 69b20815fa
+23 -6
View File
@@ -1046,11 +1046,26 @@ const server = createServer(async (req, res) => {
let authKeyName = isLocalhost ? "local" : "remote"; let authKeyName = isLocalhost ? "local" : "remote";
let authKeyId = null; let authKeyId = null;
if (!isPublicEndpoint && !isLocalhost) { if (!isPublicEndpoint) {
const auth = req.headers["authorization"] || ""; const auth = req.headers["authorization"] || "";
const token = auth.startsWith("Bearer ") ? auth.slice(7) : ""; const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
if (AUTH_MODE === "shared") { if (isLocalhost) {
// Localhost always allowed — try to identify key if provided, but never reject
if (token) {
if (ADMIN_KEY) {
const adminBuf = Buffer.from(ADMIN_KEY);
const tokenBuf = Buffer.from(token);
if (adminBuf.length === tokenBuf.length && timingSafeEqual(adminBuf, tokenBuf)) {
authKeyName = "admin";
}
}
if (authKeyName !== "admin") {
const keyInfo = validateKey(token);
if (keyInfo) { authKeyName = keyInfo.name; authKeyId = keyInfo.id; }
}
}
} else if (AUTH_MODE === "shared") {
if (PROXY_API_KEY) { if (PROXY_API_KEY) {
const tokenBuf = Buffer.from(token); const tokenBuf = Buffer.from(token);
const keyBuf = Buffer.from(PROXY_API_KEY); const keyBuf = Buffer.from(PROXY_API_KEY);
@@ -1060,9 +1075,8 @@ const server = createServer(async (req, res) => {
authKeyName = "shared"; authKeyName = "shared";
} }
} else if (AUTH_MODE === "multi") { } else if (AUTH_MODE === "multi") {
if (!token) { // If a token is provided, validate it; if not, allow as anonymous
return jsonResponse(res, 401, { error: { message: "Unauthorized: Bearer token required", type: "auth_error" } }); if (token) {
}
let isAdminToken = false; let isAdminToken = false;
if (ADMIN_KEY) { if (ADMIN_KEY) {
const adminBuf = Buffer.from(ADMIN_KEY); const adminBuf = Buffer.from(ADMIN_KEY);
@@ -1080,6 +1094,9 @@ const server = createServer(async (req, res) => {
authKeyName = keyInfo.name; authKeyName = keyInfo.name;
authKeyId = keyInfo.id; authKeyId = keyInfo.id;
} }
} else {
authKeyName = "anonymous";
}
} }
} }
@@ -1182,7 +1199,7 @@ const server = createServer(async (req, res) => {
} }
// ── Key management API ── // ── Key management API ──
const isAdmin = AUTH_MODE !== "multi" || authKeyName === "admin"; const isAdmin = AUTH_MODE !== "multi" || authKeyName === "admin" || isLocalhost;
if (req.url === "/api/keys" && req.method === "POST") { if (req.url === "/api/keys" && req.method === "POST") {
if (!isAdmin) return jsonResponse(res, 403, { error: "Admin access required" }); if (!isAdmin) return jsonResponse(res, 403, { error: "Admin access required" });