replace auth with rate limiter

This commit is contained in:
fiatcode 2025-10-19 10:52:36 +07:00
parent 1434756b10
commit 3721ea7a6a
7 changed files with 49 additions and 109 deletions

View file

@ -1,40 +0,0 @@
import { constants, createPrivateKey, privateDecrypt } from 'crypto';
import * as fs from 'fs';
import { validate as validateUUID } from 'uuid';
/**
* Simple auth implementation using RSA.
* Client sends a base64 UUID encrypted with public key, server decrypts with private key and checks validity.
*/
export class Auth {
constructor() {}
decryptToken(token: string): string {
const keyPem = fs.readFileSync('auth_key.pem', 'utf-8');
const privateKey = createPrivateKey({ key: keyPem, format: 'pem' });
const encryptedBuffer = Buffer.from(token, 'base64');
const decrypted = privateDecrypt(
{
key: privateKey,
padding: constants.RSA_PKCS1_PADDING,
},
encryptedBuffer
);
return decrypted.toString('utf-8');
}
isTokenValid(decryptedToken: string): boolean {
return validateUUID(decryptedToken);
}
isTokenExpired(issuedAt: number): boolean {
const tokenLifetimeSeconds = 300;
const allowedDriftSeconds = 10;
const now = Math.floor(Date.now() / 1000); // current unix timestamp in seconds
const diff = now - issuedAt;
return (
diff < -allowedDriftSeconds ||
diff > tokenLifetimeSeconds + allowedDriftSeconds
);
}
}

View file

@ -1,40 +0,0 @@
import type { NextFunction, Request, Response } from 'express';
import { Auth } from './auth.js';
import { AuthPayload } from './types.js';
const auth = new Auth();
export function bearerAuthMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res
.status(401)
.json({ error: 'Missing or invalid Authorization header' });
}
try {
const token = authHeader.substring('Bearer '.length);
const decrypted = auth.decryptToken(token);
if (!decrypted) {
return res.status(401).json({ error: 'Invalid token' });
}
const payload: AuthPayload = JSON.parse(decrypted);
if (!payload.token || typeof payload.issuedAt !== 'number') {
return res.status(401).json({ error: 'Token payload missing fields' });
}
if (!auth.isTokenValid(payload.token)) {
return res.status(401).json({ error: 'Token is not a valid UUID' });
}
if (auth.isTokenExpired(payload.issuedAt)) {
return res.status(401).json({ error: 'Token expired' });
}
next();
} catch (e) {
return res.status(500).json({ error: (e as Error).message });
}
}

View file

@ -1,6 +1,6 @@
import 'dotenv/config';
import express from 'express';
import { bearerAuthMiddleware } from './authMiddleware.js';
import { rateLimit } from 'express-rate-limit';
import {
onGetImages,
onGetQuoteById,
@ -10,7 +10,25 @@ import {
const app = express();
app.use(bearerAuthMiddleware);
const hourlyLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 1000,
message: 'Too many requests, please try again in an hour',
standardHeaders: true,
legacyHeaders: false,
});
const burstLimiter = rateLimit({
windowMs: 10 * 1000,
max: 50,
message: 'Slow down! Too many requests',
skipSuccessfulRequests: true,
standardHeaders: true,
legacyHeaders: false,
});
app.use(hourlyLimiter);
app.use(burstLimiter);
app.get('/quotes/random', onGetRandomQuote);
app.get('/quotes/:id', onGetQuoteById);

9
src/types.d.ts vendored
View file

@ -1,12 +1,3 @@
export interface AuthPayload {
// An UUID string, unique per request.
token: string;
// Unix time in seconds. It is the time when the token was issued.
// Will expire after a time window.
issuedAt: number;
}
export interface Quote {
id: number;
text: string;