hi-secure 1.0.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.
Files changed (210) hide show
  1. package/dist/adapters/ArgonAdapter.d.ts +8 -0
  2. package/dist/adapters/ArgonAdapter.d.ts.map +1 -0
  3. package/dist/adapters/ArgonAdapter.js +45 -0
  4. package/dist/adapters/ArgonAdapter.js.map +1 -0
  5. package/dist/adapters/BcryptAdapter.d.ts +7 -0
  6. package/dist/adapters/BcryptAdapter.d.ts.map +1 -0
  7. package/dist/adapters/BcryptAdapter.js +48 -0
  8. package/dist/adapters/BcryptAdapter.js.map +1 -0
  9. package/dist/adapters/DomPurifyAdapter.d.ts +13 -0
  10. package/dist/adapters/DomPurifyAdapter.d.ts.map +1 -0
  11. package/dist/adapters/DomPurifyAdapter.js +61 -0
  12. package/dist/adapters/DomPurifyAdapter.js.map +1 -0
  13. package/dist/adapters/ExpressRLAdapter.d.ts +13 -0
  14. package/dist/adapters/ExpressRLAdapter.d.ts.map +1 -0
  15. package/dist/adapters/ExpressRLAdapter.js +68 -0
  16. package/dist/adapters/ExpressRLAdapter.js.map +1 -0
  17. package/dist/adapters/ExpressValidatorAdapter.d.ts +6 -0
  18. package/dist/adapters/ExpressValidatorAdapter.d.ts.map +1 -0
  19. package/dist/adapters/ExpressValidatorAdapter.js +78 -0
  20. package/dist/adapters/ExpressValidatorAdapter.js.map +1 -0
  21. package/dist/adapters/GoggleAdapter.d.ts +15 -0
  22. package/dist/adapters/GoggleAdapter.d.ts.map +1 -0
  23. package/dist/adapters/GoggleAdapter.js +91 -0
  24. package/dist/adapters/GoggleAdapter.js.map +1 -0
  25. package/dist/adapters/GoogleAdapter.d.ts +15 -0
  26. package/dist/adapters/GoogleAdapter.d.ts.map +1 -0
  27. package/dist/adapters/GoogleAdapter.js +159 -0
  28. package/dist/adapters/GoogleAdapter.js.map +1 -0
  29. package/dist/adapters/JWTAdapter.d.ts +28 -0
  30. package/dist/adapters/JWTAdapter.d.ts.map +1 -0
  31. package/dist/adapters/JWTAdapter.js +276 -0
  32. package/dist/adapters/JWTAdapter.js.map +1 -0
  33. package/dist/adapters/RLFlexibleAdapter.d.ts +11 -0
  34. package/dist/adapters/RLFlexibleAdapter.d.ts.map +1 -0
  35. package/dist/adapters/RLFlexibleAdapter.js +115 -0
  36. package/dist/adapters/RLFlexibleAdapter.js.map +1 -0
  37. package/dist/adapters/SanitizeHtmlAdapter.d.ts +12 -0
  38. package/dist/adapters/SanitizeHtmlAdapter.d.ts.map +1 -0
  39. package/dist/adapters/SanitizeHtmlAdapter.js +141 -0
  40. package/dist/adapters/SanitizeHtmlAdapter.js.map +1 -0
  41. package/dist/adapters/XSSAdapter.d.ts +33 -0
  42. package/dist/adapters/XSSAdapter.d.ts.map +1 -0
  43. package/dist/adapters/XSSAdapter.js +127 -0
  44. package/dist/adapters/XSSAdapter.js.map +1 -0
  45. package/dist/adapters/ZodAdapter.d.ts +7 -0
  46. package/dist/adapters/ZodAdapter.d.ts.map +1 -0
  47. package/dist/adapters/ZodAdapter.js +39 -0
  48. package/dist/adapters/ZodAdapter.js.map +1 -0
  49. package/dist/core/HiSecure.d.ts +62 -0
  50. package/dist/core/HiSecure.d.ts.map +1 -0
  51. package/dist/core/HiSecure.js +273 -0
  52. package/dist/core/HiSecure.js.map +1 -0
  53. package/dist/core/config.d.ts +3 -0
  54. package/dist/core/config.d.ts.map +1 -0
  55. package/dist/core/config.js +53 -0
  56. package/dist/core/config.js.map +1 -0
  57. package/dist/core/constants.d.ts +37 -0
  58. package/dist/core/constants.d.ts.map +1 -0
  59. package/dist/core/constants.js +67 -0
  60. package/dist/core/constants.js.map +1 -0
  61. package/dist/core/errors/AdapterError.d.ts +5 -0
  62. package/dist/core/errors/AdapterError.d.ts.map +1 -0
  63. package/dist/core/errors/AdapterError.js +15 -0
  64. package/dist/core/errors/AdapterError.js.map +1 -0
  65. package/dist/core/errors/HttpErrror.d.ts +17 -0
  66. package/dist/core/errors/HttpErrror.d.ts.map +1 -0
  67. package/dist/core/errors/HttpErrror.js +36 -0
  68. package/dist/core/errors/HttpErrror.js.map +1 -0
  69. package/dist/core/errors/SanitizerError.d.ts +5 -0
  70. package/dist/core/errors/SanitizerError.d.ts.map +1 -0
  71. package/dist/core/errors/SanitizerError.js +14 -0
  72. package/dist/core/errors/SanitizerError.js.map +1 -0
  73. package/dist/core/errors/SecurityError.d.ts +5 -0
  74. package/dist/core/errors/SecurityError.d.ts.map +1 -0
  75. package/dist/core/errors/SecurityError.js +14 -0
  76. package/dist/core/errors/SecurityError.js.map +1 -0
  77. package/dist/core/errors/ValidationError.d.ts +5 -0
  78. package/dist/core/errors/ValidationError.d.ts.map +1 -0
  79. package/dist/core/errors/ValidationError.js +14 -0
  80. package/dist/core/errors/ValidationError.js.map +1 -0
  81. package/dist/core/types/HiSecureConfig.d.ts +47 -0
  82. package/dist/core/types/HiSecureConfig.d.ts.map +1 -0
  83. package/dist/core/types/HiSecureConfig.js +3 -0
  84. package/dist/core/types/HiSecureConfig.js.map +1 -0
  85. package/dist/core/types/SecureOptions.d.ts +30 -0
  86. package/dist/core/types/SecureOptions.d.ts.map +1 -0
  87. package/dist/core/types/SecureOptions.js +4 -0
  88. package/dist/core/types/SecureOptions.js.map +1 -0
  89. package/dist/core/useSecure.d.ts +10 -0
  90. package/dist/core/useSecure.d.ts.map +1 -0
  91. package/dist/core/useSecure.js +85 -0
  92. package/dist/core/useSecure.js.map +1 -0
  93. package/dist/examples/e1.d.ts +1 -0
  94. package/dist/examples/e1.d.ts.map +1 -0
  95. package/dist/examples/e1.js +3 -0
  96. package/dist/examples/e1.js.map +1 -0
  97. package/dist/index.d.ts +9 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +15 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/logging/index.d.ts +3 -0
  102. package/dist/logging/index.d.ts.map +1 -0
  103. package/dist/logging/index.js +19 -0
  104. package/dist/logging/index.js.map +1 -0
  105. package/dist/logging/morganSetup.d.ts +2 -0
  106. package/dist/logging/morganSetup.d.ts.map +1 -0
  107. package/dist/logging/morganSetup.js +9 -0
  108. package/dist/logging/morganSetup.js.map +1 -0
  109. package/dist/logging/winstonSetup.d.ts +6 -0
  110. package/dist/logging/winstonSetup.d.ts.map +1 -0
  111. package/dist/logging/winstonSetup.js +22 -0
  112. package/dist/logging/winstonSetup.js.map +1 -0
  113. package/dist/managers/AuthManager.d.ts +23 -0
  114. package/dist/managers/AuthManager.d.ts.map +1 -0
  115. package/dist/managers/AuthManager.js +190 -0
  116. package/dist/managers/AuthManager.js.map +1 -0
  117. package/dist/managers/CorsManager.d.ts +9 -0
  118. package/dist/managers/CorsManager.d.ts.map +1 -0
  119. package/dist/managers/CorsManager.js +55 -0
  120. package/dist/managers/CorsManager.js.map +1 -0
  121. package/dist/managers/HashManager.d.ts +22 -0
  122. package/dist/managers/HashManager.d.ts.map +1 -0
  123. package/dist/managers/HashManager.js +319 -0
  124. package/dist/managers/HashManager.js.map +1 -0
  125. package/dist/managers/JsonManager.d.ts +6 -0
  126. package/dist/managers/JsonManager.d.ts.map +1 -0
  127. package/dist/managers/JsonManager.js +142 -0
  128. package/dist/managers/JsonManager.js.map +1 -0
  129. package/dist/managers/RateLimitManager.d.ts +16 -0
  130. package/dist/managers/RateLimitManager.d.ts.map +1 -0
  131. package/dist/managers/RateLimitManager.js +108 -0
  132. package/dist/managers/RateLimitManager.js.map +1 -0
  133. package/dist/managers/SanitizerManager.d.ts +18 -0
  134. package/dist/managers/SanitizerManager.d.ts.map +1 -0
  135. package/dist/managers/SanitizerManager.js +296 -0
  136. package/dist/managers/SanitizerManager.js.map +1 -0
  137. package/dist/managers/ValidatorManager.d.ts +13 -0
  138. package/dist/managers/ValidatorManager.d.ts.map +1 -0
  139. package/dist/managers/ValidatorManager.js +218 -0
  140. package/dist/managers/ValidatorManager.js.map +1 -0
  141. package/dist/middlewares/errorHandler.d.ts +3 -0
  142. package/dist/middlewares/errorHandler.d.ts.map +1 -0
  143. package/dist/middlewares/errorHandler.js +94 -0
  144. package/dist/middlewares/errorHandler.js.map +1 -0
  145. package/dist/middlewares/index.d.ts +3 -0
  146. package/dist/middlewares/index.d.ts.map +1 -0
  147. package/dist/middlewares/index.js +19 -0
  148. package/dist/middlewares/index.js.map +1 -0
  149. package/dist/middlewares/requestLogger.d.ts +2 -0
  150. package/dist/middlewares/requestLogger.d.ts.map +1 -0
  151. package/dist/middlewares/requestLogger.js +8 -0
  152. package/dist/middlewares/requestLogger.js.map +1 -0
  153. package/dist/test/t1.d.ts +1 -0
  154. package/dist/test/t1.d.ts.map +1 -0
  155. package/dist/test/t1.js +3 -0
  156. package/dist/test/t1.js.map +1 -0
  157. package/dist/utils/deepFreeze.d.ts +2 -0
  158. package/dist/utils/deepFreeze.d.ts.map +1 -0
  159. package/dist/utils/deepFreeze.js +69 -0
  160. package/dist/utils/deepFreeze.js.map +1 -0
  161. package/dist/utils/deepMerge.d.ts +5 -0
  162. package/dist/utils/deepMerge.d.ts.map +1 -0
  163. package/dist/utils/deepMerge.js +68 -0
  164. package/dist/utils/deepMerge.js.map +1 -0
  165. package/dist/utils/normalizeOptions.d.ts +38 -0
  166. package/dist/utils/normalizeOptions.d.ts.map +1 -0
  167. package/dist/utils/normalizeOptions.js +119 -0
  168. package/dist/utils/normalizeOptions.js.map +1 -0
  169. package/package.json +50 -0
  170. package/src/adapters/ArgonAdapter.ts +41 -0
  171. package/src/adapters/BcryptAdapter.ts +49 -0
  172. package/src/adapters/ExpressRLAdapter.ts +84 -0
  173. package/src/adapters/ExpressValidatorAdapter.ts +99 -0
  174. package/src/adapters/GoogleAdapter.ts +206 -0
  175. package/src/adapters/JWTAdapter.ts +346 -0
  176. package/src/adapters/RLFlexibleAdapter.ts +139 -0
  177. package/src/adapters/SanitizeHtmlAdapter.ts +162 -0
  178. package/src/adapters/XSSAdapter.ts +153 -0
  179. package/src/adapters/ZodAdapter.ts +91 -0
  180. package/src/core/HiSecure.ts +955 -0
  181. package/src/core/config.ts +156 -0
  182. package/src/core/constants.ts +73 -0
  183. package/src/core/errors/AdapterError.ts +14 -0
  184. package/src/core/errors/HttpErrror.ts +46 -0
  185. package/src/core/errors/SanitizerError.ts +13 -0
  186. package/src/core/errors/SecurityError.ts +13 -0
  187. package/src/core/errors/ValidationError.ts +13 -0
  188. package/src/core/types/HiSecureConfig.ts +62 -0
  189. package/src/core/types/SecureOptions.ts +61 -0
  190. package/src/core/useSecure.ts +111 -0
  191. package/src/examples/e1.ts +1 -0
  192. package/src/index.ts +17 -0
  193. package/src/logging/index.ts +2 -0
  194. package/src/logging/morganSetup.ts +3 -0
  195. package/src/logging/winstonSetup.ts +17 -0
  196. package/src/managers/AuthManager.ts +237 -0
  197. package/src/managers/CorsManager.ts +58 -0
  198. package/src/managers/HashManager.ts +390 -0
  199. package/src/managers/JsonManager.ts +149 -0
  200. package/src/managers/RateLimitManager.ts +368 -0
  201. package/src/managers/SanitizerManager.ts +359 -0
  202. package/src/managers/ValidatorManager.ts +269 -0
  203. package/src/middlewares/errorHandler.ts +265 -0
  204. package/src/middlewares/index.ts +2 -0
  205. package/src/middlewares/requestLogger.ts +5 -0
  206. package/src/test/t1.ts +1 -0
  207. package/src/utils/deepFreeze.ts +76 -0
  208. package/src/utils/deepMerge.ts +87 -0
  209. package/src/utils/normalizeOptions.ts +265 -0
  210. package/tsconfig.json +30 -0
@@ -0,0 +1,346 @@
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
+ import jwt from "jsonwebtoken";
256
+ import { randomUUID } from "crypto"; // Built-in Node.js
257
+ import { AdapterError } from "../core/errors/AdapterError.js";
258
+ import { logError } from "../logging/index.js";
259
+
260
+ export interface JWTAdapterOptions {
261
+ secret: string;
262
+ expiresIn?: string | number;
263
+ algorithm?: jwt.Algorithm;
264
+ issuer?: string;
265
+ audience?: string | string[];
266
+ }
267
+
268
+ export interface SignOptions {
269
+ expiresIn?: string | number;
270
+ jti?: string; // JWT ID for token revocation
271
+ subject?: string;
272
+ issuer?: string;
273
+ audience?: string | string[];
274
+ }
275
+
276
+ export class JWTAdapter {
277
+ private secret: string;
278
+ private expiresIn?: string | number;
279
+ private algorithm: jwt.Algorithm;
280
+ private issuer?: string;
281
+ private audience?: string | string[];
282
+
283
+ constructor(options: JWTAdapterOptions) {
284
+ if (!options.secret) {
285
+ throw new AdapterError("JWT secret is required");
286
+ }
287
+
288
+ if (options.secret.length < 32) {
289
+ logError("⚠ JWT secret is too short (minimum 32 characters recommended)");
290
+ }
291
+
292
+ this.secret = options.secret;
293
+ this.expiresIn = options.expiresIn;
294
+ this.algorithm = options.algorithm || 'HS256'; // ⚠️ Default algorithm
295
+ this.issuer = options.issuer;
296
+ this.audience = options.audience;
297
+ }
298
+
299
+ sign(payload: object, options?: SignOptions) {
300
+ try {
301
+ const jwtOptions: jwt.SignOptions = {
302
+ algorithm: this.algorithm,
303
+ issuer: options?.issuer || this.issuer,
304
+ audience: options?.audience || this.audience,
305
+ jwtid: options?.jti || randomUUID(), // ✅ Using crypto.randomUUID()
306
+ subject: options?.subject
307
+ };
308
+
309
+ if (options?.expiresIn !== undefined) {
310
+ jwtOptions.expiresIn = options.expiresIn as number;
311
+ } else if (this.expiresIn !== undefined) {
312
+ jwtOptions.expiresIn = this.expiresIn as number;
313
+ }
314
+
315
+ return jwt.sign(payload, this.secret, jwtOptions);
316
+
317
+ } catch (err: any) {
318
+ logError("JWTAdapter.sign failed", { error: err?.message });
319
+ throw new AdapterError(err?.message || "JWT sign failed");
320
+ }
321
+ }
322
+
323
+ verify(token: string, options?: { audience?: string | string[] }) {
324
+ try {
325
+ const verifyOptions: jwt.VerifyOptions = {
326
+ algorithms: [this.algorithm],
327
+ issuer: this.issuer,
328
+ audience: options?.audience as string || this.audience as string
329
+ };
330
+
331
+ return jwt.verify(token, this.secret, verifyOptions);
332
+ } catch (err: any) {
333
+ logError("JWTAdapter.verify failed", { error: err?.message });
334
+
335
+ // Provide better error messages
336
+ if (err.name === 'TokenExpiredError') {
337
+ throw new AdapterError("JWT token has expired");
338
+ }
339
+ if (err.name === 'JsonWebTokenError') {
340
+ throw new AdapterError("Invalid JWT token");
341
+ }
342
+
343
+ throw new AdapterError(err?.message || "JWT verification failed");
344
+ }
345
+ }
346
+ }
@@ -0,0 +1,139 @@
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
+ import { RateLimiterMemory, RateLimiterRes } from "rate-limiter-flexible";
64
+ import { logger } from "../logging/index.js";
65
+ import { AdapterError } from "../core/errors/AdapterError.js";
66
+
67
+ export interface RLOptions {
68
+ points?: number;
69
+ duration?: number; // seconds
70
+ message?: any;
71
+ blockDuration?: number;
72
+ }
73
+
74
+ export class RLFlexibleAdapter {
75
+ getMiddleware(options: RLOptions = {}) {
76
+ try {
77
+ const defaultOptions = {
78
+ points: 100,
79
+ duration: 60,
80
+ message: "Too many requests, slow down.",
81
+ blockDuration: 0
82
+ };
83
+
84
+ const finalOptions = { ...defaultOptions, ...options };
85
+
86
+ const limiter = new RateLimiterMemory({
87
+ points: finalOptions.points,
88
+ duration: finalOptions.duration,
89
+ blockDuration: finalOptions.blockDuration
90
+ });
91
+
92
+ return async (req: any, res: any, next: any) => {
93
+ // Better IP extraction
94
+ const ip = this.extractIP(req);
95
+
96
+ try {
97
+ await limiter.consume(ip);
98
+ next();
99
+ } catch (err: any) {
100
+ const rlErr = err as RateLimiterRes;
101
+
102
+ logger.warn("⚠ RLFlexibleAdapter: rate limit exceeded", {
103
+ ip,
104
+ path: req.path,
105
+ method: req.method,
106
+ retryAfter: rlErr.msBeforeNext
107
+ });
108
+
109
+ res.setHeader('Retry-After', Math.ceil(rlErr.msBeforeNext / 1000));
110
+
111
+ return res.status(429).json({
112
+ success: false,
113
+ error: "RATE_LIMIT_EXCEEDED",
114
+ retryAfter: Math.ceil(rlErr.msBeforeNext / 1000),
115
+ message: finalOptions.message
116
+ });
117
+ }
118
+ };
119
+
120
+ } catch (err: any) {
121
+ logger.error("❌ RLFlexibleAdapter: failed to initialize limiter", {
122
+ error: err?.message || err
123
+ });
124
+ throw new AdapterError("RateLimiterFlexible creation failed.");
125
+ }
126
+ }
127
+
128
+ private extractIP(req: any): string {
129
+ // Order of priority for IP extraction
130
+ return (
131
+ req.headers['x-real-ip'] ||
132
+ req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
133
+ req.ip ||
134
+ req.connection?.remoteAddress ||
135
+ req.socket?.remoteAddress ||
136
+ 'unknown'
137
+ );
138
+ }
139
+ }
@@ -0,0 +1,162 @@
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
+ import sanitizeHtml from "sanitize-html";
84
+ import { AdapterError } from "../core/errors/AdapterError.js";
85
+ import { logger } from "../logging/index.js";
86
+
87
+ export class SanitizeHtmlAdapter {
88
+ private globalOptions: sanitizeHtml.IOptions;
89
+
90
+ constructor(options: sanitizeHtml.IOptions = {}) {
91
+ this.globalOptions = options;
92
+ }
93
+
94
+ sanitize(input: string, dynamicOptions?: any): string {
95
+ try {
96
+ const opts = { ...this.globalOptions, ...(dynamicOptions || {}) };
97
+
98
+ const clean = sanitizeHtml(input, opts);
99
+ return typeof clean === "string" ? clean : String(clean);
100
+
101
+ } catch (err: any) {
102
+ logger.error("❌ sanitize-html failed", {
103
+ error: err?.message || err,
104
+ preview: typeof input === "string" ? input.slice(0, 100) : undefined
105
+ });
106
+
107
+ throw new AdapterError("sanitize-html adapter failed.");
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Deep sanitize with recursion protection
113
+ */
114
+ private deepSanitize(obj: any, dynamicOptions?: any, visited = new WeakSet()): any {
115
+ // Handle circular references
116
+ if (obj && typeof obj === "object") {
117
+ if (visited.has(obj)) {
118
+ return obj; // Circular reference detected
119
+ }
120
+ visited.add(obj);
121
+ }
122
+
123
+ if (typeof obj === "string") {
124
+ return this.sanitize(obj, dynamicOptions);
125
+ }
126
+
127
+ if (Array.isArray(obj)) {
128
+ return obj.map((item) => this.deepSanitize(item, dynamicOptions, visited));
129
+ }
130
+
131
+ if (obj && typeof obj === "object") {
132
+ const result: any = {};
133
+ for (const key of Object.keys(obj)) {
134
+ result[key] = this.deepSanitize(obj[key], dynamicOptions, visited);
135
+ }
136
+ return result;
137
+ }
138
+
139
+ return obj;
140
+ }
141
+
142
+ middleware(dynamicOptions?: any) {
143
+ return (req: any, _res: any, next: any) => {
144
+ try {
145
+ if (req.body) {
146
+ req.body = this.deepSanitize(req.body, dynamicOptions);
147
+
148
+ logger.debug("🧼 sanitize-html applied", {
149
+ keys: Object.keys(req.body)
150
+ });
151
+ }
152
+ next();
153
+
154
+ } catch (err: any) {
155
+ logger.error("❌ sanitize-html middleware failed", {
156
+ error: err?.message || err
157
+ });
158
+ next(err);
159
+ }
160
+ };
161
+ }
162
+ }