@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
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authentication Middleware
|
|
3
|
-
*
|
|
4
|
-
* Provides authentication and authorization middleware
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Context, Next, Middleware } from '../../core/types';
|
|
8
|
-
import type { User, AuthContext, AuthStrategies } from '../types';
|
|
9
|
-
import type { AuthAdapter, PermissionAdapter } from '../adapter';
|
|
10
|
-
import { DefaultPermissionAdapter } from '../adapter';
|
|
11
|
-
import { JWTAuthAdapter } from './jwt';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Authentication middleware factory
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```ts
|
|
18
|
-
* // JWT authentication
|
|
19
|
-
* app.use(authenticate({
|
|
20
|
-
* jwt: {
|
|
21
|
-
* secret: process.env.JWT_SECRET,
|
|
22
|
-
* expiresIn: '15m'
|
|
23
|
-
* }
|
|
24
|
-
* }));
|
|
25
|
-
*
|
|
26
|
-
* // Optional authentication
|
|
27
|
-
* app.use(authenticate({ jwt: config }, { required: false }));
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export function authenticate(
|
|
31
|
-
strategies: AuthStrategies,
|
|
32
|
-
options: {
|
|
33
|
-
required?: boolean;
|
|
34
|
-
strategies?: ('jwt' | 'oauth' | 'session')[];
|
|
35
|
-
} = {}
|
|
36
|
-
): Middleware<Context, AuthContext> {
|
|
37
|
-
const adapters = new Map<string, AuthAdapter>();
|
|
38
|
-
const required = options.required !== false;
|
|
39
|
-
const strategyOrder = options.strategies || ['jwt', 'oauth', 'session'];
|
|
40
|
-
|
|
41
|
-
// Initialize adapters
|
|
42
|
-
if (strategies.jwt) {
|
|
43
|
-
adapters.set('jwt', new JWTAuthAdapter());
|
|
44
|
-
}
|
|
45
|
-
// OAuth and Session will be added in future
|
|
46
|
-
// if (strategies.oauth) adapters.set('oauth', new OAuthAdapter());
|
|
47
|
-
// if (strategies.session) adapters.set('session', new SessionAdapter());
|
|
48
|
-
|
|
49
|
-
return (async (ctx: Context, next: Next, _deps: any) => {
|
|
50
|
-
let user: User | null = null;
|
|
51
|
-
|
|
52
|
-
// Try each strategy in order
|
|
53
|
-
for (const strategyName of strategyOrder) {
|
|
54
|
-
const adapter = adapters.get(strategyName);
|
|
55
|
-
const config = (strategies as any)[strategyName];
|
|
56
|
-
|
|
57
|
-
if (adapter && config) {
|
|
58
|
-
try {
|
|
59
|
-
user = await adapter.verify(ctx, config);
|
|
60
|
-
if (user) {
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
} catch (error) {
|
|
64
|
-
// Try next strategy
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// If no user found and auth is required
|
|
71
|
-
if (!user && required) {
|
|
72
|
-
return {
|
|
73
|
-
statusCode: 401,
|
|
74
|
-
headers: { 'Content-Type': 'application/json' },
|
|
75
|
-
body: JSON.stringify({ error: 'Unauthorized' })
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Attach user to context
|
|
80
|
-
(ctx as unknown as AuthContext).user = user!;
|
|
81
|
-
|
|
82
|
-
return next(ctx as unknown as AuthContext);
|
|
83
|
-
}) as Middleware<Context, AuthContext>;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Optional authentication - doesn't throw if no auth provided
|
|
88
|
-
*/
|
|
89
|
-
export function optionalAuth(strategies: AuthStrategies): Middleware<Context, Partial<AuthContext>> {
|
|
90
|
-
return authenticate(strategies, { required: false }) as any;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Require authentication - throws 401 if not authenticated
|
|
95
|
-
*/
|
|
96
|
-
export function requireAuth(strategies: AuthStrategies): Middleware<Context, AuthContext> {
|
|
97
|
-
return authenticate(strategies, { required: true });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Permission-based authorization middleware
|
|
102
|
-
*
|
|
103
|
-
* @example
|
|
104
|
-
* ```ts
|
|
105
|
-
* app.post('/admin/users',
|
|
106
|
-
* authenticate({ jwt: config }),
|
|
107
|
-
* requirePermissions(['admin', 'write:users']),
|
|
108
|
-
* handler
|
|
109
|
-
* );
|
|
110
|
-
* ```
|
|
111
|
-
*/
|
|
112
|
-
export function requirePermissions(
|
|
113
|
-
permissions: string[],
|
|
114
|
-
adapter?: PermissionAdapter
|
|
115
|
-
): Middleware<AuthContext, AuthContext> {
|
|
116
|
-
const permissionChecker = adapter || new DefaultPermissionAdapter();
|
|
117
|
-
|
|
118
|
-
return (async (ctx: AuthContext, next: Next, _deps: any) => {
|
|
119
|
-
if (!ctx.user) {
|
|
120
|
-
return {
|
|
121
|
-
statusCode: 401,
|
|
122
|
-
headers: { 'Content-Type': 'application/json' },
|
|
123
|
-
body: JSON.stringify({ error: 'Unauthorized' })
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const result = permissionChecker.checkPermissions(ctx.user, permissions);
|
|
128
|
-
|
|
129
|
-
if (!result.allowed) {
|
|
130
|
-
return {
|
|
131
|
-
statusCode: 403,
|
|
132
|
-
headers: { 'Content-Type': 'application/json' },
|
|
133
|
-
body: JSON.stringify({
|
|
134
|
-
error: 'Forbidden',
|
|
135
|
-
message: 'Insufficient permissions',
|
|
136
|
-
missing: result.missing
|
|
137
|
-
})
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return next(ctx);
|
|
142
|
-
}) as Middleware<AuthContext, AuthContext>;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Role-based authorization middleware
|
|
147
|
-
*
|
|
148
|
-
* @example
|
|
149
|
-
* ```ts
|
|
150
|
-
* app.get('/admin/*',
|
|
151
|
-
* authenticate({ jwt: config }),
|
|
152
|
-
* requireRoles(['admin']),
|
|
153
|
-
* handler
|
|
154
|
-
* );
|
|
155
|
-
* ```
|
|
156
|
-
*/
|
|
157
|
-
export function requireRoles(
|
|
158
|
-
roles: string[],
|
|
159
|
-
adapter?: PermissionAdapter
|
|
160
|
-
): Middleware<AuthContext, AuthContext> {
|
|
161
|
-
const permissionChecker = adapter || new DefaultPermissionAdapter();
|
|
162
|
-
|
|
163
|
-
return (async (ctx: AuthContext, next: Next, _deps: any) => {
|
|
164
|
-
if (!ctx.user) {
|
|
165
|
-
return {
|
|
166
|
-
statusCode: 401,
|
|
167
|
-
headers: { 'Content-Type': 'application/json' },
|
|
168
|
-
body: JSON.stringify({ error: 'Unauthorized' })
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const hasRole = permissionChecker.checkRoles(ctx.user, roles);
|
|
173
|
-
|
|
174
|
-
if (!hasRole) {
|
|
175
|
-
return {
|
|
176
|
-
statusCode: 403,
|
|
177
|
-
headers: { 'Content-Type': 'application/json' },
|
|
178
|
-
body: JSON.stringify({
|
|
179
|
-
error: 'Forbidden',
|
|
180
|
-
message: 'Insufficient roles',
|
|
181
|
-
required: roles
|
|
182
|
-
})
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return next(ctx);
|
|
187
|
-
}) as Middleware<AuthContext, AuthContext>;
|
|
188
|
-
}
|
package/src/security/csrf.ts
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CSRF Protection Middleware
|
|
3
|
-
*
|
|
4
|
-
* Implements Cross-Site Request Forgery protection
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Context, Next, Middleware } from '../core/types';
|
|
8
|
-
import type { CSRFConfig } from './types';
|
|
9
|
-
import type { CSRFAdapter } from './adapter';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Default CSRF token adapter
|
|
13
|
-
* Uses double-submit cookie pattern
|
|
14
|
-
*/
|
|
15
|
-
class DefaultCSRFAdapter implements CSRFAdapter {
|
|
16
|
-
private readonly tokenLength: number;
|
|
17
|
-
|
|
18
|
-
constructor(tokenLength: number = 32) {
|
|
19
|
-
this.tokenLength = tokenLength;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
generateToken(_ctx: Context): string {
|
|
23
|
-
// Generate random token
|
|
24
|
-
const bytes = crypto.getRandomValues(new Uint8Array(this.tokenLength));
|
|
25
|
-
return Array.from(bytes)
|
|
26
|
-
.map(b => b.toString(16).padStart(2, '0'))
|
|
27
|
-
.join('');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
validateToken(ctx: Context, token: string): boolean {
|
|
31
|
-
// In double-submit cookie pattern, token in cookie should match token in header/body
|
|
32
|
-
const cookieToken = this.extractTokenFromCookie(ctx);
|
|
33
|
-
|
|
34
|
-
if (!cookieToken) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Constant-time comparison to prevent timing attacks
|
|
39
|
-
return this.constantTimeCompare(token, cookieToken);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
extractToken(ctx: Context): string | null {
|
|
43
|
-
// Try header first
|
|
44
|
-
const csrfHeader = ctx.headers['x-csrf-token'] || ctx.headers['X-CSRF-Token'];
|
|
45
|
-
if (csrfHeader) {
|
|
46
|
-
return Array.isArray(csrfHeader) ? csrfHeader[0] : csrfHeader;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const csrfTokenHeader = ctx.headers['csrf-token'] || ctx.headers['CSRF-Token'];
|
|
50
|
-
if (csrfTokenHeader) {
|
|
51
|
-
return Array.isArray(csrfTokenHeader) ? csrfTokenHeader[0] : csrfTokenHeader;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Try body
|
|
55
|
-
if (ctx.body && typeof ctx.body === 'object') {
|
|
56
|
-
return (ctx.body as any)._csrf || (ctx.body as any).csrf_token || null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private extractTokenFromCookie(ctx: Context): string | null {
|
|
63
|
-
const cookieHeaderRaw = ctx.headers['cookie'] || ctx.headers['Cookie'];
|
|
64
|
-
if (!cookieHeaderRaw) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const cookieHeader = Array.isArray(cookieHeaderRaw) ? cookieHeaderRaw[0] : cookieHeaderRaw;
|
|
69
|
-
|
|
70
|
-
const match = cookieHeader.match(/_csrf=([^;]+)/);
|
|
71
|
-
return match ? match[1] : null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private constantTimeCompare(a: string, b: string): boolean {
|
|
75
|
-
if (a.length !== b.length) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let result = 0;
|
|
80
|
-
for (let i = 0; i < a.length; i++) {
|
|
81
|
-
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return result === 0;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* CSRF protection middleware
|
|
90
|
-
*
|
|
91
|
-
* @example
|
|
92
|
-
* ```ts
|
|
93
|
-
* app.use(csrf({
|
|
94
|
-
* cookie: { sameSite: 'strict' },
|
|
95
|
-
* excludeRoutes: ['/api/webhook/*']
|
|
96
|
-
* }));
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
|
-
export function csrf(
|
|
100
|
-
config: CSRFConfig = {},
|
|
101
|
-
adapter?: CSRFAdapter
|
|
102
|
-
): Middleware {
|
|
103
|
-
const csrfAdapter = adapter || new DefaultCSRFAdapter(config.tokenLength);
|
|
104
|
-
const auto = config.auto !== false;
|
|
105
|
-
const excludeMethods = config.excludeMethods || ['GET', 'HEAD', 'OPTIONS'];
|
|
106
|
-
const excludeRoutes = config.excludeRoutes || [];
|
|
107
|
-
|
|
108
|
-
return async (ctx: Context, next: Next, _deps: any) => {
|
|
109
|
-
// Skip safe methods
|
|
110
|
-
if (excludeMethods.includes(ctx.method)) {
|
|
111
|
-
return next(ctx);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Skip excluded routes
|
|
115
|
-
for (const pattern of excludeRoutes) {
|
|
116
|
-
const regex = new RegExp('^' + pattern.replace('*', '.*') + '$');
|
|
117
|
-
if (regex.test(ctx.path)) {
|
|
118
|
-
return next(ctx);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Validate CSRF token
|
|
123
|
-
if (auto) {
|
|
124
|
-
const token = csrfAdapter.extractToken(ctx);
|
|
125
|
-
|
|
126
|
-
if (!token) {
|
|
127
|
-
return {
|
|
128
|
-
statusCode: 403,
|
|
129
|
-
headers: { 'Content-Type': 'application/json' },
|
|
130
|
-
body: JSON.stringify({
|
|
131
|
-
error: 'CSRF token missing'
|
|
132
|
-
})
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const valid = csrfAdapter.validateToken(ctx, token);
|
|
137
|
-
|
|
138
|
-
if (!valid) {
|
|
139
|
-
return {
|
|
140
|
-
statusCode: 403,
|
|
141
|
-
headers: { 'Content-Type': 'application/json' },
|
|
142
|
-
body: JSON.stringify({
|
|
143
|
-
error: 'CSRF token invalid'
|
|
144
|
-
})
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return next(ctx);
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Generate CSRF token middleware
|
|
155
|
-
* Attaches token to context and sets cookie
|
|
156
|
-
*
|
|
157
|
-
* @example
|
|
158
|
-
* ```ts
|
|
159
|
-
* app.use(generateCSRFToken());
|
|
160
|
-
*
|
|
161
|
-
* // In handler
|
|
162
|
-
* app.get('/form', async (ctx) => {
|
|
163
|
-
* const token = ctx.csrfToken;
|
|
164
|
-
* return ctx.html(`<input type="hidden" name="_csrf" value="${token}">`);
|
|
165
|
-
* });
|
|
166
|
-
* ```
|
|
167
|
-
*/
|
|
168
|
-
export function generateCSRFToken(
|
|
169
|
-
config: CSRFConfig = {},
|
|
170
|
-
adapter?: CSRFAdapter
|
|
171
|
-
): Middleware {
|
|
172
|
-
const csrfAdapter = adapter || new DefaultCSRFAdapter(config.tokenLength);
|
|
173
|
-
const cookieName = config.cookie?.name || '_csrf';
|
|
174
|
-
const cookieOptions = {
|
|
175
|
-
sameSite: config.cookie?.sameSite || 'strict',
|
|
176
|
-
secure: config.cookie?.secure !== false,
|
|
177
|
-
httpOnly: config.cookie?.httpOnly !== false
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
return async (ctx: Context, next: Next, _deps: any) => {
|
|
181
|
-
// Generate token
|
|
182
|
-
const token = csrfAdapter.generateToken(ctx);
|
|
183
|
-
|
|
184
|
-
// Attach to context
|
|
185
|
-
(ctx as any).csrfToken = token;
|
|
186
|
-
|
|
187
|
-
// Execute handler
|
|
188
|
-
const response = await next(ctx);
|
|
189
|
-
|
|
190
|
-
// Set cookie
|
|
191
|
-
const cookieValue = `${cookieName}=${token}; SameSite=${cookieOptions.sameSite}${cookieOptions.secure ? '; Secure' : ''}${cookieOptions.httpOnly ? '; HttpOnly' : ''}; Path=/`;
|
|
192
|
-
|
|
193
|
-
// Append cookie to response headers
|
|
194
|
-
if (!response.headers['Set-Cookie']) {
|
|
195
|
-
response.headers['Set-Cookie'] = cookieValue;
|
|
196
|
-
} else if (Array.isArray(response.headers['Set-Cookie'])) {
|
|
197
|
-
response.headers['Set-Cookie'].push(cookieValue);
|
|
198
|
-
} else {
|
|
199
|
-
response.headers['Set-Cookie'] = [response.headers['Set-Cookie'], cookieValue];
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return response;
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Combined CSRF middleware - generates and validates tokens
|
|
208
|
-
*/
|
|
209
|
-
export function csrfProtection(config: CSRFConfig = {}): Middleware {
|
|
210
|
-
const generateMiddleware = generateCSRFToken(config);
|
|
211
|
-
const validateMiddleware = csrf(config);
|
|
212
|
-
|
|
213
|
-
return async (ctx: Context, next: Next, deps: any) => {
|
|
214
|
-
// First generate token
|
|
215
|
-
return generateMiddleware(ctx, async (ctxWithToken: Context) => {
|
|
216
|
-
// Then validate on unsafe methods
|
|
217
|
-
return validateMiddleware(ctxWithToken, next, deps);
|
|
218
|
-
}, deps);
|
|
219
|
-
};
|
|
220
|
-
}
|
package/src/security/headers.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Security Headers Middleware
|
|
3
|
-
*
|
|
4
|
-
* Automatically applies security headers to responses
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Context, Next, Middleware } from '../core/types';
|
|
8
|
-
import type { SecurityHeadersConfig } from './types';
|
|
9
|
-
import type { SecurityHeadersAdapter } from './adapter';
|
|
10
|
-
import { DefaultSecurityHeadersAdapter } from './adapter';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create security headers middleware
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```ts
|
|
17
|
-
* app.use(securityHeaders({
|
|
18
|
-
* mode: 'strict',
|
|
19
|
-
* csp: {
|
|
20
|
-
* directives: {
|
|
21
|
-
* 'default-src': ["'self'"],
|
|
22
|
-
* 'script-src': ["'self'", "'nonce'"]
|
|
23
|
-
* }
|
|
24
|
-
* }
|
|
25
|
-
* }));
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export function securityHeaders(
|
|
29
|
-
config: SecurityHeadersConfig = {},
|
|
30
|
-
adapter?: SecurityHeadersAdapter
|
|
31
|
-
): Middleware {
|
|
32
|
-
const adapterInstance = adapter || new DefaultSecurityHeadersAdapter();
|
|
33
|
-
const nonces = new WeakMap<Context, string>();
|
|
34
|
-
|
|
35
|
-
return async (ctx: Context, next: Next, _deps: any) => {
|
|
36
|
-
// Generate nonce if CSP uses it
|
|
37
|
-
if (config.autoNonce && config.csp) {
|
|
38
|
-
const nonce = adapterInstance.generateNonce?.() || crypto.randomUUID();
|
|
39
|
-
nonces.set(ctx, nonce);
|
|
40
|
-
|
|
41
|
-
// Replace 'nonce' placeholder in CSP directives
|
|
42
|
-
if (config.csp.directives) {
|
|
43
|
-
for (const [key, values] of Object.entries(config.csp.directives)) {
|
|
44
|
-
config.csp.directives[key] = values.map(v =>
|
|
45
|
-
v === "'nonce'" ? `'nonce-${nonce}'` : v
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Attach nonce to context for use in templates
|
|
51
|
-
(ctx as any).nonce = nonce;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Generate headers
|
|
55
|
-
const headers = adapterInstance.generateHeaders(ctx, config);
|
|
56
|
-
|
|
57
|
-
// Execute handler
|
|
58
|
-
const response = await next(ctx);
|
|
59
|
-
|
|
60
|
-
// Apply headers to response
|
|
61
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
62
|
-
response.headers[name] = value;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return response;
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Strict security headers preset
|
|
71
|
-
*/
|
|
72
|
-
export function strictSecurityHeaders(customConfig: Partial<SecurityHeadersConfig> = {}): Middleware {
|
|
73
|
-
return securityHeaders({
|
|
74
|
-
mode: 'strict',
|
|
75
|
-
csp: {
|
|
76
|
-
directives: {
|
|
77
|
-
'default-src': ["'self'"],
|
|
78
|
-
'script-src': ["'self'"],
|
|
79
|
-
'style-src': ["'self'"],
|
|
80
|
-
'img-src': ["'self'", 'data:', 'https:'],
|
|
81
|
-
'font-src': ["'self'"],
|
|
82
|
-
'connect-src': ["'self'"],
|
|
83
|
-
'frame-ancestors': ["'none'"],
|
|
84
|
-
'base-uri': ["'self'"],
|
|
85
|
-
'form-action': ["'self'"]
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
...customConfig
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Moderate security headers preset
|
|
94
|
-
*/
|
|
95
|
-
export function moderateSecurityHeaders(customConfig: Partial<SecurityHeadersConfig> = {}): Middleware {
|
|
96
|
-
return securityHeaders({
|
|
97
|
-
mode: 'moderate',
|
|
98
|
-
csp: {
|
|
99
|
-
directives: {
|
|
100
|
-
'default-src': ["'self'"],
|
|
101
|
-
'script-src': ["'self'", "'unsafe-inline'"],
|
|
102
|
-
'style-src': ["'self'", "'unsafe-inline'"],
|
|
103
|
-
'img-src': ["'self'", 'data:', 'https:']
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
...customConfig
|
|
107
|
-
});
|
|
108
|
-
}
|
package/src/security/index.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Security Module Entry Point
|
|
3
|
-
*
|
|
4
|
-
* Exports all security features
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Types
|
|
8
|
-
export * from './types';
|
|
9
|
-
|
|
10
|
-
// Adapters
|
|
11
|
-
export * from './adapter';
|
|
12
|
-
|
|
13
|
-
// Security Headers
|
|
14
|
-
export {
|
|
15
|
-
securityHeaders,
|
|
16
|
-
strictSecurityHeaders,
|
|
17
|
-
moderateSecurityHeaders
|
|
18
|
-
} from './headers';
|
|
19
|
-
|
|
20
|
-
// Input Sanitization
|
|
21
|
-
export {
|
|
22
|
-
sanitizeInput,
|
|
23
|
-
strictSanitization,
|
|
24
|
-
lenientSanitization
|
|
25
|
-
} from './sanitization';
|
|
26
|
-
|
|
27
|
-
// Authentication
|
|
28
|
-
export {
|
|
29
|
-
authenticate,
|
|
30
|
-
optionalAuth,
|
|
31
|
-
requireAuth,
|
|
32
|
-
requirePermissions,
|
|
33
|
-
requireRoles
|
|
34
|
-
} from './auth/middleware';
|
|
35
|
-
|
|
36
|
-
export { JWTAuthAdapter } from './auth/jwt';
|
|
37
|
-
|
|
38
|
-
// JWT Provider (for DI)
|
|
39
|
-
export { JWTProvider, createJWTProvider } from './auth/JWTProvider';
|
|
40
|
-
export type { JWTProviderConfig, TokenPayload, VerifyResult } from './auth/JWTProvider';
|
|
41
|
-
|
|
42
|
-
// JWT Plugin
|
|
43
|
-
export { jwtPlugin } from './auth/JWTPlugin';
|
|
44
|
-
export type { JWTPluginConfig, JWTPluginExports } from './auth/JWTPlugin';
|
|
45
|
-
|
|
46
|
-
// Rate Limiting
|
|
47
|
-
export {
|
|
48
|
-
rateLimit,
|
|
49
|
-
strictRateLimit,
|
|
50
|
-
lenientRateLimit
|
|
51
|
-
} from './rate-limit/middleware';
|
|
52
|
-
|
|
53
|
-
export { MemoryRateLimiter } from './rate-limit/memory';
|
|
54
|
-
|
|
55
|
-
// CSRF Protection
|
|
56
|
-
export {
|
|
57
|
-
csrf,
|
|
58
|
-
generateCSRFToken,
|
|
59
|
-
csrfProtection
|
|
60
|
-
} from './csrf';
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-Memory Rate Limiter
|
|
3
|
-
*
|
|
4
|
-
* Simple in-memory rate limiting implementation
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { RateLimitAdapter } from '../adapter';
|
|
8
|
-
import type { RateLimitInfo } from '../types';
|
|
9
|
-
|
|
10
|
-
interface RateLimitEntry {
|
|
11
|
-
count: number;
|
|
12
|
-
resetTime: number;
|
|
13
|
-
firstRequest: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* In-memory rate limit storage
|
|
18
|
-
* Uses sliding window algorithm
|
|
19
|
-
*/
|
|
20
|
-
export class MemoryRateLimiter implements RateLimitAdapter {
|
|
21
|
-
private store = new Map<string, RateLimitEntry>();
|
|
22
|
-
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
23
|
-
|
|
24
|
-
constructor(cleanupIntervalMs: number = 60000) {
|
|
25
|
-
// Periodic cleanup of expired entries
|
|
26
|
-
this.cleanupInterval = setInterval(() => {
|
|
27
|
-
this.cleanup();
|
|
28
|
-
}, cleanupIntervalMs);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async increment(key: string, windowMs: number): Promise<{
|
|
32
|
-
count: number;
|
|
33
|
-
resetTime: number;
|
|
34
|
-
}> {
|
|
35
|
-
const now = Date.now();
|
|
36
|
-
const entry = this.store.get(key);
|
|
37
|
-
|
|
38
|
-
// No existing entry or expired
|
|
39
|
-
if (!entry || now >= entry.resetTime) {
|
|
40
|
-
const newEntry: RateLimitEntry = {
|
|
41
|
-
count: 1,
|
|
42
|
-
resetTime: now + windowMs,
|
|
43
|
-
firstRequest: now
|
|
44
|
-
};
|
|
45
|
-
this.store.set(key, newEntry);
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
count: 1,
|
|
49
|
-
resetTime: newEntry.resetTime
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Increment existing entry
|
|
54
|
-
entry.count++;
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
count: entry.count,
|
|
58
|
-
resetTime: entry.resetTime
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async get(key: string): Promise<RateLimitInfo | null> {
|
|
63
|
-
const entry = this.store.get(key);
|
|
64
|
-
|
|
65
|
-
if (!entry) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const now = Date.now();
|
|
70
|
-
|
|
71
|
-
// Expired
|
|
72
|
-
if (now >= entry.resetTime) {
|
|
73
|
-
this.store.delete(key);
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
limit: -1, // Will be set by middleware
|
|
79
|
-
remaining: -1, // Will be calculated by middleware
|
|
80
|
-
reset: Math.floor(entry.resetTime / 1000),
|
|
81
|
-
retryAfter: Math.ceil((entry.resetTime - now) / 1000)
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async reset(key: string): Promise<void> {
|
|
86
|
-
this.store.delete(key);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async cleanup(): Promise<void> {
|
|
90
|
-
const now = Date.now();
|
|
91
|
-
|
|
92
|
-
for (const [key, entry] of this.store.entries()) {
|
|
93
|
-
if (now >= entry.resetTime) {
|
|
94
|
-
this.store.delete(key);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Stop cleanup interval
|
|
101
|
-
*/
|
|
102
|
-
destroy(): void {
|
|
103
|
-
if (this.cleanupInterval) {
|
|
104
|
-
clearInterval(this.cleanupInterval);
|
|
105
|
-
this.cleanupInterval = null;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|