hi-secure 1.0.14 → 1.0.16
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/adapters/ArgonAdapter.d.ts +1 -1
- package/dist/adapters/ArgonAdapter.d.ts.map +1 -1
- package/dist/adapters/ArgonAdapter.js +43 -5
- package/dist/adapters/ArgonAdapter.js.map +1 -1
- package/dist/adapters/BcryptAdapter.d.ts.map +1 -1
- package/dist/adapters/BcryptAdapter.js +43 -3
- package/dist/adapters/BcryptAdapter.js.map +1 -1
- package/dist/adapters/ExpressRLAdapter.d.ts.map +1 -1
- package/dist/adapters/ExpressRLAdapter.js +48 -6
- package/dist/adapters/ExpressRLAdapter.js.map +1 -1
- package/dist/adapters/ExpressValidatorAdapter.d.ts.map +1 -1
- package/dist/adapters/ExpressValidatorAdapter.js +50 -10
- package/dist/adapters/ExpressValidatorAdapter.js.map +1 -1
- package/dist/adapters/GoogleAdapter.d.ts.map +1 -1
- package/dist/adapters/GoogleAdapter.js +82 -16
- package/dist/adapters/GoogleAdapter.js.map +1 -1
- package/dist/adapters/JWTAdapter.d.ts.map +1 -1
- package/dist/adapters/JWTAdapter.js +104 -15
- package/dist/adapters/JWTAdapter.js.map +1 -1
- package/dist/adapters/RLFlexibleAdapter.d.ts.map +1 -1
- package/dist/adapters/RLFlexibleAdapter.js +87 -12
- package/dist/adapters/RLFlexibleAdapter.js.map +1 -1
- package/dist/adapters/SanitizeHtmlAdapter.d.ts.map +1 -1
- package/dist/adapters/SanitizeHtmlAdapter.js +81 -13
- package/dist/adapters/SanitizeHtmlAdapter.js.map +1 -1
- package/dist/adapters/XSSAdapter.d.ts +1 -1
- package/dist/adapters/XSSAdapter.d.ts.map +1 -1
- package/dist/adapters/XSSAdapter.js +137 -20
- package/dist/adapters/XSSAdapter.js.map +1 -1
- package/dist/adapters/ZodAdapter.d.ts +1 -1
- package/dist/adapters/ZodAdapter.d.ts.map +1 -1
- package/dist/adapters/ZodAdapter.js +13 -8
- package/dist/adapters/ZodAdapter.js.map +1 -1
- package/dist/core/HiSecure.d.ts +3 -4
- package/dist/core/HiSecure.d.ts.map +1 -1
- package/dist/core/HiSecure.js +108 -121
- package/dist/core/HiSecure.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/logging/index.d.ts.map +1 -1
- package/dist/logging/index.js +2 -0
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/morganSetup.d.ts.map +1 -1
- package/dist/logging/morganSetup.js +22 -1
- package/dist/logging/morganSetup.js.map +1 -1
- package/dist/logging/winstonSetup.d.ts.map +1 -1
- package/dist/logging/winstonSetup.js +61 -3
- package/dist/logging/winstonSetup.js.map +1 -1
- package/dist/managers/AuthManager.d.ts +2 -2
- package/dist/managers/AuthManager.d.ts.map +1 -1
- package/dist/managers/AuthManager.js +167 -31
- package/dist/managers/AuthManager.js.map +1 -1
- package/dist/managers/CorsManager.d.ts.map +1 -1
- package/dist/managers/CorsManager.js +46 -11
- package/dist/managers/CorsManager.js.map +1 -1
- package/dist/managers/HashManager.d.ts +1 -1
- package/dist/managers/HashManager.d.ts.map +1 -1
- package/dist/managers/HashManager.js +127 -17
- package/dist/managers/HashManager.js.map +1 -1
- package/dist/managers/JsonManager.d.ts +1 -1
- package/dist/managers/JsonManager.d.ts.map +1 -1
- package/dist/managers/JsonManager.js +99 -16
- package/dist/managers/JsonManager.js.map +1 -1
- package/dist/managers/RateLimitManager.d.ts +1 -1
- package/dist/managers/RateLimitManager.d.ts.map +1 -1
- package/dist/managers/RateLimitManager.js +46 -22
- package/dist/managers/RateLimitManager.js.map +1 -1
- package/dist/managers/SanitizerManager.d.ts.map +1 -1
- package/dist/managers/SanitizerManager.js +112 -15
- package/dist/managers/SanitizerManager.js.map +1 -1
- package/dist/managers/ValidatorManager.d.ts.map +1 -1
- package/dist/managers/ValidatorManager.js +90 -7
- package/dist/managers/ValidatorManager.js.map +1 -1
- package/package.json +19 -1
- package/readme.md +3 -6
- package/src/adapters/ArgonAdapter.ts +55 -6
- package/src/adapters/BcryptAdapter.ts +56 -8
- package/src/adapters/ExpressRLAdapter.ts +62 -9
- package/src/adapters/ExpressValidatorAdapter.ts +67 -11
- package/src/adapters/GoogleAdapter.ts +106 -21
- package/src/adapters/JWTAdapter.ts +129 -21
- package/src/adapters/RLFlexibleAdapter.ts +113 -16
- package/src/adapters/SanitizeHtmlAdapter.ts +111 -18
- package/src/adapters/XSSAdapter.ts +183 -39
- package/src/adapters/ZodAdapter.ts +56 -10
- package/src/core/HiSecure.ts +496 -162
- package/src/index.ts +4 -0
- package/src/logging/index.ts +6 -0
- package/src/logging/morganSetup.ts +36 -1
- package/src/logging/winstonSetup.ts +97 -8
- package/src/managers/AuthManager.ts +205 -34
- package/src/managers/CorsManager.ts +63 -16
- package/src/managers/HashManager.ts +156 -19
- package/src/managers/JsonManager.ts +119 -15
- package/src/managers/RateLimitManager.ts +174 -29
- package/src/managers/SanitizerManager.ts +150 -25
- package/src/managers/ValidatorManager.ts +115 -15
|
@@ -1,5 +1,113 @@
|
|
|
1
|
-
import { AdapterError } from "../core/errors/AdapterError.js";
|
|
2
|
-
import { HiSecureConfig } from "../core/types/HiSecureConfig.js";
|
|
1
|
+
// import { AdapterError } from "../core/errors/AdapterError.js";
|
|
2
|
+
// import { HiSecureConfig } from "../core/types/HiSecureConfig.js";
|
|
3
|
+
// import { logger } from "../logging";
|
|
4
|
+
|
|
5
|
+
// interface HashAdapter {
|
|
6
|
+
// hash(value: string): Promise<string>;
|
|
7
|
+
// verify(value: string, hashed: string): Promise<boolean>;
|
|
8
|
+
// }
|
|
9
|
+
|
|
10
|
+
// export interface HashResult {
|
|
11
|
+
// hash: string;
|
|
12
|
+
// algorithm: string;
|
|
13
|
+
// usedFallback: boolean;
|
|
14
|
+
// }
|
|
15
|
+
|
|
16
|
+
// export class HashManager {
|
|
17
|
+
// private config: HiSecureConfig["hashing"];
|
|
18
|
+
// private primaryAdapter: HashAdapter;
|
|
19
|
+
// private fallbackAdapter: HashAdapter | null;
|
|
20
|
+
|
|
21
|
+
// constructor(
|
|
22
|
+
// config: HiSecureConfig["hashing"],
|
|
23
|
+
// primaryAdapter: HashAdapter,
|
|
24
|
+
// fallbackAdapter: HashAdapter | null
|
|
25
|
+
// ) {
|
|
26
|
+
// this.config = config;
|
|
27
|
+
// this.primaryAdapter = primaryAdapter;
|
|
28
|
+
// this.fallbackAdapter = fallbackAdapter;
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
// async hash(value: string, options?: { allowFallback?: boolean }): Promise<HashResult> {
|
|
32
|
+
// try {
|
|
33
|
+
// const hash = await this.primaryAdapter.hash(value);
|
|
34
|
+
// return {
|
|
35
|
+
// hash,
|
|
36
|
+
// algorithm: this.config.primary,
|
|
37
|
+
// usedFallback: false
|
|
38
|
+
// };
|
|
39
|
+
// } catch (err: any) {
|
|
40
|
+
// logger.warn("Primary hashing failed", {
|
|
41
|
+
// error: err.message,
|
|
42
|
+
// algorithm: this.config.primary
|
|
43
|
+
// });
|
|
44
|
+
|
|
45
|
+
// if (!options?.allowFallback || !this.fallbackAdapter) {
|
|
46
|
+
// throw new AdapterError(
|
|
47
|
+
// `Primary hashing (${this.config.primary}) failed. Fallback not allowed.`
|
|
48
|
+
// );
|
|
49
|
+
// }
|
|
50
|
+
|
|
51
|
+
// try {
|
|
52
|
+
// const hash = await this.fallbackAdapter.hash(value);
|
|
53
|
+
|
|
54
|
+
// // Log security downgrade warning
|
|
55
|
+
// logger.warn("SECURITY DOWNGRADE: Using fallback hashing", {
|
|
56
|
+
// from: this.config.primary,
|
|
57
|
+
// to: this.config.fallback
|
|
58
|
+
// });
|
|
59
|
+
|
|
60
|
+
// return {
|
|
61
|
+
// hash,
|
|
62
|
+
// algorithm: this.config.fallback || 'bcrypt',
|
|
63
|
+
// usedFallback: true
|
|
64
|
+
// };
|
|
65
|
+
// } catch (fallbackErr: any) {
|
|
66
|
+
// logger.error("Fallback hashing failed", {
|
|
67
|
+
// error: fallbackErr?.message,
|
|
68
|
+
// });
|
|
69
|
+
// throw new AdapterError(
|
|
70
|
+
// "Both primary and fallback hashing failed."
|
|
71
|
+
// );
|
|
72
|
+
// }
|
|
73
|
+
// }
|
|
74
|
+
// }
|
|
75
|
+
|
|
76
|
+
// async verify(value: string, hashed: string): Promise<boolean> {
|
|
77
|
+
// // primary adapter - first
|
|
78
|
+
// try {
|
|
79
|
+
// return await this.primaryAdapter.verify(value, hashed);
|
|
80
|
+
// } catch (primaryErr: any) {
|
|
81
|
+
// logger.warn("Primary verify failed", {
|
|
82
|
+
// error: primaryErr?.message,
|
|
83
|
+
// });
|
|
84
|
+
|
|
85
|
+
// // fallback exists - try it
|
|
86
|
+
// if (this.fallbackAdapter) {
|
|
87
|
+
// try {
|
|
88
|
+
// return await this.fallbackAdapter.verify(value, hashed);
|
|
89
|
+
// } catch (fallbackErr: any) {
|
|
90
|
+
// logger.error(" Fallback verify failed", {
|
|
91
|
+
// error: fallbackErr?.message,
|
|
92
|
+
// });
|
|
93
|
+
// throw new AdapterError(
|
|
94
|
+
// "Both primary and fallback verify failed."
|
|
95
|
+
// );
|
|
96
|
+
// }
|
|
97
|
+
// }
|
|
98
|
+
|
|
99
|
+
// throw new AdapterError(
|
|
100
|
+
// "Primary verify failed and no fallback adapter configured."
|
|
101
|
+
// );
|
|
102
|
+
// }
|
|
103
|
+
// }
|
|
104
|
+
// }
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
110
|
+
import { HiSecureConfig } from "../core/types/HiSecureConfig";
|
|
3
111
|
import { logger } from "../logging";
|
|
4
112
|
|
|
5
113
|
interface HashAdapter {
|
|
@@ -26,20 +134,33 @@ export class HashManager {
|
|
|
26
134
|
this.config = config;
|
|
27
135
|
this.primaryAdapter = primaryAdapter;
|
|
28
136
|
this.fallbackAdapter = fallbackAdapter;
|
|
137
|
+
|
|
138
|
+
logger.info("HashManager initialized", {
|
|
139
|
+
layer: "hash-manager",
|
|
140
|
+
primary: config.primary,
|
|
141
|
+
fallbackEnabled: !!fallbackAdapter
|
|
142
|
+
});
|
|
29
143
|
}
|
|
30
144
|
|
|
31
|
-
async hash(
|
|
145
|
+
async hash(
|
|
146
|
+
value: string,
|
|
147
|
+
options?: { allowFallback?: boolean }
|
|
148
|
+
): Promise<HashResult> {
|
|
32
149
|
try {
|
|
33
150
|
const hash = await this.primaryAdapter.hash(value);
|
|
151
|
+
|
|
34
152
|
return {
|
|
35
153
|
hash,
|
|
36
154
|
algorithm: this.config.primary,
|
|
37
155
|
usedFallback: false
|
|
38
156
|
};
|
|
157
|
+
|
|
39
158
|
} catch (err: any) {
|
|
40
159
|
logger.warn("Primary hashing failed", {
|
|
41
|
-
|
|
42
|
-
|
|
160
|
+
layer: "hash-manager",
|
|
161
|
+
operation: "hash",
|
|
162
|
+
algorithm: this.config.primary,
|
|
163
|
+
reason: err?.message
|
|
43
164
|
});
|
|
44
165
|
|
|
45
166
|
if (!options?.allowFallback || !this.fallbackAdapter) {
|
|
@@ -50,22 +171,30 @@ export class HashManager {
|
|
|
50
171
|
|
|
51
172
|
try {
|
|
52
173
|
const hash = await this.fallbackAdapter.hash(value);
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
logger.warn("
|
|
174
|
+
|
|
175
|
+
// ⚠️ security downgrade log (VERY GOOD PRACTICE)
|
|
176
|
+
logger.warn("Hashing fallback used (security downgrade)", {
|
|
177
|
+
layer: "hash-manager",
|
|
178
|
+
operation: "hash",
|
|
56
179
|
from: this.config.primary,
|
|
57
180
|
to: this.config.fallback
|
|
58
181
|
});
|
|
59
|
-
|
|
182
|
+
|
|
60
183
|
return {
|
|
61
184
|
hash,
|
|
62
|
-
algorithm: this.config.fallback ||
|
|
185
|
+
algorithm: this.config.fallback || "bcrypt",
|
|
63
186
|
usedFallback: true
|
|
64
187
|
};
|
|
188
|
+
|
|
65
189
|
} catch (fallbackErr: any) {
|
|
66
190
|
logger.error("Fallback hashing failed", {
|
|
67
|
-
|
|
191
|
+
layer: "hash-manager",
|
|
192
|
+
operation: "hash",
|
|
193
|
+
from: this.config.primary,
|
|
194
|
+
to: this.config.fallback,
|
|
195
|
+
reason: fallbackErr?.message
|
|
68
196
|
});
|
|
197
|
+
|
|
69
198
|
throw new AdapterError(
|
|
70
199
|
"Both primary and fallback hashing failed."
|
|
71
200
|
);
|
|
@@ -74,31 +203,39 @@ export class HashManager {
|
|
|
74
203
|
}
|
|
75
204
|
|
|
76
205
|
async verify(value: string, hashed: string): Promise<boolean> {
|
|
77
|
-
// primary adapter - first
|
|
78
206
|
try {
|
|
79
207
|
return await this.primaryAdapter.verify(value, hashed);
|
|
208
|
+
|
|
80
209
|
} catch (primaryErr: any) {
|
|
81
|
-
logger.warn("Primary
|
|
82
|
-
|
|
210
|
+
logger.warn("Primary hash verification failed", {
|
|
211
|
+
layer: "hash-manager",
|
|
212
|
+
operation: "verify",
|
|
213
|
+
algorithm: this.config.primary,
|
|
214
|
+
reason: primaryErr?.message
|
|
83
215
|
});
|
|
84
216
|
|
|
85
|
-
// fallback exists - try it
|
|
86
217
|
if (this.fallbackAdapter) {
|
|
87
218
|
try {
|
|
88
219
|
return await this.fallbackAdapter.verify(value, hashed);
|
|
220
|
+
|
|
89
221
|
} catch (fallbackErr: any) {
|
|
90
|
-
logger.error("
|
|
91
|
-
|
|
222
|
+
logger.error("Fallback hash verification failed", {
|
|
223
|
+
layer: "hash-manager",
|
|
224
|
+
operation: "verify",
|
|
225
|
+
from: this.config.primary,
|
|
226
|
+
to: this.config.fallback,
|
|
227
|
+
reason: fallbackErr?.message
|
|
92
228
|
});
|
|
229
|
+
|
|
93
230
|
throw new AdapterError(
|
|
94
231
|
"Both primary and fallback verify failed."
|
|
95
232
|
);
|
|
96
233
|
}
|
|
97
234
|
}
|
|
98
|
-
|
|
235
|
+
|
|
99
236
|
throw new AdapterError(
|
|
100
237
|
"Primary verify failed and no fallback adapter configured."
|
|
101
238
|
);
|
|
102
239
|
}
|
|
103
240
|
}
|
|
104
|
-
}
|
|
241
|
+
}
|
|
@@ -1,19 +1,97 @@
|
|
|
1
|
+
// import express from "express";
|
|
2
|
+
// import qs from "qs";
|
|
3
|
+
// import { logger } from "../logging";
|
|
4
|
+
// import { AdapterError } from "../core/errors/AdapterError.js";
|
|
5
|
+
|
|
6
|
+
// export class JsonManager {
|
|
7
|
+
// middleware(options?: any) {
|
|
8
|
+
// try {
|
|
9
|
+
// const defaultOptions = {
|
|
10
|
+
// limit: '1mb',
|
|
11
|
+
// inflate: true,
|
|
12
|
+
// strict: true
|
|
13
|
+
// };
|
|
14
|
+
// return express.json({ ...defaultOptions, ...(options || {}) });
|
|
15
|
+
// } catch (err: any) {
|
|
16
|
+
// logger.error("JSON Manager: failed to create JSON parser");
|
|
17
|
+
// throw new AdapterError("JSON parser initialization failed.");
|
|
18
|
+
// }
|
|
19
|
+
// }
|
|
20
|
+
|
|
21
|
+
// urlencoded(options?: any) {
|
|
22
|
+
// try {
|
|
23
|
+
// const defaultOptions = {
|
|
24
|
+
// extended: true,
|
|
25
|
+
// limit: '1mb',
|
|
26
|
+
// parameterLimit: 1000
|
|
27
|
+
// };
|
|
28
|
+
// const opts = { ...defaultOptions, ...(options || {}) };
|
|
29
|
+
// return express.urlencoded(opts);
|
|
30
|
+
// } catch (err: any) {
|
|
31
|
+
// logger.error("URL-encoded parser failed");
|
|
32
|
+
// throw new AdapterError("URL-encoded parser initialization failed.");
|
|
33
|
+
// }
|
|
34
|
+
// }
|
|
35
|
+
|
|
36
|
+
// queryParser(options?: any) {
|
|
37
|
+
// return (req: any, res: any, next: any) => {
|
|
38
|
+
// try {
|
|
39
|
+
// if (!req.parsedQuery && req.url.includes('?')) {
|
|
40
|
+
// const queryString = req.url.split("?")[1] || "";
|
|
41
|
+
// const parsed = qs.parse(queryString, {
|
|
42
|
+
// depth: 5,
|
|
43
|
+
// parameterLimit: 100,
|
|
44
|
+
// ...options
|
|
45
|
+
// });
|
|
46
|
+
|
|
47
|
+
// req.parsedQuery = parsed;
|
|
48
|
+
// logger.debug(" Query parsed", {
|
|
49
|
+
// keys: Object.keys(parsed)
|
|
50
|
+
// });
|
|
51
|
+
// }
|
|
52
|
+
// next();
|
|
53
|
+
// } catch (err: any) {
|
|
54
|
+
// logger.error("Failed to parse query", { error: err?.message });
|
|
55
|
+
// next(new AdapterError("Query parsing failed."));
|
|
56
|
+
// }
|
|
57
|
+
// };
|
|
58
|
+
// }
|
|
59
|
+
// }
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
1
63
|
import express from "express";
|
|
2
64
|
import qs from "qs";
|
|
3
65
|
import { logger } from "../logging";
|
|
4
|
-
import { AdapterError } from "../core/errors/AdapterError
|
|
66
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
5
67
|
|
|
6
68
|
export class JsonManager {
|
|
7
69
|
middleware(options?: any) {
|
|
8
70
|
try {
|
|
9
71
|
const defaultOptions = {
|
|
10
|
-
limit:
|
|
72
|
+
limit: "1mb",
|
|
11
73
|
inflate: true,
|
|
12
74
|
strict: true
|
|
13
75
|
};
|
|
14
|
-
|
|
76
|
+
|
|
77
|
+
const finalOptions = { ...defaultOptions, ...(options || {}) };
|
|
78
|
+
|
|
79
|
+
logger.info("JSON body parser configured", {
|
|
80
|
+
layer: "json-manager",
|
|
81
|
+
operation: "json",
|
|
82
|
+
limit: finalOptions.limit,
|
|
83
|
+
strict: finalOptions.strict
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return express.json(finalOptions);
|
|
87
|
+
|
|
15
88
|
} catch (err: any) {
|
|
16
|
-
logger.error("JSON
|
|
89
|
+
logger.error("JSON body parser initialization failed", {
|
|
90
|
+
layer: "json-manager",
|
|
91
|
+
operation: "json",
|
|
92
|
+
reason: err?.message
|
|
93
|
+
});
|
|
94
|
+
|
|
17
95
|
throw new AdapterError("JSON parser initialization failed.");
|
|
18
96
|
}
|
|
19
97
|
}
|
|
@@ -22,38 +100,64 @@ export class JsonManager {
|
|
|
22
100
|
try {
|
|
23
101
|
const defaultOptions = {
|
|
24
102
|
extended: true,
|
|
25
|
-
limit:
|
|
103
|
+
limit: "1mb",
|
|
26
104
|
parameterLimit: 1000
|
|
27
105
|
};
|
|
28
|
-
|
|
29
|
-
|
|
106
|
+
|
|
107
|
+
const finalOptions = { ...defaultOptions, ...(options || {}) };
|
|
108
|
+
|
|
109
|
+
logger.info("URL-encoded parser configured", {
|
|
110
|
+
layer: "json-manager",
|
|
111
|
+
operation: "urlencoded",
|
|
112
|
+
limit: finalOptions.limit,
|
|
113
|
+
parameterLimit: finalOptions.parameterLimit
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return express.urlencoded(finalOptions);
|
|
117
|
+
|
|
30
118
|
} catch (err: any) {
|
|
31
|
-
logger.error("URL-encoded parser failed"
|
|
119
|
+
logger.error("URL-encoded parser initialization failed", {
|
|
120
|
+
layer: "json-manager",
|
|
121
|
+
operation: "urlencoded",
|
|
122
|
+
reason: err?.message
|
|
123
|
+
});
|
|
124
|
+
|
|
32
125
|
throw new AdapterError("URL-encoded parser initialization failed.");
|
|
33
126
|
}
|
|
34
127
|
}
|
|
35
128
|
|
|
36
129
|
queryParser(options?: any) {
|
|
37
|
-
return (req: any,
|
|
130
|
+
return (req: any, _res: any, next: any) => {
|
|
38
131
|
try {
|
|
39
|
-
if (!req.parsedQuery && req.url.includes(
|
|
132
|
+
if (!req.parsedQuery && req.url.includes("?")) {
|
|
40
133
|
const queryString = req.url.split("?")[1] || "";
|
|
134
|
+
|
|
41
135
|
const parsed = qs.parse(queryString, {
|
|
42
136
|
depth: 5,
|
|
43
137
|
parameterLimit: 100,
|
|
44
138
|
...options
|
|
45
139
|
});
|
|
46
|
-
|
|
140
|
+
|
|
47
141
|
req.parsedQuery = parsed;
|
|
48
|
-
|
|
49
|
-
|
|
142
|
+
|
|
143
|
+
// ✅ visible + safe info
|
|
144
|
+
logger.info("Query parameters parsed", {
|
|
145
|
+
layer: "json-manager",
|
|
146
|
+
operation: "query-parse",
|
|
147
|
+
keyCount: Object.keys(parsed).length
|
|
50
148
|
});
|
|
51
149
|
}
|
|
150
|
+
|
|
52
151
|
next();
|
|
53
152
|
} catch (err: any) {
|
|
54
|
-
logger.error("
|
|
153
|
+
logger.error("Query parsing failed", {
|
|
154
|
+
layer: "json-manager",
|
|
155
|
+
operation: "query-parse",
|
|
156
|
+
reason: err?.message
|
|
157
|
+
});
|
|
158
|
+
|
|
55
159
|
next(new AdapterError("Query parsing failed."));
|
|
56
160
|
}
|
|
57
161
|
};
|
|
58
162
|
}
|
|
59
|
-
}
|
|
163
|
+
}
|
|
@@ -1,5 +1,118 @@
|
|
|
1
|
-
import { HiSecureConfig } from "../core/types/HiSecureConfig.js";
|
|
2
|
-
import { AdapterError } from "../core/errors/AdapterError.js";
|
|
1
|
+
// import { HiSecureConfig } from "../core/types/HiSecureConfig.js";
|
|
2
|
+
// import { AdapterError } from "../core/errors/AdapterError.js";
|
|
3
|
+
// import { logger } from "../logging";
|
|
4
|
+
|
|
5
|
+
// interface RateLimiterAdapter {
|
|
6
|
+
// getMiddleware: (options?: any) => any;
|
|
7
|
+
// }
|
|
8
|
+
|
|
9
|
+
// export class RateLimitManager {
|
|
10
|
+
// private config: HiSecureConfig["rateLimiter"];
|
|
11
|
+
// private primaryAdapter: RateLimiterAdapter;
|
|
12
|
+
// private fallbackAdapter: RateLimiterAdapter | null;
|
|
13
|
+
|
|
14
|
+
// constructor(
|
|
15
|
+
// config: HiSecureConfig["rateLimiter"],
|
|
16
|
+
// primaryAdapter: RateLimiterAdapter,
|
|
17
|
+
// fallbackAdapter: RateLimiterAdapter | null
|
|
18
|
+
// ) {
|
|
19
|
+
// this.config = config;
|
|
20
|
+
// this.primaryAdapter = primaryAdapter;
|
|
21
|
+
// this.fallbackAdapter = fallbackAdapter;
|
|
22
|
+
// }
|
|
23
|
+
|
|
24
|
+
// middleware(opts?: { mode?: "strict" | "relaxed" | "api"; options?: any }) {
|
|
25
|
+
// let finalOptions: any = {};
|
|
26
|
+
|
|
27
|
+
// if (opts?.mode === "strict") {
|
|
28
|
+
// finalOptions = {
|
|
29
|
+
// windowMs: 10_000,
|
|
30
|
+
// max: 5,
|
|
31
|
+
// message: "Too many requests, please slow down."
|
|
32
|
+
// };
|
|
33
|
+
// } else if (opts?.mode === "relaxed") {
|
|
34
|
+
// finalOptions = {
|
|
35
|
+
// windowMs: 60_000,
|
|
36
|
+
// max: 100,
|
|
37
|
+
// message: "Rate limit exceeded."
|
|
38
|
+
// };
|
|
39
|
+
// } else if (opts?.mode === "api") {
|
|
40
|
+
// finalOptions = {
|
|
41
|
+
// windowMs: 15 * 60 * 1000,
|
|
42
|
+
// max: 100,
|
|
43
|
+
// message: "API rate limit exceeded."
|
|
44
|
+
// };
|
|
45
|
+
// } else {
|
|
46
|
+
// finalOptions = {
|
|
47
|
+
// windowMs: this.config.windowMs,
|
|
48
|
+
// max: this.config.maxRequests,
|
|
49
|
+
// message: this.config.message,
|
|
50
|
+
// standardHeaders: true,
|
|
51
|
+
// legacyHeaders: false
|
|
52
|
+
// };
|
|
53
|
+
// }
|
|
54
|
+
|
|
55
|
+
// if (opts?.options) {
|
|
56
|
+
// const allowedOverrides = ['message', 'skipFailedRequests', 'standardHeaders', 'legacyHeaders'];
|
|
57
|
+
// for (const key of allowedOverrides) {
|
|
58
|
+
// if (opts.options[key] !== undefined) {
|
|
59
|
+
// finalOptions[key] = opts.options[key];
|
|
60
|
+
// }
|
|
61
|
+
// }
|
|
62
|
+
|
|
63
|
+
// const attemptedOverrides = Object.keys(opts.options).filter(
|
|
64
|
+
// k => !allowedOverrides.includes(k) && k !== 'mode'
|
|
65
|
+
// );
|
|
66
|
+
// if (attemptedOverrides.length > 0) {
|
|
67
|
+
// logger.warn("Rate limit overrides ignored", {
|
|
68
|
+
// preset: opts?.mode || 'default',
|
|
69
|
+
// ignoredOptions: attemptedOverrides
|
|
70
|
+
// });
|
|
71
|
+
// }
|
|
72
|
+
// }
|
|
73
|
+
|
|
74
|
+
// if (finalOptions.standardHeaders === undefined) {
|
|
75
|
+
// finalOptions.standardHeaders = true;
|
|
76
|
+
// }
|
|
77
|
+
// if (finalOptions.legacyHeaders === undefined) {
|
|
78
|
+
// finalOptions.legacyHeaders = false;
|
|
79
|
+
// }
|
|
80
|
+
|
|
81
|
+
// try {
|
|
82
|
+
// logger.info("Applying rate limiting", {
|
|
83
|
+
// mode: opts?.mode || 'default',
|
|
84
|
+
// windowMs: finalOptions.windowMs,
|
|
85
|
+
// max: finalOptions.max
|
|
86
|
+
// });
|
|
87
|
+
|
|
88
|
+
// return this.primaryAdapter.getMiddleware(finalOptions);
|
|
89
|
+
// } catch (err: any) {
|
|
90
|
+
// logger.warn("Primary rate limiter failed → fallback", {
|
|
91
|
+
// error: err?.message
|
|
92
|
+
// });
|
|
93
|
+
|
|
94
|
+
// if (!this.fallbackAdapter) {
|
|
95
|
+
// throw new AdapterError("Rate limiters failed; no fallback adapter.");
|
|
96
|
+
// }
|
|
97
|
+
|
|
98
|
+
// try {
|
|
99
|
+
// logger.info("Using fallback rate limiter");
|
|
100
|
+
// return this.fallbackAdapter.getMiddleware(finalOptions);
|
|
101
|
+
// } catch (fallbackErr: any) {
|
|
102
|
+
// logger.error("Fallback limiter also failed", {
|
|
103
|
+
// error: fallbackErr?.message
|
|
104
|
+
// });
|
|
105
|
+
// throw new AdapterError("Both primary and fallback limiters failed.");
|
|
106
|
+
// }
|
|
107
|
+
// }
|
|
108
|
+
// }
|
|
109
|
+
// }
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
import { HiSecureConfig } from "../core/types/HiSecureConfig";
|
|
115
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
3
116
|
import { logger } from "../logging";
|
|
4
117
|
|
|
5
118
|
interface RateLimiterAdapter {
|
|
@@ -19,26 +132,33 @@ export class RateLimitManager {
|
|
|
19
132
|
this.config = config;
|
|
20
133
|
this.primaryAdapter = primaryAdapter;
|
|
21
134
|
this.fallbackAdapter = fallbackAdapter;
|
|
135
|
+
|
|
136
|
+
logger.info("RateLimitManager initialized", {
|
|
137
|
+
layer: "rate-limit-manager",
|
|
138
|
+
primaryConfigured: true,
|
|
139
|
+
fallbackConfigured: !!fallbackAdapter
|
|
140
|
+
});
|
|
22
141
|
}
|
|
23
142
|
|
|
24
143
|
middleware(opts?: { mode?: "strict" | "relaxed" | "api"; options?: any }) {
|
|
25
144
|
let finalOptions: any = {};
|
|
145
|
+
const mode = opts?.mode || "default";
|
|
26
146
|
|
|
27
|
-
if (
|
|
147
|
+
if (mode === "strict") {
|
|
28
148
|
finalOptions = {
|
|
29
149
|
windowMs: 10_000,
|
|
30
150
|
max: 5,
|
|
31
151
|
message: "Too many requests, please slow down."
|
|
32
152
|
};
|
|
33
|
-
} else if (
|
|
153
|
+
} else if (mode === "relaxed") {
|
|
34
154
|
finalOptions = {
|
|
35
155
|
windowMs: 60_000,
|
|
36
156
|
max: 100,
|
|
37
157
|
message: "Rate limit exceeded."
|
|
38
158
|
};
|
|
39
|
-
} else if (
|
|
159
|
+
} else if (mode === "api") {
|
|
40
160
|
finalOptions = {
|
|
41
|
-
windowMs: 15 * 60 * 1000,
|
|
161
|
+
windowMs: 15 * 60 * 1000,
|
|
42
162
|
max: 100,
|
|
43
163
|
message: "API rate limit exceeded."
|
|
44
164
|
};
|
|
@@ -47,63 +167,88 @@ export class RateLimitManager {
|
|
|
47
167
|
windowMs: this.config.windowMs,
|
|
48
168
|
max: this.config.maxRequests,
|
|
49
169
|
message: this.config.message,
|
|
50
|
-
standardHeaders: true,
|
|
51
|
-
legacyHeaders: false
|
|
170
|
+
standardHeaders: true,
|
|
171
|
+
legacyHeaders: false
|
|
52
172
|
};
|
|
53
173
|
}
|
|
54
174
|
|
|
55
175
|
if (opts?.options) {
|
|
56
|
-
const allowedOverrides = [
|
|
176
|
+
const allowedOverrides = [
|
|
177
|
+
"message",
|
|
178
|
+
"skipFailedRequests",
|
|
179
|
+
"standardHeaders",
|
|
180
|
+
"legacyHeaders"
|
|
181
|
+
];
|
|
182
|
+
|
|
57
183
|
for (const key of allowedOverrides) {
|
|
58
184
|
if (opts.options[key] !== undefined) {
|
|
59
185
|
finalOptions[key] = opts.options[key];
|
|
60
186
|
}
|
|
61
187
|
}
|
|
62
|
-
|
|
188
|
+
|
|
63
189
|
const attemptedOverrides = Object.keys(opts.options).filter(
|
|
64
|
-
k => !allowedOverrides.includes(k) && k !==
|
|
190
|
+
k => !allowedOverrides.includes(k) && k !== "mode"
|
|
65
191
|
);
|
|
192
|
+
|
|
66
193
|
if (attemptedOverrides.length > 0) {
|
|
67
194
|
logger.warn("Rate limit overrides ignored", {
|
|
68
|
-
|
|
195
|
+
layer: "rate-limit-manager",
|
|
196
|
+
operation: "configure",
|
|
197
|
+
mode,
|
|
69
198
|
ignoredOptions: attemptedOverrides
|
|
70
199
|
});
|
|
71
200
|
}
|
|
72
201
|
}
|
|
73
202
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
if (finalOptions.legacyHeaders === undefined) {
|
|
78
|
-
finalOptions.legacyHeaders = false;
|
|
79
|
-
}
|
|
203
|
+
finalOptions.standardHeaders ??= true;
|
|
204
|
+
finalOptions.legacyHeaders ??= false;
|
|
80
205
|
|
|
81
206
|
try {
|
|
82
|
-
logger.info("
|
|
83
|
-
|
|
207
|
+
logger.info("Rate limiting applied", {
|
|
208
|
+
layer: "rate-limit-manager",
|
|
209
|
+
operation: "apply",
|
|
210
|
+
mode,
|
|
84
211
|
windowMs: finalOptions.windowMs,
|
|
85
212
|
max: finalOptions.max
|
|
86
213
|
});
|
|
87
|
-
|
|
214
|
+
|
|
88
215
|
return this.primaryAdapter.getMiddleware(finalOptions);
|
|
216
|
+
|
|
89
217
|
} catch (err: any) {
|
|
90
|
-
logger.warn("Primary rate limiter failed
|
|
91
|
-
|
|
218
|
+
logger.warn("Primary rate limiter failed", {
|
|
219
|
+
layer: "rate-limit-manager",
|
|
220
|
+
operation: "apply",
|
|
221
|
+
mode,
|
|
222
|
+
reason: err?.message
|
|
92
223
|
});
|
|
93
224
|
|
|
94
225
|
if (!this.fallbackAdapter) {
|
|
95
|
-
throw new AdapterError(
|
|
226
|
+
throw new AdapterError(
|
|
227
|
+
"Rate limiters failed; no fallback adapter configured."
|
|
228
|
+
);
|
|
96
229
|
}
|
|
97
230
|
|
|
98
231
|
try {
|
|
99
|
-
logger.
|
|
232
|
+
logger.warn("Using fallback rate limiter", {
|
|
233
|
+
layer: "rate-limit-manager",
|
|
234
|
+
operation: "fallback",
|
|
235
|
+
mode
|
|
236
|
+
});
|
|
237
|
+
|
|
100
238
|
return this.fallbackAdapter.getMiddleware(finalOptions);
|
|
239
|
+
|
|
101
240
|
} catch (fallbackErr: any) {
|
|
102
|
-
logger.error("Fallback limiter
|
|
103
|
-
|
|
241
|
+
logger.error("Fallback rate limiter failed", {
|
|
242
|
+
layer: "rate-limit-manager",
|
|
243
|
+
operation: "fallback",
|
|
244
|
+
mode,
|
|
245
|
+
reason: fallbackErr?.message
|
|
104
246
|
});
|
|
105
|
-
|
|
247
|
+
|
|
248
|
+
throw new AdapterError(
|
|
249
|
+
"Both primary and fallback rate limiters failed."
|
|
250
|
+
);
|
|
106
251
|
}
|
|
107
252
|
}
|
|
108
253
|
}
|
|
109
|
-
}
|
|
254
|
+
}
|