mielk-api 1.4.0 → 1.5.1
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/http/httpResponseStatus/HttpResponseStatus.d.ts +3 -0
- package/dist/http/httpResponseStatus/HttpResponseStatus.js +3 -0
- package/dist/internal/messaging/messageTags.d.ts +3 -0
- package/dist/internal/messaging/messageTags.js +3 -0
- package/dist/middlewares/index.d.ts +12 -0
- package/dist/middlewares/index.js +12 -0
- package/dist/{rateLimit → middlewares/rateLimit}/checkRateLimit.js +6 -1
- package/dist/{rateLimit → middlewares/rateLimit}/createRateLimiter.d.ts +2 -2
- package/dist/{rateLimit → middlewares/rateLimit}/createRateLimiter.js +16 -5
- package/dist/{rateLimit → middlewares/rateLimit}/index.d.ts +3 -2
- package/dist/{rateLimit → middlewares/rateLimit}/index.js +1 -1
- package/dist/middlewares/rateLimit/rateLimitConfigFactory.d.ts +7 -0
- package/dist/middlewares/rateLimit/rateLimitConfigFactory.js +30 -0
- package/dist/middlewares/rateLimit/types.d.ts +35 -0
- package/dist/{rateLimit → middlewares/redis}/redisKeysFactory.d.ts +1 -0
- package/dist/{rateLimit → middlewares/redis}/redisKeysFactory.js +4 -3
- package/dist/middlewares/redis/redisLockChecker.d.ts +5 -0
- package/dist/middlewares/redis/redisLockChecker.js +31 -0
- package/dist/middlewares/redis/redisLockCheckerConfigFactory.d.ts +4 -0
- package/dist/middlewares/redis/redisLockCheckerConfigFactory.js +16 -0
- package/dist/middlewares/redis/redisLocker.d.ts +2 -0
- package/dist/middlewares/redis/redisLocker.js +23 -0
- package/dist/middlewares/redis/redisLockerConfigFactory.d.ts +4 -0
- package/dist/middlewares/redis/redisLockerConfigFactory.js +14 -0
- package/dist/middlewares/redis/types.d.ts +20 -0
- package/dist/middlewares/redis/types.js +1 -0
- package/dist/routing/express.d.ts +2 -2
- package/dist/routing/express.js +3 -1
- package/package.json +4 -4
- package/dist/rateLimit/rateLimitConfigFactory.d.ts +0 -7
- package/dist/rateLimit/rateLimitConfigFactory.js +0 -30
- package/dist/rateLimit/types.d.ts +0 -20
- /package/dist/{rateLimit → middlewares/rateLimit}/checkRateLimit.d.ts +0 -0
- /package/dist/{rateLimit → middlewares/rateLimit}/types.js +0 -0
|
@@ -2,11 +2,14 @@ import { HttpStatus } from "../types/HttpStatus.js";
|
|
|
2
2
|
export declare const HttpResponseStatus: {
|
|
3
3
|
OK: HttpStatus;
|
|
4
4
|
CREATED: HttpStatus;
|
|
5
|
+
ACCEPTED: HttpStatus;
|
|
6
|
+
NO_CONTENT: HttpStatus;
|
|
5
7
|
BAD_REQUEST: HttpStatus;
|
|
6
8
|
UNAUTHORIZED: HttpStatus;
|
|
7
9
|
FORBIDDEN: HttpStatus;
|
|
8
10
|
NOT_FOUND: HttpStatus;
|
|
9
11
|
CONFLICT: HttpStatus;
|
|
12
|
+
LOCKED: HttpStatus;
|
|
10
13
|
TOO_MANY_REQUESTS: HttpStatus;
|
|
11
14
|
SERVER_ERROR: HttpStatus;
|
|
12
15
|
};
|
|
@@ -4,11 +4,14 @@ const msg = Msg.apiStatus;
|
|
|
4
4
|
export const HttpResponseStatus = {
|
|
5
5
|
OK: createApiStatus(true, 200, msg.ok),
|
|
6
6
|
CREATED: createApiStatus(true, 201, msg.created),
|
|
7
|
+
ACCEPTED: createApiStatus(true, 202, msg.created),
|
|
8
|
+
NO_CONTENT: createApiStatus(true, 204, msg.noContent),
|
|
7
9
|
BAD_REQUEST: createApiStatus(false, 400, msg.badRequest),
|
|
8
10
|
UNAUTHORIZED: createApiStatus(false, 401, msg.unauthorized),
|
|
9
11
|
FORBIDDEN: createApiStatus(false, 403, msg.forbidden),
|
|
10
12
|
NOT_FOUND: createApiStatus(false, 404, msg.notFound),
|
|
11
13
|
CONFLICT: createApiStatus(false, 409, msg.conflict),
|
|
14
|
+
LOCKED: createApiStatus(false, 423, msg.locked),
|
|
12
15
|
TOO_MANY_REQUESTS: createApiStatus(false, 429, msg.tooManyRequests),
|
|
13
16
|
SERVER_ERROR: createApiStatus(false, 500, msg.serverError),
|
|
14
17
|
};
|
|
@@ -2,11 +2,14 @@ export declare const Msg: {
|
|
|
2
2
|
apiStatus: {
|
|
3
3
|
ok: string;
|
|
4
4
|
created: string;
|
|
5
|
+
accepted: string;
|
|
6
|
+
noContent: string;
|
|
5
7
|
badRequest: string;
|
|
6
8
|
unauthorized: string;
|
|
7
9
|
forbidden: string;
|
|
8
10
|
notFound: string;
|
|
9
11
|
conflict: string;
|
|
12
|
+
locked: string;
|
|
10
13
|
tooManyRequests: string;
|
|
11
14
|
serverError: string;
|
|
12
15
|
};
|
|
@@ -5,11 +5,14 @@ export const Msg = {
|
|
|
5
5
|
apiStatus: {
|
|
6
6
|
ok: `${___HTTP_STATUS___}:ok`,
|
|
7
7
|
created: `${___HTTP_STATUS___}:created`,
|
|
8
|
+
accepted: `${___HTTP_STATUS___}:accepted`,
|
|
9
|
+
noContent: `${___HTTP_STATUS___}:noContent`,
|
|
8
10
|
badRequest: `${___HTTP_STATUS___}:badRequest`,
|
|
9
11
|
unauthorized: `${___HTTP_STATUS___}:unauthorized`,
|
|
10
12
|
forbidden: `${___HTTP_STATUS___}:forbidden`,
|
|
11
13
|
notFound: `${___HTTP_STATUS___}:notFound`,
|
|
12
14
|
conflict: `${___HTTP_STATUS___}:conflict`,
|
|
15
|
+
locked: `${___HTTP_STATUS___}:locked`, // Account is temporarily locked. Try again later or reset your password. OR Your account has been temporarily locked due to multiple failed login attempts. Please reset your password to regain access.
|
|
13
16
|
tooManyRequests: `${___HTTP_STATUS___}:tooManyRequests`,
|
|
14
17
|
serverError: `${___HTTP_STATUS___}:serverError`,
|
|
15
18
|
},
|
|
@@ -3,6 +3,18 @@ import { privateCors } from './cors/privateCors.js';
|
|
|
3
3
|
import { publicCors } from './cors/publicCors.js';
|
|
4
4
|
import { apiKeyAuthorization } from './requestAuth/auth.middleware.js';
|
|
5
5
|
import { validateQueryParams, validateBodyJson } from './zod/validate.js';
|
|
6
|
+
import { RedisLockerConfigFactory } from './redis/redisLockerConfigFactory.js';
|
|
7
|
+
import { RedisLockerConfig, RedisLockerConfigParams } from './redis/types.js';
|
|
8
|
+
import { setRedisLock, removeRedisLock } from './redis/redisLocker.js';
|
|
9
|
+
import { RedisLockCheckerConfigFactory } from './redis/redisLockCheckerConfigFactory.js';
|
|
10
|
+
import { RedisLockChecker, RedisLockCheckerConfig, RedisLockCheckerConfigParams } from './redis/types.js';
|
|
11
|
+
import { createRedisLockChecker, checkIfLockedInRedis } from './redis/redisLockChecker.js';
|
|
6
12
|
export { CorsConfig, initCors, privateCors, publicCors };
|
|
7
13
|
export { apiKeyAuthorization };
|
|
8
14
|
export { validateQueryParams, validateBodyJson };
|
|
15
|
+
export { RedisLockerConfigFactory };
|
|
16
|
+
export { RedisLockerConfig, RedisLockerConfigParams };
|
|
17
|
+
export { setRedisLock, removeRedisLock };
|
|
18
|
+
export { createRedisLockChecker, checkIfLockedInRedis };
|
|
19
|
+
export { RedisLockCheckerConfigFactory };
|
|
20
|
+
export { RedisLockChecker, RedisLockCheckerConfig, RedisLockCheckerConfigParams };
|
|
@@ -3,6 +3,18 @@ import { privateCors } from './cors/privateCors.js';
|
|
|
3
3
|
import { publicCors } from './cors/publicCors.js';
|
|
4
4
|
import { apiKeyAuthorization } from './requestAuth/auth.middleware.js';
|
|
5
5
|
import { validateQueryParams, validateBodyJson } from './zod/validate.js';
|
|
6
|
+
// Redis locker
|
|
7
|
+
import { RedisLockerConfigFactory } from './redis/redisLockerConfigFactory.js';
|
|
8
|
+
import { setRedisLock, removeRedisLock } from './redis/redisLocker.js';
|
|
9
|
+
// Redis lock checker
|
|
10
|
+
import { RedisLockCheckerConfigFactory } from './redis/redisLockCheckerConfigFactory.js';
|
|
11
|
+
import { createRedisLockChecker, checkIfLockedInRedis } from './redis/redisLockChecker.js';
|
|
6
12
|
export { initCors, privateCors, publicCors };
|
|
7
13
|
export { apiKeyAuthorization };
|
|
8
14
|
export { validateQueryParams, validateBodyJson };
|
|
15
|
+
// Redis locker
|
|
16
|
+
export { RedisLockerConfigFactory };
|
|
17
|
+
export { setRedisLock, removeRedisLock };
|
|
18
|
+
// Redis lock checker
|
|
19
|
+
export { createRedisLockChecker, checkIfLockedInRedis };
|
|
20
|
+
export { RedisLockCheckerConfigFactory };
|
|
@@ -8,8 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { getRedisClient } from 'mielk-fn/redis';
|
|
11
|
+
import { setRedisLock } from '../redis/redisLocker.js';
|
|
11
12
|
export const checkRateLimit = (rateLimit) => __awaiter(void 0, void 0, void 0, function* () {
|
|
12
|
-
const { key, limit, windowSec } = rateLimit;
|
|
13
|
+
const { key, limit, windowSec, redisLock } = rateLimit;
|
|
13
14
|
const client = yield getRedisClient();
|
|
14
15
|
const currentCounter = yield client.incr(key);
|
|
15
16
|
if (currentCounter === 1) {
|
|
@@ -17,6 +18,10 @@ export const checkRateLimit = (rateLimit) => __awaiter(void 0, void 0, void 0, f
|
|
|
17
18
|
}
|
|
18
19
|
const ttl = yield client.ttl(key);
|
|
19
20
|
const allowed = currentCounter <= limit;
|
|
21
|
+
if (!allowed && redisLock) {
|
|
22
|
+
const { key, ttlSeconds } = redisLock;
|
|
23
|
+
setRedisLock(key, ttlSeconds);
|
|
24
|
+
}
|
|
20
25
|
return {
|
|
21
26
|
currentCounter,
|
|
22
27
|
limit,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import { RateLimitConfig } from './types.js';
|
|
3
|
-
export declare const createGlobalRateLimiter: (config?:
|
|
2
|
+
import { RateLimitConfig, RateLimitConfigParams } from './types.js';
|
|
3
|
+
export declare const createGlobalRateLimiter: (config?: RateLimitConfigParams) => (req: Request, res: Response, next: NextFunction) => Promise<void | Response<Record<string, any>, Record<string, any>>>;
|
|
4
4
|
export declare const createRateLimiter: (config: RateLimitConfig) => (req: Request, res: Response, next: NextFunction) => Promise<void | Response<Record<string, any>, Record<string, any>>>;
|
|
@@ -8,11 +8,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { checkRateLimit } from './checkRateLimit.js';
|
|
11
|
-
import { failure, HttpResponseStatus } from '
|
|
12
|
-
import { Msg } from '../internal/messaging/messageTags.js';
|
|
11
|
+
import { failure, HttpResponseStatus } from '../../http/index.js';
|
|
13
12
|
import { RateLimitConfigFactory } from './rateLimitConfigFactory.js';
|
|
13
|
+
import { generateRedisLockKey } from '../redis/redisLockChecker.js';
|
|
14
|
+
import { Msg } from '../../internal/messaging/messageTags.js';
|
|
14
15
|
export const createGlobalRateLimiter = (config) => {
|
|
15
|
-
return createRateLimiter(
|
|
16
|
+
return createRateLimiter(RateLimitConfigFactory.globalIp(config));
|
|
16
17
|
};
|
|
17
18
|
export const createRateLimiter = (config) => {
|
|
18
19
|
return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -22,6 +23,8 @@ export const createRateLimiter = (config) => {
|
|
|
22
23
|
res.setHeader('X-RateLimit-Limit', limit);
|
|
23
24
|
res.setHeader('X-RateLimit-Remaining', Math.max(0, limit - currentCounter));
|
|
24
25
|
res.setHeader('X-RateLimit-Reset', ttl);
|
|
26
|
+
console.log('IP:', req.ip);
|
|
27
|
+
console.log('Forwarded:', req.headers['x-forwarded-for']);
|
|
25
28
|
if (!allowed) {
|
|
26
29
|
return failure(res, HttpResponseStatus.TOO_MANY_REQUESTS, message || Msg.apiStatus.tooManyRequests);
|
|
27
30
|
}
|
|
@@ -29,9 +32,17 @@ export const createRateLimiter = (config) => {
|
|
|
29
32
|
});
|
|
30
33
|
};
|
|
31
34
|
const createRateLimitFromConfig = (config, req) => {
|
|
32
|
-
const { limit, windowSec, message } = config;
|
|
35
|
+
const { limit, windowSec, message, redisLockerConfig } = config;
|
|
33
36
|
const key = generateKeyFromConfig(config, req);
|
|
34
|
-
|
|
37
|
+
const rateLimit = { key, limit, windowSec, message };
|
|
38
|
+
if (redisLockerConfig) {
|
|
39
|
+
const { keyPrefix, ttlSeconds } = redisLockerConfig;
|
|
40
|
+
const { keySuffixes } = config;
|
|
41
|
+
const key = generateRedisLockKey(keyPrefix, keySuffixes, req);
|
|
42
|
+
const redisLock = { key, ttlSeconds };
|
|
43
|
+
rateLimit.redisLock = redisLock;
|
|
44
|
+
}
|
|
45
|
+
return rateLimit;
|
|
35
46
|
};
|
|
36
47
|
const generateKeyFromConfig = (config, req) => {
|
|
37
48
|
const { keyPrefix, keySuffixes } = config;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { checkRateLimit } from './checkRateLimit.js';
|
|
2
2
|
import { createRateLimiter } from './createRateLimiter.js';
|
|
3
3
|
import { RateLimitConfigFactory } from './rateLimitConfigFactory.js';
|
|
4
|
-
import { RedisKeyFactory } from '
|
|
5
|
-
import { RateLimitConfig, RateLimit, RateLimitCheck } from './types.js';
|
|
4
|
+
import { RedisKeyFactory } from '../redis/redisKeysFactory.js';
|
|
5
|
+
import { type RateLimiter, RateLimitConfig, RateLimit, RateLimitCheck } from './types.js';
|
|
6
6
|
export { checkRateLimit, createRateLimiter, RateLimitConfigFactory, RedisKeyFactory };
|
|
7
7
|
export { RateLimitConfig, RateLimit, RateLimitCheck };
|
|
8
|
+
export type { RateLimiter };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { checkRateLimit } from './checkRateLimit.js';
|
|
2
2
|
import { createRateLimiter } from './createRateLimiter.js';
|
|
3
3
|
import { RateLimitConfigFactory } from './rateLimitConfigFactory.js';
|
|
4
|
-
import { RedisKeyFactory } from '
|
|
4
|
+
import { RedisKeyFactory } from '../redis/redisKeysFactory.js';
|
|
5
5
|
export { checkRateLimit, createRateLimiter, RateLimitConfigFactory, RedisKeyFactory };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RateLimitConfig, RateLimitConfigParams } from "./types.js";
|
|
2
|
+
export declare const RateLimitConfigFactory: {
|
|
3
|
+
globalIp: (params?: RateLimitConfigParams) => RateLimitConfig;
|
|
4
|
+
loginIp: (params?: RateLimitConfigParams) => RateLimitConfig;
|
|
5
|
+
loginIpMail: (params?: RateLimitConfigParams) => RateLimitConfig;
|
|
6
|
+
loginMail: (params?: RateLimitConfigParams) => RateLimitConfig;
|
|
7
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { RedisKeyFactory } from "../redis/redisKeysFactory.js";
|
|
2
|
+
import { Msg } from "../../internal/messaging/messageTags.js";
|
|
3
|
+
const defaultRateLimitConfig = {
|
|
4
|
+
keyPrefix: RedisKeyFactory.prefixes.login,
|
|
5
|
+
keySuffixes: RedisKeyFactory.suffixes.ip,
|
|
6
|
+
windowSec: 15 * 60,
|
|
7
|
+
limit: 5,
|
|
8
|
+
message: Msg.apiStatus.tooManyRequests,
|
|
9
|
+
};
|
|
10
|
+
const createRateLimitConfig = (params) => {
|
|
11
|
+
return Object.assign(Object.assign({}, defaultRateLimitConfig), params);
|
|
12
|
+
};
|
|
13
|
+
const globalIp = (params) => {
|
|
14
|
+
return createRateLimitConfig(Object.assign({ keyPrefix: RedisKeyFactory.prefixes.global, windowSec: 60, limit: 2 }, (params || {})));
|
|
15
|
+
};
|
|
16
|
+
const loginIp = (params) => {
|
|
17
|
+
return createRateLimitConfig(Object.assign({}, (params || {})));
|
|
18
|
+
};
|
|
19
|
+
const loginIpMail = (params) => {
|
|
20
|
+
return createRateLimitConfig(Object.assign({ keySuffixes: RedisKeyFactory.suffixes.ipMail }, (params || {})));
|
|
21
|
+
};
|
|
22
|
+
const loginMail = (params) => {
|
|
23
|
+
return createRateLimitConfig(Object.assign({ keySuffixes: RedisKeyFactory.suffixes.mail, redisLockerConfig: { keyPrefix: RedisKeyFactory.prefixes.lockEmail, ttlSeconds: 15 * 60 } }, (params || {})));
|
|
24
|
+
};
|
|
25
|
+
export const RateLimitConfigFactory = {
|
|
26
|
+
globalIp,
|
|
27
|
+
loginIp,
|
|
28
|
+
loginIpMail,
|
|
29
|
+
loginMail
|
|
30
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from 'express';
|
|
2
|
+
import { RedisLockerConfig } from '../redis/types.js';
|
|
3
|
+
export type RateLimiter = (req: Request, res: Response, next: NextFunction) => Promise<void | Response<Record<string, any>, Record<string, any>>>;
|
|
4
|
+
export type RateLimitConfigParams = {
|
|
5
|
+
keyPrefix?: string;
|
|
6
|
+
keySuffixes?: (req: Request) => string[];
|
|
7
|
+
windowSec?: number;
|
|
8
|
+
limit?: number;
|
|
9
|
+
message?: string;
|
|
10
|
+
redisLockerConfig?: RedisLockerConfig | null;
|
|
11
|
+
};
|
|
12
|
+
export interface RateLimitConfig {
|
|
13
|
+
keyPrefix: string;
|
|
14
|
+
keySuffixes: (req: Request) => string[];
|
|
15
|
+
limit: number;
|
|
16
|
+
windowSec: number;
|
|
17
|
+
message?: string;
|
|
18
|
+
redisLockerConfig?: RedisLockerConfig | null;
|
|
19
|
+
}
|
|
20
|
+
export interface RateLimit {
|
|
21
|
+
key: string;
|
|
22
|
+
limit: number;
|
|
23
|
+
windowSec: number;
|
|
24
|
+
message?: string;
|
|
25
|
+
redisLock?: {
|
|
26
|
+
key: string;
|
|
27
|
+
ttlSeconds: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface RateLimitCheck {
|
|
31
|
+
currentCounter: number;
|
|
32
|
+
limit: number;
|
|
33
|
+
ttl: number;
|
|
34
|
+
allowed: boolean;
|
|
35
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const prefixes = {
|
|
2
2
|
global: 'global',
|
|
3
3
|
login: 'login',
|
|
4
|
-
|
|
4
|
+
lockEmail: 'lock:email',
|
|
5
|
+
refresh: 'refresh',
|
|
5
6
|
};
|
|
6
7
|
const getIpSuffixCallback = (req) => {
|
|
7
8
|
const ip = req.ip || 'undefined';
|
|
@@ -9,12 +10,12 @@ const getIpSuffixCallback = (req) => {
|
|
|
9
10
|
};
|
|
10
11
|
const getEmailSuffixCallback = (req) => {
|
|
11
12
|
var _a;
|
|
12
|
-
const email = ((_a = req.body) === null || _a === void 0 ? void 0 : _a.email) || 'unknown';
|
|
13
|
+
const email = (((_a = req.body) === null || _a === void 0 ? void 0 : _a.email) || 'unknown').toLowerCase();
|
|
13
14
|
return [email];
|
|
14
15
|
};
|
|
15
16
|
const getIpEmailSuffixCallback = (req) => {
|
|
16
17
|
var _a;
|
|
17
|
-
const email = ((_a = req.body) === null || _a === void 0 ? void 0 : _a.email) || 'unknown';
|
|
18
|
+
const email = (((_a = req.body) === null || _a === void 0 ? void 0 : _a.email) || 'unknown').toLowerCase();
|
|
18
19
|
const ip = req.ip || 'unknown';
|
|
19
20
|
return [ip, email];
|
|
20
21
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { RedisLockChecker, RedisLockCheckerConfig } from "./types.js";
|
|
2
|
+
import { Request } from "express";
|
|
3
|
+
export declare const createRedisLockChecker: (config: RedisLockCheckerConfig) => RedisLockChecker;
|
|
4
|
+
export declare const generateRedisLockKey: (keyPrefix: string, keySuffixes: (req: Request) => string[], req: Request) => string;
|
|
5
|
+
export declare const checkIfLockedInRedis: (key: string) => Promise<boolean>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { getRedisClient } from "mielk-fn/redis";
|
|
11
|
+
import { failure, HttpResponseStatus } from "../../http/index.js";
|
|
12
|
+
import { Msg } from "../../internal/messaging/messageTags.js";
|
|
13
|
+
export const createRedisLockChecker = (config) => {
|
|
14
|
+
return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15
|
+
const { message, keyPrefix, keySuffixes } = config;
|
|
16
|
+
const key = generateRedisLockKey(keyPrefix, keySuffixes, req);
|
|
17
|
+
const isLocked = yield checkIfLockedInRedis(key);
|
|
18
|
+
if (isLocked) {
|
|
19
|
+
return failure(res, HttpResponseStatus.LOCKED, message || Msg.apiStatus.locked);
|
|
20
|
+
}
|
|
21
|
+
return next();
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
export const generateRedisLockKey = (keyPrefix, keySuffixes, req) => {
|
|
25
|
+
return `${keyPrefix}:${keySuffixes(req).join(':')}`;
|
|
26
|
+
};
|
|
27
|
+
export const checkIfLockedInRedis = (key) => __awaiter(void 0, void 0, void 0, function* () {
|
|
28
|
+
const redisClient = yield getRedisClient();
|
|
29
|
+
const value = yield redisClient.get(key);
|
|
30
|
+
return value !== null;
|
|
31
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RedisKeyFactory } from "./redisKeysFactory.js";
|
|
2
|
+
import { Msg } from "../../internal/messaging/messageTags.js";
|
|
3
|
+
const defaultRedisLockCheckerConfig = {
|
|
4
|
+
keyPrefix: RedisKeyFactory.prefixes.lockEmail,
|
|
5
|
+
keySuffixes: RedisKeyFactory.suffixes.mail,
|
|
6
|
+
message: Msg.apiStatus.locked,
|
|
7
|
+
};
|
|
8
|
+
const createRedisLockCheckerConfig = (params) => {
|
|
9
|
+
return Object.assign(Object.assign({}, defaultRedisLockCheckerConfig), params);
|
|
10
|
+
};
|
|
11
|
+
const mail = (params) => {
|
|
12
|
+
return createRedisLockCheckerConfig(Object.assign({}, (params || {})));
|
|
13
|
+
};
|
|
14
|
+
export const RedisLockCheckerConfigFactory = {
|
|
15
|
+
mail,
|
|
16
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { getRedisClient } from "mielk-fn/redis";
|
|
11
|
+
export const setRedisLock = (key, ttlSeconds) => __awaiter(void 0, void 0, void 0, function* () {
|
|
12
|
+
const redisClient = yield getRedisClient();
|
|
13
|
+
yield redisClient.set(key, JSON.stringify({ locked: true }), {
|
|
14
|
+
expiration: {
|
|
15
|
+
type: 'EX',
|
|
16
|
+
value: ttlSeconds
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
export const removeRedisLock = (key) => __awaiter(void 0, void 0, void 0, function* () {
|
|
21
|
+
const redisClient = yield getRedisClient();
|
|
22
|
+
yield redisClient.del(key);
|
|
23
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RedisKeyFactory } from "./redisKeysFactory.js";
|
|
2
|
+
const defaultRedisLockerConfig = {
|
|
3
|
+
keyPrefix: RedisKeyFactory.prefixes.lockEmail,
|
|
4
|
+
ttlSeconds: 30 * 60,
|
|
5
|
+
};
|
|
6
|
+
const createRedisLockerConfig = (params) => {
|
|
7
|
+
return Object.assign(Object.assign({}, defaultRedisLockerConfig), params);
|
|
8
|
+
};
|
|
9
|
+
const mail = (params) => {
|
|
10
|
+
return createRedisLockerConfig(Object.assign({}, (params || {})));
|
|
11
|
+
};
|
|
12
|
+
export const RedisLockerConfigFactory = {
|
|
13
|
+
mail,
|
|
14
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from "express";
|
|
2
|
+
export type RedisLockChecker = (req: Request, res: Response, next: NextFunction) => Promise<void | Response<Record<string, any>, Record<string, any>>>;
|
|
3
|
+
export interface RedisLockCheckerConfigParams {
|
|
4
|
+
keyPrefix?: string;
|
|
5
|
+
keySuffixes?: (req: Request) => string[];
|
|
6
|
+
message?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RedisLockCheckerConfig {
|
|
9
|
+
keyPrefix: string;
|
|
10
|
+
keySuffixes: (req: Request) => string[];
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
export interface RedisLockerConfigParams {
|
|
14
|
+
keyPrefix?: string;
|
|
15
|
+
ttlSeconds?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface RedisLockerConfig {
|
|
18
|
+
keyPrefix: string;
|
|
19
|
+
ttlSeconds: number;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -2,8 +2,8 @@ import { PostgreDbConfig } from '../db/pg/index.js';
|
|
|
2
2
|
import { MsSqlDbConfig } from '../db/mssql/index.js';
|
|
3
3
|
import { CorsConfig } from '../middlewares/index.js';
|
|
4
4
|
import { DbProvider } from './DbProvider.js';
|
|
5
|
-
import {
|
|
5
|
+
import { RateLimitConfigParams } from '../middlewares/rateLimit/types.js';
|
|
6
6
|
export type DbConfig = PostgreDbConfig | MsSqlDbConfig;
|
|
7
|
-
export declare const createExpressApp: (isProd: boolean, dbConfig: DbConfig, corsConfig: CorsConfig, rateLimitConfig?:
|
|
7
|
+
export declare const createExpressApp: (isProd: boolean, dbConfig: DbConfig, corsConfig: CorsConfig, rateLimitConfig?: RateLimitConfigParams) => Promise<import("express-serve-static-core").Express>;
|
|
8
8
|
export declare const isProd: () => boolean;
|
|
9
9
|
export declare const getDbProvider: () => DbProvider;
|
package/dist/routing/express.js
CHANGED
|
@@ -11,7 +11,7 @@ import express from 'express';
|
|
|
11
11
|
import { initDb as postgreInitDb } from '../db/pg/index.js';
|
|
12
12
|
import { initDb as msSqlInitDb } from '../db/mssql/index.js';
|
|
13
13
|
import { initCors } from '../middlewares/index.js';
|
|
14
|
-
import { createGlobalRateLimiter } from '../rateLimit/createRateLimiter.js';
|
|
14
|
+
import { createGlobalRateLimiter } from '../middlewares/rateLimit/createRateLimiter.js';
|
|
15
15
|
const env = {
|
|
16
16
|
isProd: true,
|
|
17
17
|
provider: undefined
|
|
@@ -31,6 +31,8 @@ export const createExpressApp = (isProd, dbConfig, corsConfig, rateLimitConfig)
|
|
|
31
31
|
initCors(corsConfig);
|
|
32
32
|
app.use(createGlobalRateLimiter(rateLimitConfig));
|
|
33
33
|
app.use(express.json());
|
|
34
|
+
if (isProd)
|
|
35
|
+
app.set('trust proxy', 1);
|
|
34
36
|
return app;
|
|
35
37
|
});
|
|
36
38
|
export const isProd = () => env.isProd;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mielk-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"author": "mielk",
|
|
6
6
|
"description": "Wrapper for API operations",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"default": "./dist/middlewares/index.js"
|
|
35
35
|
},
|
|
36
36
|
"./rate-limit": {
|
|
37
|
-
"types": "./dist/rateLimit/index.d.ts",
|
|
38
|
-
"import": "./dist/rateLimit/index.js",
|
|
39
|
-
"default": "./dist/rateLimit/index.js"
|
|
37
|
+
"types": "./dist/middlewares/rateLimit/index.d.ts",
|
|
38
|
+
"import": "./dist/middlewares/rateLimit/index.js",
|
|
39
|
+
"default": "./dist/middlewares/rateLimit/index.js"
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { RedisKeyFactory } from "./redisKeysFactory.js";
|
|
2
|
-
import { Msg } from "../internal/messaging/messageTags.js";
|
|
3
|
-
const defaultRateLimitConfig = {
|
|
4
|
-
keyPrefix: RedisKeyFactory.prefixes.login,
|
|
5
|
-
keySuffixes: RedisKeyFactory.suffixes.ip,
|
|
6
|
-
windowSec: 15 * 60,
|
|
7
|
-
limit: 5,
|
|
8
|
-
message: Msg.apiStatus.tooManyRequests,
|
|
9
|
-
};
|
|
10
|
-
const createRateLimitConfig = (params) => {
|
|
11
|
-
return Object.assign(Object.assign({}, defaultRateLimitConfig), params);
|
|
12
|
-
};
|
|
13
|
-
const globalIp = createRateLimitConfig({
|
|
14
|
-
keyPrefix: RedisKeyFactory.prefixes.global,
|
|
15
|
-
windowSec: 60,
|
|
16
|
-
limit: 100,
|
|
17
|
-
});
|
|
18
|
-
const loginIp = defaultRateLimitConfig;
|
|
19
|
-
const loginIpMail = createRateLimitConfig({
|
|
20
|
-
keySuffixes: RedisKeyFactory.suffixes.ipMail,
|
|
21
|
-
});
|
|
22
|
-
const loginMail = createRateLimitConfig({
|
|
23
|
-
keySuffixes: RedisKeyFactory.suffixes.mail,
|
|
24
|
-
});
|
|
25
|
-
export const RateLimitConfigFactory = {
|
|
26
|
-
globalIp,
|
|
27
|
-
loginIp,
|
|
28
|
-
loginIpMail,
|
|
29
|
-
loginMail
|
|
30
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Request } from "express";
|
|
2
|
-
export interface RateLimitConfig {
|
|
3
|
-
keyPrefix: string;
|
|
4
|
-
keySuffixes: (req: Request) => string[];
|
|
5
|
-
limit: number;
|
|
6
|
-
windowSec: number;
|
|
7
|
-
message?: string;
|
|
8
|
-
}
|
|
9
|
-
export interface RateLimit {
|
|
10
|
-
key: string;
|
|
11
|
-
limit: number;
|
|
12
|
-
windowSec: number;
|
|
13
|
-
message?: string;
|
|
14
|
-
}
|
|
15
|
-
export interface RateLimitCheck {
|
|
16
|
-
currentCounter: number;
|
|
17
|
-
limit: number;
|
|
18
|
-
ttl: number;
|
|
19
|
-
allowed: boolean;
|
|
20
|
-
}
|
|
File without changes
|
|
File without changes
|