From b0147673242d01b7517a2bbd02ff310423c323d5 Mon Sep 17 00:00:00 2001 From: nav Date: Tue, 12 May 2026 11:32:25 +0000 Subject: [PATCH] feat(guild-auth): enforce unified x-api-key auth for inbound APIs --- Fabric.Backend.Guild/.env.example | 3 ++ Fabric.Backend.Guild/src/app.module.ts | 8 +++++ .../src/common/api-key.guard.ts | 34 +++++++++++++++++++ docs/TODO-backend-center-guild.md | 2 +- 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 Fabric.Backend.Guild/src/common/api-key.guard.ts diff --git a/Fabric.Backend.Guild/.env.example b/Fabric.Backend.Guild/.env.example index 5cfdc17..fdde1f3 100644 --- a/Fabric.Backend.Guild/.env.example +++ b/Fabric.Backend.Guild/.env.example @@ -10,6 +10,9 @@ DB_NAME=fabric_guild DB_SYNC=true DB_LOGGING=false +# Unified inbound API auth +FABRIC_API_KEY=change-me-api-key + # Guild identity GUILD_NODE_ID=guild-node-1 GUILD_NODE_NAME=Guild Node 1 diff --git a/Fabric.Backend.Guild/src/app.module.ts b/Fabric.Backend.Guild/src/app.module.ts index e335d9d..38d0b3f 100644 --- a/Fabric.Backend.Guild/src/app.module.ts +++ b/Fabric.Backend.Guild/src/app.module.ts @@ -1,7 +1,9 @@ import { Module } from '@nestjs/common'; +import { APP_GUARD } from '@nestjs/core'; import { TypeOrmModule } from '@nestjs/typeorm'; import { buildTypeOrmConfig } from './database.config'; import { HealthController } from './common/health.controller'; +import { ApiKeyGuard } from './common/api-key.guard'; import { GuildsModule } from './guilds/guilds.module'; import { ChannelsModule } from './channels/channels.module'; import { MessagingModule } from './messaging/messaging.module'; @@ -16,5 +18,11 @@ import { EventsModule } from './events/events.module'; MessagingModule, ], controllers: [HealthController], + providers: [ + { + provide: APP_GUARD, + useClass: ApiKeyGuard, + }, + ], }) export class AppModule {} diff --git a/Fabric.Backend.Guild/src/common/api-key.guard.ts b/Fabric.Backend.Guild/src/common/api-key.guard.ts new file mode 100644 index 0000000..ac74af3 --- /dev/null +++ b/Fabric.Backend.Guild/src/common/api-key.guard.ts @@ -0,0 +1,34 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + ServiceUnavailableException, + UnauthorizedException, +} from '@nestjs/common'; + +@Injectable() +export class ApiKeyGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean { + const req = context.switchToHttp().getRequest<{ path?: string; headers: Record }>(); + const path = req.path ?? ''; + + // allow health check without auth + if (path.endsWith('/healthz') || path === '/healthz') { + return true; + } + + const expected = process.env.FABRIC_API_KEY; + if (!expected || expected.trim() === '') { + throw new ServiceUnavailableException('FABRIC_API_KEY is not configured'); + } + + const received = req.headers['x-api-key']; + const receivedValue = Array.isArray(received) ? received[0] : received; + + if (!receivedValue || receivedValue !== expected) { + throw new UnauthorizedException('invalid api key'); + } + + return true; + } +} diff --git a/docs/TODO-backend-center-guild.md b/docs/TODO-backend-center-guild.md index 65fe949..7ed76b3 100644 --- a/docs/TODO-backend-center-guild.md +++ b/docs/TODO-backend-center-guild.md @@ -74,7 +74,7 @@ - [x] Webhook 事件信封落地(event_id/event_type/occurred_at/data) - [x] HMAC 签名与重放防护 - [x] 出站重试队列(指数退避) -- [ ] API Key 入站调用鉴权(不区分 Bot/人类) +- [x] API Key 入站调用鉴权(不区分 Bot/人类) ---