@noony-serverless/core 0.1.0 → 0.1.5
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/build/middlewares/authenticationMiddleware.d.ts +379 -0
- package/build/middlewares/authenticationMiddleware.js +216 -0
- package/build/middlewares/bodyParserMiddleware.d.ts +99 -0
- package/build/middlewares/bodyParserMiddleware.js +99 -0
- package/build/middlewares/bodyValidationMiddleware.d.ts +68 -4
- package/build/middlewares/bodyValidationMiddleware.js +64 -0
- package/build/middlewares/dependencyInjectionMiddleware.d.ts +238 -0
- package/build/middlewares/dependencyInjectionMiddleware.js +238 -0
- package/build/middlewares/errorHandlerMiddleware.d.ts +94 -0
- package/build/middlewares/errorHandlerMiddleware.js +105 -0
- package/build/middlewares/guards/RouteGuards.d.ts +475 -0
- package/build/middlewares/guards/RouteGuards.js +604 -0
- package/build/middlewares/guards/cache/CacheAdapter.d.ts +473 -0
- package/build/middlewares/guards/cache/CacheAdapter.js +205 -0
- package/build/middlewares/guards/cache/ConservativeCacheInvalidation.d.ts +191 -0
- package/build/middlewares/guards/cache/ConservativeCacheInvalidation.js +510 -0
- package/build/middlewares/guards/cache/MemoryCacheAdapter.d.ts +228 -0
- package/build/middlewares/guards/cache/MemoryCacheAdapter.js +403 -0
- package/build/middlewares/guards/cache/NoopCacheAdapter.d.ts +95 -0
- package/build/middlewares/guards/cache/NoopCacheAdapter.js +131 -0
- package/build/middlewares/guards/config/GuardConfiguration.d.ts +612 -0
- package/build/middlewares/guards/config/GuardConfiguration.js +334 -0
- package/build/middlewares/guards/guards/FastAuthGuard.d.ts +201 -0
- package/build/middlewares/guards/guards/FastAuthGuard.js +460 -0
- package/build/middlewares/guards/guards/PermissionGuardFactory.d.ts +202 -0
- package/build/middlewares/guards/guards/PermissionGuardFactory.js +563 -0
- package/build/middlewares/guards/index.d.ts +67 -0
- package/build/middlewares/guards/index.js +192 -0
- package/build/middlewares/guards/registry/PermissionRegistry.d.ts +188 -0
- package/build/middlewares/guards/registry/PermissionRegistry.js +425 -0
- package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.d.ts +129 -0
- package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.js +451 -0
- package/build/middlewares/guards/resolvers/PermissionResolver.d.ts +155 -0
- package/build/middlewares/guards/resolvers/PermissionResolver.js +176 -0
- package/build/middlewares/guards/resolvers/PlainPermissionResolver.d.ts +101 -0
- package/build/middlewares/guards/resolvers/PlainPermissionResolver.js +248 -0
- package/build/middlewares/guards/resolvers/WildcardPermissionResolver.d.ts +146 -0
- package/build/middlewares/guards/resolvers/WildcardPermissionResolver.js +377 -0
- package/build/middlewares/guards/services/FastUserContextService.d.ts +216 -0
- package/build/middlewares/guards/services/FastUserContextService.js +435 -0
- package/build/middlewares/headerVariablesMiddleware.d.ts +118 -0
- package/build/middlewares/headerVariablesMiddleware.js +118 -0
- package/build/middlewares/httpAttributesMiddleware.d.ts +235 -0
- package/build/middlewares/httpAttributesMiddleware.js +235 -0
- package/build/middlewares/index.d.ts +1 -0
- package/build/middlewares/index.js +1 -0
- package/build/middlewares/queryParametersMiddleware.d.ts +105 -0
- package/build/middlewares/queryParametersMiddleware.js +105 -0
- package/build/middlewares/rateLimitingMiddleware.d.ts +109 -5
- package/build/middlewares/rateLimitingMiddleware.js +109 -5
- package/build/middlewares/responseWrapperMiddleware.d.ts +170 -1
- package/build/middlewares/responseWrapperMiddleware.js +170 -1
- package/build/middlewares/securityAuditMiddleware.js +5 -5
- package/build/middlewares/validationMiddleware.d.ts +145 -0
- package/build/middlewares/validationMiddleware.js +145 -0
- package/package.json +2 -2
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Fast User Context Service
|
|
4
|
+
*
|
|
5
|
+
* High-performance user context management with configurable permission resolution.
|
|
6
|
+
* This service orchestrates the permission resolution strategies, manages caching,
|
|
7
|
+
* and provides sub-millisecond user permission checks for serverless environments.
|
|
8
|
+
*
|
|
9
|
+
* Key Features:
|
|
10
|
+
* - Configurable permission resolution (pre-expansion vs on-demand)
|
|
11
|
+
* - Multi-layer caching (L1 memory + L2 distributed)
|
|
12
|
+
* - Conservative cache invalidation for security
|
|
13
|
+
* - Permission expansion and validation
|
|
14
|
+
* - Performance monitoring and metrics
|
|
15
|
+
* - TypeDI integration for dependency injection
|
|
16
|
+
*
|
|
17
|
+
* Architecture:
|
|
18
|
+
* - Uses strategy pattern for different resolution approaches
|
|
19
|
+
* - Implements repository pattern for user context storage
|
|
20
|
+
* - Follows single responsibility principle with focused methods
|
|
21
|
+
* - Provides comprehensive error handling and logging
|
|
22
|
+
*
|
|
23
|
+
* @author Noony Framework Team
|
|
24
|
+
* @version 1.0.0
|
|
25
|
+
*/
|
|
26
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
27
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
28
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
29
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
30
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
31
|
+
};
|
|
32
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
33
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
34
|
+
};
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FastUserContextService = void 0;
|
|
37
|
+
const typedi_1 = require("typedi");
|
|
38
|
+
const CacheAdapter_1 = require("../cache/CacheAdapter");
|
|
39
|
+
const GuardConfiguration_1 = require("../config/GuardConfiguration");
|
|
40
|
+
const PlainPermissionResolver_1 = require("../resolvers/PlainPermissionResolver");
|
|
41
|
+
const WildcardPermissionResolver_1 = require("../resolvers/WildcardPermissionResolver");
|
|
42
|
+
const ExpressionPermissionResolver_1 = require("../resolvers/ExpressionPermissionResolver");
|
|
43
|
+
const PermissionResolver_1 = require("../resolvers/PermissionResolver");
|
|
44
|
+
/**
|
|
45
|
+
* Fast User Context Service Implementation
|
|
46
|
+
*/
|
|
47
|
+
let FastUserContextService = class FastUserContextService {
|
|
48
|
+
cache;
|
|
49
|
+
config;
|
|
50
|
+
permissionSource;
|
|
51
|
+
_permissionRegistry;
|
|
52
|
+
// Permission resolvers
|
|
53
|
+
plainResolver;
|
|
54
|
+
wildcardResolver;
|
|
55
|
+
expressionResolver;
|
|
56
|
+
// Performance tracking
|
|
57
|
+
contextLoads = 0;
|
|
58
|
+
cacheHits = 0;
|
|
59
|
+
cacheMisses = 0;
|
|
60
|
+
permissionChecks = 0;
|
|
61
|
+
totalResolutionTimeUs = 0;
|
|
62
|
+
constructor(cache, config, permissionSource, permissionRegistry) {
|
|
63
|
+
this.cache = cache;
|
|
64
|
+
this.config = config;
|
|
65
|
+
this.permissionSource = permissionSource;
|
|
66
|
+
this._permissionRegistry = permissionRegistry;
|
|
67
|
+
// Initialize permission resolvers
|
|
68
|
+
this.plainResolver = new PlainPermissionResolver_1.PlainPermissionResolver();
|
|
69
|
+
this.wildcardResolver = new WildcardPermissionResolver_1.WildcardPermissionResolver(config.security.permissionResolutionStrategy ??
|
|
70
|
+
GuardConfiguration_1.PermissionResolutionStrategy.PRE_EXPANSION, this._permissionRegistry, cache);
|
|
71
|
+
this.expressionResolver = new ExpressionPermissionResolver_1.ExpressionPermissionResolver(cache);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get or load user context with permissions
|
|
75
|
+
*
|
|
76
|
+
* This is the primary method for retrieving user contexts with caching.
|
|
77
|
+
* It handles both pre-expansion and on-demand permission strategies.
|
|
78
|
+
*
|
|
79
|
+
* @param userId - Unique user identifier
|
|
80
|
+
* @param forceRefresh - Skip cache and force reload
|
|
81
|
+
* @returns User context with permissions or null if user not found
|
|
82
|
+
*/
|
|
83
|
+
async getUserContext(userId, forceRefresh = false) {
|
|
84
|
+
const startTime = process.hrtime.bigint();
|
|
85
|
+
this.contextLoads++;
|
|
86
|
+
try {
|
|
87
|
+
// Check cache first unless forced refresh
|
|
88
|
+
if (!forceRefresh) {
|
|
89
|
+
const cachedContext = await this.loadFromCache(userId);
|
|
90
|
+
if (cachedContext) {
|
|
91
|
+
this.cacheHits++;
|
|
92
|
+
return cachedContext;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.cacheMisses++;
|
|
96
|
+
// Load from permission source
|
|
97
|
+
const userData = await this.permissionSource.getUserPermissions(userId);
|
|
98
|
+
if (!userData) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
// Build user context
|
|
102
|
+
const context = await this.buildUserContext(userId, userData);
|
|
103
|
+
// Cache the context
|
|
104
|
+
await this.saveToCache(context);
|
|
105
|
+
return context;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
const endTime = process.hrtime.bigint();
|
|
109
|
+
this.totalResolutionTimeUs += Number(endTime - startTime) / 1000;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Check user permission using appropriate resolver
|
|
114
|
+
*
|
|
115
|
+
* Routes permission checks to the optimal resolver based on requirement type.
|
|
116
|
+
* Provides detailed results including performance metrics and cache status.
|
|
117
|
+
*
|
|
118
|
+
* @param userId - User identifier
|
|
119
|
+
* @param requirement - Permission requirement (string[], wildcard pattern, or expression)
|
|
120
|
+
* @param options - Check options
|
|
121
|
+
* @returns Detailed permission check result
|
|
122
|
+
*/
|
|
123
|
+
async checkPermission(userId, requirement, options = {}) {
|
|
124
|
+
const startTime = process.hrtime.bigint();
|
|
125
|
+
this.permissionChecks++;
|
|
126
|
+
try {
|
|
127
|
+
// Load user context
|
|
128
|
+
const userContext = await this.getUserContext(userId, !options.useCache);
|
|
129
|
+
if (!userContext) {
|
|
130
|
+
return {
|
|
131
|
+
allowed: false,
|
|
132
|
+
resolverType: PermissionResolver_1.PermissionResolverType.PLAIN,
|
|
133
|
+
resolutionTimeUs: 0,
|
|
134
|
+
cached: false,
|
|
135
|
+
reason: 'User not found',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Select appropriate resolver
|
|
139
|
+
const resolver = this.selectResolver(requirement, options.resolverType);
|
|
140
|
+
if (!resolver) {
|
|
141
|
+
return {
|
|
142
|
+
allowed: false,
|
|
143
|
+
resolverType: PermissionResolver_1.PermissionResolverType.PLAIN,
|
|
144
|
+
resolutionTimeUs: 0,
|
|
145
|
+
cached: false,
|
|
146
|
+
reason: 'No suitable resolver found',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Perform permission check
|
|
150
|
+
const permissions = userContext.expandedPermissions || userContext.permissions;
|
|
151
|
+
const result = await resolver.checkWithResult(permissions, requirement);
|
|
152
|
+
// Add audit trail if enabled
|
|
153
|
+
if (options.auditTrail && result.allowed) {
|
|
154
|
+
await this.recordAuditTrail(userId, requirement, result);
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
const endTime = process.hrtime.bigint();
|
|
160
|
+
this.totalResolutionTimeUs += Number(endTime - startTime) / 1000;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Batch check multiple permissions for a user
|
|
165
|
+
*
|
|
166
|
+
* Optimized for checking multiple permissions at once.
|
|
167
|
+
* Uses the same user context for all checks to minimize overhead.
|
|
168
|
+
*
|
|
169
|
+
* @param userId - User identifier
|
|
170
|
+
* @param requirements - Array of permission requirements
|
|
171
|
+
* @param options - Check options
|
|
172
|
+
* @returns Array of permission check results
|
|
173
|
+
*/
|
|
174
|
+
async checkPermissions(userId, requirements, options = {}) {
|
|
175
|
+
const startTime = process.hrtime.bigint();
|
|
176
|
+
try {
|
|
177
|
+
// Load user context once for all checks
|
|
178
|
+
const userContext = await this.getUserContext(userId, !options.useCache);
|
|
179
|
+
if (!userContext) {
|
|
180
|
+
const failResult = {
|
|
181
|
+
allowed: false,
|
|
182
|
+
resolverType: PermissionResolver_1.PermissionResolverType.PLAIN,
|
|
183
|
+
resolutionTimeUs: 0,
|
|
184
|
+
cached: false,
|
|
185
|
+
reason: 'User not found',
|
|
186
|
+
};
|
|
187
|
+
return requirements.map(() => ({ ...failResult }));
|
|
188
|
+
}
|
|
189
|
+
// Process all requirements
|
|
190
|
+
const results = [];
|
|
191
|
+
const permissions = userContext.expandedPermissions || userContext.permissions;
|
|
192
|
+
for (const { requirement, resolverType } of requirements) {
|
|
193
|
+
const resolver = this.selectResolver(requirement, resolverType);
|
|
194
|
+
if (!resolver) {
|
|
195
|
+
results.push({
|
|
196
|
+
allowed: false,
|
|
197
|
+
resolverType: PermissionResolver_1.PermissionResolverType.PLAIN,
|
|
198
|
+
resolutionTimeUs: 0,
|
|
199
|
+
cached: false,
|
|
200
|
+
reason: 'No suitable resolver found',
|
|
201
|
+
});
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const result = await resolver.checkWithResult(permissions, requirement);
|
|
205
|
+
results.push(result);
|
|
206
|
+
// Add audit trail for allowed permissions
|
|
207
|
+
if (options.auditTrail && result.allowed) {
|
|
208
|
+
await this.recordAuditTrail(userId, requirement, result);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return results;
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
const endTime = process.hrtime.bigint();
|
|
215
|
+
this.totalResolutionTimeUs += Number(endTime - startTime) / 1000;
|
|
216
|
+
this.permissionChecks += requirements.length;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Invalidate user context cache
|
|
221
|
+
*
|
|
222
|
+
* Removes user context from cache when permissions change.
|
|
223
|
+
* Uses conservative approach by also clearing related cached data.
|
|
224
|
+
*
|
|
225
|
+
* @param userId - User identifier
|
|
226
|
+
* @param clearRelated - Also clear permission-related caches
|
|
227
|
+
*/
|
|
228
|
+
async invalidateUserContext(userId, clearRelated = true) {
|
|
229
|
+
const cacheKey = CacheAdapter_1.CacheKeyBuilder.userContext(userId);
|
|
230
|
+
await this.cache.delete(cacheKey);
|
|
231
|
+
if (clearRelated && this.config.security.conservativeCacheInvalidation) {
|
|
232
|
+
// Clear permission check caches that might be affected
|
|
233
|
+
await this.cache.deletePattern(`perm:*:${userId}:*`);
|
|
234
|
+
await this.cache.deletePattern(`expr:*:${userId}:*`);
|
|
235
|
+
await this.cache.deletePattern(`wild:*:${userId}:*`);
|
|
236
|
+
}
|
|
237
|
+
console.log(`🔄 Invalidated user context cache`, {
|
|
238
|
+
userId,
|
|
239
|
+
clearRelated,
|
|
240
|
+
timestamp: new Date().toISOString(),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Pre-expand wildcard permissions for user context
|
|
245
|
+
*
|
|
246
|
+
* Used when pre-expansion strategy is enabled to convert
|
|
247
|
+
* wildcard permissions to concrete permission sets.
|
|
248
|
+
*
|
|
249
|
+
* @param permissions - Raw permissions from user/roles
|
|
250
|
+
* @returns Expanded permission set
|
|
251
|
+
*/
|
|
252
|
+
async expandPermissions(permissions) {
|
|
253
|
+
const expanded = new Set();
|
|
254
|
+
for (const permission of permissions) {
|
|
255
|
+
if (permission.includes('*')) {
|
|
256
|
+
// Expand wildcard
|
|
257
|
+
const concretePermissions = await this.wildcardResolver.expandWildcardPatterns([permission]);
|
|
258
|
+
concretePermissions.forEach((p) => expanded.add(p));
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
// Add concrete permission
|
|
262
|
+
expanded.add(permission);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return expanded;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get service performance statistics
|
|
269
|
+
*/
|
|
270
|
+
getStats() {
|
|
271
|
+
const totalCacheRequests = this.cacheHits + this.cacheMisses;
|
|
272
|
+
return {
|
|
273
|
+
contextLoads: this.contextLoads,
|
|
274
|
+
permissionChecks: this.permissionChecks,
|
|
275
|
+
cacheHitRate: totalCacheRequests > 0
|
|
276
|
+
? (this.cacheHits / totalCacheRequests) * 100
|
|
277
|
+
: 0,
|
|
278
|
+
cacheHits: this.cacheHits,
|
|
279
|
+
cacheMisses: this.cacheMisses,
|
|
280
|
+
averageResolutionTimeUs: this.contextLoads > 0
|
|
281
|
+
? this.totalResolutionTimeUs / this.contextLoads
|
|
282
|
+
: 0,
|
|
283
|
+
totalResolutionTimeUs: this.totalResolutionTimeUs,
|
|
284
|
+
resolverStats: {
|
|
285
|
+
plain: this.plainResolver.getStats(),
|
|
286
|
+
wildcard: this.wildcardResolver.getStats(),
|
|
287
|
+
expression: this.expressionResolver.getStats(),
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Reset performance statistics
|
|
293
|
+
*/
|
|
294
|
+
resetStats() {
|
|
295
|
+
this.contextLoads = 0;
|
|
296
|
+
this.cacheHits = 0;
|
|
297
|
+
this.cacheMisses = 0;
|
|
298
|
+
this.permissionChecks = 0;
|
|
299
|
+
this.totalResolutionTimeUs = 0;
|
|
300
|
+
this.plainResolver.resetStats();
|
|
301
|
+
this.wildcardResolver.resetStats();
|
|
302
|
+
this.expressionResolver.resetStats();
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Load user context from cache
|
|
306
|
+
*/
|
|
307
|
+
async loadFromCache(userId) {
|
|
308
|
+
const cacheKey = CacheAdapter_1.CacheKeyBuilder.userContext(userId);
|
|
309
|
+
const cachedData = await this.cache.get(cacheKey);
|
|
310
|
+
if (!cachedData) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
// Check if context is stale
|
|
314
|
+
if (await this.permissionSource.isUserContextStale(userId, cachedData.context.lastUpdated)) {
|
|
315
|
+
await this.cache.delete(cacheKey);
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
// Reconstruct context with Set objects
|
|
319
|
+
return {
|
|
320
|
+
...cachedData.context,
|
|
321
|
+
permissions: new Set(cachedData.permissions),
|
|
322
|
+
expandedPermissions: cachedData.expandedPermissions
|
|
323
|
+
? new Set(cachedData.expandedPermissions)
|
|
324
|
+
: undefined,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Save user context to cache
|
|
329
|
+
*/
|
|
330
|
+
async saveToCache(context) {
|
|
331
|
+
const cacheKey = CacheAdapter_1.CacheKeyBuilder.userContext(context.userId);
|
|
332
|
+
// Serialize Sets to arrays for caching
|
|
333
|
+
const cacheData = {
|
|
334
|
+
context: {
|
|
335
|
+
userId: context.userId,
|
|
336
|
+
roles: context.roles,
|
|
337
|
+
metadata: context.metadata,
|
|
338
|
+
lastUpdated: context.lastUpdated,
|
|
339
|
+
expiresAt: context.expiresAt,
|
|
340
|
+
},
|
|
341
|
+
permissions: Array.from(context.permissions),
|
|
342
|
+
expandedPermissions: context.expandedPermissions
|
|
343
|
+
? Array.from(context.expandedPermissions)
|
|
344
|
+
: undefined,
|
|
345
|
+
};
|
|
346
|
+
const ttlMs = this.config.cache.userContextTtlMs || 15 * 60 * 1000; // 15 minutes default
|
|
347
|
+
await this.cache.set(cacheKey, cacheData, ttlMs);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Build user context from raw user data
|
|
351
|
+
*/
|
|
352
|
+
async buildUserContext(userId, userData) {
|
|
353
|
+
const now = new Date().toISOString();
|
|
354
|
+
// Combine user and role permissions
|
|
355
|
+
const rolePermissions = await this.permissionSource.getRolePermissions(userData.roles);
|
|
356
|
+
const allPermissions = [
|
|
357
|
+
...new Set([...userData.permissions, ...rolePermissions]),
|
|
358
|
+
];
|
|
359
|
+
// Create base context
|
|
360
|
+
const context = {
|
|
361
|
+
userId,
|
|
362
|
+
permissions: new Set(allPermissions),
|
|
363
|
+
roles: userData.roles,
|
|
364
|
+
metadata: userData.metadata || {},
|
|
365
|
+
lastUpdated: now,
|
|
366
|
+
};
|
|
367
|
+
// Add expanded permissions for pre-expansion strategy
|
|
368
|
+
if (this.config.security.permissionResolutionStrategy ===
|
|
369
|
+
GuardConfiguration_1.PermissionResolutionStrategy.PRE_EXPANSION) {
|
|
370
|
+
context.expandedPermissions =
|
|
371
|
+
await this.expandPermissions(allPermissions);
|
|
372
|
+
}
|
|
373
|
+
return context;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Select appropriate permission resolver
|
|
377
|
+
*/
|
|
378
|
+
selectResolver(requirement, preferredType) {
|
|
379
|
+
// Use preferred type if specified and resolver can handle it
|
|
380
|
+
if (preferredType) {
|
|
381
|
+
const resolver = this.getResolverByType(preferredType);
|
|
382
|
+
if (resolver && resolver.canHandle(requirement)) {
|
|
383
|
+
return resolver;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// Auto-select based on requirement type
|
|
387
|
+
if (this.expressionResolver.canHandle(requirement)) {
|
|
388
|
+
return this.expressionResolver;
|
|
389
|
+
}
|
|
390
|
+
if (this.wildcardResolver.canHandle(requirement)) {
|
|
391
|
+
return this.wildcardResolver;
|
|
392
|
+
}
|
|
393
|
+
if (this.plainResolver.canHandle(requirement)) {
|
|
394
|
+
return this.plainResolver;
|
|
395
|
+
}
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get resolver by type
|
|
400
|
+
*/
|
|
401
|
+
getResolverByType(type) {
|
|
402
|
+
switch (type) {
|
|
403
|
+
case PermissionResolver_1.PermissionResolverType.PLAIN:
|
|
404
|
+
return this.plainResolver;
|
|
405
|
+
case PermissionResolver_1.PermissionResolverType.WILDCARD:
|
|
406
|
+
return this.wildcardResolver;
|
|
407
|
+
case PermissionResolver_1.PermissionResolverType.EXPRESSION:
|
|
408
|
+
return this.expressionResolver;
|
|
409
|
+
default:
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Record audit trail for permission checks
|
|
415
|
+
*/
|
|
416
|
+
async recordAuditTrail(userId, requirement, result) {
|
|
417
|
+
// In production, this would write to an audit log
|
|
418
|
+
console.log(`🔍 Permission granted`, {
|
|
419
|
+
userId,
|
|
420
|
+
requirement: typeof requirement === 'object'
|
|
421
|
+
? JSON.stringify(requirement)
|
|
422
|
+
: requirement,
|
|
423
|
+
resolverType: result.resolverType,
|
|
424
|
+
resolutionTimeUs: result.resolutionTimeUs,
|
|
425
|
+
matchedPermissions: result.matchedPermissions,
|
|
426
|
+
timestamp: new Date().toISOString(),
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
exports.FastUserContextService = FastUserContextService;
|
|
431
|
+
exports.FastUserContextService = FastUserContextService = __decorate([
|
|
432
|
+
(0, typedi_1.Service)(),
|
|
433
|
+
__metadata("design:paramtypes", [Object, GuardConfiguration_1.GuardConfiguration, Object, Object])
|
|
434
|
+
], FastUserContextService);
|
|
435
|
+
//# sourceMappingURL=FastUserContextService.js.map
|
|
@@ -1,8 +1,126 @@
|
|
|
1
1
|
import { BaseMiddleware, Context } from '../core';
|
|
2
|
+
/**
|
|
3
|
+
* Middleware class that validates the presence of required HTTP headers.
|
|
4
|
+
* Throws a ValidationError if any required header is missing or empty.
|
|
5
|
+
*
|
|
6
|
+
* @implements {BaseMiddleware}
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* API key authentication via headers:
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { Handler, HeaderVariablesMiddleware } from '@noony-serverless/core';
|
|
12
|
+
*
|
|
13
|
+
* const requiredHeaders = ['authorization', 'x-api-key', 'content-type'];
|
|
14
|
+
*
|
|
15
|
+
* const secureApiHandler = new Handler()
|
|
16
|
+
* .use(new HeaderVariablesMiddleware(requiredHeaders))
|
|
17
|
+
* .handle(async (context) => {
|
|
18
|
+
* const authHeader = context.req.headers.authorization;
|
|
19
|
+
* const apiKey = context.req.headers['x-api-key'];
|
|
20
|
+
*
|
|
21
|
+
* // Headers are guaranteed to exist after middleware validation
|
|
22
|
+
* console.log('Auth header:', authHeader);
|
|
23
|
+
* console.log('API key:', apiKey);
|
|
24
|
+
*
|
|
25
|
+
* return { success: true, authenticated: true };
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* Content negotiation requirements:
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const contentHeaders = ['accept', 'content-type', 'accept-language'];
|
|
33
|
+
*
|
|
34
|
+
* const internationalApiHandler = new Handler()
|
|
35
|
+
* .use(new HeaderVariablesMiddleware(contentHeaders))
|
|
36
|
+
* .handle(async (context) => {
|
|
37
|
+
* const acceptLang = context.req.headers['accept-language'];
|
|
38
|
+
* const contentType = context.req.headers['content-type'];
|
|
39
|
+
*
|
|
40
|
+
* const language = Array.isArray(acceptLang) ? acceptLang[0] : acceptLang;
|
|
41
|
+
* const responseData = getLocalizedContent(language);
|
|
42
|
+
*
|
|
43
|
+
* return { success: true, data: responseData, language };
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* Custom business headers validation:
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const businessHeaders = ['x-tenant-id', 'x-request-id', 'x-client-version'];
|
|
51
|
+
*
|
|
52
|
+
* const multiTenantHandler = new Handler()
|
|
53
|
+
* .use(new HeaderVariablesMiddleware(businessHeaders))
|
|
54
|
+
* .handle(async (context) => {
|
|
55
|
+
* const tenantId = context.req.headers['x-tenant-id'];
|
|
56
|
+
* const requestId = context.req.headers['x-request-id'];
|
|
57
|
+
* const clientVersion = context.req.headers['x-client-version'];
|
|
58
|
+
*
|
|
59
|
+
* console.log(`Processing request ${requestId} for tenant ${tenantId} with client ${clientVersion}`);
|
|
60
|
+
*
|
|
61
|
+
* const tenantData = await getTenantData(tenantId as string);
|
|
62
|
+
* return { success: true, tenant: tenantData };
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
2
66
|
export declare class HeaderVariablesMiddleware implements BaseMiddleware {
|
|
3
67
|
private requiredHeaders;
|
|
4
68
|
constructor(requiredHeaders: string[]);
|
|
5
69
|
before(context: Context): Promise<void>;
|
|
6
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Factory function that creates a header validation middleware.
|
|
73
|
+
* Validates that all required headers are present in the request.
|
|
74
|
+
*
|
|
75
|
+
* @param requiredHeaders - Array of header names that must be present
|
|
76
|
+
* @returns BaseMiddleware object with header validation logic
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* Simple header validation:
|
|
80
|
+
* ```typescript
|
|
81
|
+
* import { Handler, headerVariablesMiddleware } from '@noony-serverless/core';
|
|
82
|
+
*
|
|
83
|
+
* const authHandler = new Handler()
|
|
84
|
+
* .use(headerVariablesMiddleware(['authorization']))
|
|
85
|
+
* .handle(async (context) => {
|
|
86
|
+
* const token = context.req.headers.authorization;
|
|
87
|
+
* // Proceed with authentication logic
|
|
88
|
+
* return { success: true, message: 'Authenticated' };
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* Multiple required headers:
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const webhookHandler = new Handler()
|
|
96
|
+
* .use(headerVariablesMiddleware([
|
|
97
|
+
* 'x-webhook-signature',
|
|
98
|
+
* 'x-webhook-timestamp',
|
|
99
|
+
* 'content-type'
|
|
100
|
+
* ]))
|
|
101
|
+
* .handle(async (context) => {
|
|
102
|
+
* const signature = context.req.headers['x-webhook-signature'];
|
|
103
|
+
* const timestamp = context.req.headers['x-webhook-timestamp'];
|
|
104
|
+
*
|
|
105
|
+
* // Validate webhook authenticity
|
|
106
|
+
* const isValid = validateWebhookSignature(signature, timestamp, context.req.body);
|
|
107
|
+
* return { success: isValid };
|
|
108
|
+
* });
|
|
109
|
+
* ```
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* API versioning through headers:
|
|
113
|
+
* ```typescript
|
|
114
|
+
* const versionedApiHandler = new Handler()
|
|
115
|
+
* .use(headerVariablesMiddleware(['x-api-version', 'accept']))
|
|
116
|
+
* .handle(async (context) => {
|
|
117
|
+
* const apiVersion = context.req.headers['x-api-version'];
|
|
118
|
+
* const accept = context.req.headers.accept;
|
|
119
|
+
*
|
|
120
|
+
* const handler = getHandlerForVersion(apiVersion as string);
|
|
121
|
+
* return handler.process(context.req.body);
|
|
122
|
+
* });
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
7
125
|
export declare const headerVariablesMiddleware: (requiredHeaders: string[]) => BaseMiddleware;
|
|
8
126
|
//# sourceMappingURL=headerVariablesMiddleware.d.ts.map
|
|
@@ -11,6 +11,70 @@ const validateHeaders = (requiredHeaders, headers) => {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Middleware class that validates the presence of required HTTP headers.
|
|
16
|
+
* Throws a ValidationError if any required header is missing or empty.
|
|
17
|
+
*
|
|
18
|
+
* @implements {BaseMiddleware}
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* API key authentication via headers:
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { Handler, HeaderVariablesMiddleware } from '@noony-serverless/core';
|
|
24
|
+
*
|
|
25
|
+
* const requiredHeaders = ['authorization', 'x-api-key', 'content-type'];
|
|
26
|
+
*
|
|
27
|
+
* const secureApiHandler = new Handler()
|
|
28
|
+
* .use(new HeaderVariablesMiddleware(requiredHeaders))
|
|
29
|
+
* .handle(async (context) => {
|
|
30
|
+
* const authHeader = context.req.headers.authorization;
|
|
31
|
+
* const apiKey = context.req.headers['x-api-key'];
|
|
32
|
+
*
|
|
33
|
+
* // Headers are guaranteed to exist after middleware validation
|
|
34
|
+
* console.log('Auth header:', authHeader);
|
|
35
|
+
* console.log('API key:', apiKey);
|
|
36
|
+
*
|
|
37
|
+
* return { success: true, authenticated: true };
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* Content negotiation requirements:
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const contentHeaders = ['accept', 'content-type', 'accept-language'];
|
|
45
|
+
*
|
|
46
|
+
* const internationalApiHandler = new Handler()
|
|
47
|
+
* .use(new HeaderVariablesMiddleware(contentHeaders))
|
|
48
|
+
* .handle(async (context) => {
|
|
49
|
+
* const acceptLang = context.req.headers['accept-language'];
|
|
50
|
+
* const contentType = context.req.headers['content-type'];
|
|
51
|
+
*
|
|
52
|
+
* const language = Array.isArray(acceptLang) ? acceptLang[0] : acceptLang;
|
|
53
|
+
* const responseData = getLocalizedContent(language);
|
|
54
|
+
*
|
|
55
|
+
* return { success: true, data: responseData, language };
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* Custom business headers validation:
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const businessHeaders = ['x-tenant-id', 'x-request-id', 'x-client-version'];
|
|
63
|
+
*
|
|
64
|
+
* const multiTenantHandler = new Handler()
|
|
65
|
+
* .use(new HeaderVariablesMiddleware(businessHeaders))
|
|
66
|
+
* .handle(async (context) => {
|
|
67
|
+
* const tenantId = context.req.headers['x-tenant-id'];
|
|
68
|
+
* const requestId = context.req.headers['x-request-id'];
|
|
69
|
+
* const clientVersion = context.req.headers['x-client-version'];
|
|
70
|
+
*
|
|
71
|
+
* console.log(`Processing request ${requestId} for tenant ${tenantId} with client ${clientVersion}`);
|
|
72
|
+
*
|
|
73
|
+
* const tenantData = await getTenantData(tenantId as string);
|
|
74
|
+
* return { success: true, tenant: tenantData };
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
14
78
|
class HeaderVariablesMiddleware {
|
|
15
79
|
requiredHeaders;
|
|
16
80
|
constructor(requiredHeaders) {
|
|
@@ -22,6 +86,60 @@ class HeaderVariablesMiddleware {
|
|
|
22
86
|
}
|
|
23
87
|
}
|
|
24
88
|
exports.HeaderVariablesMiddleware = HeaderVariablesMiddleware;
|
|
89
|
+
/**
|
|
90
|
+
* Factory function that creates a header validation middleware.
|
|
91
|
+
* Validates that all required headers are present in the request.
|
|
92
|
+
*
|
|
93
|
+
* @param requiredHeaders - Array of header names that must be present
|
|
94
|
+
* @returns BaseMiddleware object with header validation logic
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* Simple header validation:
|
|
98
|
+
* ```typescript
|
|
99
|
+
* import { Handler, headerVariablesMiddleware } from '@noony-serverless/core';
|
|
100
|
+
*
|
|
101
|
+
* const authHandler = new Handler()
|
|
102
|
+
* .use(headerVariablesMiddleware(['authorization']))
|
|
103
|
+
* .handle(async (context) => {
|
|
104
|
+
* const token = context.req.headers.authorization;
|
|
105
|
+
* // Proceed with authentication logic
|
|
106
|
+
* return { success: true, message: 'Authenticated' };
|
|
107
|
+
* });
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* Multiple required headers:
|
|
112
|
+
* ```typescript
|
|
113
|
+
* const webhookHandler = new Handler()
|
|
114
|
+
* .use(headerVariablesMiddleware([
|
|
115
|
+
* 'x-webhook-signature',
|
|
116
|
+
* 'x-webhook-timestamp',
|
|
117
|
+
* 'content-type'
|
|
118
|
+
* ]))
|
|
119
|
+
* .handle(async (context) => {
|
|
120
|
+
* const signature = context.req.headers['x-webhook-signature'];
|
|
121
|
+
* const timestamp = context.req.headers['x-webhook-timestamp'];
|
|
122
|
+
*
|
|
123
|
+
* // Validate webhook authenticity
|
|
124
|
+
* const isValid = validateWebhookSignature(signature, timestamp, context.req.body);
|
|
125
|
+
* return { success: isValid };
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* API versioning through headers:
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const versionedApiHandler = new Handler()
|
|
133
|
+
* .use(headerVariablesMiddleware(['x-api-version', 'accept']))
|
|
134
|
+
* .handle(async (context) => {
|
|
135
|
+
* const apiVersion = context.req.headers['x-api-version'];
|
|
136
|
+
* const accept = context.req.headers.accept;
|
|
137
|
+
*
|
|
138
|
+
* const handler = getHandlerForVersion(apiVersion as string);
|
|
139
|
+
* return handler.process(context.req.body);
|
|
140
|
+
* });
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
25
143
|
const headerVariablesMiddleware = (requiredHeaders) => ({
|
|
26
144
|
async before(context) {
|
|
27
145
|
context.req.headers = context.req.headers || {};
|