@canmertinyo/rate-limit-express 1.3.0 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15 -0
- package/dist/interfaces/index.d.ts +4 -0
- package/dist/interfaces/index.js +20 -0
- package/dist/interfaces/rate-limit-record.interface.d.ts +5 -0
- package/dist/interfaces/rate-limiter-options.interface.d.ts +4 -4
- package/dist/interfaces/rate-limiter-options.interface.js +0 -7
- package/dist/interfaces/rate-limiter.interface.d.ts +7 -0
- package/dist/interfaces/rate-limiter.interface.js +2 -0
- package/dist/interfaces/redis-config.interface.d.ts +14 -0
- package/dist/interfaces/redis-config.interface.js +9 -0
- package/dist/rate.limiter.d.ts +1 -12
- package/dist/rate.limiter.js +16 -7
- package/dist/storages/in-memory.storage.d.ts +9 -0
- package/dist/storages/in-memory.storage.js +44 -0
- package/dist/storages/index.d.ts +3 -0
- package/dist/storages/index.js +19 -0
- package/dist/storages/mongo.storage.d.ts +10 -0
- package/dist/storages/mongo.storage.js +67 -0
- package/dist/storages/redis.storage.d.ts +9 -0
- package/dist/storages/redis.storage.js +60 -0
- package/dist/storages/schemas/rate-limit.schema.d.ts +17 -0
- package/dist/storages/schemas/rate-limit.schema.js +11 -0
- package/package.json +25 -23
- package/src/index.ts +2 -2
- package/src/interfaces/index.ts +4 -0
- package/src/interfaces/rate-limit-record.interface.ts +5 -0
- package/src/interfaces/rate-limiter-options.interface.ts +4 -10
- package/src/interfaces/rate-limiter.interface.ts +12 -0
- package/src/interfaces/redis-config.interface.ts +19 -0
- package/src/rate.limiter.ts +6 -23
- package/src/storages/in-memory.storage.ts +37 -0
- package/src/storages/index.ts +3 -0
- package/src/storages/mongo.storage.ts +76 -0
- package/src/storages/redis.storage.ts +63 -0
- package/src/storages/schemas/rate-limit.schema.ts +15 -0
- package/dist/in-memory-storage.d.ts +0 -8
- package/dist/in-memory-storage.js +0 -27
- package/dist/types/store.type.d.ts +0 -6
- package/src/in-memory-storage.ts +0 -32
- package/src/types/store.type.ts +0 -3
- /package/dist/{types/store.type.js → interfaces/rate-limit-record.interface.js} +0 -0
package/dist/index.d.ts
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
export
|
2
|
-
export { rateLimiter
|
1
|
+
export * from "./interfaces";
|
2
|
+
export { rateLimiter } from "./rate.limiter";
|
package/dist/index.js
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
15
|
+
};
|
2
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
17
|
exports.rateLimiter = void 0;
|
18
|
+
__exportStar(require("./interfaces"), exports);
|
4
19
|
var rate_limiter_1 = require("./rate.limiter");
|
5
20
|
Object.defineProperty(exports, "rateLimiter", { enumerable: true, get: function () { return rate_limiter_1.rateLimiter; } });
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
15
|
+
};
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
17
|
+
__exportStar(require("./rate-limit-record.interface"), exports);
|
18
|
+
__exportStar(require("./rate-limiter-options.interface"), exports);
|
19
|
+
__exportStar(require("./rate-limiter.interface"), exports);
|
20
|
+
__exportStar(require("./redis-config.interface"), exports);
|
@@ -1,13 +1,13 @@
|
|
1
|
-
import {
|
1
|
+
import { RateLimiter } from "./rate-limiter.interface";
|
2
2
|
/**
|
3
3
|
* Options for the rate limiter configuration.
|
4
4
|
*
|
5
5
|
* @property ms - The time window in milliseconds for rate limiting.
|
6
|
-
* @property maxRequest - The maximum number of allowed requests
|
6
|
+
* @property maxRequest - The maximum number of allowed requests.
|
7
|
+
* @property storage?(optional) - The storage property allows you to use a specific storage manager. [REDIS,MONGO, DEFAULT=INMEMORY]
|
7
8
|
*/
|
8
9
|
export interface RateLimiterOptions {
|
9
10
|
ms: number;
|
10
11
|
maxRequest: number;
|
11
|
-
storage?:
|
12
|
+
storage?: RateLimiter;
|
12
13
|
}
|
13
|
-
export declare const defaultOptions: RateLimiterOptions;
|
@@ -1,9 +1,2 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.defaultOptions = void 0;
|
4
|
-
const in_memory_storage_1 = require("../in-memory-storage");
|
5
|
-
exports.defaultOptions = {
|
6
|
-
ms: 1000,
|
7
|
-
maxRequest: 5,
|
8
|
-
storage: new in_memory_storage_1.InMemoryStorage(),
|
9
|
-
};
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import { RateLimitRecord } from "./rate-limit-record.interface";
|
2
|
+
export interface RateLimiter {
|
3
|
+
getRateLimitRecord(key: string): Promise<RateLimitRecord | undefined>;
|
4
|
+
createRateLimitRecord(record: RateLimitRecord): Promise<RateLimitRecord>;
|
5
|
+
updateRateLimitRecord(key: string, timestamp: number, count: number): Promise<RateLimitRecord>;
|
6
|
+
increment(key: string): Promise<RateLimitRecord>;
|
7
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
/**
|
2
|
+
* @property port - Redis server port, default : 6379
|
3
|
+
* @property host - Redis server host or IP address.
|
4
|
+
* @property username - Redis username for authentication (optional)
|
5
|
+
* @property password - Redis password for authentication (optional).
|
6
|
+
* @property db - Redis database number, default is 0 (optional).
|
7
|
+
*/
|
8
|
+
export interface RedisConfig {
|
9
|
+
port: number;
|
10
|
+
host: string;
|
11
|
+
username?: string;
|
12
|
+
password?: string;
|
13
|
+
db?: number;
|
14
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"use strict";
|
2
|
+
/**
|
3
|
+
* @property port - Redis server port, default : 6379
|
4
|
+
* @property host - Redis server host or IP address.
|
5
|
+
* @property username - Redis username for authentication (optional)
|
6
|
+
* @property password - Redis password for authentication (optional).
|
7
|
+
* @property db - Redis database number, default is 0 (optional).
|
8
|
+
*/
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/rate.limiter.d.ts
CHANGED
@@ -1,14 +1,3 @@
|
|
1
1
|
import { Request, Response, NextFunction } from "express";
|
2
2
|
import { RateLimiterOptions } from "./interfaces/rate-limiter-options.interface";
|
3
|
-
export declare function rateLimiter(options: RateLimiterOptions): (req: Request, res: Response, next: NextFunction) => void
|
4
|
-
export interface IRateLimitRecord {
|
5
|
-
key: string;
|
6
|
-
count: number;
|
7
|
-
timestamp: number;
|
8
|
-
}
|
9
|
-
export interface IRateLimiter {
|
10
|
-
getRateLimitRecord(key: string): IRateLimitRecord | undefined;
|
11
|
-
createRateLimitRecord(record: IRateLimitRecord): IRateLimitRecord;
|
12
|
-
updateRateLimitRecord(key: string, timestamp: number, count: number): IRateLimitRecord;
|
13
|
-
increment(key: string): IRateLimitRecord;
|
14
|
-
}
|
3
|
+
export declare function rateLimiter(options: RateLimiterOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
package/dist/rate.limiter.js
CHANGED
@@ -1,34 +1,43 @@
|
|
1
1
|
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
12
|
exports.rateLimiter = rateLimiter;
|
4
13
|
const utils_1 = require("./utils");
|
5
|
-
const in_memory_storage_1 = require("./in-memory
|
14
|
+
const in_memory_storage_1 = require("./storages/in-memory.storage");
|
6
15
|
const inMemoryStorage = new in_memory_storage_1.InMemoryStorage();
|
7
16
|
function rateLimiter(options) {
|
8
|
-
return (req, res, next) => {
|
17
|
+
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
9
18
|
var _a;
|
10
19
|
const ip = (0, utils_1.getIp)(req);
|
11
20
|
const currentTime = (0, utils_1.getTimestamp)();
|
12
21
|
const startTime = currentTime - options.ms;
|
13
22
|
var storage = (_a = options.storage) !== null && _a !== void 0 ? _a : inMemoryStorage;
|
14
|
-
var record = storage.getRateLimitRecord(ip);
|
23
|
+
var record = yield storage.getRateLimitRecord(ip);
|
15
24
|
if (!record) {
|
16
|
-
record = storage.createRateLimitRecord({
|
25
|
+
record = yield storage.createRateLimitRecord({
|
17
26
|
key: ip,
|
18
27
|
count: 0,
|
19
28
|
timestamp: currentTime,
|
20
29
|
});
|
21
30
|
}
|
22
31
|
if (startTime > record.timestamp) {
|
23
|
-
record = storage.updateRateLimitRecord(ip, currentTime, 1);
|
32
|
+
record = yield storage.updateRateLimitRecord(ip, currentTime, 1);
|
24
33
|
return next();
|
25
34
|
}
|
26
35
|
if (record.count < options.maxRequest) {
|
27
|
-
record = storage.increment(record.key);
|
36
|
+
record = yield storage.increment(record.key);
|
28
37
|
return next();
|
29
38
|
}
|
30
39
|
res
|
31
40
|
.status(420)
|
32
41
|
.json({ message: "Too many requests, please try again later." });
|
33
|
-
};
|
42
|
+
});
|
34
43
|
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { RateLimitRecord } from "../interfaces/rate-limit-record.interface";
|
2
|
+
import { RateLimiter } from "../interfaces/rate-limiter.interface";
|
3
|
+
export declare class InMemoryStorage implements RateLimiter {
|
4
|
+
private store;
|
5
|
+
getRateLimitRecord(key: string): Promise<RateLimitRecord | undefined>;
|
6
|
+
createRateLimitRecord(record: RateLimitRecord): Promise<RateLimitRecord>;
|
7
|
+
updateRateLimitRecord(key: string, timestamp: number, count: number): Promise<RateLimitRecord>;
|
8
|
+
increment(key: string): Promise<RateLimitRecord>;
|
9
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
+
exports.InMemoryStorage = void 0;
|
13
|
+
class InMemoryStorage {
|
14
|
+
constructor() {
|
15
|
+
this.store = [];
|
16
|
+
}
|
17
|
+
getRateLimitRecord(key) {
|
18
|
+
return __awaiter(this, void 0, void 0, function* () {
|
19
|
+
return this.store.find((x) => x.key === key);
|
20
|
+
});
|
21
|
+
}
|
22
|
+
createRateLimitRecord(record) {
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
24
|
+
let index = this.store.push(record);
|
25
|
+
return this.store[index - 1];
|
26
|
+
});
|
27
|
+
}
|
28
|
+
updateRateLimitRecord(key, timestamp, count) {
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
30
|
+
let index = this.store.findIndex((x) => x.key === key);
|
31
|
+
this.store[index].count = count;
|
32
|
+
this.store[index].timestamp = timestamp;
|
33
|
+
return this.store[index];
|
34
|
+
});
|
35
|
+
}
|
36
|
+
increment(key) {
|
37
|
+
return __awaiter(this, void 0, void 0, function* () {
|
38
|
+
let index = this.store.findIndex((x) => x.key === key);
|
39
|
+
this.store[index].count += 1;
|
40
|
+
return this.store[index];
|
41
|
+
});
|
42
|
+
}
|
43
|
+
}
|
44
|
+
exports.InMemoryStorage = InMemoryStorage;
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
15
|
+
};
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
17
|
+
__exportStar(require("./in-memory.storage"), exports);
|
18
|
+
__exportStar(require("./mongo.storage"), exports);
|
19
|
+
__exportStar(require("./redis.storage"), exports);
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { RateLimiter, RateLimitRecord } from "../interfaces";
|
2
|
+
export declare class MongoStorage implements RateLimiter {
|
3
|
+
mongoDbUrl: string;
|
4
|
+
private model;
|
5
|
+
constructor(mongoDbUrl?: string);
|
6
|
+
getRateLimitRecord(key: string): Promise<RateLimitRecord | undefined>;
|
7
|
+
createRateLimitRecord(record: RateLimitRecord): Promise<RateLimitRecord>;
|
8
|
+
updateRateLimitRecord(key: string, timestamp: number, count: number): Promise<RateLimitRecord>;
|
9
|
+
increment(key: string): Promise<RateLimitRecord>;
|
10
|
+
}
|
@@ -0,0 +1,67 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
+
exports.MongoStorage = void 0;
|
13
|
+
const mongoose_1 = require("mongoose");
|
14
|
+
const rate_limit_schema_1 = require("./schemas/rate-limit.schema");
|
15
|
+
class MongoStorage {
|
16
|
+
//DEFAULT DB URI VERRIRSEK PATLICAZ GİBİ AMA BAKARIZ, AZ KÖTÜ🤨
|
17
|
+
//connect-mongo kütüphanesini kullanarak yapsak?
|
18
|
+
constructor(mongoDbUrl = "mongodb://127.0.0.1:27017") {
|
19
|
+
this.mongoDbUrl = mongoDbUrl;
|
20
|
+
mongoose_1.default
|
21
|
+
.connect(mongoDbUrl)
|
22
|
+
.then(() => {
|
23
|
+
console.log("Db connected.");
|
24
|
+
})
|
25
|
+
.catch((err) => {
|
26
|
+
console.log("Db failed to connect.", err);
|
27
|
+
});
|
28
|
+
this.model = rate_limit_schema_1.RateLimitModel;
|
29
|
+
}
|
30
|
+
getRateLimitRecord(key) {
|
31
|
+
return __awaiter(this, void 0, void 0, function* () {
|
32
|
+
const record = yield this.model.findOne({ key });
|
33
|
+
return record ? record.toObject() : undefined;
|
34
|
+
});
|
35
|
+
}
|
36
|
+
createRateLimitRecord(record) {
|
37
|
+
return __awaiter(this, void 0, void 0, function* () {
|
38
|
+
const newRecord = yield this.model.create(record);
|
39
|
+
newRecord.save();
|
40
|
+
return newRecord.toObject();
|
41
|
+
});
|
42
|
+
}
|
43
|
+
updateRateLimitRecord(key, timestamp, count) {
|
44
|
+
return __awaiter(this, void 0, void 0, function* () {
|
45
|
+
const record = yield this.model.findOneAndUpdate({ key }, {
|
46
|
+
timestamp,
|
47
|
+
count,
|
48
|
+
}, {
|
49
|
+
new: true,
|
50
|
+
});
|
51
|
+
if (!record) {
|
52
|
+
throw new Error("No record found.");
|
53
|
+
}
|
54
|
+
return record.toObject();
|
55
|
+
});
|
56
|
+
}
|
57
|
+
increment(key) {
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
59
|
+
const record = yield this.model.findOneAndUpdate({ key }, { $inc: { count: 1 } }, { new: true });
|
60
|
+
if (!record) {
|
61
|
+
throw new Error("No record found to increment.");
|
62
|
+
}
|
63
|
+
return record.toObject();
|
64
|
+
});
|
65
|
+
}
|
66
|
+
}
|
67
|
+
exports.MongoStorage = MongoStorage;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { RateLimiter, RateLimitRecord, RedisConfig } from "../interfaces";
|
2
|
+
export declare class RedisStorage implements RateLimiter {
|
3
|
+
private redis;
|
4
|
+
constructor(redisConfig: RedisConfig);
|
5
|
+
getRateLimitRecord(key: string): Promise<RateLimitRecord | undefined>;
|
6
|
+
createRateLimitRecord(record: RateLimitRecord): Promise<RateLimitRecord>;
|
7
|
+
updateRateLimitRecord(key: string, timestamp: number, count: number): Promise<RateLimitRecord>;
|
8
|
+
increment(key: string): Promise<RateLimitRecord>;
|
9
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
+
exports.RedisStorage = void 0;
|
13
|
+
const ioredis_1 = require("ioredis");
|
14
|
+
class RedisStorage {
|
15
|
+
constructor(redisConfig) {
|
16
|
+
const { host, port, db, password, username } = redisConfig;
|
17
|
+
const resolvedPort = port ? port : 6379;
|
18
|
+
this.redis = new ioredis_1.default({
|
19
|
+
host,
|
20
|
+
port: resolvedPort,
|
21
|
+
db,
|
22
|
+
password,
|
23
|
+
username,
|
24
|
+
});
|
25
|
+
}
|
26
|
+
getRateLimitRecord(key) {
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
28
|
+
const data = yield this.redis.get(key);
|
29
|
+
return data ? JSON.parse(data) : undefined;
|
30
|
+
});
|
31
|
+
}
|
32
|
+
createRateLimitRecord(record) {
|
33
|
+
return __awaiter(this, void 0, void 0, function* () {
|
34
|
+
yield this.redis.set(record.key, JSON.stringify(record));
|
35
|
+
return record;
|
36
|
+
});
|
37
|
+
}
|
38
|
+
updateRateLimitRecord(key, timestamp, count) {
|
39
|
+
return __awaiter(this, void 0, void 0, function* () {
|
40
|
+
const record = yield this.getRateLimitRecord(key);
|
41
|
+
if (!record)
|
42
|
+
throw new Error("Record not found");
|
43
|
+
const updatedRecord = Object.assign(Object.assign({}, record), { count,
|
44
|
+
timestamp });
|
45
|
+
yield this.redis.set(key, JSON.stringify(updatedRecord));
|
46
|
+
return updatedRecord;
|
47
|
+
});
|
48
|
+
}
|
49
|
+
increment(key) {
|
50
|
+
return __awaiter(this, void 0, void 0, function* () {
|
51
|
+
const record = yield this.getRateLimitRecord(key);
|
52
|
+
if (!record)
|
53
|
+
throw new Error("Record not found");
|
54
|
+
const updatedRecord = Object.assign(Object.assign({}, record), { count: record.count + 1 });
|
55
|
+
yield this.redis.set(key, JSON.stringify(updatedRecord));
|
56
|
+
return updatedRecord;
|
57
|
+
});
|
58
|
+
}
|
59
|
+
}
|
60
|
+
exports.RedisStorage = RedisStorage;
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import mongoose from "mongoose";
|
2
|
+
import { RateLimitRecord } from "../../interfaces";
|
3
|
+
export declare const RATE_LIMIT_SCHEMA_CONST = "RateLimit";
|
4
|
+
export declare const RateLimitSchema: mongoose.Schema<RateLimitRecord, mongoose.Model<RateLimitRecord, any, any, any, mongoose.Document<unknown, any, RateLimitRecord> & RateLimitRecord & {
|
5
|
+
_id: mongoose.Types.ObjectId;
|
6
|
+
} & {
|
7
|
+
__v: number;
|
8
|
+
}, any>, {}, {}, {}, {}, mongoose.DefaultSchemaOptions, RateLimitRecord, mongoose.Document<unknown, {}, mongoose.FlatRecord<RateLimitRecord>> & mongoose.FlatRecord<RateLimitRecord> & {
|
9
|
+
_id: mongoose.Types.ObjectId;
|
10
|
+
} & {
|
11
|
+
__v: number;
|
12
|
+
}>;
|
13
|
+
export declare const RateLimitModel: mongoose.Model<RateLimitRecord, {}, {}, {}, mongoose.Document<unknown, {}, RateLimitRecord> & RateLimitRecord & {
|
14
|
+
_id: mongoose.Types.ObjectId;
|
15
|
+
} & {
|
16
|
+
__v: number;
|
17
|
+
}, any>;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.RateLimitModel = exports.RateLimitSchema = exports.RATE_LIMIT_SCHEMA_CONST = void 0;
|
4
|
+
const mongoose_1 = require("mongoose");
|
5
|
+
exports.RATE_LIMIT_SCHEMA_CONST = "RateLimit";
|
6
|
+
exports.RateLimitSchema = new mongoose_1.Schema({
|
7
|
+
key: { type: String, required: true, unique: true },
|
8
|
+
count: { type: Number, required: true },
|
9
|
+
timestamp: { type: Number, required: true },
|
10
|
+
});
|
11
|
+
exports.RateLimitModel = mongoose_1.default.model(exports.RATE_LIMIT_SCHEMA_CONST, exports.RateLimitSchema);
|
package/package.json
CHANGED
@@ -1,23 +1,25 @@
|
|
1
|
-
{
|
2
|
-
"name": "@canmertinyo/rate-limit-express",
|
3
|
-
"description": "A simple rate-limiting middleware for Express.js",
|
4
|
-
"version": "1.3.
|
5
|
-
"main": "dist/index.js",
|
6
|
-
"license": "MIT",
|
7
|
-
"types": "dist/index.d.ts",
|
8
|
-
"repository": {
|
9
|
-
"type": "git",
|
10
|
-
"url": "https://github.com/c4nzin/rate-limiter-express"
|
11
|
-
},
|
12
|
-
"scripts": {
|
13
|
-
"build": "tsc"
|
14
|
-
},
|
15
|
-
"keywords": [
|
16
|
-
"ratelimiter-express"
|
17
|
-
],
|
18
|
-
"author": "c4nzin",
|
19
|
-
"devDependencies": {
|
20
|
-
"@types/express": "^5.0.0",
|
21
|
-
"typescript": "^5.7.2"
|
22
|
-
|
23
|
-
|
1
|
+
{
|
2
|
+
"name": "@canmertinyo/rate-limit-express",
|
3
|
+
"description": "A simple rate-limiting middleware for Express.js",
|
4
|
+
"version": "1.3.3",
|
5
|
+
"main": "dist/index.js",
|
6
|
+
"license": "MIT",
|
7
|
+
"types": "dist/index.d.ts",
|
8
|
+
"repository": {
|
9
|
+
"type": "git",
|
10
|
+
"url": "https://github.com/c4nzin/rate-limiter-express"
|
11
|
+
},
|
12
|
+
"scripts": {
|
13
|
+
"build": "tsc"
|
14
|
+
},
|
15
|
+
"keywords": [
|
16
|
+
"ratelimiter-express"
|
17
|
+
],
|
18
|
+
"author": "c4nzin",
|
19
|
+
"devDependencies": {
|
20
|
+
"@types/express": "^5.0.0",
|
21
|
+
"typescript": "^5.7.2",
|
22
|
+
"ioredis": "^5.4.1",
|
23
|
+
"mongoose": "^8.8.3"
|
24
|
+
}
|
25
|
+
}
|
package/src/index.ts
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
export
|
2
|
-
export { rateLimiter
|
1
|
+
export * from "./interfaces";
|
2
|
+
export { rateLimiter } from "./rate.limiter";
|
@@ -1,20 +1,14 @@
|
|
1
|
-
import {
|
2
|
-
import { IRateLimiter } from "../rate.limiter";
|
1
|
+
import { RateLimiter } from "./rate-limiter.interface";
|
3
2
|
|
4
3
|
/**
|
5
4
|
* Options for the rate limiter configuration.
|
6
5
|
*
|
7
6
|
* @property ms - The time window in milliseconds for rate limiting.
|
8
|
-
* @property maxRequest - The maximum number of allowed requests
|
7
|
+
* @property maxRequest - The maximum number of allowed requests.
|
8
|
+
* @property storage?(optional) - The storage property allows you to use a specific storage manager. [REDIS,MONGO, DEFAULT=INMEMORY]
|
9
9
|
*/
|
10
10
|
export interface RateLimiterOptions {
|
11
11
|
ms: number;
|
12
12
|
maxRequest: number;
|
13
|
-
storage?:
|
13
|
+
storage?: RateLimiter;
|
14
14
|
}
|
15
|
-
|
16
|
-
export const defaultOptions: RateLimiterOptions = {
|
17
|
-
ms: 1000,
|
18
|
-
maxRequest: 5,
|
19
|
-
storage: new InMemoryStorage(),
|
20
|
-
};
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { RateLimitRecord } from "./rate-limit-record.interface";
|
2
|
+
|
3
|
+
export interface RateLimiter {
|
4
|
+
getRateLimitRecord(key: string): Promise<RateLimitRecord | undefined>;
|
5
|
+
createRateLimitRecord(record: RateLimitRecord): Promise<RateLimitRecord>;
|
6
|
+
updateRateLimitRecord(
|
7
|
+
key: string,
|
8
|
+
timestamp: number,
|
9
|
+
count: number
|
10
|
+
): Promise<RateLimitRecord>;
|
11
|
+
increment(key: string): Promise<RateLimitRecord>;
|
12
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
/**
|
2
|
+
* @property port - Redis server port, default : 6379
|
3
|
+
* @property host - Redis server host or IP address.
|
4
|
+
* @property username - Redis username for authentication (optional)
|
5
|
+
* @property password - Redis password for authentication (optional).
|
6
|
+
* @property db - Redis database number, default is 0 (optional).
|
7
|
+
*/
|
8
|
+
|
9
|
+
export interface RedisConfig {
|
10
|
+
port: number;
|
11
|
+
|
12
|
+
host: string;
|
13
|
+
|
14
|
+
username?: string;
|
15
|
+
|
16
|
+
password?: string;
|
17
|
+
|
18
|
+
db?: number;
|
19
|
+
}
|
package/src/rate.limiter.ts
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
import { Request, Response, NextFunction } from "express";
|
2
2
|
import { getIp, getTimestamp } from "./utils";
|
3
3
|
import { RateLimiterOptions } from "./interfaces/rate-limiter-options.interface";
|
4
|
-
import { InMemoryStorage } from "./in-memory
|
4
|
+
import { InMemoryStorage } from "./storages/in-memory.storage";
|
5
5
|
|
6
6
|
const inMemoryStorage = new InMemoryStorage();
|
7
7
|
|
8
8
|
export function rateLimiter(options: RateLimiterOptions) {
|
9
|
-
return (req: Request, res: Response, next: NextFunction) => {
|
9
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
10
10
|
const ip = getIp(req);
|
11
11
|
const currentTime = getTimestamp();
|
12
12
|
const startTime = currentTime - options.ms;
|
13
13
|
var storage = options.storage ?? inMemoryStorage;
|
14
14
|
|
15
|
-
var record = storage.getRateLimitRecord(ip);
|
15
|
+
var record = await storage.getRateLimitRecord(ip);
|
16
16
|
|
17
17
|
if (!record) {
|
18
|
-
record = storage.createRateLimitRecord({
|
18
|
+
record = await storage.createRateLimitRecord({
|
19
19
|
key: ip,
|
20
20
|
count: 0,
|
21
21
|
timestamp: currentTime,
|
@@ -23,12 +23,12 @@ export function rateLimiter(options: RateLimiterOptions) {
|
|
23
23
|
}
|
24
24
|
|
25
25
|
if (startTime > record.timestamp) {
|
26
|
-
record = storage.updateRateLimitRecord(ip, currentTime, 1);
|
26
|
+
record = await storage.updateRateLimitRecord(ip, currentTime, 1);
|
27
27
|
return next();
|
28
28
|
}
|
29
29
|
|
30
30
|
if (record.count < options.maxRequest) {
|
31
|
-
record = storage.increment(record.key);
|
31
|
+
record = await storage.increment(record.key);
|
32
32
|
return next();
|
33
33
|
}
|
34
34
|
|
@@ -37,20 +37,3 @@ export function rateLimiter(options: RateLimiterOptions) {
|
|
37
37
|
.json({ message: "Too many requests, please try again later." });
|
38
38
|
};
|
39
39
|
}
|
40
|
-
|
41
|
-
export interface IRateLimitRecord {
|
42
|
-
key: string;
|
43
|
-
count: number;
|
44
|
-
timestamp: number;
|
45
|
-
}
|
46
|
-
|
47
|
-
export interface IRateLimiter {
|
48
|
-
getRateLimitRecord(key: string): IRateLimitRecord | undefined;
|
49
|
-
createRateLimitRecord(record: IRateLimitRecord): IRateLimitRecord;
|
50
|
-
updateRateLimitRecord(
|
51
|
-
key: string,
|
52
|
-
timestamp: number,
|
53
|
-
count: number
|
54
|
-
): IRateLimitRecord;
|
55
|
-
increment(key: string): IRateLimitRecord;
|
56
|
-
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { RateLimitRecord } from "../interfaces/rate-limit-record.interface";
|
2
|
+
import { RateLimiter } from "../interfaces/rate-limiter.interface";
|
3
|
+
|
4
|
+
export class InMemoryStorage implements RateLimiter {
|
5
|
+
private store: RateLimitRecord[] = [];
|
6
|
+
|
7
|
+
public async getRateLimitRecord(
|
8
|
+
key: string
|
9
|
+
): Promise<RateLimitRecord | undefined> {
|
10
|
+
return this.store.find((x) => x.key === key);
|
11
|
+
}
|
12
|
+
|
13
|
+
public async createRateLimitRecord(
|
14
|
+
record: RateLimitRecord
|
15
|
+
): Promise<RateLimitRecord> {
|
16
|
+
let index = this.store.push(record);
|
17
|
+
return this.store[index - 1];
|
18
|
+
}
|
19
|
+
|
20
|
+
public async updateRateLimitRecord(
|
21
|
+
key: string,
|
22
|
+
timestamp: number,
|
23
|
+
count: number
|
24
|
+
): Promise<RateLimitRecord> {
|
25
|
+
let index = this.store.findIndex((x) => x.key === key);
|
26
|
+
this.store[index].count = count;
|
27
|
+
this.store[index].timestamp = timestamp;
|
28
|
+
|
29
|
+
return this.store[index];
|
30
|
+
}
|
31
|
+
|
32
|
+
public async increment(key: string): Promise<RateLimitRecord> {
|
33
|
+
let index = this.store.findIndex((x) => x.key === key);
|
34
|
+
this.store[index].count += 1;
|
35
|
+
return this.store[index];
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import { RateLimiter, RateLimitRecord } from "../interfaces";
|
2
|
+
import mongoose from "mongoose";
|
3
|
+
import { RateLimitModel } from "./schemas/rate-limit.schema";
|
4
|
+
|
5
|
+
export class MongoStorage implements RateLimiter {
|
6
|
+
private model: mongoose.Model<RateLimitRecord>;
|
7
|
+
|
8
|
+
//DEFAULT DB URI VERRIRSEK PATLICAZ GİBİ AMA BAKARIZ, AZ KÖTÜ🤨
|
9
|
+
//connect-mongo kütüphanesini kullanarak yapsak?
|
10
|
+
constructor(public mongoDbUrl: string = "mongodb://127.0.0.1:27017") {
|
11
|
+
mongoose
|
12
|
+
.connect(mongoDbUrl)
|
13
|
+
.then(() => {
|
14
|
+
console.log("Db connected.");
|
15
|
+
})
|
16
|
+
.catch((err) => {
|
17
|
+
console.log("Db failed to connect.", err);
|
18
|
+
});
|
19
|
+
|
20
|
+
this.model = RateLimitModel;
|
21
|
+
}
|
22
|
+
|
23
|
+
public async getRateLimitRecord(
|
24
|
+
key: string
|
25
|
+
): Promise<RateLimitRecord | undefined> {
|
26
|
+
const record = await this.model.findOne({ key });
|
27
|
+
|
28
|
+
return record ? record.toObject() : undefined;
|
29
|
+
}
|
30
|
+
|
31
|
+
public async createRateLimitRecord(
|
32
|
+
record: RateLimitRecord
|
33
|
+
): Promise<RateLimitRecord> {
|
34
|
+
const newRecord = await this.model.create(record);
|
35
|
+
newRecord.save();
|
36
|
+
|
37
|
+
return newRecord.toObject() as RateLimitRecord;
|
38
|
+
}
|
39
|
+
|
40
|
+
public async updateRateLimitRecord(
|
41
|
+
key: string,
|
42
|
+
timestamp: number,
|
43
|
+
count: number
|
44
|
+
): Promise<RateLimitRecord> {
|
45
|
+
const record = await this.model.findOneAndUpdate(
|
46
|
+
{ key },
|
47
|
+
{
|
48
|
+
timestamp,
|
49
|
+
count,
|
50
|
+
},
|
51
|
+
{
|
52
|
+
new: true,
|
53
|
+
}
|
54
|
+
)!;
|
55
|
+
|
56
|
+
if (!record) {
|
57
|
+
throw new Error("No record found.");
|
58
|
+
}
|
59
|
+
|
60
|
+
return record.toObject() as RateLimitRecord;
|
61
|
+
}
|
62
|
+
|
63
|
+
public async increment(key: string): Promise<RateLimitRecord> {
|
64
|
+
const record = await this.model.findOneAndUpdate(
|
65
|
+
{ key },
|
66
|
+
{ $inc: { count: 1 } },
|
67
|
+
{ new: true }
|
68
|
+
);
|
69
|
+
|
70
|
+
if (!record) {
|
71
|
+
throw new Error("No record found to increment.");
|
72
|
+
}
|
73
|
+
|
74
|
+
return record.toObject();
|
75
|
+
}
|
76
|
+
}
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import Redis from "ioredis";
|
2
|
+
import { RateLimiter, RateLimitRecord, RedisConfig } from "../interfaces";
|
3
|
+
|
4
|
+
export class RedisStorage implements RateLimiter {
|
5
|
+
private redis: Redis;
|
6
|
+
|
7
|
+
constructor(redisConfig: RedisConfig) {
|
8
|
+
const { host, port, db, password, username } = redisConfig;
|
9
|
+
|
10
|
+
const resolvedPort = port ? port : 6379;
|
11
|
+
|
12
|
+
this.redis = new Redis({
|
13
|
+
host,
|
14
|
+
port: resolvedPort,
|
15
|
+
db,
|
16
|
+
password,
|
17
|
+
username,
|
18
|
+
});
|
19
|
+
}
|
20
|
+
|
21
|
+
public async getRateLimitRecord(
|
22
|
+
key: string
|
23
|
+
): Promise<RateLimitRecord | undefined> {
|
24
|
+
const data = await this.redis.get(key);
|
25
|
+
return data ? (JSON.parse(data) as RateLimitRecord) : undefined;
|
26
|
+
}
|
27
|
+
|
28
|
+
public async createRateLimitRecord(
|
29
|
+
record: RateLimitRecord
|
30
|
+
): Promise<RateLimitRecord> {
|
31
|
+
await this.redis.set(record.key, JSON.stringify(record));
|
32
|
+
return record;
|
33
|
+
}
|
34
|
+
|
35
|
+
public async updateRateLimitRecord(
|
36
|
+
key: string,
|
37
|
+
timestamp: number,
|
38
|
+
count: number
|
39
|
+
): Promise<RateLimitRecord> {
|
40
|
+
const record = await this.getRateLimitRecord(key);
|
41
|
+
if (!record) throw new Error("Record not found");
|
42
|
+
|
43
|
+
const updatedRecord: RateLimitRecord = {
|
44
|
+
...record,
|
45
|
+
count,
|
46
|
+
timestamp,
|
47
|
+
};
|
48
|
+
await this.redis.set(key, JSON.stringify(updatedRecord));
|
49
|
+
return updatedRecord;
|
50
|
+
}
|
51
|
+
|
52
|
+
public async increment(key: string): Promise<RateLimitRecord> {
|
53
|
+
const record = await this.getRateLimitRecord(key);
|
54
|
+
if (!record) throw new Error("Record not found");
|
55
|
+
|
56
|
+
const updatedRecord: RateLimitRecord = {
|
57
|
+
...record,
|
58
|
+
count: record.count + 1,
|
59
|
+
};
|
60
|
+
await this.redis.set(key, JSON.stringify(updatedRecord));
|
61
|
+
return updatedRecord;
|
62
|
+
}
|
63
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import mongoose, { Schema } from "mongoose";
|
2
|
+
import { RateLimitRecord } from "../../interfaces";
|
3
|
+
|
4
|
+
export const RATE_LIMIT_SCHEMA_CONST = "RateLimit";
|
5
|
+
|
6
|
+
export const RateLimitSchema = new Schema<RateLimitRecord>({
|
7
|
+
key: { type: String, required: true, unique: true },
|
8
|
+
count: { type: Number, required: true },
|
9
|
+
timestamp: { type: Number, required: true },
|
10
|
+
});
|
11
|
+
|
12
|
+
export const RateLimitModel = mongoose.model<RateLimitRecord>(
|
13
|
+
RATE_LIMIT_SCHEMA_CONST,
|
14
|
+
RateLimitSchema
|
15
|
+
);
|
@@ -1,8 +0,0 @@
|
|
1
|
-
import { IRateLimiter, IRateLimitRecord } from "./rate.limiter";
|
2
|
-
export declare class InMemoryStorage implements IRateLimiter {
|
3
|
-
private store;
|
4
|
-
getRateLimitRecord(key: string): IRateLimitRecord | undefined;
|
5
|
-
createRateLimitRecord(record: IRateLimitRecord): IRateLimitRecord;
|
6
|
-
updateRateLimitRecord(key: string, timestamp: number, count: number): IRateLimitRecord;
|
7
|
-
increment(key: string): IRateLimitRecord;
|
8
|
-
}
|
@@ -1,27 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.InMemoryStorage = void 0;
|
4
|
-
class InMemoryStorage {
|
5
|
-
constructor() {
|
6
|
-
this.store = [];
|
7
|
-
}
|
8
|
-
getRateLimitRecord(key) {
|
9
|
-
return this.store.find((x) => x.key === key);
|
10
|
-
}
|
11
|
-
createRateLimitRecord(record) {
|
12
|
-
var index = this.store.push(record);
|
13
|
-
return this.store[index - 1];
|
14
|
-
}
|
15
|
-
updateRateLimitRecord(key, timestamp, count) {
|
16
|
-
var index = this.store.findIndex((x) => x.key === key);
|
17
|
-
this.store[index].count = count;
|
18
|
-
this.store[index].timestamp = timestamp;
|
19
|
-
return this.store[index];
|
20
|
-
}
|
21
|
-
increment(key) {
|
22
|
-
var index = this.store.findIndex((x) => x.key === key);
|
23
|
-
this.store[index].count += 1;
|
24
|
-
return this.store[index];
|
25
|
-
}
|
26
|
-
}
|
27
|
-
exports.InMemoryStorage = InMemoryStorage;
|
package/src/in-memory-storage.ts
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
import { IRateLimiter, IRateLimitRecord } from "./rate.limiter";
|
2
|
-
|
3
|
-
export class InMemoryStorage implements IRateLimiter {
|
4
|
-
private store: IRateLimitRecord[] = [];
|
5
|
-
|
6
|
-
getRateLimitRecord(key: string): IRateLimitRecord | undefined {
|
7
|
-
return this.store.find((x) => x.key === key);
|
8
|
-
}
|
9
|
-
|
10
|
-
createRateLimitRecord(record: IRateLimitRecord): IRateLimitRecord {
|
11
|
-
var index = this.store.push(record);
|
12
|
-
return this.store[index - 1];
|
13
|
-
}
|
14
|
-
|
15
|
-
updateRateLimitRecord(
|
16
|
-
key: string,
|
17
|
-
timestamp: number,
|
18
|
-
count: number
|
19
|
-
): IRateLimitRecord {
|
20
|
-
var index = this.store.findIndex((x) => x.key === key);
|
21
|
-
this.store[index].count = count;
|
22
|
-
this.store[index].timestamp = timestamp;
|
23
|
-
|
24
|
-
return this.store[index];
|
25
|
-
}
|
26
|
-
|
27
|
-
increment(key: string): IRateLimitRecord {
|
28
|
-
var index = this.store.findIndex((x) => x.key === key);
|
29
|
-
this.store[index].count += 1;
|
30
|
-
return this.store[index];
|
31
|
-
}
|
32
|
-
}
|
package/src/types/store.type.ts
DELETED
File without changes
|