feat(guild): validate bearer tokens via center introspection
This commit is contained in:
@@ -2,13 +2,13 @@ import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
ServiceUnavailableException,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { introspectGuildToken } from './center-auth';
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest<{ path?: string; headers: Record<string, string | string[] | undefined> }>();
|
||||
const path = req.path ?? '';
|
||||
|
||||
@@ -17,17 +17,15 @@ export class ApiKeyGuard implements CanActivate {
|
||||
return true;
|
||||
}
|
||||
|
||||
const expected = process.env.FABRIC_API_KEY;
|
||||
if (!expected || expected.trim() === '') {
|
||||
throw new ServiceUnavailableException('FABRIC_API_KEY is not configured');
|
||||
}
|
||||
const auth = req.headers['authorization'];
|
||||
const authValue = Array.isArray(auth) ? auth[0] : auth;
|
||||
const token = authValue?.startsWith('Bearer ') ? authValue.slice(7) : '';
|
||||
if (!token) throw new UnauthorizedException('missing bearer token');
|
||||
|
||||
const received = req.headers['x-api-key'];
|
||||
const receivedValue = Array.isArray(received) ? received[0] : received;
|
||||
const result = await introspectGuildToken(token);
|
||||
if (!result.active || !result.user?.id) throw new UnauthorizedException('invalid guild token');
|
||||
|
||||
if (!receivedValue || receivedValue !== expected) {
|
||||
throw new UnauthorizedException('invalid api key');
|
||||
}
|
||||
(req as { userId?: string }).userId = result.user.id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
28
src/common/center-auth.ts
Normal file
28
src/common/center-auth.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export async function introspectGuildToken(token: string): Promise<{ active: boolean; user?: { id: string; email?: string } }> {
|
||||
const centerBaseUrl = process.env.CENTER_BASE_URL;
|
||||
const sharedSecret = process.env.CENTER_SHARED_SECRET;
|
||||
const guildNodeId = process.env.GUILD_NODE_ID;
|
||||
|
||||
if (!centerBaseUrl || !sharedSecret || !guildNodeId) {
|
||||
return { active: false };
|
||||
}
|
||||
|
||||
const res = await fetch(`${centerBaseUrl}/api/auth/introspect`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-center-shared-secret': sharedSecret,
|
||||
},
|
||||
body: JSON.stringify({ token, guildNodeId }),
|
||||
});
|
||||
|
||||
if (!res.ok) return { active: false };
|
||||
const data = (await res.json()) as { active?: boolean; user?: { id: string; email?: string } };
|
||||
if (!data.active) return { active: false };
|
||||
|
||||
return {
|
||||
active: true,
|
||||
user: data.user,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user