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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-13 15:02:08 -07:00
parent 3a04416b10
commit 6db71abef9
2 changed files with 24 additions and 16 deletions

View File

@ -11,6 +11,7 @@ services:
- NODE_ENV=production - NODE_ENV=production
- STORAGE_DIR=/data/communities - STORAGE_DIR=/data/communities
- PORT=3000 - PORT=3000
- INTERNAL_API_KEY=${INTERNAL_API_KEY}
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
# Only handle subdomains (rspace-prod handles main domain) # Only handle subdomains (rspace-prod handles main domain)

View File

@ -38,6 +38,7 @@ async function getSpaceConfig(slug: string): Promise<SpaceAuthConfig | null> {
} }
const PORT = Number(process.env.PORT) || 3000; const PORT = Number(process.env.PORT) || 3000;
const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY || "";
const DIST_DIR = resolve(import.meta.dir, "../dist"); const DIST_DIR = resolve(import.meta.dir, "../dist");
// WebSocket data type // WebSocket data type
@ -486,7 +487,12 @@ async function handleAPI(req: Request, url: URL): Promise<Response> {
) { ) {
const slug = url.pathname.split("/")[3]; const slug = url.pathname.split("/")[3];
// Check space access (write required) // 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 (!isInternalCall) {
// Check space access (write required) for external calls
const token = extractToken(req.headers); const token = extractToken(req.headers);
const access = await evaluateSpaceAccess(slug, token, req.method, { const access = await evaluateSpaceAccess(slug, token, req.method, {
getSpaceConfig, getSpaceConfig,
@ -505,6 +511,7 @@ async function handleAPI(req: Request, url: URL): Promise<Response> {
{ status: 403, headers: corsHeaders } { status: 403, headers: corsHeaders }
); );
} }
}
try { try {
// Ensure community is loaded // Ensure community is loaded