mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 17:02:19 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a18de0990 |
@@ -112,6 +112,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 } };
|
||||
@@ -632,12 +650,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