hi-secure 1.0.7 → 1.0.11
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.js +1 -1
- package/dist/adapters/ArgonAdapter.js.map +1 -1
- package/dist/adapters/ExpressRLAdapter.d.ts.map +1 -1
- package/dist/adapters/ExpressRLAdapter.js +1 -2
- package/dist/adapters/ExpressRLAdapter.js.map +1 -1
- package/dist/adapters/ExpressValidatorAdapter.d.ts.map +1 -1
- package/dist/adapters/ExpressValidatorAdapter.js +1 -39
- package/dist/adapters/ExpressValidatorAdapter.js.map +1 -1
- package/dist/adapters/GoogleAdapter.d.ts.map +1 -1
- package/dist/adapters/GoogleAdapter.js +0 -101
- package/dist/adapters/GoogleAdapter.js.map +1 -1
- package/dist/adapters/JWTAdapter.d.ts.map +1 -1
- package/dist/adapters/JWTAdapter.js +3 -210
- package/dist/adapters/JWTAdapter.js.map +1 -1
- package/dist/adapters/RLFlexibleAdapter.d.ts.map +1 -1
- package/dist/adapters/RLFlexibleAdapter.js +0 -52
- package/dist/adapters/RLFlexibleAdapter.js.map +1 -1
- package/dist/adapters/SanitizeHtmlAdapter.d.ts +0 -3
- package/dist/adapters/SanitizeHtmlAdapter.d.ts.map +1 -1
- package/dist/adapters/SanitizeHtmlAdapter.js +2 -71
- package/dist/adapters/SanitizeHtmlAdapter.js.map +1 -1
- package/dist/adapters/XSSAdapter.d.ts +0 -10
- package/dist/adapters/XSSAdapter.d.ts.map +1 -1
- package/dist/adapters/XSSAdapter.js +2 -19
- package/dist/adapters/XSSAdapter.js.map +1 -1
- package/dist/adapters/ZodAdapter.d.ts.map +1 -1
- package/dist/adapters/ZodAdapter.js +2 -6
- package/dist/adapters/ZodAdapter.js.map +1 -1
- package/dist/core/HiSecure.d.ts +0 -2
- package/dist/core/HiSecure.d.ts.map +1 -1
- package/dist/core/HiSecure.js +8 -20
- package/dist/core/HiSecure.js.map +1 -1
- package/dist/core/useSecure.d.ts +0 -3
- package/dist/core/useSecure.d.ts.map +1 -1
- package/dist/core/useSecure.js +1 -5
- package/dist/core/useSecure.js.map +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/managers/AuthManager.d.ts.map +1 -1
- package/dist/managers/AuthManager.js +1 -89
- package/dist/managers/AuthManager.js.map +1 -1
- package/dist/managers/CorsManager.d.ts.map +1 -1
- package/dist/managers/CorsManager.js +1 -19
- package/dist/managers/CorsManager.js.map +1 -1
- package/dist/managers/HashManager.d.ts.map +1 -1
- package/dist/managers/HashManager.js +0 -243
- package/dist/managers/HashManager.js.map +1 -1
- package/dist/managers/JsonManager.d.ts.map +1 -1
- package/dist/managers/JsonManager.js +1 -77
- package/dist/managers/JsonManager.js.map +1 -1
- package/dist/managers/RateLimitManager.d.ts.map +1 -1
- package/dist/managers/RateLimitManager.js +3 -17
- package/dist/managers/RateLimitManager.js.map +1 -1
- package/dist/managers/SanitizerManager.d.ts +0 -6
- package/dist/managers/SanitizerManager.d.ts.map +1 -1
- package/dist/managers/SanitizerManager.js +1 -213
- package/dist/managers/SanitizerManager.js.map +1 -1
- package/dist/managers/ValidatorManager.d.ts.map +1 -1
- package/dist/managers/ValidatorManager.js +1 -109
- package/dist/managers/ValidatorManager.js.map +1 -1
- package/dist/middlewares/errorHandler.d.ts.map +1 -1
- package/dist/middlewares/errorHandler.js +0 -19
- package/dist/middlewares/errorHandler.js.map +1 -1
- package/dist/utils/deepFreeze.d.ts.map +1 -1
- package/dist/utils/deepFreeze.js +0 -25
- package/dist/utils/deepFreeze.js.map +1 -1
- package/dist/utils/deepMerge.d.ts.map +1 -1
- package/dist/utils/deepMerge.js +0 -26
- package/dist/utils/deepMerge.js.map +1 -1
- package/dist/utils/normalizeOptions.d.ts +1 -3
- package/dist/utils/normalizeOptions.d.ts.map +1 -1
- package/dist/utils/normalizeOptions.js +8 -9
- package/dist/utils/normalizeOptions.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/ArgonAdapter.ts +1 -1
- package/src/adapters/ExpressRLAdapter.ts +1 -2
- package/src/adapters/ExpressValidatorAdapter.ts +1 -54
- package/src/adapters/GoogleAdapter.ts +0 -129
- package/src/adapters/JWTAdapter.ts +5 -259
- package/src/adapters/RLFlexibleAdapter.ts +2 -65
- package/src/adapters/SanitizeHtmlAdapter.ts +3 -87
- package/src/adapters/XSSAdapter.ts +11 -19
- package/src/adapters/ZodAdapter.ts +2 -51
- package/src/core/HiSecure.ts +13 -24
- package/src/core/useSecure.ts +5 -7
- package/src/index.ts +4 -5
- package/src/managers/AuthManager.ts +5 -109
- package/src/managers/CorsManager.ts +1 -25
- package/src/managers/HashManager.ts +0 -286
- package/src/managers/JsonManager.ts +1 -91
- package/src/managers/RateLimitManager.ts +3 -262
- package/src/managers/SanitizerManager.ts +4 -263
- package/src/managers/ValidatorManager.ts +1 -135
- package/src/middlewares/errorHandler.ts +1 -176
- package/src/utils/deepFreeze.ts +0 -32
- package/src/utils/deepMerge.ts +0 -35
- package/src/utils/normalizeOptions.ts +16 -133
- package/src/examples/e1.ts +0 -1
- package/src/test/t1.ts +0 -1
|
@@ -1,259 +1,5 @@
|
|
|
1
|
-
// // import jwt from "jsonwebtoken";
|
|
2
|
-
// // import { AdapterError } from "../core/errors/AdapterError";
|
|
3
|
-
// // import { logError } from "../logging";
|
|
4
|
-
|
|
5
|
-
// // export interface JWTAdapterOptions {
|
|
6
|
-
// // secret: string;
|
|
7
|
-
// // expiresIn?: string | number | undefined;
|
|
8
|
-
// // }
|
|
9
|
-
|
|
10
|
-
// // export class JWTAdapter {
|
|
11
|
-
// // private secret: string;
|
|
12
|
-
// // private expiresIn?: string | number;
|
|
13
|
-
|
|
14
|
-
// // constructor(options: JWTAdapterOptions) {
|
|
15
|
-
// // if (!options.secret) {
|
|
16
|
-
// // throw new AdapterError("JWT secret is required");
|
|
17
|
-
// // }
|
|
18
|
-
|
|
19
|
-
// // this.secret = options.secret;
|
|
20
|
-
|
|
21
|
-
// // // Normalize expiresIn
|
|
22
|
-
// // if (options.expiresIn !== undefined) {
|
|
23
|
-
// // this.expiresIn = options.expiresIn as string | number;
|
|
24
|
-
// // }
|
|
25
|
-
// // }
|
|
26
|
-
|
|
27
|
-
// // sign(
|
|
28
|
-
// // payload: object,
|
|
29
|
-
// // options?: { expiresIn?: string | number }
|
|
30
|
-
// // ) {
|
|
31
|
-
// // try {
|
|
32
|
-
// // const finalExpires =
|
|
33
|
-
// // options?.expiresIn ?? this.expiresIn;
|
|
34
|
-
|
|
35
|
-
// // const jwtOptions: jwt.SignOptions = {};
|
|
36
|
-
|
|
37
|
-
// // if (finalExpires !== undefined) {
|
|
38
|
-
// // // Force safe cast → matches SignOptions type
|
|
39
|
-
// // jwtOptions.expiresIn = finalExpires as number | any;
|
|
40
|
-
// // }
|
|
41
|
-
|
|
42
|
-
// // return jwt.sign(payload, this.secret, jwtOptions);
|
|
43
|
-
|
|
44
|
-
// // } catch (err: any) {
|
|
45
|
-
// // logError("JWTAdapter.sign failed", { error: err?.message });
|
|
46
|
-
// // throw new AdapterError(err?.message || "JWT sign failed");
|
|
47
|
-
// // }
|
|
48
|
-
// // }
|
|
49
|
-
|
|
50
|
-
// // verify(token: string) {
|
|
51
|
-
// // try {
|
|
52
|
-
// // return jwt.verify(token, this.secret);
|
|
53
|
-
// // } catch (err: any) {
|
|
54
|
-
// // logError("JWTAdapter.verify failed", { error: err?.message });
|
|
55
|
-
// // throw new AdapterError(err?.message || "JWT verify failed");
|
|
56
|
-
// // }
|
|
57
|
-
// // }
|
|
58
|
-
// // }
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// // // src/adapters/JWTAdapter.ts - FIXED (Security hardening)
|
|
63
|
-
// // import jwt from "jsonwebtoken";
|
|
64
|
-
// // import { v4 as uuidv4 } from "uuid";
|
|
65
|
-
// // import { AdapterError } from "../core/errors/AdapterError.js";
|
|
66
|
-
// // import { logError } from "../logging/index.js";
|
|
67
|
-
|
|
68
|
-
// // export interface JWTAdapterOptions {
|
|
69
|
-
// // secret: string;
|
|
70
|
-
// // expiresIn?: string | number;
|
|
71
|
-
// // algorithm?: jwt.Algorithm;
|
|
72
|
-
// // issuer?: string;
|
|
73
|
-
// // audience?: string | string[];
|
|
74
|
-
// // }
|
|
75
|
-
|
|
76
|
-
// // export interface SignOptions {
|
|
77
|
-
// // expiresIn?: string | number;
|
|
78
|
-
// // jti?: string; // JWT ID for token revocation
|
|
79
|
-
// // subject?: string;
|
|
80
|
-
// // issuer?: string;
|
|
81
|
-
// // audience?: string | string[];
|
|
82
|
-
// // }
|
|
83
|
-
|
|
84
|
-
// // export class JWTAdapter {
|
|
85
|
-
// // private secret: string;
|
|
86
|
-
// // private expiresIn?: string | number;
|
|
87
|
-
// // private algorithm: jwt.Algorithm;
|
|
88
|
-
// // private issuer?: string;
|
|
89
|
-
// // private audience?: string | string[];
|
|
90
|
-
|
|
91
|
-
// // constructor(options: JWTAdapterOptions) {
|
|
92
|
-
// // if (!options.secret) {
|
|
93
|
-
// // throw new AdapterError("JWT secret is required");
|
|
94
|
-
// // }
|
|
95
|
-
|
|
96
|
-
// // if (options.secret.length < 32) {
|
|
97
|
-
// // logError("⚠ JWT secret is too short (minimum 32 characters recommended)");
|
|
98
|
-
// // }
|
|
99
|
-
|
|
100
|
-
// // this.secret = options.secret;
|
|
101
|
-
// // this.expiresIn = options.expiresIn;
|
|
102
|
-
// // this.algorithm = options.algorithm || 'HS256'; // ⚠️ Default algorithm
|
|
103
|
-
// // this.issuer = options.issuer;
|
|
104
|
-
// // this.audience = options.audience;
|
|
105
|
-
// // }
|
|
106
|
-
|
|
107
|
-
// // sign(payload: object, options?: SignOptions) {
|
|
108
|
-
// // try {
|
|
109
|
-
// // const jwtOptions: jwt.SignOptions = {
|
|
110
|
-
// // algorithm: this.algorithm,
|
|
111
|
-
// // issuer: options?.issuer || this.issuer,
|
|
112
|
-
// // audience: options?.audience || this.audience,
|
|
113
|
-
// // jwtid: options?.jti || uuidv4(), // Unique token ID
|
|
114
|
-
// // subject: options?.subject
|
|
115
|
-
// // };
|
|
116
|
-
|
|
117
|
-
// // if (options?.expiresIn !== undefined) {
|
|
118
|
-
// // jwtOptions.expiresIn = options.expiresIn as number;
|
|
119
|
-
// // } else if (this.expiresIn !== undefined) {
|
|
120
|
-
// // jwtOptions.expiresIn = this.expiresIn as number;
|
|
121
|
-
// // }
|
|
122
|
-
|
|
123
|
-
// // return jwt.sign(payload, this.secret, jwtOptions);
|
|
124
|
-
|
|
125
|
-
// // } catch (err: any) {
|
|
126
|
-
// // logError("JWTAdapter.sign failed", { error: err?.message });
|
|
127
|
-
// // throw new AdapterError(err?.message || "JWT sign failed");
|
|
128
|
-
// // }
|
|
129
|
-
// // }
|
|
130
|
-
|
|
131
|
-
// // verify(token: string, options?: { audience?: string | string[] }) {
|
|
132
|
-
// // try {
|
|
133
|
-
// // const verifyOptions: jwt.VerifyOptions = {
|
|
134
|
-
// // algorithms: [this.algorithm],
|
|
135
|
-
// // issuer: this.issuer,
|
|
136
|
-
// // audience: options?.audience as string || this.audience as string
|
|
137
|
-
// // };
|
|
138
|
-
|
|
139
|
-
// // return jwt.verify(token, this.secret, verifyOptions);
|
|
140
|
-
// // } catch (err: any) {
|
|
141
|
-
// // logError("JWTAdapter.verify failed", { error: err?.message });
|
|
142
|
-
|
|
143
|
-
// // // Provide better error messages
|
|
144
|
-
// // if (err.name === 'TokenExpiredError') {
|
|
145
|
-
// // throw new AdapterError("JWT token has expired");
|
|
146
|
-
// // }
|
|
147
|
-
// // if (err.name === 'JsonWebTokenError') {
|
|
148
|
-
// // throw new AdapterError("Invalid JWT token");
|
|
149
|
-
// // }
|
|
150
|
-
|
|
151
|
-
// // throw new AdapterError(err?.message || "JWT verification failed");
|
|
152
|
-
// // }
|
|
153
|
-
// // }
|
|
154
|
-
// // }
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// // src/adapters/JWTAdapter.ts - FIXED (Security hardening)
|
|
158
|
-
// import jwt from "jsonwebtoken";
|
|
159
|
-
// import { nanoid } from "nanoid"; // CHANGED: from uuid to nanoid
|
|
160
|
-
// import { AdapterError } from "../core/errors/AdapterError.js";
|
|
161
|
-
// import { logError } from "../logging/index.js";
|
|
162
|
-
|
|
163
|
-
// export interface JWTAdapterOptions {
|
|
164
|
-
// secret: string;
|
|
165
|
-
// expiresIn?: string | number;
|
|
166
|
-
// algorithm?: jwt.Algorithm;
|
|
167
|
-
// issuer?: string;
|
|
168
|
-
// audience?: string | string[];
|
|
169
|
-
// }
|
|
170
|
-
|
|
171
|
-
// export interface SignOptions {
|
|
172
|
-
// expiresIn?: string | number;
|
|
173
|
-
// jti?: string; // JWT ID for token revocation
|
|
174
|
-
// subject?: string;
|
|
175
|
-
// issuer?: string;
|
|
176
|
-
// audience?: string | string[];
|
|
177
|
-
// }
|
|
178
|
-
|
|
179
|
-
// export class JWTAdapter {
|
|
180
|
-
// private secret: string;
|
|
181
|
-
// private expiresIn?: string | number;
|
|
182
|
-
// private algorithm: jwt.Algorithm;
|
|
183
|
-
// private issuer?: string;
|
|
184
|
-
// private audience?: string | string[];
|
|
185
|
-
|
|
186
|
-
// constructor(options: JWTAdapterOptions) {
|
|
187
|
-
// if (!options.secret) {
|
|
188
|
-
// throw new AdapterError("JWT secret is required");
|
|
189
|
-
// }
|
|
190
|
-
|
|
191
|
-
// if (options.secret.length < 32) {
|
|
192
|
-
// logError("⚠ JWT secret is too short (minimum 32 characters recommended)");
|
|
193
|
-
// }
|
|
194
|
-
|
|
195
|
-
// this.secret = options.secret;
|
|
196
|
-
// this.expiresIn = options.expiresIn;
|
|
197
|
-
// this.algorithm = options.algorithm || 'HS256'; // ⚠️ Default algorithm
|
|
198
|
-
// this.issuer = options.issuer;
|
|
199
|
-
// this.audience = options.audience;
|
|
200
|
-
// }
|
|
201
|
-
|
|
202
|
-
// sign(payload: object, options?: SignOptions) {
|
|
203
|
-
// try {
|
|
204
|
-
// const jwtOptions: jwt.SignOptions = {
|
|
205
|
-
// algorithm: this.algorithm,
|
|
206
|
-
// issuer: options?.issuer || this.issuer,
|
|
207
|
-
// audience: options?.audience || this.audience,
|
|
208
|
-
// jwtid: options?.jti || nanoid(), // CHANGED: uuidv4() to nanoid()
|
|
209
|
-
// subject: options?.subject
|
|
210
|
-
// };
|
|
211
|
-
|
|
212
|
-
// if (options?.expiresIn !== undefined) {
|
|
213
|
-
// jwtOptions.expiresIn = options.expiresIn as number;
|
|
214
|
-
// } else if (this.expiresIn !== undefined) {
|
|
215
|
-
// jwtOptions.expiresIn = this.expiresIn as number;
|
|
216
|
-
// }
|
|
217
|
-
|
|
218
|
-
// return jwt.sign(payload, this.secret, jwtOptions);
|
|
219
|
-
|
|
220
|
-
// } catch (err: any) {
|
|
221
|
-
// logError("JWTAdapter.sign failed", { error: err?.message });
|
|
222
|
-
// throw new AdapterError(err?.message || "JWT sign failed");
|
|
223
|
-
// }
|
|
224
|
-
// }
|
|
225
|
-
|
|
226
|
-
// verify(token: string, options?: { audience?: string | string[] }) {
|
|
227
|
-
// try {
|
|
228
|
-
// const verifyOptions: jwt.VerifyOptions = {
|
|
229
|
-
// algorithms: [this.algorithm],
|
|
230
|
-
// issuer: this.issuer,
|
|
231
|
-
// audience: options?.audience as string || this.audience as string
|
|
232
|
-
// };
|
|
233
|
-
|
|
234
|
-
// return jwt.verify(token, this.secret, verifyOptions);
|
|
235
|
-
// } catch (err: any) {
|
|
236
|
-
// logError("JWTAdapter.verify failed", { error: err?.message });
|
|
237
|
-
|
|
238
|
-
// // Provide better error messages
|
|
239
|
-
// if (err.name === 'TokenExpiredError') {
|
|
240
|
-
// throw new AdapterError("JWT token has expired");
|
|
241
|
-
// }
|
|
242
|
-
// if (err.name === 'JsonWebTokenError') {
|
|
243
|
-
// throw new AdapterError("Invalid JWT token");
|
|
244
|
-
// }
|
|
245
|
-
|
|
246
|
-
// throw new AdapterError(err?.message || "JWT verification failed");
|
|
247
|
-
// }
|
|
248
|
-
// }
|
|
249
|
-
// }
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// src/adapters/JWTAdapter.ts - FIXED (Using crypto.randomUUID())
|
|
255
1
|
import jwt from "jsonwebtoken";
|
|
256
|
-
import { randomUUID } from "crypto";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
257
3
|
import { AdapterError } from "../core/errors/AdapterError.js";
|
|
258
4
|
import { logError } from "../logging/index.js";
|
|
259
5
|
import { logger } from "../logging";
|
|
@@ -268,7 +14,7 @@ export interface JWTAdapterOptions {
|
|
|
268
14
|
|
|
269
15
|
export interface SignOptions {
|
|
270
16
|
expiresIn?: string | number;
|
|
271
|
-
jti?: string;
|
|
17
|
+
jti?: string;
|
|
272
18
|
subject?: string;
|
|
273
19
|
issuer?: string;
|
|
274
20
|
audience?: string | string[];
|
|
@@ -293,7 +39,7 @@ export class JWTAdapter {
|
|
|
293
39
|
|
|
294
40
|
this.secret = options.secret;
|
|
295
41
|
this.expiresIn = options.expiresIn;
|
|
296
|
-
this.algorithm = options.algorithm || 'HS256'; //
|
|
42
|
+
this.algorithm = options.algorithm || 'HS256'; // Default algorithm
|
|
297
43
|
this.issuer = options.issuer;
|
|
298
44
|
this.audience = options.audience;
|
|
299
45
|
}
|
|
@@ -304,7 +50,7 @@ export class JWTAdapter {
|
|
|
304
50
|
algorithm: this.algorithm,
|
|
305
51
|
issuer: options?.issuer || this.issuer,
|
|
306
52
|
audience: options?.audience || this.audience,
|
|
307
|
-
jwtid: options?.jti || randomUUID(),
|
|
53
|
+
jwtid: options?.jti || randomUUID(),
|
|
308
54
|
subject: options?.subject
|
|
309
55
|
};
|
|
310
56
|
|
|
@@ -334,7 +80,7 @@ export class JWTAdapter {
|
|
|
334
80
|
} catch (err: any) {
|
|
335
81
|
logError("JWTAdapter.verify failed", { error: err?.message });
|
|
336
82
|
|
|
337
|
-
|
|
83
|
+
|
|
338
84
|
if (err.name === 'TokenExpiredError') {
|
|
339
85
|
throw new AdapterError("JWT token has expired");
|
|
340
86
|
}
|
|
@@ -1,72 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
// import { RateLimiterMemory, RateLimiterRes } from "rate-limiter-flexible";
|
|
3
|
-
// import { logger } from "../logging";
|
|
4
|
-
// import { AdapterError } from "../core/errors/AdapterError";
|
|
5
|
-
|
|
6
|
-
// export class RLFlexibleAdapter {
|
|
7
|
-
|
|
8
|
-
// /**
|
|
9
|
-
// * Create middleware dynamically using options.
|
|
10
|
-
// */
|
|
11
|
-
// getMiddleware(options: {
|
|
12
|
-
// points?: number;
|
|
13
|
-
// duration?: number; // seconds
|
|
14
|
-
// message?: any;
|
|
15
|
-
// } = {}) {
|
|
16
|
-
|
|
17
|
-
// try {
|
|
18
|
-
// const limiter = new RateLimiterMemory({
|
|
19
|
-
// points: options.points ?? 100,
|
|
20
|
-
// duration: options.duration ?? 60,
|
|
21
|
-
// });
|
|
22
|
-
|
|
23
|
-
// return async (req: any, res: any, next: any) => {
|
|
24
|
-
// const ip = req.ip ||
|
|
25
|
-
// req.headers["x-forwarded-for"] ||
|
|
26
|
-
// req.connection?.remoteAddress ||
|
|
27
|
-
// "unknown";
|
|
28
|
-
|
|
29
|
-
// try {
|
|
30
|
-
// await limiter.consume(ip);
|
|
31
|
-
// next();
|
|
32
|
-
// } catch (err: any) {
|
|
33
|
-
// const rlErr = err as RateLimiterRes;
|
|
34
|
-
|
|
35
|
-
// logger.warn("⚠ RLFlexibleAdapter: rate limit exceeded", {
|
|
36
|
-
// ip,
|
|
37
|
-
// path: req.path,
|
|
38
|
-
// method: req.method,
|
|
39
|
-
// retryAfter: rlErr.msBeforeNext
|
|
40
|
-
// });
|
|
41
|
-
|
|
42
|
-
// return res.status(429).json({
|
|
43
|
-
// success: false,
|
|
44
|
-
// error: "RATE_LIMIT_EXCEEDED",
|
|
45
|
-
// retryAfter: Math.ceil(rlErr.msBeforeNext / 1000),
|
|
46
|
-
// message: options.message ?? "Too many requests, slow down."
|
|
47
|
-
// });
|
|
48
|
-
// }
|
|
49
|
-
// };
|
|
50
|
-
|
|
51
|
-
// } catch (err: any) {
|
|
52
|
-
// logger.error("❌ RLFlexibleAdapter: failed to initialize limiter", {
|
|
53
|
-
// error: err?.message || err
|
|
54
|
-
// });
|
|
55
|
-
// throw new AdapterError("RateLimiterFlexible creation failed.");
|
|
56
|
-
// }
|
|
57
|
-
// }
|
|
58
|
-
// }
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// src/adapters/RLFlexibleAdapter.ts - IMPROVED
|
|
63
1
|
import { RateLimiterMemory, RateLimiterRes } from "rate-limiter-flexible";
|
|
64
2
|
import { logger } from "../logging/index.js";
|
|
65
3
|
import { AdapterError } from "../core/errors/AdapterError.js";
|
|
66
4
|
|
|
67
5
|
export interface RLOptions {
|
|
68
6
|
points?: number;
|
|
69
|
-
duration?: number;
|
|
7
|
+
duration?: number;
|
|
70
8
|
message?: any;
|
|
71
9
|
blockDuration?: number;
|
|
72
10
|
}
|
|
@@ -90,7 +28,7 @@ export class RLFlexibleAdapter {
|
|
|
90
28
|
});
|
|
91
29
|
|
|
92
30
|
return async (req: any, res: any, next: any) => {
|
|
93
|
-
|
|
31
|
+
|
|
94
32
|
const ip = this.extractIP(req);
|
|
95
33
|
|
|
96
34
|
try {
|
|
@@ -126,7 +64,6 @@ export class RLFlexibleAdapter {
|
|
|
126
64
|
}
|
|
127
65
|
|
|
128
66
|
private extractIP(req: any): string {
|
|
129
|
-
// Order of priority for IP extraction
|
|
130
67
|
return (
|
|
131
68
|
req.headers['x-real-ip'] ||
|
|
132
69
|
req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
|
|
@@ -1,85 +1,3 @@
|
|
|
1
|
-
// import sanitizeHtml from "sanitize-html";
|
|
2
|
-
// import { AdapterError } from "../core/errors/AdapterError";
|
|
3
|
-
// import { logger } from "../logging";
|
|
4
|
-
|
|
5
|
-
// export class SanitizeHtmlAdapter {
|
|
6
|
-
// private globalOptions: sanitizeHtml.IOptions;
|
|
7
|
-
|
|
8
|
-
// constructor(options: sanitizeHtml.IOptions = {}) {
|
|
9
|
-
// this.globalOptions = options;
|
|
10
|
-
// }
|
|
11
|
-
|
|
12
|
-
// /**
|
|
13
|
-
// * Sanitize a string with merged global + dynamic options
|
|
14
|
-
// */
|
|
15
|
-
// sanitize(input: string, dynamicOptions?: any): string {
|
|
16
|
-
// try {
|
|
17
|
-
// const opts = { ...this.globalOptions, ...(dynamicOptions || {}) };
|
|
18
|
-
|
|
19
|
-
// const clean = sanitizeHtml(input, opts);
|
|
20
|
-
|
|
21
|
-
// return typeof clean === "string" ? clean : String(clean);
|
|
22
|
-
|
|
23
|
-
// } catch (err: any) {
|
|
24
|
-
// logger.error("❌ sanitize-html failed", {
|
|
25
|
-
// error: err?.message || err,
|
|
26
|
-
// preview: typeof input === "string" ? input.slice(0, 100) : undefined
|
|
27
|
-
// });
|
|
28
|
-
|
|
29
|
-
// throw new AdapterError("sanitize-html adapter failed.");
|
|
30
|
-
// }
|
|
31
|
-
// }
|
|
32
|
-
|
|
33
|
-
// /**
|
|
34
|
-
// * Deep sanitize nested objects, arrays, strings
|
|
35
|
-
// */
|
|
36
|
-
// private deepSanitize(obj: any, dynamicOptions?: any): any {
|
|
37
|
-
// if (typeof obj === "string") {
|
|
38
|
-
// return this.sanitize(obj, dynamicOptions);
|
|
39
|
-
// }
|
|
40
|
-
|
|
41
|
-
// if (Array.isArray(obj)) {
|
|
42
|
-
// return obj.map((item) => this.deepSanitize(item, dynamicOptions));
|
|
43
|
-
// }
|
|
44
|
-
|
|
45
|
-
// if (obj && typeof obj === "object") {
|
|
46
|
-
// const result: any = {};
|
|
47
|
-
// for (const key of Object.keys(obj)) {
|
|
48
|
-
// result[key] = this.deepSanitize(obj[key], dynamicOptions);
|
|
49
|
-
// }
|
|
50
|
-
// return result;
|
|
51
|
-
// }
|
|
52
|
-
|
|
53
|
-
// return obj;
|
|
54
|
-
// }
|
|
55
|
-
|
|
56
|
-
// /**
|
|
57
|
-
// * Middleware wrapper with dynamic per-route options
|
|
58
|
-
// */
|
|
59
|
-
// middleware(dynamicOptions?: any) {
|
|
60
|
-
// return (req: any, _res: any, next: any) => {
|
|
61
|
-
// try {
|
|
62
|
-
// if (req.body) {
|
|
63
|
-
// req.body = this.deepSanitize(req.body, dynamicOptions);
|
|
64
|
-
|
|
65
|
-
// logger.debug("🧼 sanitize-html applied", {
|
|
66
|
-
// keys: Object.keys(req.body)
|
|
67
|
-
// });
|
|
68
|
-
// }
|
|
69
|
-
// next();
|
|
70
|
-
|
|
71
|
-
// } catch (err: any) {
|
|
72
|
-
// logger.error("❌ sanitize-html middleware failed", {
|
|
73
|
-
// error: err?.message || err
|
|
74
|
-
// });
|
|
75
|
-
// next(err);
|
|
76
|
-
// }
|
|
77
|
-
// };
|
|
78
|
-
// }
|
|
79
|
-
// }
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// src/adapters/SanitizeHtmlAdapter.ts - FIXED
|
|
83
1
|
import sanitizeHtml from "sanitize-html";
|
|
84
2
|
import { AdapterError } from "../core/errors/AdapterError.js";
|
|
85
3
|
import { logger } from "../logging/index.js";
|
|
@@ -108,14 +26,12 @@ export class SanitizeHtmlAdapter {
|
|
|
108
26
|
}
|
|
109
27
|
}
|
|
110
28
|
|
|
111
|
-
|
|
112
|
-
* Deep sanitize with recursion protection
|
|
113
|
-
*/
|
|
29
|
+
// Deep Sanitization - Recursively
|
|
114
30
|
private deepSanitize(obj: any, dynamicOptions?: any, visited = new WeakSet()): any {
|
|
115
|
-
|
|
31
|
+
|
|
116
32
|
if (obj && typeof obj === "object") {
|
|
117
33
|
if (visited.has(obj)) {
|
|
118
|
-
return obj;
|
|
34
|
+
return obj;
|
|
119
35
|
}
|
|
120
36
|
visited.add(obj);
|
|
121
37
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// src/adapters/XSSAdapter.ts - NEW FILE
|
|
2
1
|
import { FilterXSS, getDefaultWhiteList, whiteList } from 'xss';
|
|
3
2
|
import { AdapterError } from "../core/errors/AdapterError.js";
|
|
4
3
|
import { logger } from "../logging/index.js";
|
|
@@ -25,12 +24,12 @@ export class XSSAdapter {
|
|
|
25
24
|
// Default safe configuration
|
|
26
25
|
const defaultOptions: XSSOptions = {
|
|
27
26
|
whiteList: getDefaultWhiteList(),
|
|
28
|
-
stripIgnoreTag: true,
|
|
27
|
+
stripIgnoreTag: true,
|
|
29
28
|
stripIgnoreTagBody: ['script', 'style', 'iframe', 'object', 'embed'],
|
|
30
29
|
allowCommentTag: false,
|
|
31
|
-
css: false,
|
|
30
|
+
css: false,
|
|
32
31
|
onTag: (tag, html, options) => {
|
|
33
|
-
|
|
32
|
+
|
|
34
33
|
if (tag === 'a') {
|
|
35
34
|
return html.replace(/<a /i, '<a target="_blank" rel="noopener noreferrer" ');
|
|
36
35
|
}
|
|
@@ -42,21 +41,19 @@ export class XSSAdapter {
|
|
|
42
41
|
this.defaultFilter = new FilterXSS(finalOptions);
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
* Sanitize a string with global + dynamic merged options
|
|
47
|
-
*/
|
|
44
|
+
|
|
48
45
|
sanitize(input: string, dynamicOptions?: XSSOptions): string {
|
|
49
46
|
try {
|
|
50
47
|
if (typeof input !== "string") {
|
|
51
48
|
return input as any;
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
|
|
55
52
|
if (!dynamicOptions || Object.keys(dynamicOptions).length === 0) {
|
|
56
53
|
return this.defaultFilter.process(input);
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
|
|
56
|
+
|
|
60
57
|
const mergedOptions = { ...this.globalOptions, ...dynamicOptions };
|
|
61
58
|
const customFilter = new FilterXSS(mergedOptions);
|
|
62
59
|
|
|
@@ -71,10 +68,7 @@ export class XSSAdapter {
|
|
|
71
68
|
}
|
|
72
69
|
}
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
* Middleware wrapper WITH dynamic options
|
|
76
|
-
* Doesn't mutate original request - creates sanitized copy
|
|
77
|
-
*/
|
|
71
|
+
|
|
78
72
|
middleware(dynamicOptions?: XSSOptions) {
|
|
79
73
|
return (req: any, _res: any, next: any) => {
|
|
80
74
|
try {
|
|
@@ -94,14 +88,14 @@ export class XSSAdapter {
|
|
|
94
88
|
: v
|
|
95
89
|
);
|
|
96
90
|
} else if (val && typeof val === "object") {
|
|
97
|
-
|
|
91
|
+
|
|
98
92
|
sanitizedBody[key] = this.deepSanitize(val, dynamicOptions);
|
|
99
93
|
} else {
|
|
100
94
|
sanitizedBody[key] = val;
|
|
101
95
|
}
|
|
102
96
|
}
|
|
103
97
|
|
|
104
|
-
|
|
98
|
+
|
|
105
99
|
req.sanitizedBody = sanitizedBody;
|
|
106
100
|
|
|
107
101
|
logger.debug("🛡️ XSS sanitizer applied", {
|
|
@@ -120,11 +114,9 @@ export class XSSAdapter {
|
|
|
120
114
|
};
|
|
121
115
|
}
|
|
122
116
|
|
|
123
|
-
|
|
124
|
-
* Deep sanitize nested objects/arrays
|
|
125
|
-
*/
|
|
117
|
+
|
|
126
118
|
private deepSanitize(obj: any, options?: XSSOptions, visited = new WeakSet()): any {
|
|
127
|
-
|
|
119
|
+
|
|
128
120
|
if (obj && typeof obj === "object") {
|
|
129
121
|
if (visited.has(obj)) {
|
|
130
122
|
return obj;
|
|
@@ -1,54 +1,5 @@
|
|
|
1
|
-
// import { ZodSchema, ZodError } from "zod";
|
|
2
|
-
// import { ValidationError } from "../core/errors/ValidationError";
|
|
3
|
-
// import { logger } from "../logging";
|
|
4
|
-
|
|
5
|
-
// export class ZodAdapter {
|
|
6
|
-
// private globalSchema?: ZodSchema;
|
|
7
|
-
|
|
8
|
-
// constructor(globalSchema?: ZodSchema) {
|
|
9
|
-
// this.globalSchema = globalSchema as any;
|
|
10
|
-
// }
|
|
11
|
-
|
|
12
|
-
// /**
|
|
13
|
-
// * Validate with global + dynamic schema (dynamic overrides global)
|
|
14
|
-
// */
|
|
15
|
-
// validate(dynamicSchema?: ZodSchema) {
|
|
16
|
-
// return (req: any, res: any, next: any) => {
|
|
17
|
-
// const schema = dynamicSchema || this.globalSchema;
|
|
18
|
-
|
|
19
|
-
// if (!schema) return next(); // no validation for this route
|
|
20
|
-
|
|
21
|
-
// const result = schema.safeParse(req.body);
|
|
22
|
-
|
|
23
|
-
// if (result.success) return next();
|
|
24
|
-
|
|
25
|
-
// const zodErr: ZodError = result.error;
|
|
26
|
-
|
|
27
|
-
// const issues = zodErr.issues.map(issue => ({
|
|
28
|
-
// message: issue.message,
|
|
29
|
-
// path: issue.path.join("."),
|
|
30
|
-
// code: issue.code
|
|
31
|
-
// }));
|
|
32
|
-
|
|
33
|
-
// logger.warn("⚠ Zod validation failed", {
|
|
34
|
-
// path: req.path,
|
|
35
|
-
// method: req.method,
|
|
36
|
-
// issues,
|
|
37
|
-
// preview: JSON.stringify(req.body).slice(0, 200)
|
|
38
|
-
// });
|
|
39
|
-
|
|
40
|
-
// return next(
|
|
41
|
-
// new ValidationError(issues[0]?.message || "Validation failed.")
|
|
42
|
-
// );
|
|
43
|
-
// };
|
|
44
|
-
// }
|
|
45
|
-
// }
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// src/adapters/ZodAdapter.ts - FIXED
|
|
50
1
|
import { ZodSchema, ZodError } from "zod";
|
|
51
|
-
import { ValidationError } from "../core/errors/ValidationError.js";
|
|
2
|
+
import { ValidationError } from "../core/errors/ValidationError.js";
|
|
52
3
|
import { logger } from "../logging/index.js";
|
|
53
4
|
|
|
54
5
|
export class ZodAdapter {
|
|
@@ -84,7 +35,7 @@ export class ZodAdapter {
|
|
|
84
35
|
});
|
|
85
36
|
|
|
86
37
|
return next(
|
|
87
|
-
new ValidationError("Validation failed.", issues as any)
|
|
38
|
+
new ValidationError("Validation failed.", issues as any)
|
|
88
39
|
);
|
|
89
40
|
};
|
|
90
41
|
}
|