@rodit/rodit-auth-be 9.11.14
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/CHANGELOG.md +54 -0
- package/README.md +3543 -0
- package/index.js +1884 -0
- package/lib/auth/authentication.js +1971 -0
- package/lib/auth/roditmanager.js +627 -0
- package/lib/auth/sessionmanager.js +1302 -0
- package/lib/auth/tokenservice.js +2418 -0
- package/lib/blockchain/blockchainservice.js +1715 -0
- package/lib/blockchain/statemanager.js +1614 -0
- package/lib/middleware/authenticationmw.js +2301 -0
- package/lib/middleware/environcredentialstoremw.js +176 -0
- package/lib/middleware/filecredentialstoremw.js +158 -0
- package/lib/middleware/loggingmw.js +82 -0
- package/lib/middleware/performanceexamplemw.js +58 -0
- package/lib/middleware/performancemw.js +172 -0
- package/lib/middleware/ratelimitmw.js +171 -0
- package/lib/middleware/validatepermissionsmw.js +439 -0
- package/lib/middleware/vaultcredentialstoremw.js +617 -0
- package/lib/middleware/versioningmw.js +142 -0
- package/lib/middleware/webhookhandlermw.js +1388 -0
- package/package.json +57 -0
- package/services/configsdk.js +588 -0
- package/services/env.js +34 -0
- package/services/error-response.js +29 -0
- package/services/logger.js +160 -0
- package/services/performanceservice.js +568 -0
- package/services/utils.js +1024 -0
- package/services/versionmanager.js +81 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limiting middleware
|
|
3
|
+
* Copyright (c) 2026 Discernible IO. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Rate limiting middleware for API protection
|
|
8
|
+
* Updated to work with express-rate-limit v7.x
|
|
9
|
+
*/
|
|
10
|
+
const rateLimit = require('express-rate-limit');
|
|
11
|
+
const logger = require('../../services/logger');
|
|
12
|
+
const { createLogContext, logErrorWithMetrics } = require('../../services/logger');
|
|
13
|
+
const { ulid } = require('ulid');
|
|
14
|
+
const { sendError } = require('../../services/error-response');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a rate limiting middleware with the specified configuration
|
|
18
|
+
*
|
|
19
|
+
* @param {number} maxRequests - Maximum number of requests allowed per window
|
|
20
|
+
* @param {number} windowMinutes - Time window in minutes for rate limiting
|
|
21
|
+
* @returns {Function} - Express middleware function
|
|
22
|
+
*/
|
|
23
|
+
function ratelimitmw(maxRequests = 100, windowMinutes = 15) {
|
|
24
|
+
const requestId = ulid();
|
|
25
|
+
const startTime = Date.now();
|
|
26
|
+
|
|
27
|
+
const baseContext = createLogContext(
|
|
28
|
+
"RateLimitMiddleware",
|
|
29
|
+
"ratelimitmw",
|
|
30
|
+
{
|
|
31
|
+
requestId,
|
|
32
|
+
maxRequests,
|
|
33
|
+
windowMinutes
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
logger.infoWithContext('Rate limiting middleware initialized', {
|
|
38
|
+
...baseContext,
|
|
39
|
+
result: 'call',
|
|
40
|
+
reason: 'Rate limiting middleware initialized'
|
|
41
|
+
}); // Function call log
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const limiter = rateLimit({
|
|
45
|
+
// Define window in milliseconds (converting from minutes)
|
|
46
|
+
windowMs: windowMinutes * 60 * 1000,
|
|
47
|
+
|
|
48
|
+
// Maximum number of requests per window
|
|
49
|
+
max: maxRequests,
|
|
50
|
+
|
|
51
|
+
// Return rate limit info in the headers
|
|
52
|
+
standardHeaders: true,
|
|
53
|
+
|
|
54
|
+
// Disable X-RateLimit-* headers
|
|
55
|
+
legacyHeaders: false,
|
|
56
|
+
|
|
57
|
+
// Handler for when the rate limit is exceeded
|
|
58
|
+
handler: (req, res, next, handleroptions) => {
|
|
59
|
+
const exceedRequestId = ulid();
|
|
60
|
+
|
|
61
|
+
const exceedContext = createLogContext(
|
|
62
|
+
"RateLimitMiddleware",
|
|
63
|
+
"rateLimitExceeded",
|
|
64
|
+
{
|
|
65
|
+
requestId: exceedRequestId,
|
|
66
|
+
ip: req.ip,
|
|
67
|
+
path: req.path,
|
|
68
|
+
method: req.method,
|
|
69
|
+
userId: req.user ? req.user.id : 'anonymous',
|
|
70
|
+
maxRequests: handleroptions.max,
|
|
71
|
+
windowMinutes: handleroptions.windowMs / (60 * 1000)
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Log rate limit exceeded events
|
|
76
|
+
logger.warnWithContext('Rate limit exceeded', {
|
|
77
|
+
...exceedContext,
|
|
78
|
+
result: 'failure',
|
|
79
|
+
reason: 'Rate limit exceeded'
|
|
80
|
+
});
|
|
81
|
+
// Add metric for rate limit exceeded
|
|
82
|
+
logger.metric("rate_limit_operations", 0, {
|
|
83
|
+
operation: "limit_exceeded",
|
|
84
|
+
path: req.path,
|
|
85
|
+
method: req.method,
|
|
86
|
+
result: "blocked",
|
|
87
|
+
reason: 'Rate limit exceeded'
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Send error response
|
|
91
|
+
sendError(res, {
|
|
92
|
+
statusCode: handleroptions.statusCode,
|
|
93
|
+
requestId: exceedRequestId,
|
|
94
|
+
code: 'RATE_LIMIT_EXCEEDED',
|
|
95
|
+
message: handleroptions.message,
|
|
96
|
+
details: {
|
|
97
|
+
maxRequests: handleroptions.max,
|
|
98
|
+
windowMinutes: handleroptions.windowMs / (60 * 1000)
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// Skip rate limiting for certain requests (optional)
|
|
104
|
+
skip: (req, res) => {
|
|
105
|
+
const skipRequestId = ulid();
|
|
106
|
+
|
|
107
|
+
const skipContext = createLogContext(
|
|
108
|
+
"RateLimitMiddleware",
|
|
109
|
+
"skipRateLimit",
|
|
110
|
+
{
|
|
111
|
+
requestId: skipRequestId,
|
|
112
|
+
path: req.path,
|
|
113
|
+
method: req.method
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Example: Skip rate limiting for health check endpoints
|
|
118
|
+
const shouldSkip = req.path === '/api/health' || req.path === '/metrics';
|
|
119
|
+
|
|
120
|
+
if (shouldSkip) {
|
|
121
|
+
logger.debugWithContext('Skipping rate limit for endpoint', skipContext);
|
|
122
|
+
|
|
123
|
+
// Add metric for skipped rate limiting
|
|
124
|
+
logger.metric("rate_limit_operations", 0, {
|
|
125
|
+
operation: "skip",
|
|
126
|
+
path: req.path,
|
|
127
|
+
method: req.method,
|
|
128
|
+
result: "skipped"
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return shouldSkip;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const duration = Date.now() - startTime;
|
|
137
|
+
logger.infoWithContext('Rate limiting middleware created successfully', {
|
|
138
|
+
...baseContext,
|
|
139
|
+
duration
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Add metric for middleware creation
|
|
143
|
+
logger.metric("rate_limit_operations", duration, {
|
|
144
|
+
operation: "create",
|
|
145
|
+
result: "success"
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return limiter;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
const duration = Date.now() - startTime;
|
|
151
|
+
|
|
152
|
+
logErrorWithMetrics(
|
|
153
|
+
"Failed to create rate limiting middleware",
|
|
154
|
+
{
|
|
155
|
+
...baseContext,
|
|
156
|
+
duration
|
|
157
|
+
},
|
|
158
|
+
error,
|
|
159
|
+
"rate_limit_error",
|
|
160
|
+
{
|
|
161
|
+
operation: "create",
|
|
162
|
+
result: "error",
|
|
163
|
+
duration
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = ratelimitmw;
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission validation middleware
|
|
3
|
+
* Copyright (c) 2026 Discernible IO. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const logger = require("../../services/logger");
|
|
7
|
+
const { createLogContext, logErrorWithMetrics } = logger;
|
|
8
|
+
const { sendError } = require("../../services/error-response");
|
|
9
|
+
const crypto = require("crypto");
|
|
10
|
+
const { ulid } = require("ulid");
|
|
11
|
+
const config = require('../../services/configsdk');
|
|
12
|
+
|
|
13
|
+
// Dynamic import for ESM 'jose' in CommonJS context
|
|
14
|
+
let _josePromise;
|
|
15
|
+
async function getJose() {
|
|
16
|
+
if (!_josePromise) {
|
|
17
|
+
_josePromise = import("jose");
|
|
18
|
+
}
|
|
19
|
+
return _josePromise;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class PermissionValidator {
|
|
23
|
+
constructor() {
|
|
24
|
+
// Load method permission map from config or use default if not available
|
|
25
|
+
this.methodPermissionMap = config.get('METHOD_PERMISSION_MAP');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse the rate value from the permission string
|
|
30
|
+
*
|
|
31
|
+
* @param {string} rateValue - The permission value from the token (e.g., "+0")
|
|
32
|
+
* @returns {Object} An object containing the rate limit and whether it's unlimited
|
|
33
|
+
* { limit: number|null, unlimited: boolean }
|
|
34
|
+
*/
|
|
35
|
+
parseRateLimit(rateValue) {
|
|
36
|
+
// Skip if no value or not a string
|
|
37
|
+
if (!rateValue || typeof rateValue !== 'string') {
|
|
38
|
+
return { limit: null, unlimited: false };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Remove the permission prefix (+ or -)
|
|
42
|
+
let rateString = rateValue;
|
|
43
|
+
if (rateString.startsWith('+') || rateString.startsWith('-')) {
|
|
44
|
+
rateString = rateString.substring(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Try to parse the rate as a number (could be in scientific notation)
|
|
48
|
+
try {
|
|
49
|
+
const rate = parseFloat(rateString);
|
|
50
|
+
|
|
51
|
+
// Check if it's a valid number
|
|
52
|
+
if (isNaN(rate)) {
|
|
53
|
+
return { limit: null, unlimited: false };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Special case: 0 indicates no limits
|
|
57
|
+
if (rate === 0) {
|
|
58
|
+
return { limit: null, unlimited: true };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Return the actual rate limit
|
|
62
|
+
return { limit: rate, unlimited: false };
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return { limit: null, unlimited: false };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Determine the permission scope from the rate value prefix
|
|
70
|
+
*
|
|
71
|
+
* @param {string} rateValue - The permission value from the token (e.g., "+0")
|
|
72
|
+
* @returns {string} The permission scope (entityAndProperties, propertiesOnly, or entityOnly)
|
|
73
|
+
*/
|
|
74
|
+
getPermissionScope(rateValue) {
|
|
75
|
+
if (rateValue.startsWith("+")) {
|
|
76
|
+
return "entityAndProperties";
|
|
77
|
+
} else if (rateValue.startsWith("-")) {
|
|
78
|
+
return "propertiesOnly";
|
|
79
|
+
}
|
|
80
|
+
return "entityOnly";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
isMethodAllowed(method, permissionScope) {
|
|
84
|
+
const startTime = Date.now();
|
|
85
|
+
const requestId = ulid();
|
|
86
|
+
|
|
87
|
+
// Create a base context for this method
|
|
88
|
+
const baseContext = createLogContext(
|
|
89
|
+
"PermissionValidator",
|
|
90
|
+
"isMethodAllowed",
|
|
91
|
+
{
|
|
92
|
+
requestId,
|
|
93
|
+
requestedMethod: method,
|
|
94
|
+
permissionScope
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (!this.methodPermissionMap[method]) {
|
|
99
|
+
logErrorWithMetrics(
|
|
100
|
+
"Unknown method detected",
|
|
101
|
+
{
|
|
102
|
+
...baseContext,
|
|
103
|
+
duration: Date.now() - startTime
|
|
104
|
+
},
|
|
105
|
+
new Error(`Unknown method: ${method}`),
|
|
106
|
+
"permission_validation_error",
|
|
107
|
+
{ error_type: "unknown_method" }
|
|
108
|
+
);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const methodConfig = this.methodPermissionMap[method];
|
|
113
|
+
|
|
114
|
+
// Handle both old array format and new object format
|
|
115
|
+
const scopes = Array.isArray(methodConfig) ? methodConfig : methodConfig.scopes;
|
|
116
|
+
const isAllowed = scopes.includes(permissionScope);
|
|
117
|
+
|
|
118
|
+
logger.debugWithContext("Method permission check completed", {
|
|
119
|
+
...baseContext,
|
|
120
|
+
isAllowed,
|
|
121
|
+
duration: Date.now() - startTime
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return isAllowed;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
findMatchingEntity(entities, fullPath, requestId) {
|
|
128
|
+
const startTime = Date.now();
|
|
129
|
+
// Use provided requestId or generate a new one
|
|
130
|
+
const contextRequestId = requestId || ulid();
|
|
131
|
+
|
|
132
|
+
// Handle entities as an object with name and methods
|
|
133
|
+
const entity = entities.name;
|
|
134
|
+
const methods = entities.methods;
|
|
135
|
+
|
|
136
|
+
// Create a base context for this method
|
|
137
|
+
const baseContext = createLogContext(
|
|
138
|
+
"PermissionValidator",
|
|
139
|
+
"findMatchingEntity",
|
|
140
|
+
{
|
|
141
|
+
requestId: contextRequestId,
|
|
142
|
+
fullPath,
|
|
143
|
+
entity
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Log available methods for debugging
|
|
148
|
+
logger.debugWithContext("Checking permission for path", {
|
|
149
|
+
...baseContext,
|
|
150
|
+
availableMethods: Object.keys(methods)
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Check for an exact match in the methods
|
|
154
|
+
if (methods.hasOwnProperty(fullPath)) {
|
|
155
|
+
const rateValue = methods[fullPath];
|
|
156
|
+
// Extract the operation name (last part of the path)
|
|
157
|
+
const operation = fullPath.split("/").pop();
|
|
158
|
+
|
|
159
|
+
logger.infoWithContext("Processing permission request", {
|
|
160
|
+
...baseContext,
|
|
161
|
+
requestedMethod: operation,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const permissionScope = this.getPermissionScope(rateValue);
|
|
165
|
+
const isPermitted = this.isMethodAllowed(operation, permissionScope);
|
|
166
|
+
|
|
167
|
+
logger.debugWithContext("Permission check result", {
|
|
168
|
+
...baseContext,
|
|
169
|
+
requestedMethod: operation,
|
|
170
|
+
permissionScope,
|
|
171
|
+
rateValue,
|
|
172
|
+
isPermitted,
|
|
173
|
+
duration: Date.now() - startTime
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (isPermitted) {
|
|
177
|
+
// Parse the rate limit from the permission value
|
|
178
|
+
const rateLimitInfo = this.parseRateLimit(rateValue);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
isPermitted: true,
|
|
182
|
+
commentsRate: rateValue,
|
|
183
|
+
permissionScope,
|
|
184
|
+
rateLimit: rateLimitInfo.limit,
|
|
185
|
+
unlimited: rateLimitInfo.unlimited,
|
|
186
|
+
operation, // Add the operation name for future rate limiting
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// No special handling for session routes - all routes must be explicitly defined in the token
|
|
192
|
+
|
|
193
|
+
logger.warnWithContext("No matching permission found", {
|
|
194
|
+
...baseContext,
|
|
195
|
+
duration: Date.now() - startTime
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
isPermitted: false,
|
|
200
|
+
commentsRate: null,
|
|
201
|
+
permissionScope: null,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async validate(req) {
|
|
206
|
+
const requestId = req.headers["x-request-id"] || ulid();
|
|
207
|
+
const startTime = Date.now();
|
|
208
|
+
|
|
209
|
+
// Create a base context for this method
|
|
210
|
+
const baseContext = createLogContext(
|
|
211
|
+
"PermissionValidator",
|
|
212
|
+
"validate",
|
|
213
|
+
{
|
|
214
|
+
requestId,
|
|
215
|
+
path: req.path,
|
|
216
|
+
method: req.method,
|
|
217
|
+
ip: req.ip
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const token = req.header("Authorization");
|
|
222
|
+
if (!token) {
|
|
223
|
+
logger.debugWithContext("Authorization token missing", {
|
|
224
|
+
...baseContext,
|
|
225
|
+
userAgent: req.headers["user-agent"],
|
|
226
|
+
duration: Date.now() - startTime
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
isValid: false,
|
|
231
|
+
status: 401,
|
|
232
|
+
message: "Access denied. No token provided.",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const { decodeJwt } = await getJose();
|
|
238
|
+
const decodedToken = decodeJwt(token);
|
|
239
|
+
|
|
240
|
+
// Update context with user information
|
|
241
|
+
const contextWithUser = {
|
|
242
|
+
...baseContext,
|
|
243
|
+
userId: decodedToken.sub || "unknown"
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
logger.infoWithContext("Endpoint access attempt", contextWithUser);
|
|
247
|
+
|
|
248
|
+
let permissionedRoutes;
|
|
249
|
+
try {
|
|
250
|
+
if (typeof decodedToken.rodit_permissionedroutes === 'string') {
|
|
251
|
+
permissionedRoutes = JSON.parse(decodedToken.rodit_permissionedroutes);
|
|
252
|
+
} else {
|
|
253
|
+
permissionedRoutes = decodedToken.rodit_permissionedroutes;
|
|
254
|
+
}
|
|
255
|
+
} catch (error) {
|
|
256
|
+
logErrorWithMetrics(
|
|
257
|
+
"Failed to parse permissioned routes",
|
|
258
|
+
contextWithUser,
|
|
259
|
+
error,
|
|
260
|
+
"permission_validation_error",
|
|
261
|
+
{
|
|
262
|
+
error_type: "parse_error",
|
|
263
|
+
permissionedRoutesType: typeof decodedToken.rodit_permissionedroutes,
|
|
264
|
+
valuePreview: decodedToken.rodit_permissionedroutes ?
|
|
265
|
+
decodedToken.rodit_permissionedroutes.substring(0, 100) : "undefined"
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Add debug logging for permission routes content
|
|
272
|
+
logger.debugWithContext("Permission routes content", {
|
|
273
|
+
...contextWithUser,
|
|
274
|
+
permissionedRoutes: JSON.stringify(permissionedRoutes).substring(0, 200) + "..."
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Use entities directly, assuming it's an object
|
|
278
|
+
const entities = permissionedRoutes.entities;
|
|
279
|
+
|
|
280
|
+
// Construct the full path for permission checking
|
|
281
|
+
// This must exactly match what's defined in the RODiT token
|
|
282
|
+
let fullPath = req.baseUrl + req.path;
|
|
283
|
+
|
|
284
|
+
// Normalize path by removing trailing slashes (except for root path)
|
|
285
|
+
// This handles common URL variations like /api/list_agents vs /api/agents/
|
|
286
|
+
if (fullPath.length > 1 && fullPath.endsWith('/')) {
|
|
287
|
+
fullPath = fullPath.slice(0, -1);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
logger.debugWithContext("Validating permission for full path", {
|
|
291
|
+
...contextWithUser,
|
|
292
|
+
baseUrl: req.baseUrl,
|
|
293
|
+
path: req.path,
|
|
294
|
+
fullPath
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const { isPermitted, commentsRate, permissionScope, rateLimit, operation } =
|
|
298
|
+
this.findMatchingEntity(entities, fullPath, requestId);
|
|
299
|
+
|
|
300
|
+
if (!isPermitted) {
|
|
301
|
+
logger.warnWithContext("Permission denied", {
|
|
302
|
+
...contextWithUser,
|
|
303
|
+
path: req.path,
|
|
304
|
+
fullPath,
|
|
305
|
+
userId: decodedToken.sub || "unknown",
|
|
306
|
+
duration: Date.now() - startTime,
|
|
307
|
+
requestId,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
isValid: false,
|
|
312
|
+
status: 403,
|
|
313
|
+
message: "Permission denied",
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
logger.infoWithContext("Authorization successful", {
|
|
318
|
+
...contextWithUser,
|
|
319
|
+
fullPath,
|
|
320
|
+
permissionScope,
|
|
321
|
+
rateValue: commentsRate,
|
|
322
|
+
duration: Date.now() - startTime
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
isValid: true,
|
|
327
|
+
commentsRate,
|
|
328
|
+
permissionScope,
|
|
329
|
+
rateLimit,
|
|
330
|
+
operation
|
|
331
|
+
};
|
|
332
|
+
} catch (error) {
|
|
333
|
+
// Use logErrorWithMetrics for better error tracking
|
|
334
|
+
logErrorWithMetrics(
|
|
335
|
+
"Permission check failed",
|
|
336
|
+
{
|
|
337
|
+
...baseContext,
|
|
338
|
+
duration: Date.now() - startTime
|
|
339
|
+
},
|
|
340
|
+
error,
|
|
341
|
+
"permission_validation_error",
|
|
342
|
+
{
|
|
343
|
+
error_type: "validation_error",
|
|
344
|
+
errorCode: "112"
|
|
345
|
+
}
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
isValid: false,
|
|
350
|
+
status: 400,
|
|
351
|
+
message: "Error 119: Invalid token or permissions.",
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const permissionValidator = new PermissionValidator();
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @swagger
|
|
361
|
+
* /authorize:
|
|
362
|
+
* get:
|
|
363
|
+
* summary: Authorize user and check JWT token fields
|
|
364
|
+
* description: Verify the JWT token and check specific fields before granting access
|
|
365
|
+
* security:
|
|
366
|
+
* - bearerAuth: []
|
|
367
|
+
* responses:
|
|
368
|
+
* 200:
|
|
369
|
+
* description: Authorization successful
|
|
370
|
+
* 401:
|
|
371
|
+
* description: Unauthorized - Invalid token or missing required fields
|
|
372
|
+
*/
|
|
373
|
+
async function validatepermissions(req, res, next) {
|
|
374
|
+
const startTime = Date.now();
|
|
375
|
+
const requestId = req.headers["x-request-id"] || ulid();
|
|
376
|
+
|
|
377
|
+
if (!req.headers["x-request-id"]) {
|
|
378
|
+
req.headers["x-request-id"] = requestId;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Create a base context for this middleware
|
|
382
|
+
const baseContext = createLogContext(
|
|
383
|
+
"validatepermissions",
|
|
384
|
+
"middleware",
|
|
385
|
+
{
|
|
386
|
+
requestId,
|
|
387
|
+
path: req.path,
|
|
388
|
+
baseUrl: req.baseUrl,
|
|
389
|
+
fullPath: req.baseUrl + req.path,
|
|
390
|
+
method: req.method,
|
|
391
|
+
ip: req.ip
|
|
392
|
+
}
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
logger.debugWithContext("Permission validation started", baseContext);
|
|
396
|
+
|
|
397
|
+
const result = await permissionValidator.validate(req);
|
|
398
|
+
|
|
399
|
+
if (!result.isValid) {
|
|
400
|
+
logger.warnWithContext("Permission validation failed", {
|
|
401
|
+
...baseContext,
|
|
402
|
+
status: result.status,
|
|
403
|
+
message: result.message,
|
|
404
|
+
duration: Date.now() - startTime
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
return sendError(res, {
|
|
408
|
+
statusCode: result.status,
|
|
409
|
+
requestId,
|
|
410
|
+
code: result.code || "PERMISSION_DENIED",
|
|
411
|
+
message: result.message
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (result.commentsRate) {
|
|
416
|
+
req.commentsRate = result.commentsRate;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Make rate limit information available for future implementation
|
|
420
|
+
req.rateLimit = {
|
|
421
|
+
value: result.rateLimit,
|
|
422
|
+
unlimited: result.unlimited === true,
|
|
423
|
+
operation: result.operation,
|
|
424
|
+
path: req.path,
|
|
425
|
+
timeWindow: 60 // Default time window in seconds
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
req.permissionScope = result.permissionScope;
|
|
429
|
+
|
|
430
|
+
logger.debugWithContext("Permission validation successful", {
|
|
431
|
+
...baseContext,
|
|
432
|
+
permissionScope: result.permissionScope,
|
|
433
|
+
duration: Date.now() - startTime
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
next();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
module.exports = validatepermissions;
|