hi-secure 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/ArgonAdapter.d.ts +1 -1
- package/dist/adapters/ArgonAdapter.d.ts.map +1 -1
- package/dist/adapters/ArgonAdapter.js +7 -5
- package/dist/adapters/ArgonAdapter.js.map +1 -1
- package/dist/adapters/BcryptAdapter.d.ts.map +1 -1
- package/dist/adapters/BcryptAdapter.js +7 -3
- package/dist/adapters/BcryptAdapter.js.map +1 -1
- package/dist/adapters/ExpressRLAdapter.d.ts.map +1 -1
- package/dist/adapters/ExpressRLAdapter.js +10 -6
- package/dist/adapters/ExpressRLAdapter.js.map +1 -1
- package/dist/adapters/ExpressValidatorAdapter.d.ts.map +1 -1
- package/dist/adapters/ExpressValidatorAdapter.js +14 -10
- package/dist/adapters/ExpressValidatorAdapter.js.map +1 -1
- package/dist/adapters/GoogleAdapter.d.ts.map +1 -1
- package/dist/adapters/GoogleAdapter.js +19 -16
- package/dist/adapters/GoogleAdapter.js.map +1 -1
- package/dist/adapters/JWTAdapter.d.ts.map +1 -1
- package/dist/adapters/JWTAdapter.js +25 -15
- package/dist/adapters/JWTAdapter.js.map +1 -1
- package/dist/adapters/RLFlexibleAdapter.d.ts.map +1 -1
- package/dist/adapters/RLFlexibleAdapter.js +23 -12
- package/dist/adapters/RLFlexibleAdapter.js.map +1 -1
- package/dist/adapters/SanitizeHtmlAdapter.d.ts.map +1 -1
- package/dist/adapters/SanitizeHtmlAdapter.js +17 -13
- package/dist/adapters/SanitizeHtmlAdapter.js.map +1 -1
- package/dist/adapters/XSSAdapter.d.ts +1 -1
- package/dist/adapters/XSSAdapter.d.ts.map +1 -1
- package/dist/adapters/XSSAdapter.js +21 -20
- package/dist/adapters/XSSAdapter.js.map +1 -1
- package/dist/adapters/ZodAdapter.d.ts +1 -1
- package/dist/adapters/ZodAdapter.d.ts.map +1 -1
- package/dist/adapters/ZodAdapter.js +10 -8
- package/dist/adapters/ZodAdapter.js.map +1 -1
- package/dist/core/HiSecure.d.ts +3 -4
- package/dist/core/HiSecure.d.ts.map +1 -1
- package/dist/core/HiSecure.js +91 -120
- package/dist/core/HiSecure.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/logging/morganSetup.d.ts.map +1 -1
- package/dist/logging/morganSetup.js +8 -1
- package/dist/logging/morganSetup.js.map +1 -1
- package/dist/logging/winstonSetup.d.ts.map +1 -1
- package/dist/logging/winstonSetup.js +17 -3
- package/dist/logging/winstonSetup.js.map +1 -1
- package/dist/managers/AuthManager.d.ts +2 -2
- package/dist/managers/AuthManager.d.ts.map +1 -1
- package/dist/managers/AuthManager.js +59 -31
- package/dist/managers/AuthManager.js.map +1 -1
- package/dist/managers/CorsManager.d.ts.map +1 -1
- package/dist/managers/CorsManager.js +18 -11
- package/dist/managers/CorsManager.js.map +1 -1
- package/dist/managers/HashManager.d.ts +1 -1
- package/dist/managers/HashManager.d.ts.map +1 -1
- package/dist/managers/HashManager.js +35 -17
- package/dist/managers/HashManager.js.map +1 -1
- package/dist/managers/JsonManager.d.ts +1 -1
- package/dist/managers/JsonManager.d.ts.map +1 -1
- package/dist/managers/JsonManager.js +44 -16
- package/dist/managers/JsonManager.js.map +1 -1
- package/dist/managers/RateLimitManager.d.ts +1 -1
- package/dist/managers/RateLimitManager.d.ts.map +1 -1
- package/dist/managers/RateLimitManager.js +43 -22
- package/dist/managers/RateLimitManager.js.map +1 -1
- package/dist/managers/SanitizerManager.d.ts.map +1 -1
- package/dist/managers/SanitizerManager.js +32 -15
- package/dist/managers/SanitizerManager.js.map +1 -1
- package/dist/managers/ValidatorManager.d.ts.map +1 -1
- package/dist/managers/ValidatorManager.js +31 -7
- package/dist/managers/ValidatorManager.js.map +1 -1
- package/package.json +2 -6
- package/readme.md +3 -6
- package/src/adapters/ArgonAdapter.ts +10 -6
- package/src/adapters/BcryptAdapter.ts +7 -8
- package/src/adapters/ExpressRLAdapter.ts +14 -9
- package/src/adapters/ExpressValidatorAdapter.ts +17 -11
- package/src/adapters/GoogleAdapter.ts +24 -21
- package/src/adapters/JWTAdapter.ts +33 -21
- package/src/adapters/RLFlexibleAdapter.ts +31 -16
- package/src/adapters/SanitizeHtmlAdapter.ts +28 -18
- package/src/adapters/XSSAdapter.ts +33 -38
- package/src/adapters/ZodAdapter.ts +10 -10
- package/src/core/HiSecure.ts +127 -161
- package/src/index.ts +4 -0
- package/src/logging/morganSetup.ts +11 -1
- package/src/logging/winstonSetup.ts +35 -8
- package/src/managers/AuthManager.ts +64 -34
- package/src/managers/CorsManager.ts +23 -16
- package/src/managers/HashManager.ts +48 -19
- package/src/managers/JsonManager.ts +57 -15
- package/src/managers/RateLimitManager.ts +61 -29
- package/src/managers/SanitizerManager.ts +47 -25
- package/src/managers/ValidatorManager.ts +40 -15
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { JWTAdapter } from "../adapters/JWTAdapter
|
|
2
|
-
import { GoogleAdapter } from "../adapters/GoogleAdapter
|
|
3
|
-
import { AdapterError } from "../core/errors/AdapterError
|
|
4
|
-
import { HttpError } from "../core/errors/HttpError
|
|
1
|
+
import { JWTAdapter } from "../adapters/JWTAdapter";
|
|
2
|
+
import { GoogleAdapter } from "../adapters/GoogleAdapter";
|
|
3
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
4
|
+
import { HttpError } from "../core/errors/HttpError";
|
|
5
5
|
import { Request, Response, NextFunction } from "express";
|
|
6
6
|
import { logger } from "../logging";
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
export interface AuthOptions {
|
|
10
9
|
jwtSecret: string;
|
|
11
10
|
jwtExpiresIn?: string | number;
|
|
@@ -27,29 +26,47 @@ export class AuthManager {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
if (opts.jwtSecret.length < 32) {
|
|
30
|
-
logger.warn(" JWT secret
|
|
29
|
+
logger.warn("Weak JWT secret detected", {
|
|
30
|
+
layer: "auth-manager",
|
|
31
|
+
operation: "init",
|
|
32
|
+
secretLength: opts.jwtSecret.length
|
|
33
|
+
});
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
logger.info("AuthManager initialized"
|
|
36
|
+
logger.info("AuthManager initialized", {
|
|
37
|
+
layer: "auth-manager",
|
|
38
|
+
jwtExpiresIn: opts.jwtExpiresIn ?? "1d",
|
|
39
|
+
googleEnabled: !!opts.googleClientId
|
|
40
|
+
});
|
|
34
41
|
|
|
35
42
|
this.jwtAdapter = new JWTAdapter({
|
|
36
43
|
secret: opts.jwtSecret,
|
|
37
|
-
expiresIn: opts.jwtExpiresIn ?? "1d"
|
|
44
|
+
expiresIn: opts.jwtExpiresIn ?? "1d"
|
|
38
45
|
});
|
|
39
46
|
|
|
40
47
|
if (opts.googleClientId) {
|
|
41
48
|
this.googleAdapter = new GoogleAdapter(opts.googleClientId);
|
|
42
|
-
logger.info("
|
|
49
|
+
logger.info("Google authentication enabled", {
|
|
50
|
+
layer: "auth-manager"
|
|
51
|
+
});
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
sign(payload: object, options?: { expiresIn?: string | number
|
|
47
|
-
logger.info("JWT
|
|
55
|
+
sign(payload: object, options?: { expiresIn?: string | number; jti?: string }) {
|
|
56
|
+
logger.info("JWT sign requested", {
|
|
57
|
+
layer: "auth-manager",
|
|
58
|
+
operation: "sign"
|
|
59
|
+
});
|
|
60
|
+
|
|
48
61
|
return this.jwtAdapter.sign(payload, options);
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
verify(token: string) {
|
|
52
|
-
logger.info("JWT
|
|
65
|
+
logger.info("JWT verify requested", {
|
|
66
|
+
layer: "auth-manager",
|
|
67
|
+
operation: "verify"
|
|
68
|
+
});
|
|
69
|
+
|
|
53
70
|
return this.jwtAdapter.verify(token);
|
|
54
71
|
}
|
|
55
72
|
|
|
@@ -58,12 +75,20 @@ export class AuthManager {
|
|
|
58
75
|
throw new AdapterError("GoogleAdapter not configured.");
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
logger.info("Google ID
|
|
78
|
+
logger.info("Google ID token verification requested", {
|
|
79
|
+
layer: "auth-manager",
|
|
80
|
+
operation: "google-verify"
|
|
81
|
+
});
|
|
62
82
|
|
|
63
83
|
try {
|
|
64
84
|
return await this.googleAdapter.verifyIdToken(idToken);
|
|
65
85
|
} catch (err: any) {
|
|
66
|
-
logger.error("Google ID
|
|
86
|
+
logger.error("Google ID token verification failed", {
|
|
87
|
+
layer: "auth-manager",
|
|
88
|
+
operation: "google-verify",
|
|
89
|
+
reason: err?.message
|
|
90
|
+
});
|
|
91
|
+
|
|
67
92
|
throw HttpError.Unauthorized("Invalid Google ID token");
|
|
68
93
|
}
|
|
69
94
|
}
|
|
@@ -72,27 +97,28 @@ export class AuthManager {
|
|
|
72
97
|
const required = options?.required ?? true;
|
|
73
98
|
const roles = options?.roles;
|
|
74
99
|
|
|
75
|
-
return (req: Request,
|
|
100
|
+
return (req: Request, _res: Response, next: NextFunction) => {
|
|
76
101
|
const header = req.headers["authorization"];
|
|
77
102
|
|
|
78
|
-
|
|
79
103
|
if (!required && !header) {
|
|
80
104
|
return next();
|
|
81
105
|
}
|
|
82
106
|
|
|
83
|
-
|
|
84
107
|
if (!header) {
|
|
85
|
-
logger.warn("
|
|
108
|
+
logger.warn("Authorization header missing", {
|
|
109
|
+
layer: "auth-manager",
|
|
110
|
+
operation: "protect",
|
|
86
111
|
path: req.path,
|
|
87
112
|
method: req.method
|
|
88
113
|
});
|
|
89
114
|
return next(HttpError.Unauthorized("Missing Authorization header"));
|
|
90
115
|
}
|
|
91
116
|
|
|
92
|
-
|
|
93
117
|
const [type, token] = String(header).split(" ");
|
|
94
118
|
if (type !== "Bearer" || !token) {
|
|
95
|
-
logger.warn("Invalid Authorization header", {
|
|
119
|
+
logger.warn("Invalid Authorization header format", {
|
|
120
|
+
layer: "auth-manager",
|
|
121
|
+
operation: "protect",
|
|
96
122
|
path: req.path,
|
|
97
123
|
method: req.method
|
|
98
124
|
});
|
|
@@ -100,36 +126,40 @@ export class AuthManager {
|
|
|
100
126
|
}
|
|
101
127
|
|
|
102
128
|
try {
|
|
103
|
-
|
|
104
|
-
// Verify JWT
|
|
105
129
|
const decoded = this.verify(token);
|
|
106
|
-
|
|
107
|
-
// Attach to request
|
|
130
|
+
|
|
108
131
|
(req as any).auth = decoded;
|
|
109
|
-
(req as any).user = decoded;
|
|
110
|
-
|
|
111
|
-
// Role-based authorization - role added Middleware
|
|
132
|
+
(req as any).user = decoded;
|
|
133
|
+
|
|
112
134
|
if (roles && roles.length > 0) {
|
|
113
|
-
const userRole =
|
|
135
|
+
const userRole =
|
|
136
|
+
(decoded as any).role || (decoded as any).roles?.[0];
|
|
137
|
+
|
|
114
138
|
if (!userRole || !roles.includes(userRole)) {
|
|
115
|
-
logger.warn("
|
|
139
|
+
logger.warn("Access denied: insufficient role", {
|
|
140
|
+
layer: "auth-manager",
|
|
141
|
+
operation: "authorize",
|
|
116
142
|
path: req.path,
|
|
117
143
|
requiredRoles: roles,
|
|
118
144
|
userRole
|
|
119
145
|
});
|
|
146
|
+
|
|
120
147
|
return next(HttpError.Forbidden("Insufficient permissions"));
|
|
121
148
|
}
|
|
122
149
|
}
|
|
123
|
-
|
|
150
|
+
|
|
124
151
|
return next();
|
|
125
152
|
} catch (err: any) {
|
|
126
|
-
logger.error("JWT
|
|
127
|
-
|
|
153
|
+
logger.error("JWT authentication failed", {
|
|
154
|
+
layer: "auth-manager",
|
|
155
|
+
operation: "protect",
|
|
128
156
|
path: req.path,
|
|
129
|
-
method: req.method
|
|
157
|
+
method: req.method,
|
|
158
|
+
reason: err?.message
|
|
130
159
|
});
|
|
160
|
+
|
|
131
161
|
return next(HttpError.Unauthorized("Invalid or expired token"));
|
|
132
162
|
}
|
|
133
163
|
};
|
|
134
164
|
}
|
|
135
|
-
}
|
|
165
|
+
}
|
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
import cors from "cors";
|
|
2
2
|
import { logger } from "../logging";
|
|
3
|
-
import { AdapterError } from "../core/errors/AdapterError
|
|
3
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
4
4
|
|
|
5
5
|
export class CorsManager {
|
|
6
|
-
|
|
7
6
|
middleware(options?: any) {
|
|
8
7
|
try {
|
|
9
8
|
const defaultOptions = {
|
|
10
|
-
origin:
|
|
11
|
-
methods: [
|
|
12
|
-
allowedHeaders: [
|
|
9
|
+
origin: "*",
|
|
10
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
11
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
13
12
|
credentials: false,
|
|
14
13
|
maxAge: 86400
|
|
15
14
|
};
|
|
16
|
-
|
|
17
|
-
const finalOptions = options
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
|
|
16
|
+
const finalOptions = options
|
|
17
|
+
? { ...defaultOptions, ...options }
|
|
18
|
+
: defaultOptions;
|
|
19
|
+
|
|
20
|
+
// ✅ visible + clean log
|
|
21
|
+
logger.info("CORS middleware configured", {
|
|
22
|
+
layer: "cors-manager",
|
|
23
|
+
operation: "init",
|
|
20
24
|
origin: finalOptions.origin,
|
|
21
|
-
methods: finalOptions.methods
|
|
25
|
+
methods: finalOptions.methods,
|
|
26
|
+
credentials: finalOptions.credentials
|
|
22
27
|
});
|
|
23
|
-
|
|
28
|
+
|
|
24
29
|
return cors(finalOptions);
|
|
25
|
-
|
|
30
|
+
|
|
26
31
|
} catch (err: any) {
|
|
27
|
-
logger.error("
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
logger.error("CORS middleware initialization failed", {
|
|
33
|
+
layer: "cors-manager",
|
|
34
|
+
operation: "init",
|
|
35
|
+
reason: err?.message
|
|
30
36
|
});
|
|
37
|
+
|
|
31
38
|
throw new AdapterError("CORS middleware initialization failed.");
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
|
-
}
|
|
41
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AdapterError } from "../core/errors/AdapterError
|
|
2
|
-
import { HiSecureConfig } from "../core/types/HiSecureConfig
|
|
1
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
2
|
+
import { HiSecureConfig } from "../core/types/HiSecureConfig";
|
|
3
3
|
import { logger } from "../logging";
|
|
4
4
|
|
|
5
5
|
interface HashAdapter {
|
|
@@ -26,20 +26,33 @@ export class HashManager {
|
|
|
26
26
|
this.config = config;
|
|
27
27
|
this.primaryAdapter = primaryAdapter;
|
|
28
28
|
this.fallbackAdapter = fallbackAdapter;
|
|
29
|
+
|
|
30
|
+
logger.info("HashManager initialized", {
|
|
31
|
+
layer: "hash-manager",
|
|
32
|
+
primary: config.primary,
|
|
33
|
+
fallbackEnabled: !!fallbackAdapter
|
|
34
|
+
});
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
async hash(
|
|
37
|
+
async hash(
|
|
38
|
+
value: string,
|
|
39
|
+
options?: { allowFallback?: boolean }
|
|
40
|
+
): Promise<HashResult> {
|
|
32
41
|
try {
|
|
33
42
|
const hash = await this.primaryAdapter.hash(value);
|
|
43
|
+
|
|
34
44
|
return {
|
|
35
45
|
hash,
|
|
36
46
|
algorithm: this.config.primary,
|
|
37
47
|
usedFallback: false
|
|
38
48
|
};
|
|
49
|
+
|
|
39
50
|
} catch (err: any) {
|
|
40
51
|
logger.warn("Primary hashing failed", {
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
layer: "hash-manager",
|
|
53
|
+
operation: "hash",
|
|
54
|
+
algorithm: this.config.primary,
|
|
55
|
+
reason: err?.message
|
|
43
56
|
});
|
|
44
57
|
|
|
45
58
|
if (!options?.allowFallback || !this.fallbackAdapter) {
|
|
@@ -50,22 +63,30 @@ export class HashManager {
|
|
|
50
63
|
|
|
51
64
|
try {
|
|
52
65
|
const hash = await this.fallbackAdapter.hash(value);
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
logger.warn("
|
|
66
|
+
|
|
67
|
+
// ⚠️ security downgrade log (VERY GOOD PRACTICE)
|
|
68
|
+
logger.warn("Hashing fallback used (security downgrade)", {
|
|
69
|
+
layer: "hash-manager",
|
|
70
|
+
operation: "hash",
|
|
56
71
|
from: this.config.primary,
|
|
57
72
|
to: this.config.fallback
|
|
58
73
|
});
|
|
59
|
-
|
|
74
|
+
|
|
60
75
|
return {
|
|
61
76
|
hash,
|
|
62
|
-
algorithm: this.config.fallback ||
|
|
77
|
+
algorithm: this.config.fallback || "bcrypt",
|
|
63
78
|
usedFallback: true
|
|
64
79
|
};
|
|
80
|
+
|
|
65
81
|
} catch (fallbackErr: any) {
|
|
66
82
|
logger.error("Fallback hashing failed", {
|
|
67
|
-
|
|
83
|
+
layer: "hash-manager",
|
|
84
|
+
operation: "hash",
|
|
85
|
+
from: this.config.primary,
|
|
86
|
+
to: this.config.fallback,
|
|
87
|
+
reason: fallbackErr?.message
|
|
68
88
|
});
|
|
89
|
+
|
|
69
90
|
throw new AdapterError(
|
|
70
91
|
"Both primary and fallback hashing failed."
|
|
71
92
|
);
|
|
@@ -74,31 +95,39 @@ export class HashManager {
|
|
|
74
95
|
}
|
|
75
96
|
|
|
76
97
|
async verify(value: string, hashed: string): Promise<boolean> {
|
|
77
|
-
// primary adapter - first
|
|
78
98
|
try {
|
|
79
99
|
return await this.primaryAdapter.verify(value, hashed);
|
|
100
|
+
|
|
80
101
|
} catch (primaryErr: any) {
|
|
81
|
-
logger.warn("Primary
|
|
82
|
-
|
|
102
|
+
logger.warn("Primary hash verification failed", {
|
|
103
|
+
layer: "hash-manager",
|
|
104
|
+
operation: "verify",
|
|
105
|
+
algorithm: this.config.primary,
|
|
106
|
+
reason: primaryErr?.message
|
|
83
107
|
});
|
|
84
108
|
|
|
85
|
-
// fallback exists - try it
|
|
86
109
|
if (this.fallbackAdapter) {
|
|
87
110
|
try {
|
|
88
111
|
return await this.fallbackAdapter.verify(value, hashed);
|
|
112
|
+
|
|
89
113
|
} catch (fallbackErr: any) {
|
|
90
|
-
logger.error("
|
|
91
|
-
|
|
114
|
+
logger.error("Fallback hash verification failed", {
|
|
115
|
+
layer: "hash-manager",
|
|
116
|
+
operation: "verify",
|
|
117
|
+
from: this.config.primary,
|
|
118
|
+
to: this.config.fallback,
|
|
119
|
+
reason: fallbackErr?.message
|
|
92
120
|
});
|
|
121
|
+
|
|
93
122
|
throw new AdapterError(
|
|
94
123
|
"Both primary and fallback verify failed."
|
|
95
124
|
);
|
|
96
125
|
}
|
|
97
126
|
}
|
|
98
|
-
|
|
127
|
+
|
|
99
128
|
throw new AdapterError(
|
|
100
129
|
"Primary verify failed and no fallback adapter configured."
|
|
101
130
|
);
|
|
102
131
|
}
|
|
103
132
|
}
|
|
104
|
-
}
|
|
133
|
+
}
|
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
import express from "express";
|
|
2
2
|
import qs from "qs";
|
|
3
3
|
import { logger } from "../logging";
|
|
4
|
-
import { AdapterError } from "../core/errors/AdapterError
|
|
4
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
5
5
|
|
|
6
6
|
export class JsonManager {
|
|
7
7
|
middleware(options?: any) {
|
|
8
8
|
try {
|
|
9
9
|
const defaultOptions = {
|
|
10
|
-
limit:
|
|
10
|
+
limit: "1mb",
|
|
11
11
|
inflate: true,
|
|
12
12
|
strict: true
|
|
13
13
|
};
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
const finalOptions = { ...defaultOptions, ...(options || {}) };
|
|
16
|
+
|
|
17
|
+
logger.info("JSON body parser configured", {
|
|
18
|
+
layer: "json-manager",
|
|
19
|
+
operation: "json",
|
|
20
|
+
limit: finalOptions.limit,
|
|
21
|
+
strict: finalOptions.strict
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return express.json(finalOptions);
|
|
25
|
+
|
|
15
26
|
} catch (err: any) {
|
|
16
|
-
logger.error("JSON
|
|
27
|
+
logger.error("JSON body parser initialization failed", {
|
|
28
|
+
layer: "json-manager",
|
|
29
|
+
operation: "json",
|
|
30
|
+
reason: err?.message
|
|
31
|
+
});
|
|
32
|
+
|
|
17
33
|
throw new AdapterError("JSON parser initialization failed.");
|
|
18
34
|
}
|
|
19
35
|
}
|
|
@@ -22,38 +38,64 @@ export class JsonManager {
|
|
|
22
38
|
try {
|
|
23
39
|
const defaultOptions = {
|
|
24
40
|
extended: true,
|
|
25
|
-
limit:
|
|
41
|
+
limit: "1mb",
|
|
26
42
|
parameterLimit: 1000
|
|
27
43
|
};
|
|
28
|
-
|
|
29
|
-
|
|
44
|
+
|
|
45
|
+
const finalOptions = { ...defaultOptions, ...(options || {}) };
|
|
46
|
+
|
|
47
|
+
logger.info("URL-encoded parser configured", {
|
|
48
|
+
layer: "json-manager",
|
|
49
|
+
operation: "urlencoded",
|
|
50
|
+
limit: finalOptions.limit,
|
|
51
|
+
parameterLimit: finalOptions.parameterLimit
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return express.urlencoded(finalOptions);
|
|
55
|
+
|
|
30
56
|
} catch (err: any) {
|
|
31
|
-
logger.error("URL-encoded parser failed"
|
|
57
|
+
logger.error("URL-encoded parser initialization failed", {
|
|
58
|
+
layer: "json-manager",
|
|
59
|
+
operation: "urlencoded",
|
|
60
|
+
reason: err?.message
|
|
61
|
+
});
|
|
62
|
+
|
|
32
63
|
throw new AdapterError("URL-encoded parser initialization failed.");
|
|
33
64
|
}
|
|
34
65
|
}
|
|
35
66
|
|
|
36
67
|
queryParser(options?: any) {
|
|
37
|
-
return (req: any,
|
|
68
|
+
return (req: any, _res: any, next: any) => {
|
|
38
69
|
try {
|
|
39
|
-
if (!req.parsedQuery && req.url.includes(
|
|
70
|
+
if (!req.parsedQuery && req.url.includes("?")) {
|
|
40
71
|
const queryString = req.url.split("?")[1] || "";
|
|
72
|
+
|
|
41
73
|
const parsed = qs.parse(queryString, {
|
|
42
74
|
depth: 5,
|
|
43
75
|
parameterLimit: 100,
|
|
44
76
|
...options
|
|
45
77
|
});
|
|
46
|
-
|
|
78
|
+
|
|
47
79
|
req.parsedQuery = parsed;
|
|
48
|
-
|
|
49
|
-
|
|
80
|
+
|
|
81
|
+
// ✅ visible + safe info
|
|
82
|
+
logger.info("Query parameters parsed", {
|
|
83
|
+
layer: "json-manager",
|
|
84
|
+
operation: "query-parse",
|
|
85
|
+
keyCount: Object.keys(parsed).length
|
|
50
86
|
});
|
|
51
87
|
}
|
|
88
|
+
|
|
52
89
|
next();
|
|
53
90
|
} catch (err: any) {
|
|
54
|
-
logger.error("
|
|
91
|
+
logger.error("Query parsing failed", {
|
|
92
|
+
layer: "json-manager",
|
|
93
|
+
operation: "query-parse",
|
|
94
|
+
reason: err?.message
|
|
95
|
+
});
|
|
96
|
+
|
|
55
97
|
next(new AdapterError("Query parsing failed."));
|
|
56
98
|
}
|
|
57
99
|
};
|
|
58
100
|
}
|
|
59
|
-
}
|
|
101
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HiSecureConfig } from "../core/types/HiSecureConfig
|
|
2
|
-
import { AdapterError } from "../core/errors/AdapterError
|
|
1
|
+
import { HiSecureConfig } from "../core/types/HiSecureConfig";
|
|
2
|
+
import { AdapterError } from "../core/errors/AdapterError";
|
|
3
3
|
import { logger } from "../logging";
|
|
4
4
|
|
|
5
5
|
interface RateLimiterAdapter {
|
|
@@ -19,26 +19,33 @@ export class RateLimitManager {
|
|
|
19
19
|
this.config = config;
|
|
20
20
|
this.primaryAdapter = primaryAdapter;
|
|
21
21
|
this.fallbackAdapter = fallbackAdapter;
|
|
22
|
+
|
|
23
|
+
logger.info("RateLimitManager initialized", {
|
|
24
|
+
layer: "rate-limit-manager",
|
|
25
|
+
primaryConfigured: true,
|
|
26
|
+
fallbackConfigured: !!fallbackAdapter
|
|
27
|
+
});
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
middleware(opts?: { mode?: "strict" | "relaxed" | "api"; options?: any }) {
|
|
25
31
|
let finalOptions: any = {};
|
|
32
|
+
const mode = opts?.mode || "default";
|
|
26
33
|
|
|
27
|
-
if (
|
|
34
|
+
if (mode === "strict") {
|
|
28
35
|
finalOptions = {
|
|
29
36
|
windowMs: 10_000,
|
|
30
37
|
max: 5,
|
|
31
38
|
message: "Too many requests, please slow down."
|
|
32
39
|
};
|
|
33
|
-
} else if (
|
|
40
|
+
} else if (mode === "relaxed") {
|
|
34
41
|
finalOptions = {
|
|
35
42
|
windowMs: 60_000,
|
|
36
43
|
max: 100,
|
|
37
44
|
message: "Rate limit exceeded."
|
|
38
45
|
};
|
|
39
|
-
} else if (
|
|
46
|
+
} else if (mode === "api") {
|
|
40
47
|
finalOptions = {
|
|
41
|
-
windowMs: 15 * 60 * 1000,
|
|
48
|
+
windowMs: 15 * 60 * 1000,
|
|
42
49
|
max: 100,
|
|
43
50
|
message: "API rate limit exceeded."
|
|
44
51
|
};
|
|
@@ -47,63 +54,88 @@ export class RateLimitManager {
|
|
|
47
54
|
windowMs: this.config.windowMs,
|
|
48
55
|
max: this.config.maxRequests,
|
|
49
56
|
message: this.config.message,
|
|
50
|
-
standardHeaders: true,
|
|
51
|
-
legacyHeaders: false
|
|
57
|
+
standardHeaders: true,
|
|
58
|
+
legacyHeaders: false
|
|
52
59
|
};
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
if (opts?.options) {
|
|
56
|
-
const allowedOverrides = [
|
|
63
|
+
const allowedOverrides = [
|
|
64
|
+
"message",
|
|
65
|
+
"skipFailedRequests",
|
|
66
|
+
"standardHeaders",
|
|
67
|
+
"legacyHeaders"
|
|
68
|
+
];
|
|
69
|
+
|
|
57
70
|
for (const key of allowedOverrides) {
|
|
58
71
|
if (opts.options[key] !== undefined) {
|
|
59
72
|
finalOptions[key] = opts.options[key];
|
|
60
73
|
}
|
|
61
74
|
}
|
|
62
|
-
|
|
75
|
+
|
|
63
76
|
const attemptedOverrides = Object.keys(opts.options).filter(
|
|
64
|
-
k => !allowedOverrides.includes(k) && k !==
|
|
77
|
+
k => !allowedOverrides.includes(k) && k !== "mode"
|
|
65
78
|
);
|
|
79
|
+
|
|
66
80
|
if (attemptedOverrides.length > 0) {
|
|
67
81
|
logger.warn("Rate limit overrides ignored", {
|
|
68
|
-
|
|
82
|
+
layer: "rate-limit-manager",
|
|
83
|
+
operation: "configure",
|
|
84
|
+
mode,
|
|
69
85
|
ignoredOptions: attemptedOverrides
|
|
70
86
|
});
|
|
71
87
|
}
|
|
72
88
|
}
|
|
73
89
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
if (finalOptions.legacyHeaders === undefined) {
|
|
78
|
-
finalOptions.legacyHeaders = false;
|
|
79
|
-
}
|
|
90
|
+
finalOptions.standardHeaders ??= true;
|
|
91
|
+
finalOptions.legacyHeaders ??= false;
|
|
80
92
|
|
|
81
93
|
try {
|
|
82
|
-
logger.info("
|
|
83
|
-
|
|
94
|
+
logger.info("Rate limiting applied", {
|
|
95
|
+
layer: "rate-limit-manager",
|
|
96
|
+
operation: "apply",
|
|
97
|
+
mode,
|
|
84
98
|
windowMs: finalOptions.windowMs,
|
|
85
99
|
max: finalOptions.max
|
|
86
100
|
});
|
|
87
|
-
|
|
101
|
+
|
|
88
102
|
return this.primaryAdapter.getMiddleware(finalOptions);
|
|
103
|
+
|
|
89
104
|
} catch (err: any) {
|
|
90
|
-
logger.warn("Primary rate limiter failed
|
|
91
|
-
|
|
105
|
+
logger.warn("Primary rate limiter failed", {
|
|
106
|
+
layer: "rate-limit-manager",
|
|
107
|
+
operation: "apply",
|
|
108
|
+
mode,
|
|
109
|
+
reason: err?.message
|
|
92
110
|
});
|
|
93
111
|
|
|
94
112
|
if (!this.fallbackAdapter) {
|
|
95
|
-
throw new AdapterError(
|
|
113
|
+
throw new AdapterError(
|
|
114
|
+
"Rate limiters failed; no fallback adapter configured."
|
|
115
|
+
);
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
try {
|
|
99
|
-
logger.
|
|
119
|
+
logger.warn("Using fallback rate limiter", {
|
|
120
|
+
layer: "rate-limit-manager",
|
|
121
|
+
operation: "fallback",
|
|
122
|
+
mode
|
|
123
|
+
});
|
|
124
|
+
|
|
100
125
|
return this.fallbackAdapter.getMiddleware(finalOptions);
|
|
126
|
+
|
|
101
127
|
} catch (fallbackErr: any) {
|
|
102
|
-
logger.error("Fallback limiter
|
|
103
|
-
|
|
128
|
+
logger.error("Fallback rate limiter failed", {
|
|
129
|
+
layer: "rate-limit-manager",
|
|
130
|
+
operation: "fallback",
|
|
131
|
+
mode,
|
|
132
|
+
reason: fallbackErr?.message
|
|
104
133
|
});
|
|
105
|
-
|
|
134
|
+
|
|
135
|
+
throw new AdapterError(
|
|
136
|
+
"Both primary and fallback rate limiters failed."
|
|
137
|
+
);
|
|
106
138
|
}
|
|
107
139
|
}
|
|
108
140
|
}
|
|
109
|
-
}
|
|
141
|
+
}
|