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:
parent
3a04416b10
commit
6db71abef9
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,24 +487,30 @@ 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 token = extractToken(req.headers);
|
const internalKey = req.headers.get("X-Internal-Key");
|
||||||
const access = await evaluateSpaceAccess(slug, token, req.method, {
|
const isInternalCall = INTERNAL_API_KEY && internalKey === INTERNAL_API_KEY;
|
||||||
getSpaceConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!access.allowed) {
|
if (!isInternalCall) {
|
||||||
return Response.json(
|
// Check space access (write required) for external calls
|
||||||
{ error: access.reason },
|
const token = extractToken(req.headers);
|
||||||
{ status: access.claims ? 403 : 401, headers: corsHeaders }
|
const access = await evaluateSpaceAccess(slug, token, req.method, {
|
||||||
);
|
getSpaceConfig,
|
||||||
}
|
});
|
||||||
|
|
||||||
if (access.readOnly) {
|
if (!access.allowed) {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{ error: "Write access required to add shapes" },
|
{ error: access.reason },
|
||||||
{ status: 403, headers: corsHeaders }
|
{ 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 {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue