fix(api): stop SSR org lookups from hitting the global rate limit
Resolve sessions before rate limiting so keys are per-user, exempt trusted server API key calls and get-session, and raise the GET /organization budget for layout loads. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+1
-1
@@ -49,7 +49,6 @@ export const app = new Hono()
|
|||||||
|
|
||||||
return sessionCors(c, next);
|
return sessionCors(c, next);
|
||||||
})
|
})
|
||||||
.use('*', rateLimiter)
|
|
||||||
.use('*', async (c, next) => {
|
.use('*', async (c, next) => {
|
||||||
if (isPublicCorsPath(c.req.path)) {
|
if (isPublicCorsPath(c.req.path)) {
|
||||||
c.set('user', null);
|
c.set('user', null);
|
||||||
@@ -88,6 +87,7 @@ export const app = new Hono()
|
|||||||
|
|
||||||
await next();
|
await next();
|
||||||
})
|
})
|
||||||
|
.use('*', rateLimiter)
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
.get('/', (c) =>
|
.get('/', (c) =>
|
||||||
|
|||||||
@@ -5,12 +5,23 @@ import {
|
|||||||
RATE_LIMIT_HEADERS
|
RATE_LIMIT_HEADERS
|
||||||
} from '@api/constants/rate-limiter';
|
} from '@api/constants/rate-limiter';
|
||||||
|
|
||||||
import type { MiddlewareHandler } from 'hono';
|
import type { Context, MiddlewareHandler } from 'hono';
|
||||||
import { RedisRateLimiter } from '@api/utils/redis/limiter';
|
import { RedisRateLimiter } from '@api/utils/redis/limiter';
|
||||||
import { userKeyGenerator } from '../utils/redis/key-generators';
|
import { userKeyGenerator } from '../utils/redis/key-generators';
|
||||||
import { env } from '@api/config/env';
|
import { env } from '@api/config/env';
|
||||||
import { logRedisUnavailableOnce, redis } from '@api/utils/redis/redis';
|
import { logRedisUnavailableOnce, redis } from '@api/utils/redis/redis';
|
||||||
|
|
||||||
|
/** Dashboard SSR and other internal callers authenticate with PRIVATE_SERVER_KEY. */
|
||||||
|
export function isTrustedServerApiKeyRequest(c: Context): boolean {
|
||||||
|
const expectedKey = env.PRIVATE_SERVER_KEY;
|
||||||
|
if (!expectedKey) return false;
|
||||||
|
|
||||||
|
const authHeader = c.req.header('Authorization');
|
||||||
|
if (!authHeader?.startsWith('Bearer ')) return false;
|
||||||
|
|
||||||
|
return authHeader.slice('Bearer '.length).trim() === expectedKey;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RateLimiterOptions {
|
export interface RateLimiterOptions {
|
||||||
windowMs?: number;
|
windowMs?: number;
|
||||||
maxRequests?: number | ((c: any) => number);
|
maxRequests?: number | ((c: any) => number);
|
||||||
@@ -86,7 +97,21 @@ export const createRateLimiter = (options: RateLimiterOptions = {}): MiddlewareH
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function defaultMaxRequests(c: Context): number {
|
||||||
|
// Org lookup runs on every SSR layout load; keep it out of the tight global bucket.
|
||||||
|
if (c.req.method === 'GET' && c.req.path === '/organization') return 500;
|
||||||
|
|
||||||
|
return DEFAULT_RATE_LIMITER_OPTIONS.maxRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldSkipRateLimit(c: Context): boolean {
|
||||||
|
if (isTrustedServerApiKeyRequest(c)) return true;
|
||||||
|
|
||||||
|
return c.req.path === '/api/auth/get-session';
|
||||||
|
}
|
||||||
|
|
||||||
// Default rate limiter middleware with standard configuration
|
// Default rate limiter middleware with standard configuration
|
||||||
export default createRateLimiter({
|
export default createRateLimiter({
|
||||||
maxRequests: (c) => (c.req.path === '/api/auth/get-session' ? 1000 : DEFAULT_RATE_LIMITER_OPTIONS.maxRequests)
|
maxRequests: defaultMaxRequests,
|
||||||
|
skip: shouldSkipRateLimit
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user