feat(guild): system-key bypass + announce-only system path + gen CLI

Three coupled changes that let Dialectic.Backend (and future system
broadcasters) post to announce channels without needing a Fabric user
bearer.

1. ApiKeyGuard: when x-fabric-system-key matches
   FABRIC_BACKEND_GUILD_SYSTEM_API_KEY env, skip the Bearer requirement
   and set req.isSystem=true. Pre-Bearer system bypass; no per-user
   session token needed. Empty env -> bypass disabled (closed by default).

2. messaging.controller POST /channels/:id/messages: when req.isSystem,
   skip assertParticipant + fetch channel directly. Enforce xType=announce
   (system key only writes to announce channels - never to regular chats).
   Persist with sentinel author 00000000-0000-0000-0000-000000000000.
   Emit message.created + realtime.emitMessageCreated with xType=announce
   so the Phase 1 busy-discard logic kicks in for recipients.

3. New cli: src/cli/gen-system-api-key.ts. Generates a random 32-byte
   hex key (same shape as agent + admin keys) and prints it. Does NOT
   store - operator pastes into compose env and restarts guild. Pattern
   mirrors the existing print-commands-sync-key.ts.

Removes the need for a FABRIC_BOT_BEARER_TOKEN concept entirely - the
system key alone is sufficient. announce-channel posts by regular
authenticated users (who happen to know channel id but no system key)
are now 403 announce_system_only.
This commit is contained in:
h z
2026-05-23 17:49:53 +01:00
parent 80ee9082f3
commit 985b06a886
3 changed files with 113 additions and 16 deletions

View File

@@ -21,6 +21,23 @@ export class ApiKeyGuard implements CanActivate {
return true;
}
// System-key bypass: when a caller presents x-fabric-system-key matching
// FABRIC_BACKEND_GUILD_SYSTEM_API_KEY, skip the Bearer requirement and
// mark this as a system caller (no userId). Downstream handlers (e.g.
// messaging.controller for announce-type channels) gate per-route on
// req.isSystem instead of req.userId.
//
// This is what makes Dialectic.Backend's lifecycle broadcasts work
// without needing a per-user Fabric session token — the system key
// alone is sufficient for posting to announce channels.
const sysExpected = process.env.FABRIC_BACKEND_GUILD_SYSTEM_API_KEY ?? '';
const sysHeader = req.headers['x-fabric-system-key'];
const sysProvided = Array.isArray(sysHeader) ? sysHeader[0] : sysHeader;
if (sysExpected && sysProvided && sysProvided === sysExpected) {
(req as { isSystem?: boolean }).isSystem = true;
return true;
}
const auth = req.headers['authorization'];
const authValue = Array.isArray(auth) ? auth[0] : auth;
let token = authValue?.startsWith('Bearer ') ? authValue.slice(7) : '';