mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 00:48:18 +00:00
fix(serve-http): make OAuth /token rate limit configurable via env (#3114)
Adds GBRAIN_OAUTH_TOKEN_RATE_LIMIT_MAX and GBRAIN_OAUTH_TOKEN_RATE_LIMIT_WINDOW_MS to tune the /token client_credentials limiter (default unchanged: 50 req / 15 min). Invalid, zero, or negative values fall back to the default. Takeover of #2501 (mechanical rebase onto master after #2625 shifted the surrounding context in serve-http.ts). Fixes #2463. Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: techtony2018 <techtony2018@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Garry Tan
techtony2018
Claude Fable 5
parent
66f4cb6d82
commit
58606cc924
@@ -113,6 +113,24 @@ export function shouldSuppressBootstrapPrint(opts: {
|
||||
return !opts.isTty;
|
||||
}
|
||||
|
||||
export type OAuthTokenRateLimitConfig = {
|
||||
windowMs: number;
|
||||
max: number;
|
||||
};
|
||||
|
||||
function parsePositiveIntEnv(value: string | undefined, fallback: number): number {
|
||||
if (value === undefined) return fallback;
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
export function resolveOAuthTokenRateLimit(env: NodeJS.ProcessEnv = process.env): OAuthTokenRateLimitConfig {
|
||||
return {
|
||||
windowMs: parsePositiveIntEnv(env.GBRAIN_OAUTH_TOKEN_RATE_LIMIT_WINDOW_MS, 15 * 60 * 1000),
|
||||
max: parsePositiveIntEnv(env.GBRAIN_OAUTH_TOKEN_RATE_LIMIT_MAX, 50),
|
||||
};
|
||||
}
|
||||
|
||||
export type ProbeHealthResult =
|
||||
| { ok: true; status: 200; body: { status: 'ok'; version: string; engine: string; [k: string]: unknown } }
|
||||
| { ok: false; status: 503; body: { error: 'service_unavailable'; error_description: string } };
|
||||
@@ -633,12 +651,13 @@ export async function runServeHttp(engine: BrainEngine, options: ServeHttpOption
|
||||
// Custom client_credentials handler (before mcpAuthRouter)
|
||||
// SDK's token handler only supports authorization_code and refresh_token
|
||||
// ---------------------------------------------------------------------------
|
||||
const oauthTokenRateLimit = resolveOAuthTokenRateLimit();
|
||||
const ccRateLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 50,
|
||||
windowMs: oauthTokenRateLimit.windowMs,
|
||||
max: oauthTokenRateLimit.max,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { error: 'too_many_requests', error_description: 'Rate limit exceeded. Try again in 15 minutes.' },
|
||||
message: { error: 'too_many_requests', error_description: 'Rate limit exceeded. Try again later.' },
|
||||
});
|
||||
|
||||
// Magic-link rate limiter: 10 requests/min/IP. The bootstrap token is
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Tests for resolveOAuthTokenRateLimit() in src/commands/serve-http.ts.
|
||||
*
|
||||
* The /token client_credentials limiter should keep the historical default
|
||||
* while letting operators tune busy remote MCP hosts without patching source.
|
||||
*/
|
||||
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { resolveOAuthTokenRateLimit } from '../src/commands/serve-http.ts';
|
||||
|
||||
describe('resolveOAuthTokenRateLimit', () => {
|
||||
test('unset env keeps the historical 50 requests per 15 minutes default', () => {
|
||||
expect(resolveOAuthTokenRateLimit({})).toEqual({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 50,
|
||||
});
|
||||
});
|
||||
|
||||
test('env overrides allow a busy host to use 200 requests per minute', () => {
|
||||
expect(resolveOAuthTokenRateLimit({
|
||||
GBRAIN_OAUTH_TOKEN_RATE_LIMIT_WINDOW_MS: '60000',
|
||||
GBRAIN_OAUTH_TOKEN_RATE_LIMIT_MAX: '200',
|
||||
})).toEqual({
|
||||
windowMs: 60_000,
|
||||
max: 200,
|
||||
});
|
||||
});
|
||||
|
||||
test('blank, non-numeric, zero, and negative values fall back safely', () => {
|
||||
expect(resolveOAuthTokenRateLimit({
|
||||
GBRAIN_OAUTH_TOKEN_RATE_LIMIT_WINDOW_MS: '',
|
||||
GBRAIN_OAUTH_TOKEN_RATE_LIMIT_MAX: 'nope',
|
||||
})).toEqual({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 50,
|
||||
});
|
||||
|
||||
expect(resolveOAuthTokenRateLimit({
|
||||
GBRAIN_OAUTH_TOKEN_RATE_LIMIT_WINDOW_MS: '0',
|
||||
GBRAIN_OAUTH_TOKEN_RATE_LIMIT_MAX: '-10',
|
||||
})).toEqual({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 50,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user