mielk-api 1.3.9 → 1.4.0

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.
@@ -4,7 +4,9 @@ export declare const HttpResponseStatus: {
4
4
  CREATED: HttpStatus;
5
5
  BAD_REQUEST: HttpStatus;
6
6
  UNAUTHORIZED: HttpStatus;
7
+ FORBIDDEN: HttpStatus;
7
8
  NOT_FOUND: HttpStatus;
8
9
  CONFLICT: HttpStatus;
10
+ TOO_MANY_REQUESTS: HttpStatus;
9
11
  SERVER_ERROR: HttpStatus;
10
12
  };
@@ -6,7 +6,9 @@ export const HttpResponseStatus = {
6
6
  CREATED: createApiStatus(true, 201, msg.created),
7
7
  BAD_REQUEST: createApiStatus(false, 400, msg.badRequest),
8
8
  UNAUTHORIZED: createApiStatus(false, 401, msg.unauthorized),
9
+ FORBIDDEN: createApiStatus(false, 403, msg.forbidden),
9
10
  NOT_FOUND: createApiStatus(false, 404, msg.notFound),
10
11
  CONFLICT: createApiStatus(false, 409, msg.conflict),
12
+ TOO_MANY_REQUESTS: createApiStatus(false, 429, msg.tooManyRequests),
11
13
  SERVER_ERROR: createApiStatus(false, 500, msg.serverError),
12
14
  };
@@ -2,10 +2,12 @@ export declare const Msg: {
2
2
  apiStatus: {
3
3
  ok: string;
4
4
  created: string;
5
- unauthorized: string;
6
5
  badRequest: string;
6
+ unauthorized: string;
7
+ forbidden: string;
7
8
  notFound: string;
8
9
  conflict: string;
10
+ tooManyRequests: string;
9
11
  serverError: string;
10
12
  };
11
13
  connection: {
@@ -5,10 +5,12 @@ export const Msg = {
5
5
  apiStatus: {
6
6
  ok: `${___HTTP_STATUS___}:ok`,
7
7
  created: `${___HTTP_STATUS___}:created`,
8
- unauthorized: `${___HTTP_STATUS___}:unauthorized`,
9
8
  badRequest: `${___HTTP_STATUS___}:badRequest`,
9
+ unauthorized: `${___HTTP_STATUS___}:unauthorized`,
10
+ forbidden: `${___HTTP_STATUS___}:forbidden`,
10
11
  notFound: `${___HTTP_STATUS___}:notFound`,
11
12
  conflict: `${___HTTP_STATUS___}:conflict`,
13
+ tooManyRequests: `${___HTTP_STATUS___}:tooManyRequests`,
12
14
  serverError: `${___HTTP_STATUS___}:serverError`,
13
15
  },
14
16
  connection: {
@@ -15,4 +15,5 @@ export const privateCors = cors({
15
15
  },
16
16
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
17
17
  allowedHeaders: ['Content-Type', 'Authorization'],
18
+ credentials: true
18
19
  });
@@ -7,4 +7,5 @@ export const publicCors = cors({
7
7
  },
8
8
  methods: ['GET'],
9
9
  allowedHeaders: ['Content-Type', 'Authorization'],
10
+ credentials: true
10
11
  });
@@ -1,10 +1,8 @@
1
1
  import { CorsConfig, initCors } from './cors/cors.js';
2
2
  import { privateCors } from './cors/privateCors.js';
3
3
  import { publicCors } from './cors/publicCors.js';
4
- import { RateLimitConfig, setRateLimit } from './rateLimit/rateLimit.js';
5
4
  import { apiKeyAuthorization } from './requestAuth/auth.middleware.js';
6
5
  import { validateQueryParams, validateBodyJson } from './zod/validate.js';
7
6
  export { CorsConfig, initCors, privateCors, publicCors };
8
- export { RateLimitConfig, setRateLimit };
9
7
  export { apiKeyAuthorization };
10
8
  export { validateQueryParams, validateBodyJson };
@@ -1,10 +1,8 @@
1
1
  import { initCors } from './cors/cors.js';
2
2
  import { privateCors } from './cors/privateCors.js';
3
3
  import { publicCors } from './cors/publicCors.js';
4
- import { setRateLimit } from './rateLimit/rateLimit.js';
5
4
  import { apiKeyAuthorization } from './requestAuth/auth.middleware.js';
6
5
  import { validateQueryParams, validateBodyJson } from './zod/validate.js';
7
6
  export { initCors, privateCors, publicCors };
8
- export { setRateLimit };
9
7
  export { apiKeyAuthorization };
10
8
  export { validateQueryParams, validateBodyJson };
@@ -0,0 +1,2 @@
1
+ import { RateLimitCheck, RateLimit } from './types.js';
2
+ export declare const checkRateLimit: (rateLimit: RateLimit) => Promise<RateLimitCheck>;
@@ -0,0 +1,26 @@
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 checkRateLimit = (rateLimit) => __awaiter(void 0, void 0, void 0, function* () {
12
+ const { key, limit, windowSec } = rateLimit;
13
+ const client = yield getRedisClient();
14
+ const currentCounter = yield client.incr(key);
15
+ if (currentCounter === 1) {
16
+ yield client.expire(key, windowSec);
17
+ }
18
+ const ttl = yield client.ttl(key);
19
+ const allowed = currentCounter <= limit;
20
+ return {
21
+ currentCounter,
22
+ limit,
23
+ ttl,
24
+ allowed
25
+ };
26
+ });
@@ -0,0 +1,4 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { RateLimitConfig } from './types.js';
3
+ export declare const createGlobalRateLimiter: (config?: RateLimitConfig) => (req: Request, res: Response, next: NextFunction) => Promise<void | Response<Record<string, any>, Record<string, any>>>;
4
+ export declare const createRateLimiter: (config: RateLimitConfig) => (req: Request, res: Response, next: NextFunction) => Promise<void | Response<Record<string, any>, Record<string, any>>>;
@@ -0,0 +1,39 @@
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 { checkRateLimit } from './checkRateLimit.js';
11
+ import { failure, HttpResponseStatus } from '../http/index.js';
12
+ import { Msg } from '../internal/messaging/messageTags.js';
13
+ import { RateLimitConfigFactory } from './rateLimitConfigFactory.js';
14
+ export const createGlobalRateLimiter = (config) => {
15
+ return createRateLimiter(config || RateLimitConfigFactory.globalIp);
16
+ };
17
+ export const createRateLimiter = (config) => {
18
+ return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
19
+ const { message } = config;
20
+ const rateLimit = createRateLimitFromConfig(config, req);
21
+ const { limit, currentCounter, ttl, allowed } = yield checkRateLimit(rateLimit);
22
+ res.setHeader('X-RateLimit-Limit', limit);
23
+ res.setHeader('X-RateLimit-Remaining', Math.max(0, limit - currentCounter));
24
+ res.setHeader('X-RateLimit-Reset', ttl);
25
+ if (!allowed) {
26
+ return failure(res, HttpResponseStatus.TOO_MANY_REQUESTS, message || Msg.apiStatus.tooManyRequests);
27
+ }
28
+ return next();
29
+ });
30
+ };
31
+ const createRateLimitFromConfig = (config, req) => {
32
+ const { limit, windowSec, message } = config;
33
+ const key = generateKeyFromConfig(config, req);
34
+ return { key, limit, windowSec, message };
35
+ };
36
+ const generateKeyFromConfig = (config, req) => {
37
+ const { keyPrefix, keySuffixes } = config;
38
+ return `${keyPrefix}:${keySuffixes(req).join(':')}`;
39
+ };
@@ -0,0 +1,7 @@
1
+ import { checkRateLimit } from './checkRateLimit.js';
2
+ import { createRateLimiter } from './createRateLimiter.js';
3
+ import { RateLimitConfigFactory } from './rateLimitConfigFactory.js';
4
+ import { RedisKeyFactory } from './redisKeysFactory.js';
5
+ import { RateLimitConfig, RateLimit, RateLimitCheck } from './types.js';
6
+ export { checkRateLimit, createRateLimiter, RateLimitConfigFactory, RedisKeyFactory };
7
+ export { RateLimitConfig, RateLimit, RateLimitCheck };
@@ -0,0 +1,5 @@
1
+ import { checkRateLimit } from './checkRateLimit.js';
2
+ import { createRateLimiter } from './createRateLimiter.js';
3
+ import { RateLimitConfigFactory } from './rateLimitConfigFactory.js';
4
+ import { RedisKeyFactory } from './redisKeysFactory.js';
5
+ export { checkRateLimit, createRateLimiter, RateLimitConfigFactory, RedisKeyFactory };
@@ -0,0 +1,7 @@
1
+ import { RateLimitConfig } from "./types.js";
2
+ export declare const RateLimitConfigFactory: {
3
+ globalIp: RateLimitConfig;
4
+ loginIp: RateLimitConfig;
5
+ loginIpMail: RateLimitConfig;
6
+ loginMail: RateLimitConfig;
7
+ };
@@ -0,0 +1,30 @@
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
+ };
@@ -0,0 +1,13 @@
1
+ import { Request } from 'express';
2
+ export declare const RedisKeyFactory: {
3
+ prefixes: {
4
+ readonly global: "global";
5
+ readonly login: "login";
6
+ readonly refresh: "refresh";
7
+ };
8
+ suffixes: {
9
+ readonly ip: (req: Request) => string[];
10
+ readonly ipMail: (req: Request) => string[];
11
+ readonly mail: (req: Request) => string[];
12
+ };
13
+ };
@@ -0,0 +1,29 @@
1
+ const prefixes = {
2
+ global: 'global',
3
+ login: 'login',
4
+ refresh: 'refresh'
5
+ };
6
+ const getIpSuffixCallback = (req) => {
7
+ const ip = req.ip || 'undefined';
8
+ return [ip];
9
+ };
10
+ const getEmailSuffixCallback = (req) => {
11
+ var _a;
12
+ const email = ((_a = req.body) === null || _a === void 0 ? void 0 : _a.email) || 'unknown';
13
+ return [email];
14
+ };
15
+ const getIpEmailSuffixCallback = (req) => {
16
+ var _a;
17
+ const email = ((_a = req.body) === null || _a === void 0 ? void 0 : _a.email) || 'unknown';
18
+ const ip = req.ip || 'unknown';
19
+ return [ip, email];
20
+ };
21
+ const suffixCallbacks = {
22
+ 'ip': getIpSuffixCallback,
23
+ 'ipMail': getIpEmailSuffixCallback,
24
+ 'mail': getEmailSuffixCallback,
25
+ };
26
+ export const RedisKeyFactory = {
27
+ prefixes: prefixes,
28
+ suffixes: suffixCallbacks
29
+ };
@@ -0,0 +1,20 @@
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
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,8 +1,9 @@
1
1
  import { PostgreDbConfig } from '../db/pg/index.js';
2
2
  import { MsSqlDbConfig } from '../db/mssql/index.js';
3
- import { CorsConfig, RateLimitConfig } from '../middlewares/index.js';
3
+ import { CorsConfig } from '../middlewares/index.js';
4
4
  import { DbProvider } from './DbProvider.js';
5
+ import { RateLimitConfig } from '../rateLimit/types.js';
5
6
  export type DbConfig = PostgreDbConfig | MsSqlDbConfig;
6
- export declare const createExpressApp: (isProd: boolean, dbConfig: DbConfig, corsConfig: CorsConfig, rateLimitConfig?: RateLimitConfig) => import("express-serve-static-core").Express;
7
+ export declare const createExpressApp: (isProd: boolean, dbConfig: DbConfig, corsConfig: CorsConfig, rateLimitConfig?: RateLimitConfig) => Promise<import("express-serve-static-core").Express>;
7
8
  export declare const isProd: () => boolean;
8
9
  export declare const getDbProvider: () => DbProvider;
@@ -1,12 +1,22 @@
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
+ };
1
10
  import express from 'express';
2
11
  import { initDb as postgreInitDb } from '../db/pg/index.js';
3
12
  import { initDb as msSqlInitDb } from '../db/mssql/index.js';
4
- import { initCors, setRateLimit } from '../middlewares/index.js';
13
+ import { initCors } from '../middlewares/index.js';
14
+ import { createGlobalRateLimiter } from '../rateLimit/createRateLimiter.js';
5
15
  const env = {
6
16
  isProd: true,
7
17
  provider: undefined
8
18
  };
9
- export const createExpressApp = (isProd, dbConfig, corsConfig, rateLimitConfig) => {
19
+ export const createExpressApp = (isProd, dbConfig, corsConfig, rateLimitConfig) => __awaiter(void 0, void 0, void 0, function* () {
10
20
  const app = express();
11
21
  env.isProd = isProd;
12
22
  env.provider = dbConfig.provider;
@@ -19,9 +29,9 @@ export const createExpressApp = (isProd, dbConfig, corsConfig, rateLimitConfig)
19
29
  break;
20
30
  }
21
31
  initCors(corsConfig);
22
- setRateLimit(app, rateLimitConfig);
32
+ app.use(createGlobalRateLimiter(rateLimitConfig));
23
33
  app.use(express.json());
24
34
  return app;
25
- };
35
+ });
26
36
  export const isProd = () => env.isProd;
27
37
  export const getDbProvider = () => env.provider;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mielk-api",
3
- "version": "1.3.9",
3
+ "version": "1.4.0",
4
4
  "keywords": [],
5
5
  "author": "mielk",
6
6
  "description": "Wrapper for API operations",
@@ -32,6 +32,11 @@
32
32
  "types": "./dist/middlewares/index.d.ts",
33
33
  "import": "./dist/middlewares/index.js",
34
34
  "default": "./dist/middlewares/index.js"
35
+ },
36
+ "./rate-limit": {
37
+ "types": "./dist/rateLimit/index.d.ts",
38
+ "import": "./dist/rateLimit/index.js",
39
+ "default": "./dist/rateLimit/index.js"
35
40
  }
36
41
  },
37
42
  "scripts": {
@@ -71,7 +76,6 @@
71
76
  ],
72
77
  "peerDependencies": {
73
78
  "express": "^5",
74
- "express-rate-limit": "^8",
75
79
  "zod": "^4"
76
80
  },
77
81
  "optionalDependencies": {
@@ -79,7 +83,8 @@
79
83
  },
80
84
  "dependencies": {
81
85
  "cors": "^2.8.6",
82
- "mielk-fn": "^1.1.1",
86
+ "express-rate-limit": "^8.3.1",
87
+ "mielk-fn": "^1.2.0",
83
88
  "pg": "^8.20.0",
84
89
  "tunnel-ssh": "^5.2.0"
85
90
  }
@@ -1,6 +0,0 @@
1
- import { Express } from 'express';
2
- export interface RateLimitConfig {
3
- windowsMs?: number;
4
- max?: number;
5
- }
6
- export declare const setRateLimit: (app: Express, customConfig?: RateLimitConfig) => void;
@@ -1,10 +0,0 @@
1
- import rateLimit from 'express-rate-limit';
2
- const rateLimitConfig = {
3
- windowMs: 60 * 1000,
4
- max: 100,
5
- };
6
- export const setRateLimit = (app, customConfig) => {
7
- const config = Object.assign(Object.assign({}, rateLimitConfig), customConfig);
8
- const limiter = rateLimit(config);
9
- app.use(limiter);
10
- };