initial wcag-ada
This commit is contained in:
parent
042b8cb83a
commit
d52cfe7de2
112 changed files with 9069 additions and 0 deletions
75
apps/wcag-ada/api/src/middleware/rate-limiter.ts
Normal file
75
apps/wcag-ada/api/src/middleware/rate-limiter.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { Context, Next } from 'hono';
|
||||
import Redis from 'ioredis';
|
||||
import { config } from '../config';
|
||||
import { logger } from '../utils/logger';
|
||||
import { getWorkerConfig } from '@wcag-ada/config';
|
||||
|
||||
const workerConfig = getWorkerConfig();
|
||||
const redis = new Redis({
|
||||
host: workerConfig.redis.host,
|
||||
port: workerConfig.redis.port,
|
||||
password: workerConfig.redis.password,
|
||||
db: workerConfig.redis.db,
|
||||
});
|
||||
|
||||
interface RateLimitOptions {
|
||||
limit?: number;
|
||||
window?: number; // in milliseconds
|
||||
keyGenerator?: (c: Context) => string;
|
||||
}
|
||||
|
||||
export const rateLimiter = (options: RateLimitOptions = {}) => {
|
||||
const {
|
||||
limit = config.API_RATE_LIMIT,
|
||||
window = config.API_RATE_WINDOW,
|
||||
keyGenerator = (c) => {
|
||||
const userId = c.get('userId');
|
||||
const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown';
|
||||
return userId ? `rate:user:${userId}` : `rate:ip:${ip}`;
|
||||
},
|
||||
} = options;
|
||||
|
||||
return async (c: Context, next: Next) => {
|
||||
const key = keyGenerator(c);
|
||||
|
||||
try {
|
||||
// Get current count
|
||||
const current = await redis.get(key);
|
||||
const count = current ? parseInt(current) : 0;
|
||||
|
||||
// Check if limit exceeded
|
||||
if (count >= limit) {
|
||||
const ttl = await redis.pttl(key);
|
||||
|
||||
c.header('X-RateLimit-Limit', limit.toString());
|
||||
c.header('X-RateLimit-Remaining', '0');
|
||||
c.header('X-RateLimit-Reset', new Date(Date.now() + ttl).toISOString());
|
||||
|
||||
return c.json(
|
||||
{
|
||||
error: 'Rate limit exceeded',
|
||||
retryAfter: Math.ceil(ttl / 1000),
|
||||
},
|
||||
429
|
||||
);
|
||||
}
|
||||
|
||||
// Increment counter
|
||||
const pipeline = redis.pipeline();
|
||||
pipeline.incr(key);
|
||||
pipeline.pexpire(key, window);
|
||||
await pipeline.exec();
|
||||
|
||||
// Set headers
|
||||
c.header('X-RateLimit-Limit', limit.toString());
|
||||
c.header('X-RateLimit-Remaining', (limit - count - 1).toString());
|
||||
c.header('X-RateLimit-Reset', new Date(Date.now() + window).toISOString());
|
||||
|
||||
return next();
|
||||
} catch (error) {
|
||||
logger.error('Rate limiter error:', error);
|
||||
// Continue on error - don't block requests due to rate limiter failure
|
||||
return next();
|
||||
}
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue