Rate limiting on individual handlers
This commit is contained in:
parent
4d02ed9c97
commit
1415c2db92
5 changed files with 106 additions and 9 deletions
|
|
@ -84,8 +84,8 @@ describe('Authorization Handler', () => {
|
|||
beforeEach(() => {
|
||||
app = express();
|
||||
options = { provider: mockProvider };
|
||||
app.get('/authorize', authorizationHandler(options));
|
||||
app.post('/authorize', authorizationHandler(options));
|
||||
const handler = authorizationHandler(options);
|
||||
app.use('/authorize', handler);
|
||||
});
|
||||
|
||||
describe('HTTP method validation', () => {
|
||||
|
|
@ -94,7 +94,7 @@ describe('Authorization Handler', () => {
|
|||
.put('/authorize')
|
||||
.query({ client_id: 'valid-client' });
|
||||
|
||||
expect(response.status).toBe(404); // Express filtering before reaching handler
|
||||
expect(response.status).toBe(405); // Method not allowed response from handler
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
import { RequestHandler } from "express";
|
||||
import { z } from "zod";
|
||||
import express from "express";
|
||||
import { OAuthServerProvider } from "../provider.js";
|
||||
import { rateLimit, Options as RateLimitOptions } from "express-rate-limit";
|
||||
|
||||
export type AuthorizationHandlerOptions = {
|
||||
provider: OAuthServerProvider;
|
||||
/**
|
||||
* Rate limiting configuration for the authorization endpoint.
|
||||
* Set to false to disable rate limiting for this endpoint.
|
||||
*/
|
||||
rateLimit?: Partial<RateLimitOptions> | false;
|
||||
};
|
||||
|
||||
// Parameters that must be validated in order to issue redirects.
|
||||
|
|
@ -21,8 +28,27 @@ const RequestAuthorizationParamsSchema = z.object({
|
|||
state: z.string().optional(),
|
||||
});
|
||||
|
||||
export function authorizationHandler({ provider }: AuthorizationHandlerOptions): RequestHandler {
|
||||
return async (req, res) => {
|
||||
export function authorizationHandler({ provider, rateLimit: rateLimitConfig }: AuthorizationHandlerOptions): RequestHandler {
|
||||
// Create a router to apply middleware
|
||||
const router = express.Router();
|
||||
|
||||
// Apply rate limiting unless explicitly disabled
|
||||
if (rateLimitConfig !== false) {
|
||||
router.use(rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100, // 100 requests per windowMs
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: {
|
||||
error: 'too_many_requests',
|
||||
error_description: 'You have exceeded the rate limit for authorization requests'
|
||||
},
|
||||
...rateLimitConfig
|
||||
}));
|
||||
}
|
||||
|
||||
// Define the handler
|
||||
router.all("/", async (req, res) => {
|
||||
if (req.method !== "GET" && req.method !== "POST") {
|
||||
res.status(405).end("Method Not Allowed");
|
||||
return;
|
||||
|
|
@ -88,5 +114,7 @@ export function authorizationHandler({ provider }: AuthorizationHandlerOptions):
|
|||
redirectUri: redirect_uri,
|
||||
codeChallenge: params.code_challenge,
|
||||
}, res);
|
||||
};
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import { OAuthClientInformationFull, OAuthClientMetadataSchema } from "../../../
|
|||
import crypto from 'node:crypto';
|
||||
import cors from 'cors';
|
||||
import { OAuthRegisteredClientsStore } from "../clients.js";
|
||||
import { rateLimit, Options as RateLimitOptions } from "express-rate-limit";
|
||||
|
||||
export type ClientRegistrationHandlerOptions = {
|
||||
/**
|
||||
|
|
@ -16,11 +17,22 @@ export type ClientRegistrationHandlerOptions = {
|
|||
* If not set, defaults to 30 days.
|
||||
*/
|
||||
clientSecretExpirySeconds?: number;
|
||||
|
||||
/**
|
||||
* Rate limiting configuration for the client registration endpoint.
|
||||
* Set to false to disable rate limiting for this endpoint.
|
||||
* Registration endpoints are particularly sensitive to abuse and should be rate limited.
|
||||
*/
|
||||
rateLimit?: Partial<RateLimitOptions> | false;
|
||||
};
|
||||
|
||||
const DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS = 30 * 24 * 60 * 60; // 30 days
|
||||
|
||||
export function clientRegistrationHandler({ clientsStore, clientSecretExpirySeconds = DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS }: ClientRegistrationHandlerOptions): RequestHandler {
|
||||
export function clientRegistrationHandler({
|
||||
clientsStore,
|
||||
clientSecretExpirySeconds = DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS,
|
||||
rateLimit: rateLimitConfig
|
||||
}: ClientRegistrationHandlerOptions): RequestHandler {
|
||||
if (!clientsStore.registerClient) {
|
||||
throw new Error("Client registration store does not support registering clients");
|
||||
}
|
||||
|
|
@ -31,6 +43,21 @@ export function clientRegistrationHandler({ clientsStore, clientSecretExpirySeco
|
|||
|
||||
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
|
||||
router.use(cors());
|
||||
|
||||
// Apply rate limiting unless explicitly disabled - stricter limits for registration
|
||||
if (rateLimitConfig !== false) {
|
||||
router.use(rateLimit({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 20, // 20 requests per hour - stricter as registration is sensitive
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: {
|
||||
error: 'too_many_requests',
|
||||
error_description: 'You have exceeded the rate limit for client registration requests'
|
||||
},
|
||||
...rateLimitConfig
|
||||
}));
|
||||
}
|
||||
|
||||
router.post("/", async (req, res) => {
|
||||
let clientMetadata;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,18 @@ import express, { RequestHandler } from "express";
|
|||
import cors from "cors";
|
||||
import { authenticateClient } from "../middleware/clientAuth.js";
|
||||
import { OAuthTokenRevocationRequestSchema } from "../../../shared/auth.js";
|
||||
import { rateLimit, Options as RateLimitOptions } from "express-rate-limit";
|
||||
|
||||
export type RevocationHandlerOptions = {
|
||||
provider: OAuthServerProvider;
|
||||
/**
|
||||
* Rate limiting configuration for the token revocation endpoint.
|
||||
* Set to false to disable rate limiting for this endpoint.
|
||||
*/
|
||||
rateLimit?: Partial<RateLimitOptions> | false;
|
||||
};
|
||||
|
||||
export function revocationHandler({ provider }: RevocationHandlerOptions): RequestHandler {
|
||||
export function revocationHandler({ provider, rateLimit: rateLimitConfig }: RevocationHandlerOptions): RequestHandler {
|
||||
if (!provider.revokeToken) {
|
||||
throw new Error("Auth provider does not support revoking tokens");
|
||||
}
|
||||
|
|
@ -19,6 +25,21 @@ export function revocationHandler({ provider }: RevocationHandlerOptions): Reque
|
|||
|
||||
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
|
||||
router.use(cors());
|
||||
|
||||
// Apply rate limiting unless explicitly disabled
|
||||
if (rateLimitConfig !== false) {
|
||||
router.use(rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 50, // 50 requests per windowMs
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: {
|
||||
error: 'too_many_requests',
|
||||
error_description: 'You have exceeded the rate limit for token revocation requests'
|
||||
},
|
||||
...rateLimitConfig
|
||||
}));
|
||||
}
|
||||
|
||||
// Authenticate and extract client details
|
||||
router.use(authenticateClient({ clientsStore: provider.clientsStore }));
|
||||
|
|
|
|||
|
|
@ -4,9 +4,15 @@ import { OAuthServerProvider } from "../provider.js";
|
|||
import cors from "cors";
|
||||
import { verifyChallenge } from "pkce-challenge";
|
||||
import { authenticateClient } from "../middleware/clientAuth.js";
|
||||
import { rateLimit, Options as RateLimitOptions } from "express-rate-limit";
|
||||
|
||||
export type TokenHandlerOptions = {
|
||||
provider: OAuthServerProvider;
|
||||
/**
|
||||
* Rate limiting configuration for the token endpoint.
|
||||
* Set to false to disable rate limiting for this endpoint.
|
||||
*/
|
||||
rateLimit?: Partial<RateLimitOptions> | false;
|
||||
};
|
||||
|
||||
const TokenRequestSchema = z.object({
|
||||
|
|
@ -23,13 +29,28 @@ const RefreshTokenGrantSchema = z.object({
|
|||
scope: z.string().optional(),
|
||||
});
|
||||
|
||||
export function tokenHandler({ provider }: TokenHandlerOptions): RequestHandler {
|
||||
export function tokenHandler({ provider, rateLimit: rateLimitConfig }: TokenHandlerOptions): RequestHandler {
|
||||
// Nested router so we can configure middleware and restrict HTTP method
|
||||
const router = express.Router();
|
||||
router.use(express.urlencoded({ extended: false }));
|
||||
|
||||
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
|
||||
router.use(cors());
|
||||
|
||||
// Apply rate limiting unless explicitly disabled
|
||||
if (rateLimitConfig !== false) {
|
||||
router.use(rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 50, // 50 requests per windowMs
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: {
|
||||
error: 'too_many_requests',
|
||||
error_description: 'You have exceeded the rate limit for token requests'
|
||||
},
|
||||
...rateLimitConfig
|
||||
}));
|
||||
}
|
||||
|
||||
// Authenticate and extract client details
|
||||
router.use(authenticateClient({ clientsStore: provider.clientsStore }));
|
||||
|
|
|
|||
Loading…
Reference in a new issue