@engjts/nexus 0.1.7 → 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/dist/advanced/playground/generatePlaygroundHTML.d.ts.map +1 -1
- package/dist/advanced/playground/generatePlaygroundHTML.js +107 -0
- package/dist/advanced/playground/generatePlaygroundHTML.js.map +1 -1
- package/dist/advanced/playground/playground.d.ts +19 -0
- package/dist/advanced/playground/playground.d.ts.map +1 -1
- package/dist/advanced/playground/playground.js +70 -0
- package/dist/advanced/playground/playground.js.map +1 -1
- package/dist/advanced/playground/types.d.ts +20 -0
- package/dist/advanced/playground/types.d.ts.map +1 -1
- package/dist/core/application.d.ts +14 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/application.js +173 -71
- package/dist/core/application.js.map +1 -1
- package/dist/core/context-pool.d.ts +2 -13
- package/dist/core/context-pool.d.ts.map +1 -1
- package/dist/core/context-pool.js +7 -45
- package/dist/core/context-pool.js.map +1 -1
- package/dist/core/context.d.ts +108 -5
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +449 -53
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/middleware.d.ts +6 -0
- package/dist/core/middleware.d.ts.map +1 -1
- package/dist/core/middleware.js +83 -84
- package/dist/core/middleware.js.map +1 -1
- package/dist/core/performance/fast-json.d.ts +149 -0
- package/dist/core/performance/fast-json.d.ts.map +1 -0
- package/dist/core/performance/fast-json.js +473 -0
- package/dist/core/performance/fast-json.js.map +1 -0
- package/dist/core/router/file-router.d.ts +20 -7
- package/dist/core/router/file-router.d.ts.map +1 -1
- package/dist/core/router/file-router.js +41 -13
- package/dist/core/router/file-router.js.map +1 -1
- package/dist/core/router/index.d.ts +6 -0
- package/dist/core/router/index.d.ts.map +1 -1
- package/dist/core/router/index.js +33 -6
- package/dist/core/router/index.js.map +1 -1
- package/dist/core/router/radix-tree.d.ts +4 -1
- package/dist/core/router/radix-tree.d.ts.map +1 -1
- package/dist/core/router/radix-tree.js +7 -3
- package/dist/core/router/radix-tree.js.map +1 -1
- package/dist/core/serializer.d.ts +251 -0
- package/dist/core/serializer.d.ts.map +1 -0
- package/dist/core/serializer.js +290 -0
- package/dist/core/serializer.js.map +1 -0
- package/dist/core/types.d.ts +39 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- 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 -1849
- 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 -170
- package/src/advanced/playground/types.ts +0 -20
- 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 -1335
- package/src/core/context-pool.ts +0 -127
- package/src/core/context.ts +0 -412
- package/src/core/index.ts +0 -80
- package/src/core/middleware.ts +0 -262
- 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 -594
- package/src/core/router/index.ts +0 -227
- package/src/core/router/radix-tree.ts +0 -226
- 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 -574
- 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 -264
- 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,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JWT Provider
|
|
3
|
-
*
|
|
4
|
-
* Provider untuk JWT authentication yang bisa di-inject via DI system.
|
|
5
|
-
* Mudah digunakan di route handler tanpa perlu setup middleware manual.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Context } from '../../core';
|
|
9
|
-
import type { User, JWTConfig } from '../types';
|
|
10
|
-
|
|
11
|
-
export interface JWTProviderConfig {
|
|
12
|
-
secret: string;
|
|
13
|
-
expiresIn?: string | number; // '1h', '7d', 3600, etc.
|
|
14
|
-
issuer?: string;
|
|
15
|
-
audience?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface TokenPayload {
|
|
19
|
-
id: string | number;
|
|
20
|
-
email?: string;
|
|
21
|
-
username?: string;
|
|
22
|
-
roles?: string[];
|
|
23
|
-
permissions?: string[];
|
|
24
|
-
[key: string]: any;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface VerifyResult {
|
|
28
|
-
valid: boolean;
|
|
29
|
-
user: User | null;
|
|
30
|
-
error?: string;
|
|
31
|
-
expired?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* JWT Provider Class
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```typescript
|
|
39
|
-
* // Setup di app
|
|
40
|
-
* const jwt = new JWTProvider({
|
|
41
|
-
* secret: process.env.JWT_SECRET!,
|
|
42
|
-
* expiresIn: '1h'
|
|
43
|
-
* });
|
|
44
|
-
*
|
|
45
|
-
* const app = createApp().provide({ jwt });
|
|
46
|
-
*
|
|
47
|
-
* // Gunakan di route
|
|
48
|
-
* app.post('/login', async (ctx, { jwt }) => {
|
|
49
|
-
* const token = await jwt.sign({ id: user.id, email: user.email });
|
|
50
|
-
* return { token };
|
|
51
|
-
* });
|
|
52
|
-
*
|
|
53
|
-
* app.get('/profile', async (ctx, { jwt }) => {
|
|
54
|
-
* const result = await jwt.verify(ctx);
|
|
55
|
-
* if (!result.valid) return ctx.response.status(401).json({ error: 'Unauthorized' });
|
|
56
|
-
* return { user: result.user };
|
|
57
|
-
* });
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
export class JWTProvider {
|
|
61
|
-
private config: JWTProviderConfig;
|
|
62
|
-
|
|
63
|
-
constructor(config: JWTProviderConfig) {
|
|
64
|
-
if (!config.secret) {
|
|
65
|
-
throw new Error('JWT secret is required');
|
|
66
|
-
}
|
|
67
|
-
this.config = {
|
|
68
|
-
expiresIn: '1h',
|
|
69
|
-
...config
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Generate JWT token dari user/payload
|
|
75
|
-
*/
|
|
76
|
-
async sign(payload: TokenPayload, options?: Partial<JWTProviderConfig>): Promise<string> {
|
|
77
|
-
const config = { ...this.config, ...options };
|
|
78
|
-
|
|
79
|
-
const header = { alg: 'HS256', typ: 'JWT' };
|
|
80
|
-
|
|
81
|
-
// Calculate expiry
|
|
82
|
-
let exp: number;
|
|
83
|
-
const expiresIn = config.expiresIn || '1h';
|
|
84
|
-
if (typeof expiresIn === 'number') {
|
|
85
|
-
exp = Math.floor(Date.now() / 1000) + expiresIn;
|
|
86
|
-
} else {
|
|
87
|
-
exp = Math.floor(Date.now() / 1000) + this.parseExpiry(expiresIn);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const tokenPayload = {
|
|
91
|
-
...payload,
|
|
92
|
-
iat: Math.floor(Date.now() / 1000),
|
|
93
|
-
exp,
|
|
94
|
-
...(config.issuer && { iss: config.issuer }),
|
|
95
|
-
...(config.audience && { aud: config.audience })
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
|
|
99
|
-
const encodedPayload = this.base64UrlEncode(JSON.stringify(tokenPayload));
|
|
100
|
-
const signature = await this.createSignature(`${encodedHeader}.${encodedPayload}`, config.secret);
|
|
101
|
-
|
|
102
|
-
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Verify token dari Authorization header atau cookie
|
|
107
|
-
*/
|
|
108
|
-
async verify(ctx: Context, options?: { cookieName?: string }): Promise<VerifyResult> {
|
|
109
|
-
const token = this.extractToken(ctx, options?.cookieName);
|
|
110
|
-
|
|
111
|
-
if (!token) {
|
|
112
|
-
return { valid: false, user: null, error: 'No token provided' };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return this.verifyToken(token);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Verify token string langsung
|
|
120
|
-
*/
|
|
121
|
-
async verifyToken(token: string): Promise<VerifyResult> {
|
|
122
|
-
try {
|
|
123
|
-
const parts = token.split('.');
|
|
124
|
-
if (parts.length !== 3) {
|
|
125
|
-
return { valid: false, user: null, error: 'Invalid token format' };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const [encodedHeader, encodedPayload, signature] = parts;
|
|
129
|
-
|
|
130
|
-
// Verify signature
|
|
131
|
-
const expectedSignature = await this.createSignature(
|
|
132
|
-
`${encodedHeader}.${encodedPayload}`,
|
|
133
|
-
this.config.secret
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
if (signature !== expectedSignature) {
|
|
137
|
-
return { valid: false, user: null, error: 'Invalid signature' };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Decode payload
|
|
141
|
-
const payload = JSON.parse(this.base64UrlDecode(encodedPayload));
|
|
142
|
-
|
|
143
|
-
// Check expiry
|
|
144
|
-
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
|
|
145
|
-
return { valid: false, user: null, error: 'Token expired', expired: true };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Check issuer
|
|
149
|
-
if (this.config.issuer && payload.iss !== this.config.issuer) {
|
|
150
|
-
return { valid: false, user: null, error: 'Invalid issuer' };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Check audience
|
|
154
|
-
if (this.config.audience && payload.aud !== this.config.audience) {
|
|
155
|
-
return { valid: false, user: null, error: 'Invalid audience' };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const user: User = {
|
|
159
|
-
id: payload.id,
|
|
160
|
-
email: payload.email,
|
|
161
|
-
username: payload.username,
|
|
162
|
-
roles: payload.roles || [],
|
|
163
|
-
permissions: payload.permissions || []
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
return { valid: true, user };
|
|
167
|
-
|
|
168
|
-
} catch (error) {
|
|
169
|
-
return { valid: false, user: null, error: 'Token verification failed' };
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Decode token tanpa verify (untuk debugging)
|
|
175
|
-
*/
|
|
176
|
-
decode(token: string): TokenPayload | null {
|
|
177
|
-
try {
|
|
178
|
-
const parts = token.split('.');
|
|
179
|
-
if (parts.length !== 3) return null;
|
|
180
|
-
|
|
181
|
-
const payload = JSON.parse(this.base64UrlDecode(parts[1]));
|
|
182
|
-
return payload;
|
|
183
|
-
} catch {
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Refresh token (generate token baru dengan expiry baru)
|
|
190
|
-
*/
|
|
191
|
-
async refresh(token: string, options?: Partial<JWTProviderConfig>): Promise<string | null> {
|
|
192
|
-
const result = await this.verifyToken(token);
|
|
193
|
-
|
|
194
|
-
if (!result.valid || !result.user) {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Generate new token with same user data
|
|
199
|
-
return this.sign({
|
|
200
|
-
id: result.user.id,
|
|
201
|
-
email: result.user.email,
|
|
202
|
-
username: result.user.username,
|
|
203
|
-
roles: result.user.roles,
|
|
204
|
-
permissions: result.user.permissions
|
|
205
|
-
}, options);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Create middleware untuk protect route
|
|
210
|
-
*/
|
|
211
|
-
middleware(options?: { cookieName?: string }) {
|
|
212
|
-
return async (ctx: Context, next: (ctx: Context) => Promise<any>) => {
|
|
213
|
-
const result = await this.verify(ctx, options);
|
|
214
|
-
|
|
215
|
-
if (!result.valid) {
|
|
216
|
-
return ctx.response.status(401).json({
|
|
217
|
-
error: 'Unauthorized',
|
|
218
|
-
message: result.error
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Attach user ke context
|
|
223
|
-
(ctx as any).user = result.user;
|
|
224
|
-
|
|
225
|
-
return next(ctx);
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Check apakah user punya role tertentu
|
|
231
|
-
*/
|
|
232
|
-
hasRole(user: User, role: string | string[]): boolean {
|
|
233
|
-
const roles = Array.isArray(role) ? role : [role];
|
|
234
|
-
return roles.some(r => user.roles?.includes(r));
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Check apakah user punya permission tertentu
|
|
239
|
-
*/
|
|
240
|
-
hasPermission(user: User, permission: string | string[]): boolean {
|
|
241
|
-
const permissions = Array.isArray(permission) ? permission : [permission];
|
|
242
|
-
return permissions.some(p => user.permissions?.includes(p));
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// === Private Methods ===
|
|
246
|
-
|
|
247
|
-
private extractToken(ctx: Context, cookieName?: string): string | null {
|
|
248
|
-
// 1. Check Authorization header
|
|
249
|
-
const authHeader = ctx.headers?.authorization || ctx.headers?.Authorization;
|
|
250
|
-
if (authHeader) {
|
|
251
|
-
const header = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
252
|
-
if (header.startsWith('Bearer ')) {
|
|
253
|
-
return header.slice(7);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// 2. Check cookie
|
|
258
|
-
if (cookieName) {
|
|
259
|
-
const cookieHeader = ctx.headers?.cookie;
|
|
260
|
-
if (cookieHeader) {
|
|
261
|
-
const cookies = Array.isArray(cookieHeader) ? cookieHeader[0] : cookieHeader;
|
|
262
|
-
const match = cookies.match(new RegExp(`${cookieName}=([^;]+)`));
|
|
263
|
-
if (match) return match[1];
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// 3. Check query parameter (for websocket atau special cases)
|
|
268
|
-
if (ctx.query?.token) {
|
|
269
|
-
return ctx.query.token as string;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private parseExpiry(expiry: string): number {
|
|
276
|
-
const match = expiry.match(/^(\d+)([smhd])$/);
|
|
277
|
-
if (!match) return 3600; // default 1h
|
|
278
|
-
|
|
279
|
-
const value = parseInt(match[1]);
|
|
280
|
-
const unit = match[2];
|
|
281
|
-
|
|
282
|
-
switch (unit) {
|
|
283
|
-
case 's': return value;
|
|
284
|
-
case 'm': return value * 60;
|
|
285
|
-
case 'h': return value * 3600;
|
|
286
|
-
case 'd': return value * 86400;
|
|
287
|
-
default: return 3600;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
private base64UrlEncode(str: string): string {
|
|
292
|
-
const base64 = Buffer.from(str).toString('base64');
|
|
293
|
-
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
private base64UrlDecode(str: string): string {
|
|
297
|
-
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
298
|
-
while (base64.length % 4) base64 += '=';
|
|
299
|
-
return Buffer.from(base64, 'base64').toString('utf-8');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
private async createSignature(data: string, secret: string): Promise<string> {
|
|
303
|
-
const crypto = await import('crypto');
|
|
304
|
-
const hmac = crypto.createHmac('sha256', secret);
|
|
305
|
-
hmac.update(data);
|
|
306
|
-
const signature = hmac.digest('base64');
|
|
307
|
-
return signature.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Factory function untuk create JWTProvider
|
|
313
|
-
*/
|
|
314
|
-
export function createJWTProvider(config: JWTProviderConfig): JWTProvider {
|
|
315
|
-
return new JWTProvider(config);
|
|
316
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Adapter Exports
|
|
3
|
-
*
|
|
4
|
-
* Re-exports authentication adapters
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export * from '../adapter';
|
|
8
|
-
export { JWTAuthAdapter } from './jwt';
|
|
9
|
-
|
|
10
|
-
// OAuth and Session adapters will be implemented in future
|
|
11
|
-
// export { OAuthAdapter } from './oauth';
|
|
12
|
-
// export { SessionAdapter } from './session';
|
package/src/security/auth/jwt.ts
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JWT Authentication Adapter
|
|
3
|
-
*
|
|
4
|
-
* Implements JWT-based authentication
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Context } from '../../core/types';
|
|
8
|
-
import type { User, JWTConfig } from '../types';
|
|
9
|
-
import type { AuthAdapter } from '../adapter';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Simple JWT implementation (in production, use a library like jsonwebtoken)
|
|
13
|
-
* This is a basic implementation for demonstration
|
|
14
|
-
*/
|
|
15
|
-
export class JWTAuthAdapter implements AuthAdapter<JWTConfig> {
|
|
16
|
-
/**
|
|
17
|
-
* Verify JWT token and extract user
|
|
18
|
-
*/
|
|
19
|
-
async verify(ctx: Context, config: JWTConfig): Promise<User | null> {
|
|
20
|
-
const token = this.extractToken(ctx, config);
|
|
21
|
-
|
|
22
|
-
if (!token) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const payload = this.decodeToken(token, config.secret);
|
|
28
|
-
|
|
29
|
-
// Check expiration
|
|
30
|
-
if (payload.exp && payload.exp < Date.now() / 1000) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Extract user from payload
|
|
35
|
-
// Destructure known fields and spread the rest to preserve custom fields like 'type'
|
|
36
|
-
const { sub, id, email, username, roles, permissions, user: nestedUser, iat, exp, ...rest } = payload;
|
|
37
|
-
|
|
38
|
-
const user: User = {
|
|
39
|
-
id: sub || id,
|
|
40
|
-
email,
|
|
41
|
-
username,
|
|
42
|
-
roles,
|
|
43
|
-
permissions,
|
|
44
|
-
...nestedUser, // Spread nested user object if exists
|
|
45
|
-
...rest // Spread remaining custom fields (e.g., type)
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return user;
|
|
49
|
-
} catch (error) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Generate JWT token for user
|
|
56
|
-
*/
|
|
57
|
-
async generateToken(user: User, config: JWTConfig): Promise<string> {
|
|
58
|
-
const payload: any = {
|
|
59
|
-
sub: user.id,
|
|
60
|
-
email: user.email,
|
|
61
|
-
username: user.username,
|
|
62
|
-
roles: user.roles,
|
|
63
|
-
permissions: user.permissions,
|
|
64
|
-
iat: Math.floor(Date.now() / 1000)
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Add expiration
|
|
68
|
-
if (config.expiresIn) {
|
|
69
|
-
const expiresIn = typeof config.expiresIn === 'string'
|
|
70
|
-
? this.parseExpiration(config.expiresIn)
|
|
71
|
-
: config.expiresIn;
|
|
72
|
-
payload.exp = payload.iat + expiresIn;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return this.encodeToken(payload, config.secret);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Refresh JWT token (placeholder for future implementation)
|
|
80
|
-
*/
|
|
81
|
-
async refreshToken(token: string, config: JWTConfig): Promise<string | null> {
|
|
82
|
-
if (!config.refresh?.enabled) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const payload = this.decodeToken(token, config.secret);
|
|
88
|
-
|
|
89
|
-
// Re-generate with new exp
|
|
90
|
-
const newPayload = {
|
|
91
|
-
...payload,
|
|
92
|
-
iat: Math.floor(Date.now() / 1000)
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
if (config.refresh.expiresIn) {
|
|
96
|
-
const expiresIn = typeof config.refresh.expiresIn === 'string'
|
|
97
|
-
? this.parseExpiration(config.refresh.expiresIn)
|
|
98
|
-
: config.refresh.expiresIn;
|
|
99
|
-
newPayload.exp = newPayload.iat + expiresIn;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return this.encodeToken(newPayload, config.secret);
|
|
103
|
-
} catch (error) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Extract token from request
|
|
110
|
-
*/
|
|
111
|
-
private extractToken(ctx: Context, config: JWTConfig): string | null {
|
|
112
|
-
// Use custom extractor if provided
|
|
113
|
-
if (config.getToken) {
|
|
114
|
-
return config.getToken(ctx);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Try Authorization header
|
|
118
|
-
const authHeader = ctx.headers['authorization'] || ctx.headers['Authorization'];
|
|
119
|
-
if (authHeader) {
|
|
120
|
-
const headerValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
121
|
-
if (headerValue?.startsWith('Bearer ')) {
|
|
122
|
-
return headerValue.substring(7);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Try cookie
|
|
127
|
-
const cookieHeader = ctx.headers['cookie'] || ctx.headers['Cookie'];
|
|
128
|
-
if (cookieHeader) {
|
|
129
|
-
const cookieValue = Array.isArray(cookieHeader) ? cookieHeader[0] : cookieHeader;
|
|
130
|
-
if (cookieValue) {
|
|
131
|
-
const match = cookieValue.match(/token=([^;]+)/);
|
|
132
|
-
if (match) {
|
|
133
|
-
return match[1];
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Encode JWT token (simplified HMAC-SHA256)
|
|
143
|
-
*/
|
|
144
|
-
private encodeToken(payload: any, secret: string): string {
|
|
145
|
-
const header = { alg: 'HS256', typ: 'JWT' };
|
|
146
|
-
|
|
147
|
-
const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
|
|
148
|
-
const encodedPayload = this.base64UrlEncode(JSON.stringify(payload));
|
|
149
|
-
|
|
150
|
-
const signature = this.sign(`${encodedHeader}.${encodedPayload}`, secret);
|
|
151
|
-
|
|
152
|
-
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Decode and verify JWT token
|
|
157
|
-
*/
|
|
158
|
-
private decodeToken(token: string, secret: string): any {
|
|
159
|
-
const parts = token.split('.');
|
|
160
|
-
|
|
161
|
-
if (parts.length !== 3) {
|
|
162
|
-
throw new Error('Invalid token format');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const [encodedHeader, encodedPayload, signature] = parts;
|
|
166
|
-
|
|
167
|
-
// Verify signature
|
|
168
|
-
const expectedSignature = this.sign(`${encodedHeader}.${encodedPayload}`, secret);
|
|
169
|
-
if (signature !== expectedSignature) {
|
|
170
|
-
throw new Error('Invalid signature');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Decode payload
|
|
174
|
-
const payload = JSON.parse(this.base64UrlDecode(encodedPayload));
|
|
175
|
-
|
|
176
|
-
return payload;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Base64 URL encode
|
|
181
|
-
*/
|
|
182
|
-
private base64UrlEncode(str: string): string {
|
|
183
|
-
const base64 = Buffer.from(str).toString('base64');
|
|
184
|
-
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Base64 URL decode
|
|
189
|
-
*/
|
|
190
|
-
private base64UrlDecode(str: string): string {
|
|
191
|
-
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
192
|
-
|
|
193
|
-
// Add padding
|
|
194
|
-
while (base64.length % 4) {
|
|
195
|
-
base64 += '=';
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return Buffer.from(base64, 'base64').toString('utf-8');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Sign data with HMAC-SHA256
|
|
203
|
-
*/
|
|
204
|
-
private sign(data: string, secret: string): string {
|
|
205
|
-
const crypto = require('crypto');
|
|
206
|
-
const hmac = crypto.createHmac('sha256', secret);
|
|
207
|
-
hmac.update(data);
|
|
208
|
-
const signature = hmac.digest('base64');
|
|
209
|
-
return signature.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Parse expiration string to seconds
|
|
214
|
-
*/
|
|
215
|
-
private parseExpiration(exp: string): number {
|
|
216
|
-
const match = exp.match(/^(\d+)([smhd])$/);
|
|
217
|
-
|
|
218
|
-
if (!match) {
|
|
219
|
-
throw new Error('Invalid expiration format');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const value = parseInt(match[1]);
|
|
223
|
-
const unit = match[2];
|
|
224
|
-
|
|
225
|
-
const multipliers: Record<string, number> = {
|
|
226
|
-
s: 1,
|
|
227
|
-
m: 60,
|
|
228
|
-
h: 3600,
|
|
229
|
-
d: 86400
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
return value * multipliers[unit];
|
|
233
|
-
}
|
|
234
|
-
}
|