feat: add API key authentication for AI proxy endpoints

- Add API_KEY config (env var) to all entry points
- requireAuth helper checks Authorization: Bearer or x-api-key header
- Auth applied to /v1/chat/completions, /v1/messages, /v1/models
- When API_KEY is empty/unset, auth is disabled (backward compatible)
- Update wrangler.toml with API_KEY variable documentation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-11 00:56:18 +07:00
co-authored by Claude Fable 5
parent 2be57c0838
commit c0a02279bd
4 changed files with 69 additions and 0 deletions
+21
View File
@@ -40,6 +40,8 @@ export interface Env {
RATE_LIMIT_WINDOW_MS?: string;
/** Server listen port (unused on Workers, here for local dev compatibility) */
PORT?: string;
/** API key for AI proxy auth (empty = disabled) */
API_KEY?: string;
}
// ─── Helpers ─────────────────────────────────────────────────────────────────────
@@ -66,6 +68,19 @@ function getClientIP(req: Request): string {
return "unknown";
}
// ─── Auth Helper ─────────────────────────────────────────────────────────────────
function requireAuth(req: Request, apiKey: string | undefined): Response | null {
if (!apiKey) return null; // auth disabled
const header = req.headers.get("authorization") ?? req.headers.get("x-api-key") ?? "";
const key = header.replace(/^Bearer\s+/i, "").trim();
if (key === apiKey) return null;
return new Response(
JSON.stringify({ error: { message: "Unauthorized", type: "auth_error" } }),
{ status: 401, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" } },
);
}
// ─── Route Handlers ──────────────────────────────────────────────────────────────
const SERVER_START_TIME = Date.now();
@@ -396,6 +411,8 @@ export default {
if (url.pathname === "/v1/chat/completions") {
if (req.method === "OPTIONS") return createCorsPreflightResponse();
if (req.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
const authErr = requireAuth(req, env.API_KEY);
if (authErr) return authErr;
try {
const body = await req.json();
return handleChatCompletion(body);
@@ -411,6 +428,8 @@ export default {
if (url.pathname === "/v1/messages") {
if (req.method === "OPTIONS") return createCorsPreflightResponse();
if (req.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
const authErr = requireAuth(req, env.API_KEY);
if (authErr) return authErr;
try {
const body = await req.json();
return handleAnthropicMessages(body);
@@ -424,6 +443,8 @@ export default {
// Models list
if (url.pathname === "/v1/models" && req.method === "GET") {
const authErr = requireAuth(req, env.API_KEY);
if (authErr) return authErr;
const models = listModels().map((id) => ({
id,
object: "model",