express-rate-limiter-ts
Version:
A powerful, flexible and modern rate limiter middleware for Express.js written in TypeScript.
150 lines (149 loc) • 6.37 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostgresStore = void 0;
const pg_1 = require("pg");
// PostgresStore: Production için tam çalışan, asenkron, sayaç ve ban destekli bir store.
class PostgresStore {
constructor(options) {
this.windowMs = options.windowMs;
this.client = new pg_1.Client({ connectionString: options.connectionString });
this.connect();
}
// Bağlantıyı başlat
connect() {
return __awaiter(this, void 0, void 0, function* () {
yield this.client.connect();
// Gerekli tabloları oluştur (idempotent)
yield this.client.query(`
CREATE TABLE IF NOT EXISTS rate_limiter_counters (
key TEXT PRIMARY KEY,
count INTEGER NOT NULL,
expires_at BIGINT NOT NULL
);
CREATE TABLE IF NOT EXISTS rate_limiter_bans (
key TEXT PRIMARY KEY,
reason TEXT,
expires_at BIGINT NOT NULL
);
CREATE TABLE IF NOT EXISTS rate_limiter_stats (
route TEXT PRIMARY KEY,
count INTEGER NOT NULL,
last_triggered BIGINT NOT NULL
);
`);
});
}
// Sayaç artırma ve limiti kontrol etme
incr(key) {
return __awaiter(this, void 0, void 0, function* () {
yield this.connect();
const now = Date.now();
const expiresAt = now + this.windowMs;
// UPSERT ile sayaç artır
const res = yield this.client.query(`INSERT INTO rate_limiter_counters (key, count, expires_at)
VALUES ($1, 1, $2)
ON CONFLICT (key) DO UPDATE SET count = rate_limiter_counters.count + 1, expires_at = $2
RETURNING count, expires_at`, [key, expiresAt]);
return {
current: res.rows[0].count,
remaining: 0, // Kalan limit middleware'de hesaplanacak
resetTime: new Date(Number(res.rows[0].expires_at)),
exceeded: false,
};
});
}
// Sayaç okuma
get(key) {
return __awaiter(this, void 0, void 0, function* () {
yield this.connect();
const res = yield this.client.query('SELECT count, expires_at FROM rate_limiter_counters WHERE key = $1', [key]);
if (res.rows.length === 0)
return { count: 0, resetTime: Date.now() + this.windowMs };
return { count: res.rows[0].count, resetTime: Number(res.rows[0].expires_at) };
});
}
// Sayaç sıfırlama
reset(key) {
return __awaiter(this, void 0, void 0, function* () {
yield this.connect();
yield this.client.query('DELETE FROM rate_limiter_counters WHERE key = $1', [key]);
});
}
// İstatistik kaydı
recordStat(route) {
return __awaiter(this, void 0, void 0, function* () {
yield this.connect();
const now = Date.now();
yield this.client.query(`INSERT INTO rate_limiter_stats (route, count, last_triggered)
VALUES ($1, 1, $2)
ON CONFLICT (route) DO UPDATE SET count = rate_limiter_stats.count + 1, last_triggered = $2`, [route, now]);
});
}
// İstatistikleri döndür
getStats() {
return __awaiter(this, void 0, void 0, function* () {
yield this.connect();
const res = yield this.client.query('SELECT route, count, last_triggered FROM rate_limiter_stats');
const stats = {};
for (const row of res.rows) {
stats[row.route] = {
count: row.count,
lastTriggered: new Date(Number(row.last_triggered)),
};
}
return stats;
});
}
// Ban uygula
ban(key, seconds, reason) {
return __awaiter(this, void 0, void 0, function* () {
yield this.connect();
const expiresAt = Date.now() + seconds * 1000;
yield this.client.query(`INSERT INTO rate_limiter_bans (key, reason, expires_at)
VALUES ($1, $2, $3)
ON CONFLICT (key) DO UPDATE SET reason = $2, expires_at = $3`, [key, reason, expiresAt]);
});
}
// Ban kontrolü
isBanned(key) {
return __awaiter(this, void 0, void 0, function* () {
yield this.connect();
const res = yield this.client.query('SELECT reason, expires_at FROM rate_limiter_bans WHERE key = $1', [key]);
if (res.rows.length && res.rows[0].expires_at > Date.now()) {
return { banned: true, banExpiresAt: new Date(Number(res.rows[0].expires_at)), reason: res.rows[0].reason };
}
return { banned: false };
});
}
// Banlı anahtarları döndür
getBanned() {
return __awaiter(this, void 0, void 0, function* () {
yield this.connect();
const now = Date.now();
const res = yield this.client.query('SELECT key, reason, expires_at FROM rate_limiter_bans WHERE expires_at > $1', [now]);
const result = {};
for (const row of res.rows) {
result[row.key] = { banExpiresAt: new Date(Number(row.expires_at)), reason: row.reason };
}
return result;
});
}
// Ban kaldır
unban(key) {
return __awaiter(this, void 0, void 0, function* () {
yield this.connect();
yield this.client.query('DELETE FROM rate_limiter_bans WHERE key = $1', [key]);
yield this.client.query('DELETE FROM rate_limiter_counters WHERE key = $1', [key]);
});
}
}
exports.PostgresStore = PostgresStore;