feat(guild-auth): enforce unified x-api-key auth for inbound APIs
This commit is contained in:
@@ -10,6 +10,9 @@ DB_NAME=fabric_guild
|
|||||||
DB_SYNC=true
|
DB_SYNC=true
|
||||||
DB_LOGGING=false
|
DB_LOGGING=false
|
||||||
|
|
||||||
|
# Unified inbound API auth
|
||||||
|
FABRIC_API_KEY=change-me-api-key
|
||||||
|
|
||||||
# Guild identity
|
# Guild identity
|
||||||
GUILD_NODE_ID=guild-node-1
|
GUILD_NODE_ID=guild-node-1
|
||||||
GUILD_NODE_NAME=Guild Node 1
|
GUILD_NODE_NAME=Guild Node 1
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { buildTypeOrmConfig } from './database.config';
|
import { buildTypeOrmConfig } from './database.config';
|
||||||
import { HealthController } from './common/health.controller';
|
import { HealthController } from './common/health.controller';
|
||||||
|
import { ApiKeyGuard } from './common/api-key.guard';
|
||||||
import { GuildsModule } from './guilds/guilds.module';
|
import { GuildsModule } from './guilds/guilds.module';
|
||||||
import { ChannelsModule } from './channels/channels.module';
|
import { ChannelsModule } from './channels/channels.module';
|
||||||
import { MessagingModule } from './messaging/messaging.module';
|
import { MessagingModule } from './messaging/messaging.module';
|
||||||
@@ -16,5 +18,11 @@ import { EventsModule } from './events/events.module';
|
|||||||
MessagingModule,
|
MessagingModule,
|
||||||
],
|
],
|
||||||
controllers: [HealthController],
|
controllers: [HealthController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: ApiKeyGuard,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
34
Fabric.Backend.Guild/src/common/api-key.guard.ts
Normal file
34
Fabric.Backend.Guild/src/common/api-key.guard.ts
Normal file
@@ -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<string, string | string[] | undefined> }>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
- [x] Webhook 事件信封落地(event_id/event_type/occurred_at/data)
|
- [x] Webhook 事件信封落地(event_id/event_type/occurred_at/data)
|
||||||
- [x] HMAC 签名与重放防护
|
- [x] HMAC 签名与重放防护
|
||||||
- [x] 出站重试队列(指数退避)
|
- [x] 出站重试队列(指数退避)
|
||||||
- [ ] API Key 入站调用鉴权(不区分 Bot/人类)
|
- [x] API Key 入站调用鉴权(不区分 Bot/人类)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user