feat: end to end api type safety with redis rate limiting (#573)
* feat: support hono rpc * add wrangler file * update name of wrangler file * add wrangler as a dependency * add compatibility_flags * trigger ci * core: add health check to docker file * expose port 80 * update custom healthcheck * feat: implement openapi * install dependencies in root * debug yml * update yml * update port * update dockerfile with envs * completely move away from apiClient * rename backend to api * refactor: update server descriptions and comment out local spec saving * update lock file * use api.classroomio.com * add support for redis * fix: add missing line break in WelcomeModal.svelte * refactor: remove wrangler configuration and update Vite config for API integration
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { Context, Next } from 'hono';
|
||||
|
||||
import { validateUser } from '$src/utils/auth/validate-user';
|
||||
|
||||
export const authMiddleware = async (c: Context, next: Next) => {
|
||||
try {
|
||||
const authHeader = c.req.header('authorization');
|
||||
|
||||
if (!authHeader) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
message: 'No authorization header provided'
|
||||
},
|
||||
401
|
||||
);
|
||||
}
|
||||
|
||||
// Extract the token from "Bearer <token>"
|
||||
const token = authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid authorization header format'
|
||||
},
|
||||
401
|
||||
);
|
||||
}
|
||||
|
||||
const user = await validateUser(token);
|
||||
c.set('user', user); // Attach the user to the context
|
||||
await next();
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
message: 'Unauthorized'
|
||||
},
|
||||
401
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
DEFAULT_RATE_LIMITER_OPTIONS,
|
||||
ERROR_MESSAGES,
|
||||
HTTP_STATUS,
|
||||
RATE_LIMIT_HEADERS
|
||||
} from '$src/constants/rate-limiter';
|
||||
|
||||
import type { MiddlewareHandler } from 'hono';
|
||||
import { rateLimiter } from '$src/utils/redis/limiter';
|
||||
import { userKeyGenerator } from '../utils/redis/key-generators';
|
||||
|
||||
export interface RateLimiterOptions {
|
||||
windowMs?: number;
|
||||
maxRequests?: number;
|
||||
keyGenerator?: (c: any) => string;
|
||||
skipSuccessfulRequests?: boolean;
|
||||
skipFailedRequests?: boolean;
|
||||
message?: string;
|
||||
standardHeaders?: boolean;
|
||||
legacyHeaders?: boolean;
|
||||
}
|
||||
|
||||
const defaultOptions: Required<RateLimiterOptions> = {
|
||||
...DEFAULT_RATE_LIMITER_OPTIONS,
|
||||
keyGenerator: userKeyGenerator
|
||||
};
|
||||
|
||||
export const createRateLimiter = (options: RateLimiterOptions = {}): MiddlewareHandler => {
|
||||
const opts = { ...defaultOptions, ...options };
|
||||
|
||||
return async (c, next) => {
|
||||
try {
|
||||
// Generate rate limit key
|
||||
const key = opts.keyGenerator(c);
|
||||
|
||||
// Check rate limit
|
||||
const result = await rateLimiter.isAllowed(key);
|
||||
|
||||
// Set rate limit headers
|
||||
if (opts.standardHeaders) {
|
||||
c.header(RATE_LIMIT_HEADERS.LIMIT, opts.maxRequests.toString());
|
||||
c.header(RATE_LIMIT_HEADERS.REMAINING, result.remaining.toString());
|
||||
c.header(RATE_LIMIT_HEADERS.RESET, new Date(result.resetTime).toISOString());
|
||||
}
|
||||
|
||||
if (opts.legacyHeaders) {
|
||||
c.header(RATE_LIMIT_HEADERS.LIMIT, opts.maxRequests.toString());
|
||||
c.header(RATE_LIMIT_HEADERS.REMAINING, result.remaining.toString());
|
||||
c.header(RATE_LIMIT_HEADERS.RESET, Math.ceil(result.resetTime / 1000).toString());
|
||||
}
|
||||
|
||||
if (!result.allowed) {
|
||||
const retryAfter = Math.ceil((result.resetTime - Date.now()) / 1000);
|
||||
|
||||
if (opts.standardHeaders) {
|
||||
c.header(RATE_LIMIT_HEADERS.RETRY_AFTER, retryAfter.toString());
|
||||
}
|
||||
|
||||
return c.json(
|
||||
{
|
||||
error: ERROR_MESSAGES.RATE_LIMIT_EXCEEDED,
|
||||
message: opts.message,
|
||||
retryAfter
|
||||
},
|
||||
HTTP_STATUS.TOO_MANY_REQUESTS
|
||||
);
|
||||
}
|
||||
|
||||
await next();
|
||||
} catch (error) {
|
||||
console.error('Rate limiter error:', error);
|
||||
// If Redis is down, allow the request but log the error
|
||||
await next();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Default rate limiter middleware with standard configuration
|
||||
export const rateLimiterMiddleware = createRateLimiter();
|
||||
Reference in New Issue
Block a user