From 6db71abef9b891d821a1ede869a694a28feda08e Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Fri, 13 Feb 2026 15:02:08 -0700 Subject: [PATCH] feat: add internal API key bypass for service-to-service calls Allows trusted internal services (e.g. rnotes) to push shapes without EncryptID auth by passing X-Internal-Key header. Key is set via INTERNAL_API_KEY env var. Co-Authored-By: Claude Opus 4.6 --- docker-compose.yml | 1 + server/index.ts | 39 +++++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2068a15..a4eddc3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: - NODE_ENV=production - STORAGE_DIR=/data/communities - PORT=3000 + - INTERNAL_API_KEY=${INTERNAL_API_KEY} labels: - "traefik.enable=true" # Only handle subdomains (rspace-prod handles main domain) diff --git a/server/index.ts b/server/index.ts index 3e71ea7..3f75d39 100644 --- a/server/index.ts +++ b/server/index.ts @@ -38,6 +38,7 @@ async function getSpaceConfig(slug: string): Promise { } const PORT = Number(process.env.PORT) || 3000; +const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY || ""; const DIST_DIR = resolve(import.meta.dir, "../dist"); // WebSocket data type @@ -486,24 +487,30 @@ async function handleAPI(req: Request, url: URL): Promise { ) { const slug = url.pathname.split("/")[3]; - // Check space access (write required) - const token = extractToken(req.headers); - const access = await evaluateSpaceAccess(slug, token, req.method, { - getSpaceConfig, - }); + // Allow internal service-to-service calls with shared key + const internalKey = req.headers.get("X-Internal-Key"); + const isInternalCall = INTERNAL_API_KEY && internalKey === INTERNAL_API_KEY; - if (!access.allowed) { - return Response.json( - { error: access.reason }, - { status: access.claims ? 403 : 401, headers: corsHeaders } - ); - } + if (!isInternalCall) { + // Check space access (write required) for external calls + const token = extractToken(req.headers); + const access = await evaluateSpaceAccess(slug, token, req.method, { + getSpaceConfig, + }); - if (access.readOnly) { - return Response.json( - { error: "Write access required to add shapes" }, - { status: 403, headers: corsHeaders } - ); + if (!access.allowed) { + return Response.json( + { error: access.reason }, + { status: access.claims ? 403 : 401, headers: corsHeaders } + ); + } + + if (access.readOnly) { + return Response.json( + { error: "Write access required to add shapes" }, + { status: 403, headers: corsHeaders } + ); + } } try {