Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions packages/pharaoh_rate_limit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Pharaoh Rate Limit

Rate limiting middleware for the Pharaoh web framework. Provides token bucket and sliding window algorithms to protect your APIs from abuse and ensure fair usage.

## Features

- **Multiple algorithms**: Token bucket and sliding window rate limiting
- **Flexible configuration**: Customizable limits, time windows, and key generation
- **Standard headers**: Supports both modern and legacy rate limit headers
- **Skip functionality**: Bypass rate limiting for specific requests
- **Per-client tracking**: Automatic IP-based or custom key generation
- **Production ready**: Comprehensive test coverage and error handling

## Installation

Add this to your package's `pubspec.yaml` file:

```yaml
dependencies:
pharaoh_rate_limit: ^1.0.0
```

## Quick Start

```dart
import 'package:pharaoh/pharaoh.dart';
import 'package:pharaoh_rate_limit/pharaoh_rate_limit.dart';

final app = Pharaoh();

void main() async {
// Basic rate limiting: 100 requests per 15 minutes
app.use(rateLimit(
max: 100,
windowMs: Duration(minutes: 15),
));

app.get('/api/data', (req, res) {
return res.json({'message': 'Hello World!'});
});

await app.listen(port: 3000);
}
```

## Configuration Options

### Basic Options

```dart
app.use(rateLimit(
max: 100, // Maximum requests per window
windowMs: Duration(minutes: 15), // Time window
message: 'Too many requests!', // Custom error message
statusCode: 429, // HTTP status code for rate limited requests
));
```

### Advanced Options

```dart
app.use(rateLimit(
max: 50,
windowMs: Duration(minutes: 1),

// Custom key generation (default: IP address)
keyGenerator: (req) => req.headers['user-id']?.toString() ?? req.ipAddr,

// Skip rate limiting for certain requests
skip: (req) => req.headers['x-api-key'] == 'admin-key',

// Response headers
standardHeaders: true, // RateLimit-* headers (default: true)
legacyHeaders: false, // X-RateLimit-* headers (default: false)

// Rate limiting algorithm
algorithm: RateLimitAlgorithm.tokenBucket, // or slidingWindow
));
```

## Algorithms

### Token Bucket (Default)

Tokens are added to a bucket at a fixed rate. Each request consumes a token. When the bucket is empty, requests are rate limited.

```dart
app.use(rateLimit(
max: 10,
windowMs: Duration(seconds: 60),
algorithm: RateLimitAlgorithm.tokenBucket,
));
```

### Sliding Window

Tracks requests in a sliding time window. More memory intensive but provides smoother rate limiting.

```dart
app.use(rateLimit(
max: 10,
windowMs: Duration(seconds: 60),
algorithm: RateLimitAlgorithm.slidingWindow,
));
```

## Response Headers

When rate limiting is active, the following headers are added to responses:

### Standard Headers (enabled by default)
- `RateLimit-Limit`: Request limit per window
- `RateLimit-Remaining`: Remaining requests in current window
- `RateLimit-Reset`: Unix timestamp when the window resets
- `Retry-After`: Seconds to wait before retrying (when rate limited)

### Legacy Headers (optional)
- `X-RateLimit-Limit`: Request limit per window
- `X-RateLimit-Remaining`: Remaining requests in current window
- `X-RateLimit-Reset`: Unix timestamp when the window resets

## Examples

### Per-Route Rate Limiting

```dart
// Global rate limiting
app.use(rateLimit(max: 1000, windowMs: Duration(hours: 1)));

// Stricter limits for auth endpoints
app.use('/auth', rateLimit(
max: 5,
windowMs: Duration(minutes: 15),
message: 'Too many login attempts',
));

app.post('/auth/login', (req, res) {
// Login logic
});
```

### User-Based Rate Limiting

```dart
app.use(rateLimit(
max: 100,
windowMs: Duration(hours: 1),
keyGenerator: (req) {
// Rate limit by user ID instead of IP
final userId = req.auth?['userId'];
return userId?.toString() ?? req.ipAddr;
},
));
```

### Skip Rate Limiting

```dart
app.use(rateLimit(
max: 50,
windowMs: Duration(minutes: 1),
skip: (req) {
// Skip rate limiting for admin users
return req.auth?['role'] == 'admin';
},
));
```

## Testing

Run the test suite:

```bash
cd packages/pharaoh_rate_limit
dart test
```

## Contributing

Contributions are welcome! Please read the [contributing guidelines](../../CONTRIBUTING.md) before submitting PRs.

## License

This project is licensed under the MIT License - see the [LICENSE](../../LICENSE) file for details.
55 changes: 55 additions & 0 deletions packages/pharaoh_rate_limit/example/basic_rate_limiting.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:pharaoh/pharaoh.dart';
import 'package:pharaoh_rate_limit/pharaoh_rate_limit.dart';

final app = Pharaoh();

void main() async {
// Basic rate limiting: 100 requests per 15 minutes
app.use(rateLimit(
max: 100,
windowMs: Duration(minutes: 15),
message: 'Too many requests from this IP, please try again later.',
));

// API routes
app.get('/api/users', (req, res) {
return res.json([
{'id': 1, 'name': 'John Doe'},
{'id': 2, 'name': 'Jane Smith'},
]);
});

app.get('/api/posts', (req, res) {
return res.json([
{'id': 1, 'title': 'Hello World', 'author': 'John'},
{'id': 2, 'title': 'Dart is Awesome', 'author': 'Jane'},
]);
});

// More restrictive rate limiting for auth endpoints
final authLimiter = rateLimit(
max: 5,
windowMs: Duration(minutes: 15),
message: 'Too many authentication attempts, please try again later.',
statusCode: 429,
);

app.use(authLimiter);

app.post('/auth/login', (req, res) {
// Simulate login logic
final body = req.body as Map<String, dynamic>?;
final username = body?['username'];
final password = body?['password'];

if (username == 'admin' && password == 'secret') {
return res.json({'token': 'fake-jwt-token', 'user': username});
}

return res.status(401).json({'error': 'Invalid credentials'});
});

await app.listen(port: 3000);
print('Server running on http://localhost:3000');
print('Try making multiple requests to see rate limiting in action!');
}
10 changes: 10 additions & 0 deletions packages/pharaoh_rate_limit/lib/pharaoh_rate_limit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// Rate limiting middleware for Pharaoh web framework.
///
/// Provides token bucket and sliding window rate limiting algorithms
/// to protect APIs from abuse and ensure fair usage.
library;

export 'src/rate_limiter.dart';
export 'src/token_bucket.dart';
export 'src/sliding_window.dart';
export 'src/rate_limit_middleware.dart';
Loading