@engjts/nexus 0.1.8 → 0.1.9
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/package.json +1 -1
- package/BENCHMARK_REPORT.md +0 -343
- package/documentation/01-getting-started.md +0 -240
- package/documentation/02-context.md +0 -335
- package/documentation/03-routing.md +0 -397
- package/documentation/04-middleware.md +0 -483
- package/documentation/05-validation.md +0 -514
- package/documentation/06-error-handling.md +0 -465
- package/documentation/07-performance.md +0 -364
- package/documentation/08-adapters.md +0 -470
- package/documentation/09-api-reference.md +0 -548
- package/documentation/10-examples.md +0 -582
- package/documentation/11-deployment.md +0 -477
- package/documentation/12-sentry.md +0 -620
- package/documentation/13-sentry-data-storage.md +0 -996
- package/documentation/14-sentry-data-reference.md +0 -457
- package/documentation/15-sentry-summary.md +0 -409
- package/documentation/16-alerts-system.md +0 -745
- package/documentation/17-alert-adapters.md +0 -696
- package/documentation/18-alerts-implementation-summary.md +0 -385
- package/documentation/19-class-based-routing.md +0 -840
- package/documentation/20-websocket-realtime.md +0 -813
- package/documentation/21-cache-system.md +0 -510
- package/documentation/22-job-queue.md +0 -772
- package/documentation/23-sentry-plugin.md +0 -551
- package/documentation/24-testing-utilities.md +0 -1287
- package/documentation/25-api-versioning.md +0 -533
- package/documentation/26-context-store.md +0 -607
- package/documentation/27-dependency-injection.md +0 -329
- package/documentation/28-lifecycle-hooks.md +0 -521
- package/documentation/29-package-structure.md +0 -196
- package/documentation/30-plugin-system.md +0 -414
- package/documentation/31-jwt-authentication.md +0 -597
- package/documentation/32-cli.md +0 -268
- package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
- package/documentation/ALERTS-INDEX.md +0 -330
- package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
- package/documentation/README.md +0 -178
- package/documentation/index.html +0 -34
- package/modern_framework_paper.md +0 -1870
- package/public/css/style.css +0 -87
- package/public/index.html +0 -34
- package/public/js/app.js +0 -27
- package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
- package/src/advanced/cache/MultiTierCache.ts +0 -194
- package/src/advanced/cache/RedisCacheStore.ts +0 -341
- package/src/advanced/cache/index.ts +0 -5
- package/src/advanced/cache/types.ts +0 -40
- package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
- package/src/advanced/graphql/index.ts +0 -22
- package/src/advanced/graphql/server.ts +0 -252
- package/src/advanced/graphql/types.ts +0 -42
- package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
- package/src/advanced/jobs/JobQueue.ts +0 -556
- package/src/advanced/jobs/RedisQueueStore.ts +0 -367
- package/src/advanced/jobs/index.ts +0 -5
- package/src/advanced/jobs/types.ts +0 -70
- package/src/advanced/observability/APMManager.ts +0 -163
- package/src/advanced/observability/AlertManager.ts +0 -109
- package/src/advanced/observability/MetricRegistry.ts +0 -151
- package/src/advanced/observability/ObservabilityCenter.ts +0 -304
- package/src/advanced/observability/StructuredLogger.ts +0 -154
- package/src/advanced/observability/TracingManager.ts +0 -117
- package/src/advanced/observability/adapters.ts +0 -304
- package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
- package/src/advanced/observability/index.ts +0 -11
- package/src/advanced/observability/types.ts +0 -174
- package/src/advanced/playground/extractPathParams.ts +0 -6
- package/src/advanced/playground/generateFieldExample.ts +0 -31
- package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1956
- package/src/advanced/playground/generateSummary.ts +0 -19
- package/src/advanced/playground/getTagFromPath.ts +0 -9
- package/src/advanced/playground/index.ts +0 -8
- package/src/advanced/playground/playground.ts +0 -250
- package/src/advanced/playground/types.ts +0 -49
- package/src/advanced/playground/zodToExample.ts +0 -16
- package/src/advanced/playground/zodToParams.ts +0 -15
- package/src/advanced/postman/buildAuth.ts +0 -31
- package/src/advanced/postman/buildBody.ts +0 -15
- package/src/advanced/postman/buildQueryParams.ts +0 -27
- package/src/advanced/postman/buildRequestItem.ts +0 -36
- package/src/advanced/postman/buildResponses.ts +0 -11
- package/src/advanced/postman/buildUrl.ts +0 -33
- package/src/advanced/postman/capitalize.ts +0 -4
- package/src/advanced/postman/generateCollection.ts +0 -59
- package/src/advanced/postman/generateEnvironment.ts +0 -34
- package/src/advanced/postman/generateExampleFromZod.ts +0 -21
- package/src/advanced/postman/generateFieldExample.ts +0 -45
- package/src/advanced/postman/generateName.ts +0 -20
- package/src/advanced/postman/generateUUID.ts +0 -11
- package/src/advanced/postman/getTagFromPath.ts +0 -10
- package/src/advanced/postman/index.ts +0 -28
- package/src/advanced/postman/postman.ts +0 -156
- package/src/advanced/postman/slugify.ts +0 -7
- package/src/advanced/postman/types.ts +0 -140
- package/src/advanced/realtime/index.ts +0 -18
- package/src/advanced/realtime/websocket.ts +0 -231
- package/src/advanced/sentry/index.ts +0 -1236
- package/src/advanced/sentry/types.ts +0 -355
- package/src/advanced/static/generateDirectoryListing.ts +0 -47
- package/src/advanced/static/generateETag.ts +0 -7
- package/src/advanced/static/getMimeType.ts +0 -9
- package/src/advanced/static/index.ts +0 -32
- package/src/advanced/static/isSafePath.ts +0 -13
- package/src/advanced/static/publicDir.ts +0 -21
- package/src/advanced/static/serveStatic.ts +0 -225
- package/src/advanced/static/spa.ts +0 -24
- package/src/advanced/static/types.ts +0 -159
- package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
- package/src/advanced/swagger/buildOperation.ts +0 -61
- package/src/advanced/swagger/buildParameters.ts +0 -61
- package/src/advanced/swagger/buildRequestBody.ts +0 -21
- package/src/advanced/swagger/buildResponses.ts +0 -54
- package/src/advanced/swagger/capitalize.ts +0 -5
- package/src/advanced/swagger/convertPath.ts +0 -9
- package/src/advanced/swagger/createSwagger.ts +0 -12
- package/src/advanced/swagger/generateOperationId.ts +0 -21
- package/src/advanced/swagger/generateSpec.ts +0 -105
- package/src/advanced/swagger/generateSummary.ts +0 -24
- package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
- package/src/advanced/swagger/generateThemeCss.ts +0 -53
- package/src/advanced/swagger/index.ts +0 -25
- package/src/advanced/swagger/swagger.ts +0 -237
- package/src/advanced/swagger/types.ts +0 -206
- package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
- package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
- package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
- package/src/advanced/testing/factory.ts +0 -509
- package/src/advanced/testing/harness.ts +0 -612
- package/src/advanced/testing/index.ts +0 -430
- package/src/advanced/testing/load-test.ts +0 -618
- package/src/advanced/testing/mock-server.ts +0 -498
- package/src/advanced/testing/mock.ts +0 -670
- package/src/cli/bin.ts +0 -9
- package/src/cli/cli.ts +0 -158
- package/src/cli/commands/add.ts +0 -178
- package/src/cli/commands/build.ts +0 -73
- package/src/cli/commands/create.ts +0 -166
- package/src/cli/commands/dev.ts +0 -85
- package/src/cli/commands/generate.ts +0 -99
- package/src/cli/commands/help.ts +0 -95
- package/src/cli/commands/init.ts +0 -91
- package/src/cli/commands/version.ts +0 -38
- package/src/cli/index.ts +0 -6
- package/src/cli/templates/generators.ts +0 -359
- package/src/cli/templates/index.ts +0 -680
- package/src/cli/utils/exec.ts +0 -52
- package/src/cli/utils/file-system.ts +0 -78
- package/src/cli/utils/logger.ts +0 -111
- package/src/core/adapter.ts +0 -88
- package/src/core/application.ts +0 -1453
- package/src/core/context-pool.ts +0 -79
- package/src/core/context.ts +0 -856
- package/src/core/index.ts +0 -94
- package/src/core/middleware.ts +0 -272
- package/src/core/performance/buffer-pool.ts +0 -108
- package/src/core/performance/middleware-optimizer.ts +0 -162
- package/src/core/plugin/PluginManager.ts +0 -435
- package/src/core/plugin/builder.ts +0 -358
- package/src/core/plugin/index.ts +0 -50
- package/src/core/plugin/types.ts +0 -214
- package/src/core/router/file-router.ts +0 -623
- package/src/core/router/index.ts +0 -260
- package/src/core/router/radix-tree.ts +0 -242
- package/src/core/serializer.ts +0 -397
- package/src/core/store/index.ts +0 -30
- package/src/core/store/registry.ts +0 -178
- package/src/core/store/request-store.ts +0 -240
- package/src/core/store/types.ts +0 -233
- package/src/core/types.ts +0 -616
- package/src/database/adapter.ts +0 -35
- package/src/database/adapters/index.ts +0 -1
- package/src/database/adapters/mysql.ts +0 -669
- package/src/database/database.ts +0 -70
- package/src/database/dialect.ts +0 -388
- package/src/database/index.ts +0 -12
- package/src/database/migrations.ts +0 -86
- package/src/database/optimizer.ts +0 -125
- package/src/database/query-builder.ts +0 -404
- package/src/database/realtime.ts +0 -53
- package/src/database/schema.ts +0 -71
- package/src/database/transactions.ts +0 -56
- package/src/database/types.ts +0 -87
- package/src/deployment/cluster.ts +0 -471
- package/src/deployment/config.ts +0 -454
- package/src/deployment/docker.ts +0 -599
- package/src/deployment/graceful-shutdown.ts +0 -373
- package/src/deployment/index.ts +0 -56
- package/src/index.ts +0 -281
- package/src/security/adapter.ts +0 -318
- package/src/security/auth/JWTPlugin.ts +0 -234
- package/src/security/auth/JWTProvider.ts +0 -316
- package/src/security/auth/adapter.ts +0 -12
- package/src/security/auth/jwt.ts +0 -234
- package/src/security/auth/middleware.ts +0 -188
- package/src/security/csrf.ts +0 -220
- package/src/security/headers.ts +0 -108
- package/src/security/index.ts +0 -60
- package/src/security/rate-limit/adapter.ts +0 -7
- package/src/security/rate-limit/memory.ts +0 -108
- package/src/security/rate-limit/middleware.ts +0 -181
- package/src/security/sanitization.ts +0 -75
- package/src/security/types.ts +0 -240
- package/src/security/utils.ts +0 -52
- package/tsconfig.json +0 -39
package/src/security/adapter.ts
DELETED
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Security Adapter Interfaces
|
|
3
|
-
*
|
|
4
|
-
* Extensible adapter system for security components
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Context } from '../core/types';
|
|
8
|
-
import type {
|
|
9
|
-
User,
|
|
10
|
-
SecurityHeadersConfig,
|
|
11
|
-
SanitizationConfig,
|
|
12
|
-
RateLimitInfo,
|
|
13
|
-
PermissionCheckResult
|
|
14
|
-
} from './types';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Security headers adapter interface
|
|
18
|
-
* Allows custom security headers implementation
|
|
19
|
-
*/
|
|
20
|
-
export interface SecurityHeadersAdapter {
|
|
21
|
-
/**
|
|
22
|
-
* Generate security headers for response
|
|
23
|
-
*/
|
|
24
|
-
generateHeaders(ctx: Context, config: SecurityHeadersConfig): Record<string, string>;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Generate CSP nonce if needed
|
|
28
|
-
*/
|
|
29
|
-
generateNonce?(): string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Input sanitization adapter interface
|
|
34
|
-
* Allows custom sanitization logic
|
|
35
|
-
*/
|
|
36
|
-
export interface SanitizationAdapter {
|
|
37
|
-
/**
|
|
38
|
-
* Sanitize input value
|
|
39
|
-
* @returns Sanitized value or throws if in strict mode
|
|
40
|
-
*/
|
|
41
|
-
sanitize(value: any, config: SanitizationConfig): any;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Check if value contains malicious patterns
|
|
45
|
-
*/
|
|
46
|
-
isMalicious(value: any, config: SanitizationConfig): boolean;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Base authentication adapter interface
|
|
51
|
-
*/
|
|
52
|
-
export interface AuthAdapter<TConfig = any> {
|
|
53
|
-
/**
|
|
54
|
-
* Verify credentials and return user
|
|
55
|
-
* @returns User object if valid, null otherwise
|
|
56
|
-
*/
|
|
57
|
-
verify(ctx: Context, config: TConfig): Promise<User | null>;
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Generate authentication token/session
|
|
61
|
-
*/
|
|
62
|
-
generateToken?(user: User, config: TConfig): Promise<string>;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Refresh authentication token
|
|
66
|
-
*/
|
|
67
|
-
refreshToken?(token: string, config: TConfig): Promise<string | null>;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Rate limiting storage adapter interface
|
|
72
|
-
*/
|
|
73
|
-
export interface RateLimitAdapter {
|
|
74
|
-
/**
|
|
75
|
-
* Increment request count for key
|
|
76
|
-
* @returns Current count and reset time
|
|
77
|
-
*/
|
|
78
|
-
increment(key: string, windowMs: number): Promise<{
|
|
79
|
-
count: number;
|
|
80
|
-
resetTime: number;
|
|
81
|
-
}>;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Get current rate limit info for key
|
|
85
|
-
*/
|
|
86
|
-
get(key: string): Promise<RateLimitInfo | null>;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Reset rate limit for key
|
|
90
|
-
*/
|
|
91
|
-
reset(key: string): Promise<void>;
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Clean up expired entries
|
|
95
|
-
*/
|
|
96
|
-
cleanup?(): Promise<void>;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* CSRF token adapter interface
|
|
101
|
-
*/
|
|
102
|
-
export interface CSRFAdapter {
|
|
103
|
-
/**
|
|
104
|
-
* Generate CSRF token
|
|
105
|
-
*/
|
|
106
|
-
generateToken(ctx: Context): string;
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Validate CSRF token
|
|
110
|
-
*/
|
|
111
|
-
validateToken(ctx: Context, token: string): boolean;
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Extract token from request
|
|
115
|
-
*/
|
|
116
|
-
extractToken(ctx: Context): string | null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Permission checker adapter interface
|
|
121
|
-
* Allows custom RBAC logic
|
|
122
|
-
*/
|
|
123
|
-
export interface PermissionAdapter {
|
|
124
|
-
/**
|
|
125
|
-
* Check if user has required permissions
|
|
126
|
-
*/
|
|
127
|
-
checkPermissions(user: User, required: string[]): PermissionCheckResult;
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Check if user has required roles
|
|
131
|
-
*/
|
|
132
|
-
checkRoles(user: User, required: string[]): boolean;
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Expand role to permissions
|
|
136
|
-
*/
|
|
137
|
-
expandRole?(role: string): string[] | undefined;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Default security headers adapter
|
|
142
|
-
*/
|
|
143
|
-
export class DefaultSecurityHeadersAdapter implements SecurityHeadersAdapter {
|
|
144
|
-
generateHeaders(_ctx: Context, config: SecurityHeadersConfig): Record<string, string> {
|
|
145
|
-
const mode = config.mode || 'moderate';
|
|
146
|
-
const headers: Record<string, string> = {};
|
|
147
|
-
|
|
148
|
-
// Base headers
|
|
149
|
-
headers['X-Content-Type-Options'] = 'nosniff';
|
|
150
|
-
headers['X-Frame-Options'] = mode === 'strict' ? 'DENY' : 'SAMEORIGIN';
|
|
151
|
-
headers['X-XSS-Protection'] = '1; mode=block';
|
|
152
|
-
|
|
153
|
-
if (mode === 'strict' || mode === 'moderate') {
|
|
154
|
-
headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains';
|
|
155
|
-
headers['Referrer-Policy'] = 'no-referrer';
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// CSP
|
|
159
|
-
if (config.csp) {
|
|
160
|
-
let cspValue = '';
|
|
161
|
-
const directives = config.csp.directives || {
|
|
162
|
-
'default-src': ["'self'"]
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
for (const [directive, values] of Object.entries(directives)) {
|
|
166
|
-
cspValue += `${directive} ${values.join(' ')}; `;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (config.csp.reportUri) {
|
|
170
|
-
cspValue += `report-uri ${config.csp.reportUri}; `;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const headerName = config.csp.reportOnly
|
|
174
|
-
? 'Content-Security-Policy-Report-Only'
|
|
175
|
-
: 'Content-Security-Policy';
|
|
176
|
-
headers[headerName] = cspValue.trim();
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Custom headers override
|
|
180
|
-
if (config.customHeaders) {
|
|
181
|
-
Object.assign(headers, config.customHeaders);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return headers;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
generateNonce?(): string {
|
|
188
|
-
return crypto.randomUUID().replace(/-/g, '');
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Default input sanitization adapter
|
|
194
|
-
*/
|
|
195
|
-
export class DefaultSanitizationAdapter implements SanitizationAdapter {
|
|
196
|
-
private readonly defaultPatterns = {
|
|
197
|
-
sql: /((\bUNION\b)|(\bSELECT\b)|(\bINSERT\b)|(\bUPDATE\b)|(\bDELETE\b)|(\bDROP\b)|(\bCREATE\b)|(\bALTER\b))/gi,
|
|
198
|
-
xss: /(<script|javascript:|onerror=|onload=|<iframe|<object|<embed)/gi,
|
|
199
|
-
pathTraversal: /(\.\.[\/\\])/g,
|
|
200
|
-
noSqlInjection: /(\$where|\$ne|\$gt|\$lt|\$regex)/gi
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
sanitize(value: any, config: SanitizationConfig): any {
|
|
204
|
-
if (value === null || value === undefined) {
|
|
205
|
-
return value;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (typeof value === 'string') {
|
|
209
|
-
return this.sanitizeString(value, config);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (Array.isArray(value)) {
|
|
213
|
-
return value.map(item => this.sanitize(item, config));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (typeof value === 'object') {
|
|
217
|
-
const sanitized: any = {};
|
|
218
|
-
for (const [key, val] of Object.entries(value)) {
|
|
219
|
-
sanitized[key] = this.sanitize(val, config);
|
|
220
|
-
}
|
|
221
|
-
return sanitized;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return value;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
private sanitizeString(value: string, config: SanitizationConfig): string {
|
|
228
|
-
const patterns = config.patterns || [];
|
|
229
|
-
const allPatterns = [...Object.values(this.defaultPatterns), ...patterns.map(p => p.pattern)];
|
|
230
|
-
|
|
231
|
-
for (const pattern of allPatterns) {
|
|
232
|
-
if (pattern.test(value)) {
|
|
233
|
-
if (config.strict) {
|
|
234
|
-
throw new Error('Malicious input detected');
|
|
235
|
-
}
|
|
236
|
-
// Remove malicious content
|
|
237
|
-
value = value.replace(pattern, '');
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return value;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
isMalicious(value: any, config: SanitizationConfig): boolean {
|
|
245
|
-
if (typeof value !== 'string') {
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const patterns = config.patterns || [];
|
|
250
|
-
const allPatterns = [...Object.values(this.defaultPatterns), ...patterns.map(p => p.pattern)];
|
|
251
|
-
|
|
252
|
-
return allPatterns.some(pattern => pattern.test(value));
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Default permission checker adapter
|
|
258
|
-
*/
|
|
259
|
-
export class DefaultPermissionAdapter implements PermissionAdapter {
|
|
260
|
-
private roleHierarchy: Map<string, string[]> = new Map();
|
|
261
|
-
|
|
262
|
-
checkPermissions(user: User, required: string[]): PermissionCheckResult {
|
|
263
|
-
const userPermissions = new Set(user.permissions || []);
|
|
264
|
-
|
|
265
|
-
// Check for wildcard permission
|
|
266
|
-
if (userPermissions.has('*')) {
|
|
267
|
-
return { allowed: true };
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Expand roles to permissions
|
|
271
|
-
if (user.roles) {
|
|
272
|
-
for (const role of user.roles) {
|
|
273
|
-
const rolePerms = this.expandRole?.(role);
|
|
274
|
-
if (rolePerms) {
|
|
275
|
-
rolePerms.forEach(p => userPermissions.add(p));
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Check required permissions
|
|
281
|
-
const missing = required.filter(perm => {
|
|
282
|
-
// Check for wildcard match (e.g., 'read:*' matches 'read:users')
|
|
283
|
-
const hasWildcard = Array.from(userPermissions).some(userPerm => {
|
|
284
|
-
if (userPerm.endsWith('*')) {
|
|
285
|
-
const prefix = userPerm.slice(0, -1);
|
|
286
|
-
return perm.startsWith(prefix);
|
|
287
|
-
}
|
|
288
|
-
return userPerm === perm;
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
return !hasWildcard && !userPermissions.has(perm);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
allowed: missing.length === 0,
|
|
296
|
-
missing: missing.length > 0 ? missing : undefined
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
checkRoles(user: User, required: string[]): boolean {
|
|
301
|
-
if (!user.roles || user.roles.length === 0) {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return required.some(role => user.roles!.includes(role));
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
expandRole?(role: string): string[] | undefined {
|
|
309
|
-
return this.roleHierarchy.get(role);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Define role permissions
|
|
314
|
-
*/
|
|
315
|
-
defineRole(role: string, permissions: string[]): void {
|
|
316
|
-
this.roleHierarchy.set(role, permissions);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JWT Authentication Plugin
|
|
3
|
-
*
|
|
4
|
-
* Plugin untuk JWT authentication yang otomatis:
|
|
5
|
-
* - Menambahkan jwt provider ke dependencies
|
|
6
|
-
* - Menambahkan auth middleware (opsional)
|
|
7
|
-
* - Mendekorasi context dengan user property
|
|
8
|
-
* - Export API untuk plugin lain
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { definePlugin } from '../../core/plugin';
|
|
12
|
-
import { JWTProvider, JWTProviderConfig, VerifyResult } from './JWTProvider';
|
|
13
|
-
import type { User } from '../types';
|
|
14
|
-
import type { Context } from '../../core';
|
|
15
|
-
|
|
16
|
-
export interface JWTPluginConfig extends JWTProviderConfig {
|
|
17
|
-
/**
|
|
18
|
-
* Auto-protect semua route (default: false)
|
|
19
|
-
* Jika true, semua route akan butuh auth kecuali yang ada di `publicPaths`
|
|
20
|
-
*/
|
|
21
|
-
autoProtect?: boolean;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Path yang tidak perlu auth (hanya berlaku jika autoProtect: true)
|
|
25
|
-
*/
|
|
26
|
-
publicPaths?: string[];
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Nama cookie untuk menyimpan token (opsional)
|
|
30
|
-
*/
|
|
31
|
-
cookieName?: string;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Custom handler ketika unauthorized
|
|
35
|
-
*/
|
|
36
|
-
onUnauthorized?: (ctx: Context, error: string) => any;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface JWTPluginExports {
|
|
40
|
-
/**
|
|
41
|
-
* JWT Provider instance
|
|
42
|
-
*/
|
|
43
|
-
provider: JWTProvider;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Sign token dari payload
|
|
47
|
-
*/
|
|
48
|
-
sign: (payload: { id: string | number; [key: string]: any }) => Promise<string>;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Verify token dari context
|
|
52
|
-
*/
|
|
53
|
-
verify: (ctx: Context) => Promise<VerifyResult>;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Verify token string
|
|
57
|
-
*/
|
|
58
|
-
verifyToken: (token: string) => Promise<VerifyResult>;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Decode token tanpa verify
|
|
62
|
-
*/
|
|
63
|
-
decode: (token: string) => any;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Refresh token
|
|
67
|
-
*/
|
|
68
|
-
refresh: (token: string) => Promise<string | null>;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Check role
|
|
72
|
-
*/
|
|
73
|
-
hasRole: (user: User, role: string | string[]) => boolean;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Check permission
|
|
77
|
-
*/
|
|
78
|
-
hasPermission: (user: User, permission: string | string[]) => boolean;
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get middleware for protecting routes
|
|
82
|
-
*/
|
|
83
|
-
middleware: () => (ctx: Context, next: any) => Promise<any>;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Create JWT Plugin
|
|
88
|
-
*
|
|
89
|
-
* @example
|
|
90
|
-
* ```typescript
|
|
91
|
-
* import { createApp } from 'nexus';
|
|
92
|
-
* import { jwtPlugin } from 'nexus/security';
|
|
93
|
-
*
|
|
94
|
-
* const app = createApp()
|
|
95
|
-
* .plugin(jwtPlugin, {
|
|
96
|
-
* secret: process.env.JWT_SECRET!,
|
|
97
|
-
* expiresIn: '7d',
|
|
98
|
-
* autoProtect: false
|
|
99
|
-
* });
|
|
100
|
-
*
|
|
101
|
-
* // Akses JWT via plugin exports
|
|
102
|
-
* app.post('/login', async (ctx) => {
|
|
103
|
-
* const jwt = app.getPluginExports<JWTPluginExports>('jwt');
|
|
104
|
-
* const token = await jwt.sign({ id: user.id, email: user.email });
|
|
105
|
-
* return { token };
|
|
106
|
-
* });
|
|
107
|
-
*
|
|
108
|
-
* // Atau via context decorator
|
|
109
|
-
* app.get('/profile', async (ctx) => {
|
|
110
|
-
* const result = await ctx.jwt.verify(ctx);
|
|
111
|
-
* if (!result.valid) return ctx.response.status(401).json({ error: 'Unauthorized' });
|
|
112
|
-
* return { user: result.user };
|
|
113
|
-
* });
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
|
-
export const jwtPlugin = definePlugin('jwt')
|
|
117
|
-
.version('1.0.0')
|
|
118
|
-
.description('JWT Authentication Plugin')
|
|
119
|
-
.author('Nexus Team')
|
|
120
|
-
.tags('security', 'authentication', 'jwt')
|
|
121
|
-
.priority('high')
|
|
122
|
-
|
|
123
|
-
// Type-safe configuration
|
|
124
|
-
.config<JWTPluginConfig>()
|
|
125
|
-
.defaults({
|
|
126
|
-
expiresIn: '1h',
|
|
127
|
-
autoProtect: false,
|
|
128
|
-
publicPaths: ['/health', '/api/auth/login', '/api/auth/register']
|
|
129
|
-
})
|
|
130
|
-
.validate((config) => {
|
|
131
|
-
if (!config.secret) {
|
|
132
|
-
return 'JWT secret is required. Set it via config or JWT_SECRET environment variable.';
|
|
133
|
-
}
|
|
134
|
-
if (config.secret.length < 32) {
|
|
135
|
-
return 'JWT secret should be at least 32 characters for security.';
|
|
136
|
-
}
|
|
137
|
-
return true;
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
// Configure phase - create provider early
|
|
141
|
-
.configure((ctx) => {
|
|
142
|
-
const provider = new JWTProvider({
|
|
143
|
-
secret: ctx.config.secret,
|
|
144
|
-
expiresIn: ctx.config.expiresIn,
|
|
145
|
-
issuer: ctx.config.issuer,
|
|
146
|
-
audience: ctx.config.audience
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
ctx.storage.set('provider', provider);
|
|
150
|
-
ctx.log.debug('JWT Provider initialized');
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
// Register phase - add middleware if autoProtect
|
|
154
|
-
.register((ctx) => {
|
|
155
|
-
const provider = ctx.storage.get('provider') as JWTProvider;
|
|
156
|
-
|
|
157
|
-
if (ctx.config.autoProtect) {
|
|
158
|
-
ctx.app.use(async (reqCtx: Context, next: any) => {
|
|
159
|
-
// Check if path is public
|
|
160
|
-
const publicPaths = ctx.config.publicPaths || [];
|
|
161
|
-
const isPublic = publicPaths.some(p => {
|
|
162
|
-
if (p.endsWith('*')) {
|
|
163
|
-
return reqCtx.path.startsWith(p.slice(0, -1));
|
|
164
|
-
}
|
|
165
|
-
return reqCtx.path === p;
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
if (isPublic) {
|
|
169
|
-
return next(reqCtx);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Verify token
|
|
173
|
-
const result = await provider.verify(reqCtx, {
|
|
174
|
-
cookieName: ctx.config.cookieName
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
if (!result.valid) {
|
|
178
|
-
if (ctx.config.onUnauthorized) {
|
|
179
|
-
return ctx.config.onUnauthorized(reqCtx, result.error || 'Unauthorized');
|
|
180
|
-
}
|
|
181
|
-
return reqCtx.response.status(401).json({
|
|
182
|
-
error: 'Unauthorized',
|
|
183
|
-
message: result.error
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Attach user to context
|
|
188
|
-
(reqCtx as any).user = result.user;
|
|
189
|
-
|
|
190
|
-
return next(reqCtx);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
ctx.log.info('JWT auto-protection enabled');
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
// Decorate context dengan jwt helper
|
|
198
|
-
.decorate((ctx) => {
|
|
199
|
-
const provider = ctx.storage.get('provider') as JWTProvider;
|
|
200
|
-
|
|
201
|
-
(ctx as any).jwt = {
|
|
202
|
-
sign: (payload: any) => provider.sign(payload),
|
|
203
|
-
verify: (reqCtx: Context) => provider.verify(reqCtx),
|
|
204
|
-
decode: (token: string) => provider.decode(token),
|
|
205
|
-
hasRole: (user: User, role: string | string[]) => provider.hasRole(user, role),
|
|
206
|
-
hasPermission: (user: User, perm: string | string[]) => provider.hasPermission(user, perm)
|
|
207
|
-
};
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
// Export API untuk plugin lain
|
|
211
|
-
.export<JWTPluginExports>((ctx) => {
|
|
212
|
-
const provider = ctx.storage.get('provider') as JWTProvider;
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
provider,
|
|
216
|
-
sign: (payload) => provider.sign(payload),
|
|
217
|
-
verify: (reqCtx) => provider.verify(reqCtx),
|
|
218
|
-
verifyToken: (token) => provider.verifyToken(token),
|
|
219
|
-
decode: (token) => provider.decode(token),
|
|
220
|
-
refresh: (token) => provider.refresh(token),
|
|
221
|
-
hasRole: (user, role) => provider.hasRole(user, role),
|
|
222
|
-
hasPermission: (user, perm) => provider.hasPermission(user, perm),
|
|
223
|
-
middleware: () => provider.middleware({ cookieName: ctx.config.cookieName })
|
|
224
|
-
};
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
// Ready phase
|
|
228
|
-
.ready((ctx) => {
|
|
229
|
-
ctx.log.info(`JWT Plugin ready (autoProtect: ${ctx.config.autoProtect})`);
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
.build();
|
|
233
|
-
|
|
234
|
-
export default jwtPlugin;
|