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.
- package/dist/adapters/ArgonAdapter.d.ts +8 -0
- package/dist/adapters/ArgonAdapter.d.ts.map +1 -0
- package/dist/adapters/ArgonAdapter.js +45 -0
- package/dist/adapters/ArgonAdapter.js.map +1 -0
- package/dist/adapters/BcryptAdapter.d.ts +7 -0
- package/dist/adapters/BcryptAdapter.d.ts.map +1 -0
- package/dist/adapters/BcryptAdapter.js +48 -0
- package/dist/adapters/BcryptAdapter.js.map +1 -0
- package/dist/adapters/DomPurifyAdapter.d.ts +13 -0
- package/dist/adapters/DomPurifyAdapter.d.ts.map +1 -0
- package/dist/adapters/DomPurifyAdapter.js +61 -0
- package/dist/adapters/DomPurifyAdapter.js.map +1 -0
- package/dist/adapters/ExpressRLAdapter.d.ts +13 -0
- package/dist/adapters/ExpressRLAdapter.d.ts.map +1 -0
- package/dist/adapters/ExpressRLAdapter.js +68 -0
- package/dist/adapters/ExpressRLAdapter.js.map +1 -0
- package/dist/adapters/ExpressValidatorAdapter.d.ts +6 -0
- package/dist/adapters/ExpressValidatorAdapter.d.ts.map +1 -0
- package/dist/adapters/ExpressValidatorAdapter.js +78 -0
- package/dist/adapters/ExpressValidatorAdapter.js.map +1 -0
- package/dist/adapters/GoggleAdapter.d.ts +15 -0
- package/dist/adapters/GoggleAdapter.d.ts.map +1 -0
- package/dist/adapters/GoggleAdapter.js +91 -0
- package/dist/adapters/GoggleAdapter.js.map +1 -0
- package/dist/adapters/GoogleAdapter.d.ts +15 -0
- package/dist/adapters/GoogleAdapter.d.ts.map +1 -0
- package/dist/adapters/GoogleAdapter.js +159 -0
- package/dist/adapters/GoogleAdapter.js.map +1 -0
- package/dist/adapters/JWTAdapter.d.ts +28 -0
- package/dist/adapters/JWTAdapter.d.ts.map +1 -0
- package/dist/adapters/JWTAdapter.js +276 -0
- package/dist/adapters/JWTAdapter.js.map +1 -0
- package/dist/adapters/RLFlexibleAdapter.d.ts +11 -0
- package/dist/adapters/RLFlexibleAdapter.d.ts.map +1 -0
- package/dist/adapters/RLFlexibleAdapter.js +115 -0
- package/dist/adapters/RLFlexibleAdapter.js.map +1 -0
- package/dist/adapters/SanitizeHtmlAdapter.d.ts +12 -0
- package/dist/adapters/SanitizeHtmlAdapter.d.ts.map +1 -0
- package/dist/adapters/SanitizeHtmlAdapter.js +141 -0
- package/dist/adapters/SanitizeHtmlAdapter.js.map +1 -0
- package/dist/adapters/XSSAdapter.d.ts +33 -0
- package/dist/adapters/XSSAdapter.d.ts.map +1 -0
- package/dist/adapters/XSSAdapter.js +127 -0
- package/dist/adapters/XSSAdapter.js.map +1 -0
- package/dist/adapters/ZodAdapter.d.ts +7 -0
- package/dist/adapters/ZodAdapter.d.ts.map +1 -0
- package/dist/adapters/ZodAdapter.js +39 -0
- package/dist/adapters/ZodAdapter.js.map +1 -0
- package/dist/core/HiSecure.d.ts +62 -0
- package/dist/core/HiSecure.d.ts.map +1 -0
- package/dist/core/HiSecure.js +273 -0
- package/dist/core/HiSecure.js.map +1 -0
- package/dist/core/config.d.ts +3 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +53 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/constants.d.ts +37 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/constants.js +67 -0
- package/dist/core/constants.js.map +1 -0
- package/dist/core/errors/AdapterError.d.ts +5 -0
- package/dist/core/errors/AdapterError.d.ts.map +1 -0
- package/dist/core/errors/AdapterError.js +15 -0
- package/dist/core/errors/AdapterError.js.map +1 -0
- package/dist/core/errors/HttpErrror.d.ts +17 -0
- package/dist/core/errors/HttpErrror.d.ts.map +1 -0
- package/dist/core/errors/HttpErrror.js +36 -0
- package/dist/core/errors/HttpErrror.js.map +1 -0
- package/dist/core/errors/SanitizerError.d.ts +5 -0
- package/dist/core/errors/SanitizerError.d.ts.map +1 -0
- package/dist/core/errors/SanitizerError.js +14 -0
- package/dist/core/errors/SanitizerError.js.map +1 -0
- package/dist/core/errors/SecurityError.d.ts +5 -0
- package/dist/core/errors/SecurityError.d.ts.map +1 -0
- package/dist/core/errors/SecurityError.js +14 -0
- package/dist/core/errors/SecurityError.js.map +1 -0
- package/dist/core/errors/ValidationError.d.ts +5 -0
- package/dist/core/errors/ValidationError.d.ts.map +1 -0
- package/dist/core/errors/ValidationError.js +14 -0
- package/dist/core/errors/ValidationError.js.map +1 -0
- package/dist/core/types/HiSecureConfig.d.ts +47 -0
- package/dist/core/types/HiSecureConfig.d.ts.map +1 -0
- package/dist/core/types/HiSecureConfig.js +3 -0
- package/dist/core/types/HiSecureConfig.js.map +1 -0
- package/dist/core/types/SecureOptions.d.ts +30 -0
- package/dist/core/types/SecureOptions.d.ts.map +1 -0
- package/dist/core/types/SecureOptions.js +4 -0
- package/dist/core/types/SecureOptions.js.map +1 -0
- package/dist/core/useSecure.d.ts +10 -0
- package/dist/core/useSecure.d.ts.map +1 -0
- package/dist/core/useSecure.js +85 -0
- package/dist/core/useSecure.js.map +1 -0
- package/dist/examples/e1.d.ts +1 -0
- package/dist/examples/e1.d.ts.map +1 -0
- package/dist/examples/e1.js +3 -0
- package/dist/examples/e1.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +19 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/morganSetup.d.ts +2 -0
- package/dist/logging/morganSetup.d.ts.map +1 -0
- package/dist/logging/morganSetup.js +9 -0
- package/dist/logging/morganSetup.js.map +1 -0
- package/dist/logging/winstonSetup.d.ts +6 -0
- package/dist/logging/winstonSetup.d.ts.map +1 -0
- package/dist/logging/winstonSetup.js +22 -0
- package/dist/logging/winstonSetup.js.map +1 -0
- package/dist/managers/AuthManager.d.ts +23 -0
- package/dist/managers/AuthManager.d.ts.map +1 -0
- package/dist/managers/AuthManager.js +190 -0
- package/dist/managers/AuthManager.js.map +1 -0
- package/dist/managers/CorsManager.d.ts +9 -0
- package/dist/managers/CorsManager.d.ts.map +1 -0
- package/dist/managers/CorsManager.js +55 -0
- package/dist/managers/CorsManager.js.map +1 -0
- package/dist/managers/HashManager.d.ts +22 -0
- package/dist/managers/HashManager.d.ts.map +1 -0
- package/dist/managers/HashManager.js +319 -0
- package/dist/managers/HashManager.js.map +1 -0
- package/dist/managers/JsonManager.d.ts +6 -0
- package/dist/managers/JsonManager.d.ts.map +1 -0
- package/dist/managers/JsonManager.js +142 -0
- package/dist/managers/JsonManager.js.map +1 -0
- package/dist/managers/RateLimitManager.d.ts +16 -0
- package/dist/managers/RateLimitManager.d.ts.map +1 -0
- package/dist/managers/RateLimitManager.js +108 -0
- package/dist/managers/RateLimitManager.js.map +1 -0
- package/dist/managers/SanitizerManager.d.ts +18 -0
- package/dist/managers/SanitizerManager.d.ts.map +1 -0
- package/dist/managers/SanitizerManager.js +296 -0
- package/dist/managers/SanitizerManager.js.map +1 -0
- package/dist/managers/ValidatorManager.d.ts +13 -0
- package/dist/managers/ValidatorManager.d.ts.map +1 -0
- package/dist/managers/ValidatorManager.js +218 -0
- package/dist/managers/ValidatorManager.js.map +1 -0
- package/dist/middlewares/errorHandler.d.ts +3 -0
- package/dist/middlewares/errorHandler.d.ts.map +1 -0
- package/dist/middlewares/errorHandler.js +94 -0
- package/dist/middlewares/errorHandler.js.map +1 -0
- package/dist/middlewares/index.d.ts +3 -0
- package/dist/middlewares/index.d.ts.map +1 -0
- package/dist/middlewares/index.js +19 -0
- package/dist/middlewares/index.js.map +1 -0
- package/dist/middlewares/requestLogger.d.ts +2 -0
- package/dist/middlewares/requestLogger.d.ts.map +1 -0
- package/dist/middlewares/requestLogger.js +8 -0
- package/dist/middlewares/requestLogger.js.map +1 -0
- package/dist/test/t1.d.ts +1 -0
- package/dist/test/t1.d.ts.map +1 -0
- package/dist/test/t1.js +3 -0
- package/dist/test/t1.js.map +1 -0
- package/dist/utils/deepFreeze.d.ts +2 -0
- package/dist/utils/deepFreeze.d.ts.map +1 -0
- package/dist/utils/deepFreeze.js +69 -0
- package/dist/utils/deepFreeze.js.map +1 -0
- package/dist/utils/deepMerge.d.ts +5 -0
- package/dist/utils/deepMerge.d.ts.map +1 -0
- package/dist/utils/deepMerge.js +68 -0
- package/dist/utils/deepMerge.js.map +1 -0
- package/dist/utils/normalizeOptions.d.ts +38 -0
- package/dist/utils/normalizeOptions.d.ts.map +1 -0
- package/dist/utils/normalizeOptions.js +119 -0
- package/dist/utils/normalizeOptions.js.map +1 -0
- package/package.json +50 -0
- package/src/adapters/ArgonAdapter.ts +41 -0
- package/src/adapters/BcryptAdapter.ts +49 -0
- package/src/adapters/ExpressRLAdapter.ts +84 -0
- package/src/adapters/ExpressValidatorAdapter.ts +99 -0
- package/src/adapters/GoogleAdapter.ts +206 -0
- package/src/adapters/JWTAdapter.ts +346 -0
- package/src/adapters/RLFlexibleAdapter.ts +139 -0
- package/src/adapters/SanitizeHtmlAdapter.ts +162 -0
- package/src/adapters/XSSAdapter.ts +153 -0
- package/src/adapters/ZodAdapter.ts +91 -0
- package/src/core/HiSecure.ts +955 -0
- package/src/core/config.ts +156 -0
- package/src/core/constants.ts +73 -0
- package/src/core/errors/AdapterError.ts +14 -0
- package/src/core/errors/HttpErrror.ts +46 -0
- package/src/core/errors/SanitizerError.ts +13 -0
- package/src/core/errors/SecurityError.ts +13 -0
- package/src/core/errors/ValidationError.ts +13 -0
- package/src/core/types/HiSecureConfig.ts +62 -0
- package/src/core/types/SecureOptions.ts +61 -0
- package/src/core/useSecure.ts +111 -0
- package/src/examples/e1.ts +1 -0
- package/src/index.ts +17 -0
- package/src/logging/index.ts +2 -0
- package/src/logging/morganSetup.ts +3 -0
- package/src/logging/winstonSetup.ts +17 -0
- package/src/managers/AuthManager.ts +237 -0
- package/src/managers/CorsManager.ts +58 -0
- package/src/managers/HashManager.ts +390 -0
- package/src/managers/JsonManager.ts +149 -0
- package/src/managers/RateLimitManager.ts +368 -0
- package/src/managers/SanitizerManager.ts +359 -0
- package/src/managers/ValidatorManager.ts +269 -0
- package/src/middlewares/errorHandler.ts +265 -0
- package/src/middlewares/index.ts +2 -0
- package/src/middlewares/requestLogger.ts +5 -0
- package/src/test/t1.ts +1 -0
- package/src/utils/deepFreeze.ts +76 -0
- package/src/utils/deepMerge.ts +87 -0
- package/src/utils/normalizeOptions.ts +265 -0
- 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
|
+
}
|