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
- STORAGE_DIR=/data/communities
- PORT=3000
- INTERNAL_API_KEY=${INTERNAL_API_KEY}
labels:
- "traefik.enable=true"
# 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 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<Response> {
) {
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 {