fiberx-backend-toolkit 0.0.74 → 0.0.76
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/config/constants.d.ts +30 -1
- package/dist/config/constants.js +71 -2
- package/dist/middle_ware/cookie_manager_middle_ware.d.ts +1 -1
- package/dist/middle_ware/cookie_manager_middle_ware.js +3 -3
- package/dist/middle_ware/cors_middle_ware.d.ts +1 -0
- package/dist/middle_ware/cors_middle_ware.js +30 -36
- package/dist/middle_ware/rate_limiter_middle_ware.d.ts +1 -0
- package/dist/middle_ware/rate_limiter_middle_ware.js +6 -4
- package/dist/middle_ware/secure_headers_middle_ware.d.ts +7 -6
- package/dist/middle_ware/secure_headers_middle_ware.js +29 -41
- package/dist/types/middle_ware_type.d.ts +8 -4
- package/dist/utils/input_transformer_util.d.ts +1 -0
- package/dist/utils/input_transformer_util.js +8 -0
- package/package.json +1 -1
|
@@ -16,7 +16,36 @@ export declare const DEVICE_ID_COOKIE_MAX_AGE: number;
|
|
|
16
16
|
export declare const DEVICE_ID_COOKIE_NAME = "device_id";
|
|
17
17
|
export declare const DEVICE_ID_HEADERS_NAME = "X-Device-Id";
|
|
18
18
|
export declare const CORS_ALLOWED_METHODS: string[];
|
|
19
|
-
export declare const
|
|
19
|
+
export declare const HEADERS_KEY_NAME: {
|
|
20
|
+
readonly content_type: "Content-Type";
|
|
21
|
+
readonly authorization: "Authorization";
|
|
22
|
+
readonly user_agent: "User-Agent";
|
|
23
|
+
readonly origin: "Origin";
|
|
24
|
+
readonly vary: "Vary";
|
|
25
|
+
readonly device_id: "X-Device-Id";
|
|
26
|
+
readonly device_name: "X-Device-Name";
|
|
27
|
+
readonly request_id: "X-Request-Id";
|
|
28
|
+
readonly rate_limit_limit: "X-RateLimit-Limit";
|
|
29
|
+
readonly rate_limit_remaining: "X-RateLimit-Remaining";
|
|
30
|
+
readonly rate_limit_reset: "X-RateLimit-Reset";
|
|
31
|
+
readonly rate_limit_retry_after: "X-RateLimit-Retry-After";
|
|
32
|
+
readonly login_challenge_token: "X-Login-Challenge-Token";
|
|
33
|
+
readonly csrf_token_header: "X-CSRF-Token";
|
|
34
|
+
readonly access_control_allow_origin: "Access-Control-Allow-Origin";
|
|
35
|
+
readonly access_control_allow_methods: "Access-Control-Allow-Methods";
|
|
36
|
+
readonly access_control_allow_headers: "Access-Control-Allow-Headers";
|
|
37
|
+
readonly access_control_allow_credentials: "Access-Control-Allow-Credentials";
|
|
38
|
+
readonly access_control_max_age: "Access-Control-Max-Age";
|
|
39
|
+
readonly xss_protection: "X-XSS-Protection";
|
|
40
|
+
readonly content_type_options: "X-Content-Type-Options";
|
|
41
|
+
readonly frame_options: "X-Frame-Options";
|
|
42
|
+
readonly referrer_policy: "Referrer-Policy";
|
|
43
|
+
readonly content_security_policy: "Content-Security-Policy";
|
|
44
|
+
readonly strict_transport_security: "Strict-Transport-Security";
|
|
45
|
+
readonly cross_origin_resource_policy: "Cross-Origin-Resource-Policy";
|
|
46
|
+
readonly cross_origin_opener_policy: "Cross-Origin-Opener-Policy";
|
|
47
|
+
};
|
|
48
|
+
export declare const CORS_ALLOWED_HEADERS: ("X-Request-Id" | "X-Device-Id" | "Content-Type" | "Authorization" | "User-Agent" | "Origin" | "Vary" | "X-Device-Name" | "X-RateLimit-Limit" | "X-RateLimit-Remaining" | "X-RateLimit-Reset" | "X-Login-Challenge-Token" | "X-CSRF-Token" | "Access-Control-Allow-Origin" | "Access-Control-Allow-Methods" | "Access-Control-Allow-Headers" | "Access-Control-Allow-Credentials" | "Access-Control-Max-Age")[];
|
|
20
49
|
export declare const CORS_MAX_AGE_IN_SECONDS = 600;
|
|
21
50
|
export declare const CORS_MAX_AGE_IN_MICRO_SECONDS: number;
|
|
22
51
|
export declare const REQUEST_RATE_LIMITTER_OPTIONS: {
|
package/dist/config/constants.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DEFAULT_CONTENT_CACHE_TTL = exports.DEFAULT_CONTENT_LANG = exports.DEFAULT_CONTENT_URL = exports.CONTENT_LANG_KEY = exports.CONTENT_URL_KEY = exports.CONTENT_CACHE_TTL_KEY = exports.EMAIL_ENQUEUE_UTIL_FILE_NAME = exports.EMAIL_ENQUEUE_TYPES_FILE_NAME = exports.DEFAULT_RBAC_CACHE_TTL = exports.DEFAULT_RBAC_CACHE_KEY = exports.DEFAULT_TOTP_WINDOW = exports.DEFAULT_TOTP_DIGITS = exports.DEFAULT_TOTP_STEP = exports.ALPHABET_CORPUS = exports.CHARACTER_CORPUS = exports.REQUEST_RATE_LIMITTER_OPTIONS = exports.CORS_MAX_AGE_IN_MICRO_SECONDS = exports.CORS_MAX_AGE_IN_SECONDS = exports.CORS_ALLOWED_HEADERS = exports.CORS_ALLOWED_METHODS = exports.DEVICE_ID_HEADERS_NAME = exports.DEVICE_ID_COOKIE_NAME = exports.DEVICE_ID_COOKIE_MAX_AGE = exports.REQUEST_ID_HEADERS_NAME = exports.REQUEST_ID_COOKIE_NAME = exports.REQUEST_ID_COOKIE_MAX_AGE = exports.SEQUELIZE_SEEDER_META_TABLE_NAME = exports.SEQUELIZE_META_TABLE_NAME = exports.EMAIL_ENQUEUE_DIR = exports.SEEDERS_DIR = exports.MIGRATIONS_DIR = exports.MODELS_DIR = exports.SCHEMA_SNAPSHOTS_DIR = exports.SCHEMAS_DIR = exports.ENV_VAR_DIR = exports.LOG_DIR = exports.BASE_DIR = void 0;
|
|
6
|
+
exports.DEFAULT_CONTENT_CACHE_TTL = exports.DEFAULT_CONTENT_LANG = exports.DEFAULT_CONTENT_URL = exports.CONTENT_LANG_KEY = exports.CONTENT_URL_KEY = exports.CONTENT_CACHE_TTL_KEY = exports.EMAIL_ENQUEUE_UTIL_FILE_NAME = exports.EMAIL_ENQUEUE_TYPES_FILE_NAME = exports.DEFAULT_RBAC_CACHE_TTL = exports.DEFAULT_RBAC_CACHE_KEY = exports.DEFAULT_TOTP_WINDOW = exports.DEFAULT_TOTP_DIGITS = exports.DEFAULT_TOTP_STEP = exports.ALPHABET_CORPUS = exports.CHARACTER_CORPUS = exports.REQUEST_RATE_LIMITTER_OPTIONS = exports.CORS_MAX_AGE_IN_MICRO_SECONDS = exports.CORS_MAX_AGE_IN_SECONDS = exports.CORS_ALLOWED_HEADERS = exports.HEADERS_KEY_NAME = exports.CORS_ALLOWED_METHODS = exports.DEVICE_ID_HEADERS_NAME = exports.DEVICE_ID_COOKIE_NAME = exports.DEVICE_ID_COOKIE_MAX_AGE = exports.REQUEST_ID_HEADERS_NAME = exports.REQUEST_ID_COOKIE_NAME = exports.REQUEST_ID_COOKIE_MAX_AGE = exports.SEQUELIZE_SEEDER_META_TABLE_NAME = exports.SEQUELIZE_META_TABLE_NAME = exports.EMAIL_ENQUEUE_DIR = exports.SEEDERS_DIR = exports.MIGRATIONS_DIR = exports.MODELS_DIR = exports.SCHEMA_SNAPSHOTS_DIR = exports.SCHEMAS_DIR = exports.ENV_VAR_DIR = exports.LOG_DIR = exports.BASE_DIR = void 0;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
exports.BASE_DIR = process.cwd();
|
|
9
9
|
exports.LOG_DIR = path_1.default.join(exports.BASE_DIR, "logs");
|
|
@@ -24,7 +24,76 @@ exports.DEVICE_ID_COOKIE_NAME = "device_id";
|
|
|
24
24
|
exports.DEVICE_ID_HEADERS_NAME = "X-Device-Id";
|
|
25
25
|
// CORS constants
|
|
26
26
|
exports.CORS_ALLOWED_METHODS = ["GET", "POST", "PATCH", "DELETE", "OPTIONS"];
|
|
27
|
-
exports.
|
|
27
|
+
exports.HEADERS_KEY_NAME = {
|
|
28
|
+
// ======================
|
|
29
|
+
// Standard Headers
|
|
30
|
+
// ======================
|
|
31
|
+
content_type: "Content-Type",
|
|
32
|
+
authorization: "Authorization",
|
|
33
|
+
user_agent: "User-Agent",
|
|
34
|
+
origin: "Origin",
|
|
35
|
+
vary: "Vary",
|
|
36
|
+
// ======================
|
|
37
|
+
// Device
|
|
38
|
+
// ======================
|
|
39
|
+
device_id: "X-Device-Id",
|
|
40
|
+
device_name: "X-Device-Name",
|
|
41
|
+
// ======================
|
|
42
|
+
// Request Tracking
|
|
43
|
+
// ======================
|
|
44
|
+
request_id: "X-Request-Id",
|
|
45
|
+
// ======================
|
|
46
|
+
// Rate limit
|
|
47
|
+
// ======================
|
|
48
|
+
rate_limit_limit: "X-RateLimit-Limit",
|
|
49
|
+
rate_limit_remaining: "X-RateLimit-Remaining",
|
|
50
|
+
rate_limit_reset: "X-RateLimit-Reset",
|
|
51
|
+
rate_limit_retry_after: "X-RateLimit-Retry-After",
|
|
52
|
+
// ======================
|
|
53
|
+
// Auth / Security
|
|
54
|
+
// ======================
|
|
55
|
+
login_challenge_token: "X-Login-Challenge-Token",
|
|
56
|
+
csrf_token_header: "X-CSRF-Token",
|
|
57
|
+
// ======================
|
|
58
|
+
// CORS Headers
|
|
59
|
+
// ======================
|
|
60
|
+
access_control_allow_origin: "Access-Control-Allow-Origin",
|
|
61
|
+
access_control_allow_methods: "Access-Control-Allow-Methods",
|
|
62
|
+
access_control_allow_headers: "Access-Control-Allow-Headers",
|
|
63
|
+
access_control_allow_credentials: "Access-Control-Allow-Credentials",
|
|
64
|
+
access_control_max_age: "Access-Control-Max-Age",
|
|
65
|
+
// ======================
|
|
66
|
+
// Secure Headers
|
|
67
|
+
// ======================
|
|
68
|
+
xss_protection: "X-XSS-Protection",
|
|
69
|
+
content_type_options: "X-Content-Type-Options",
|
|
70
|
+
frame_options: "X-Frame-Options",
|
|
71
|
+
referrer_policy: "Referrer-Policy",
|
|
72
|
+
content_security_policy: "Content-Security-Policy",
|
|
73
|
+
strict_transport_security: "Strict-Transport-Security",
|
|
74
|
+
cross_origin_resource_policy: "Cross-Origin-Resource-Policy",
|
|
75
|
+
cross_origin_opener_policy: "Cross-Origin-Opener-Policy",
|
|
76
|
+
};
|
|
77
|
+
exports.CORS_ALLOWED_HEADERS = [
|
|
78
|
+
exports.HEADERS_KEY_NAME.content_type,
|
|
79
|
+
exports.HEADERS_KEY_NAME.authorization,
|
|
80
|
+
exports.HEADERS_KEY_NAME.user_agent,
|
|
81
|
+
exports.HEADERS_KEY_NAME.origin,
|
|
82
|
+
exports.HEADERS_KEY_NAME.vary,
|
|
83
|
+
exports.HEADERS_KEY_NAME.device_id,
|
|
84
|
+
exports.HEADERS_KEY_NAME.device_name,
|
|
85
|
+
exports.HEADERS_KEY_NAME.request_id,
|
|
86
|
+
exports.HEADERS_KEY_NAME.rate_limit_limit,
|
|
87
|
+
exports.HEADERS_KEY_NAME.rate_limit_remaining,
|
|
88
|
+
exports.HEADERS_KEY_NAME.rate_limit_reset,
|
|
89
|
+
exports.HEADERS_KEY_NAME.login_challenge_token,
|
|
90
|
+
exports.HEADERS_KEY_NAME.csrf_token_header,
|
|
91
|
+
exports.HEADERS_KEY_NAME.access_control_allow_origin,
|
|
92
|
+
exports.HEADERS_KEY_NAME.access_control_allow_methods,
|
|
93
|
+
exports.HEADERS_KEY_NAME.access_control_allow_headers,
|
|
94
|
+
exports.HEADERS_KEY_NAME.access_control_allow_credentials,
|
|
95
|
+
exports.HEADERS_KEY_NAME.access_control_max_age,
|
|
96
|
+
];
|
|
28
97
|
exports.CORS_MAX_AGE_IN_SECONDS = (600); // default: 10 min
|
|
29
98
|
exports.CORS_MAX_AGE_IN_MICRO_SECONDS = (1000 * 60 * 10); // 10 minutes
|
|
30
99
|
// Rate limitter
|
|
@@ -12,7 +12,7 @@ declare class CookieManagerMiddleWare {
|
|
|
12
12
|
private device_id_cookie_name;
|
|
13
13
|
private device_id_header_name;
|
|
14
14
|
private device_id_generator;
|
|
15
|
-
constructor(options
|
|
15
|
+
constructor(options: CookieManagerOptions);
|
|
16
16
|
private getDefaultCookieOptions;
|
|
17
17
|
private handleRequestId;
|
|
18
18
|
private handleDeviceId;
|
|
@@ -27,7 +27,7 @@ class CookieManagerMiddleWare {
|
|
|
27
27
|
device_id_cookie_name;
|
|
28
28
|
device_id_header_name;
|
|
29
29
|
device_id_generator;
|
|
30
|
-
constructor(options
|
|
30
|
+
constructor(options) {
|
|
31
31
|
this.request_id_max_age = options?.request_id_max_age || constants_1.REQUEST_ID_COOKIE_MAX_AGE;
|
|
32
32
|
this.request_id_cookie_name = options.request_id_cookie_name || constants_1.REQUEST_ID_COOKIE_NAME;
|
|
33
33
|
this.request_id_header_name = options.request_id_header_name || constants_1.REQUEST_ID_HEADERS_NAME;
|
|
@@ -50,7 +50,7 @@ class CookieManagerMiddleWare {
|
|
|
50
50
|
}
|
|
51
51
|
// Method to Ensure request_id exists + rolling expiration
|
|
52
52
|
handleRequestId(req, res) {
|
|
53
|
-
let request_id = this.get(req,
|
|
53
|
+
let request_id = this.get(req, this.request_id_cookie_name);
|
|
54
54
|
if (!request_id) {
|
|
55
55
|
request_id = this.request_id_generator();
|
|
56
56
|
this.logger.info(`Generated request_id: ${request_id}`);
|
|
@@ -59,7 +59,7 @@ class CookieManagerMiddleWare {
|
|
|
59
59
|
this.set(res, "request_id", request_id, {
|
|
60
60
|
max_age: this.request_id_max_age,
|
|
61
61
|
});
|
|
62
|
-
res.setHeader(
|
|
62
|
+
res.setHeader(this.request_id_header_name, request_id);
|
|
63
63
|
return request_id;
|
|
64
64
|
}
|
|
65
65
|
// Method to Ensure device_id exists (long-lived)
|
|
@@ -17,57 +17,53 @@ class CorsMiddleWare {
|
|
|
17
17
|
origin_cache = new main_1.InMemoryCacheUtil();
|
|
18
18
|
origins;
|
|
19
19
|
methods;
|
|
20
|
+
headers_key_name;
|
|
20
21
|
headers;
|
|
21
22
|
credentials;
|
|
22
23
|
max_age;
|
|
23
24
|
allow_null_origin;
|
|
24
25
|
origin_resolver;
|
|
25
26
|
constructor(options = {}) {
|
|
26
|
-
this.origins = options.origins
|
|
27
|
+
this.origins = options.origins ?? [];
|
|
27
28
|
this.methods = options.methods ?? constants_1.CORS_ALLOWED_METHODS;
|
|
28
|
-
this.headers = options.headers ?? constants_1.CORS_ALLOWED_HEADERS;
|
|
29
29
|
this.credentials = options.credentials ?? true;
|
|
30
30
|
this.max_age = options.max_age ?? constants_1.CORS_MAX_AGE_IN_SECONDS;
|
|
31
|
-
this.
|
|
32
|
-
|
|
31
|
+
this.headers_key_name = options.headers_key_name ?? constants_1.HEADERS_KEY_NAME;
|
|
32
|
+
this.allow_null_origin = options.allow_null_origin ?? false;
|
|
33
|
+
this.origin_resolver = options.origin_resolver ?? null;
|
|
34
|
+
// generate headers array dynamically from the key names
|
|
35
|
+
this.headers = Object.values(this.headers_key_name);
|
|
33
36
|
main_1.SafeExecuteUtil.setNamedInstance(this.name, this);
|
|
34
37
|
}
|
|
35
|
-
//
|
|
38
|
+
// -------------------------
|
|
39
|
+
// Origin resolution
|
|
40
|
+
// -------------------------
|
|
36
41
|
async resolveAllowedOrigins() {
|
|
37
|
-
if (Array.isArray(this.origins) && this.origins.length)
|
|
42
|
+
if (Array.isArray(this.origins) && this.origins.length)
|
|
38
43
|
return this.origins;
|
|
39
|
-
|
|
40
|
-
else if (this.origin_resolver && typeof this.origin_resolver === "function") {
|
|
44
|
+
if (this.origin_resolver) {
|
|
41
45
|
const result = await this.origin_resolver();
|
|
42
46
|
return Array.isArray(result) ? result : [];
|
|
43
47
|
}
|
|
44
48
|
return [];
|
|
45
49
|
}
|
|
46
|
-
// Method to validate origin
|
|
47
50
|
async isValidOrigin(origin) {
|
|
48
|
-
if (!origin)
|
|
51
|
+
if (!origin)
|
|
49
52
|
return this.allow_null_origin;
|
|
50
|
-
|
|
51
|
-
// Cached
|
|
52
|
-
if (this.origin_cache.has(origin)) {
|
|
53
|
+
if (this.origin_cache.has(origin))
|
|
53
54
|
return true;
|
|
54
|
-
|
|
55
|
-
// Function validator
|
|
56
|
-
else if (this.origin_resolver && typeof this.origin_resolver === "function") {
|
|
55
|
+
if (this.origin_resolver) {
|
|
57
56
|
const result = await this.origin_resolver(origin);
|
|
58
57
|
if (typeof result === "boolean") {
|
|
59
|
-
if (result)
|
|
58
|
+
if (result)
|
|
60
59
|
this.origin_cache.set(origin, true, constants_1.CORS_MAX_AGE_IN_MICRO_SECONDS);
|
|
61
|
-
}
|
|
62
60
|
return result;
|
|
63
61
|
}
|
|
64
62
|
}
|
|
65
|
-
// List validator
|
|
66
63
|
const allowed = await this.resolveAllowedOrigins();
|
|
67
64
|
const is_valid = allowed.includes(origin);
|
|
68
|
-
if (is_valid)
|
|
65
|
+
if (is_valid)
|
|
69
66
|
this.origin_cache.set(origin, true, constants_1.CORS_MAX_AGE_IN_MICRO_SECONDS);
|
|
70
|
-
}
|
|
71
67
|
return is_valid;
|
|
72
68
|
}
|
|
73
69
|
// -------------------------
|
|
@@ -75,13 +71,12 @@ class CorsMiddleWare {
|
|
|
75
71
|
// -------------------------
|
|
76
72
|
async getRequestOrigin(req) {
|
|
77
73
|
const { headers, protocol } = req;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
const originHeader = headers?.[this.headers_key_name.origin.toLowerCase()];
|
|
75
|
+
if (originHeader)
|
|
76
|
+
return originHeader;
|
|
77
|
+
if (headers?.referer)
|
|
82
78
|
return new URL(headers.referer).origin;
|
|
83
|
-
}
|
|
84
|
-
return `${protocol}://${req?.get('host')}`;
|
|
79
|
+
return `${protocol}://${req.get("host")}`;
|
|
85
80
|
}
|
|
86
81
|
// -------------------------
|
|
87
82
|
// Middleware
|
|
@@ -89,26 +84,25 @@ class CorsMiddleWare {
|
|
|
89
84
|
async middleWare(req, res, next) {
|
|
90
85
|
const origin = await this.getRequestOrigin(req);
|
|
91
86
|
const is_allowed = await this.isValidOrigin(origin);
|
|
92
|
-
res.setHeader(
|
|
87
|
+
res.setHeader(this.headers_key_name.vary, this.headers_key_name.origin);
|
|
93
88
|
if (!is_allowed) {
|
|
94
89
|
this.logger.error(`Blocked CORS request from origin: ${origin}`);
|
|
95
90
|
return res.status(403).json({ status: "error", code: 403, msg: "cors_blocked" });
|
|
96
91
|
}
|
|
97
|
-
|
|
98
|
-
res.setHeader(
|
|
99
|
-
res.setHeader(
|
|
92
|
+
// Use constants from this.headers_key_name
|
|
93
|
+
res.setHeader(this.headers_key_name.access_control_allow_origin, origin);
|
|
94
|
+
res.setHeader(this.headers_key_name.access_control_allow_methods, this.methods.join(","));
|
|
95
|
+
res.setHeader(this.headers_key_name.access_control_allow_headers, this.headers.join(","));
|
|
100
96
|
if (this.credentials) {
|
|
101
|
-
res.setHeader(
|
|
97
|
+
res.setHeader(this.headers_key_name.access_control_allow_credentials, "true");
|
|
102
98
|
}
|
|
103
99
|
if (this.max_age) {
|
|
104
|
-
res.setHeader(
|
|
100
|
+
res.setHeader(this.headers_key_name.access_control_max_age, this.max_age.toString());
|
|
105
101
|
}
|
|
106
|
-
if (req.method === "OPTIONS")
|
|
102
|
+
if (req.method === "OPTIONS")
|
|
107
103
|
return res.sendStatus(204);
|
|
108
|
-
}
|
|
109
104
|
return next();
|
|
110
105
|
}
|
|
111
|
-
;
|
|
112
106
|
}
|
|
113
107
|
__decorate([
|
|
114
108
|
main_1.SafeExecuteUtil.safeExecuteReturn("cors_middle_ware", []),
|
|
@@ -19,10 +19,12 @@ class RateLimiterMiddleWare {
|
|
|
19
19
|
cache;
|
|
20
20
|
logger;
|
|
21
21
|
key_generator;
|
|
22
|
+
headers_key_name;
|
|
22
23
|
constructor(options = {}) {
|
|
23
24
|
this.window_ms = options?.window_ms ?? constants_1.REQUEST_RATE_LIMITTER_OPTIONS.window_ms;
|
|
24
25
|
this.max_requests = options?.max_requests ?? constants_1.REQUEST_RATE_LIMITTER_OPTIONS.max_requests;
|
|
25
26
|
this.message = options.message ?? constants_1.REQUEST_RATE_LIMITTER_OPTIONS.message;
|
|
27
|
+
this.headers_key_name = options.headers_key_name ?? constants_1.HEADERS_KEY_NAME;
|
|
26
28
|
this.key_generator = options?.key_generator || this.generateRequestIdentitfier;
|
|
27
29
|
this.cache = new main_1.InMemoryCacheUtil();
|
|
28
30
|
this.logger = new main_1.LoggerUtil(this.name);
|
|
@@ -54,12 +56,12 @@ class RateLimiterMiddleWare {
|
|
|
54
56
|
const remaining = Math.max(this.max_requests - record.count, 0);
|
|
55
57
|
const reset_seconds = this.getResetSeconds(record.expires_at);
|
|
56
58
|
// RFC-ish headers
|
|
57
|
-
res.setHeader(
|
|
58
|
-
res.setHeader(
|
|
59
|
-
res.setHeader(
|
|
59
|
+
res.setHeader(this.headers_key_name.rate_limit_limit, this.max_requests);
|
|
60
|
+
res.setHeader(this.headers_key_name.rate_limit_remaining, remaining);
|
|
61
|
+
res.setHeader(this.headers_key_name.rate_limit_reset, reset_seconds);
|
|
60
62
|
if (record.count > this.max_requests) {
|
|
61
63
|
this.logger.alert(`⚠️ Rate limit exceeded for key: ${key} (retry in ${reset_seconds}s)`);
|
|
62
|
-
res.setHeader(
|
|
64
|
+
res.setHeader(this.headers_key_name.rate_limit_retry_after, reset_seconds);
|
|
63
65
|
return res.status(429).json({
|
|
64
66
|
status: "error",
|
|
65
67
|
code: 429,
|
|
@@ -3,17 +3,18 @@ import { SecureHeadersOptions } from "../types/middle_ware_type";
|
|
|
3
3
|
declare class SecureHeadersMiddleWare {
|
|
4
4
|
private name;
|
|
5
5
|
private logger;
|
|
6
|
-
private readonly enabled
|
|
7
|
-
private readonly HSTS_enabled
|
|
8
|
-
private readonly HSTS_max_age
|
|
6
|
+
private readonly enabled;
|
|
7
|
+
private readonly HSTS_enabled;
|
|
8
|
+
private readonly HSTS_max_age;
|
|
9
|
+
private readonly HSTS_Extras;
|
|
9
10
|
private readonly frame_options;
|
|
10
|
-
private readonly xss_protection
|
|
11
|
-
private readonly content_type_options
|
|
11
|
+
private readonly xss_protection;
|
|
12
|
+
private readonly content_type_options;
|
|
12
13
|
private readonly referrer_policy;
|
|
13
14
|
private readonly content_security_policy;
|
|
14
|
-
private readonly HSTS_Extras;
|
|
15
15
|
private readonly cors_policy;
|
|
16
16
|
private readonly cors_opener_policy;
|
|
17
|
+
private headers_key_name;
|
|
17
18
|
constructor(options?: SecureHeadersOptions);
|
|
18
19
|
private getDefaultContentSecurityPolicy;
|
|
19
20
|
private getContentSecurityPolicyString;
|
|
@@ -10,35 +10,37 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const main_1 = require("../utils/main");
|
|
13
|
+
const constants_1 = require("../config/constants");
|
|
13
14
|
class SecureHeadersMiddleWare {
|
|
14
15
|
name = "secure_headers_middle_ware";
|
|
15
16
|
logger = new main_1.LoggerUtil(this.name);
|
|
16
17
|
enabled;
|
|
17
18
|
HSTS_enabled;
|
|
18
19
|
HSTS_max_age;
|
|
20
|
+
HSTS_Extras;
|
|
19
21
|
frame_options;
|
|
20
22
|
xss_protection;
|
|
21
23
|
content_type_options;
|
|
22
24
|
referrer_policy;
|
|
23
25
|
content_security_policy;
|
|
24
|
-
HSTS_Extras;
|
|
25
26
|
cors_policy;
|
|
26
27
|
cors_opener_policy;
|
|
28
|
+
headers_key_name;
|
|
27
29
|
constructor(options = {}) {
|
|
28
30
|
this.enabled = options.enabled ?? true;
|
|
29
|
-
this.HSTS_enabled = options
|
|
30
|
-
this.HSTS_max_age = options
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
|
|
37
|
-
this.cors_policy = options
|
|
38
|
-
this.cors_opener_policy = options
|
|
31
|
+
this.HSTS_enabled = options.HSTS_enabled ?? main_1.InputValidatorUtil.isProduction();
|
|
32
|
+
this.HSTS_max_age = options.HSTS_max_age ?? 63072000;
|
|
33
|
+
this.HSTS_Extras = options.HSTS_Extras ?? "includeSubDomains; preload;";
|
|
34
|
+
this.frame_options = options.frame_options ?? "SAMEORIGIN";
|
|
35
|
+
this.xss_protection = options.xss_protection ?? true;
|
|
36
|
+
this.content_type_options = options.content_type_options ?? true;
|
|
37
|
+
this.referrer_policy = options.referrer_policy ?? "no-referrer-when-downgrade";
|
|
38
|
+
this.content_security_policy = options.content_security_policy ?? this.getDefaultContentSecurityPolicy();
|
|
39
|
+
this.cors_policy = options.cors_policy ?? "same-origin";
|
|
40
|
+
this.cors_opener_policy = options.cors_opener_policy ?? "same-origin";
|
|
41
|
+
this.headers_key_name = options.headers_key_name ?? constants_1.HEADERS_KEY_NAME;
|
|
39
42
|
main_1.SafeExecuteUtil.setNamedInstance(this.name, this);
|
|
40
43
|
}
|
|
41
|
-
// Method to get default content security policy
|
|
42
44
|
getDefaultContentSecurityPolicy() {
|
|
43
45
|
return {
|
|
44
46
|
"default-src": ["'self'"],
|
|
@@ -52,46 +54,32 @@ class SecureHeadersMiddleWare {
|
|
|
52
54
|
"frame-ancestors": ["'self'"],
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
|
-
// Method to convert content security policy object to string
|
|
56
57
|
getContentSecurityPolicyString(policy) {
|
|
57
58
|
return Object.entries(policy)
|
|
58
59
|
.map(([key, value]) => `${key} ${value.join(" ")}`)
|
|
59
60
|
.join("; ");
|
|
60
61
|
}
|
|
61
|
-
// -------------------------
|
|
62
|
-
// Middleware
|
|
63
|
-
// -------------------------
|
|
64
62
|
async middleWare(req, res, next) {
|
|
65
|
-
if (!this.enabled)
|
|
63
|
+
if (!this.enabled)
|
|
66
64
|
return next();
|
|
67
|
-
}
|
|
68
65
|
// Prevent XSS
|
|
69
|
-
|
|
70
|
-
res.setHeader("X-XSS-Protection", "1; mode=block");
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
res.setHeader("X-XSS-Protection", "0");
|
|
74
|
-
}
|
|
66
|
+
res.setHeader(this.headers_key_name.xss_protection ?? "X-XSS-Protection", this.xss_protection ? "1; mode=block" : "0");
|
|
75
67
|
// Prevent MIME-type sniffing
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
res.setHeader("
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (this.cors_opener_policy) {
|
|
86
|
-
res.setHeader("Cross-Origin-Opener-Policy", this.cors_opener_policy);
|
|
87
|
-
}
|
|
68
|
+
res.setHeader(this.headers_key_name.content_type_options ?? "X-Content-Type-Options", this.content_type_options ? "nosniff" : "0");
|
|
69
|
+
// Clickjacking protection
|
|
70
|
+
res.setHeader(this.headers_key_name.frame_options ?? "X-Frame-Options", this.frame_options);
|
|
71
|
+
// Referrer Policy
|
|
72
|
+
res.setHeader(this.headers_key_name.referrer_policy ?? "Referrer-Policy", this.referrer_policy);
|
|
73
|
+
// Cross-Origin Resource Policy
|
|
74
|
+
res.setHeader(this.headers_key_name.cross_origin_resource_policy ?? "Cross-Origin-Resource-Policy", this.cors_policy);
|
|
75
|
+
// Cross-Origin Opener Policy
|
|
76
|
+
res.setHeader(this.headers_key_name.cross_origin_opener_policy ?? "Cross-Origin-Opener-Policy", this.cors_opener_policy);
|
|
88
77
|
// Content Security Policy
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
78
|
+
const csp_string = this.getContentSecurityPolicyString(this.content_security_policy);
|
|
79
|
+
res.setHeader(this.headers_key_name.content_security_policy ?? "Content-Security-Policy", csp_string);
|
|
80
|
+
// HSTS
|
|
93
81
|
if (this.HSTS_enabled) {
|
|
94
|
-
res.setHeader("Strict-Transport-Security", `max-age=${this.HSTS_max_age}; ${this.HSTS_Extras}`);
|
|
82
|
+
res.setHeader(this.headers_key_name.strict_transport_security ?? "Strict-Transport-Security", `max-age=${this.HSTS_max_age}; ${this.HSTS_Extras}`);
|
|
95
83
|
}
|
|
96
84
|
return next();
|
|
97
85
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Model } from "sequelize";
|
|
2
2
|
import { Request, Response, NextFunction } from "express";
|
|
3
|
+
import { HEADERS_KEY_NAME } from "../config/constants";
|
|
3
4
|
export type CorsOriginResolver = ((origin?: string) => boolean | Promise<boolean>) | (() => Promise<string[]>);
|
|
4
5
|
export interface CorsOptions {
|
|
5
6
|
origins?: string[];
|
|
@@ -9,11 +10,13 @@ export interface CorsOptions {
|
|
|
9
10
|
credentials?: boolean;
|
|
10
11
|
max_age?: number;
|
|
11
12
|
allow_null_origin?: boolean;
|
|
13
|
+
headers_key_name?: typeof HEADERS_KEY_NAME;
|
|
12
14
|
}
|
|
13
15
|
export interface RateLimiterOptions {
|
|
14
16
|
window_ms?: number;
|
|
15
17
|
max_requests?: number;
|
|
16
18
|
message?: string;
|
|
19
|
+
headers_key_name?: typeof HEADERS_KEY_NAME;
|
|
17
20
|
key_generator?: (req: Request) => string;
|
|
18
21
|
}
|
|
19
22
|
export type RateLimitRecord = {
|
|
@@ -22,12 +25,12 @@ export type RateLimitRecord = {
|
|
|
22
25
|
};
|
|
23
26
|
export interface CookieManagerOptions {
|
|
24
27
|
request_id_max_age?: number;
|
|
25
|
-
request_id_cookie_name
|
|
26
|
-
request_id_header_name
|
|
28
|
+
request_id_cookie_name: string;
|
|
29
|
+
request_id_header_name: string;
|
|
27
30
|
request_id_generator?: () => string;
|
|
28
31
|
device_id_max_age?: number;
|
|
29
|
-
device_id_cookie_name
|
|
30
|
-
device_id_header_name
|
|
32
|
+
device_id_cookie_name: string;
|
|
33
|
+
device_id_header_name: string;
|
|
31
34
|
device_id_generator?: () => string;
|
|
32
35
|
}
|
|
33
36
|
export interface ForceHTTPSOptions {
|
|
@@ -47,6 +50,7 @@ export interface SecureHeadersOptions {
|
|
|
47
50
|
HSTS_Extras?: string;
|
|
48
51
|
cors_policy?: string;
|
|
49
52
|
cors_opener_policy?: string;
|
|
53
|
+
headers_key_name?: typeof HEADERS_KEY_NAME;
|
|
50
54
|
}
|
|
51
55
|
export interface ResponseFormatterOptions {
|
|
52
56
|
include_request_id?: boolean;
|
|
@@ -113,5 +113,6 @@ declare class InputTransformerUtil {
|
|
|
113
113
|
static formatLinksObject(links: Record<string, string>): Record<string, string>;
|
|
114
114
|
static buildNestedObject(paths: string[]): any;
|
|
115
115
|
static minifyHtml(html: string): string;
|
|
116
|
+
static joinStringsBy(parts: string | string[], separator: string): string;
|
|
116
117
|
}
|
|
117
118
|
export default InputTransformerUtil;
|
|
@@ -349,5 +349,13 @@ class InputTransformerUtil {
|
|
|
349
349
|
});
|
|
350
350
|
return html;
|
|
351
351
|
}
|
|
352
|
+
// Method to join strings by
|
|
353
|
+
static joinStringsBy(parts, separator) {
|
|
354
|
+
// Ensure parts is always a flat array of strings
|
|
355
|
+
const flat_parts = Array.isArray(parts) ? parts : [parts];
|
|
356
|
+
// Filter out empty/null/undefined strings to avoid extra separators
|
|
357
|
+
const valid_parts = flat_parts.filter(part => part !== null && part !== undefined && part !== "");
|
|
358
|
+
return valid_parts.join(separator);
|
|
359
|
+
}
|
|
352
360
|
}
|
|
353
361
|
exports.default = InputTransformerUtil;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fiberx-backend-toolkit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.76",
|
|
4
4
|
"description": "A TypeScript backend toolkit providing shared domain logic, infrastructure helpers, and utilities for FiberX server-side applications and services.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./dist/index.js",
|