@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,597 +0,0 @@
|
|
|
1
|
-
# JWT Authentication
|
|
2
|
-
|
|
3
|
-
Nexus Framework menyediakan **JWT Provider** dan **JWT Plugin** untuk autentikasi berbasis JSON Web Token yang mudah digunakan, type-safe, dan terintegrasi dengan sistem DI dan Plugin.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
### Menggunakan JWT Provider (Simple)
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
import { createApp } from 'nexus';
|
|
11
|
-
import { JWTProvider } from 'nexus/security';
|
|
12
|
-
|
|
13
|
-
// 1. Create JWT Provider
|
|
14
|
-
const jwt = new JWTProvider({
|
|
15
|
-
secret: process.env.JWT_SECRET!,
|
|
16
|
-
expiresIn: '7d'
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// 2. Inject ke app via DI
|
|
20
|
-
const app = createApp().provide({ jwt });
|
|
21
|
-
|
|
22
|
-
// 3. Gunakan di route handler
|
|
23
|
-
app.post('/api/auth/login', async (ctx, { jwt }) => {
|
|
24
|
-
const { email, password } = ctx.body;
|
|
25
|
-
|
|
26
|
-
// Validasi credentials...
|
|
27
|
-
const user = await validateUser(email, password);
|
|
28
|
-
|
|
29
|
-
// Generate token
|
|
30
|
-
const token = await jwt.sign({
|
|
31
|
-
id: user.id,
|
|
32
|
-
email: user.email,
|
|
33
|
-
roles: user.roles
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return { success: true, token };
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// 4. Protected route
|
|
40
|
-
app.get('/api/profile', async (ctx, { jwt }) => {
|
|
41
|
-
const result = await jwt.verify(ctx);
|
|
42
|
-
|
|
43
|
-
if (!result.valid) {
|
|
44
|
-
return ctx.response.status(401).json({
|
|
45
|
-
error: 'Unauthorized',
|
|
46
|
-
message: result.error
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return { user: result.user };
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
app.listen(3000);
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Menggunakan JWT Plugin (Full Featured)
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
import { createApp } from 'nexus';
|
|
60
|
-
import { jwtPlugin } from 'nexus/security';
|
|
61
|
-
|
|
62
|
-
const app = createApp()
|
|
63
|
-
.plugin(jwtPlugin, {
|
|
64
|
-
secret: process.env.JWT_SECRET!,
|
|
65
|
-
expiresIn: '7d',
|
|
66
|
-
autoProtect: true,
|
|
67
|
-
publicPaths: ['/api/auth/login', '/api/auth/register', '/health']
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
await app.initialize();
|
|
71
|
-
|
|
72
|
-
// Login route (public)
|
|
73
|
-
app.post('/api/auth/login', async (ctx) => {
|
|
74
|
-
const jwt = app.getPluginExports('jwt');
|
|
75
|
-
const token = await jwt.sign({ id: user.id, email: user.email });
|
|
76
|
-
return { token };
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Protected route - user otomatis ada di ctx.user
|
|
80
|
-
app.get('/api/profile', async (ctx) => {
|
|
81
|
-
return { user: ctx.user };
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
app.listen(3000);
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## JWT Provider API
|
|
88
|
-
|
|
89
|
-
### Configuration
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
interface JWTProviderConfig {
|
|
93
|
-
secret: string; // JWT secret key (wajib)
|
|
94
|
-
expiresIn?: string | number; // Token expiry: '1h', '7d', 3600
|
|
95
|
-
issuer?: string; // Token issuer (opsional)
|
|
96
|
-
audience?: string; // Token audience (opsional)
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### Methods
|
|
101
|
-
|
|
102
|
-
#### `sign(payload, options?)`
|
|
103
|
-
|
|
104
|
-
Generate JWT token dari payload.
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
const token = await jwt.sign({
|
|
108
|
-
id: 'user_123',
|
|
109
|
-
email: 'john@example.com',
|
|
110
|
-
username: 'john',
|
|
111
|
-
roles: ['user', 'admin'],
|
|
112
|
-
permissions: ['create:posts', 'delete:posts']
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Dengan custom options
|
|
116
|
-
const token = await jwt.sign(
|
|
117
|
-
{ id: user.id },
|
|
118
|
-
{ expiresIn: '30d' } // Override default expiry
|
|
119
|
-
);
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
#### `verify(ctx, options?)`
|
|
123
|
-
|
|
124
|
-
Verify token dari request context (header/cookie/query).
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
const result = await jwt.verify(ctx);
|
|
128
|
-
|
|
129
|
-
if (result.valid) {
|
|
130
|
-
console.log(result.user); // { id, email, roles, ... }
|
|
131
|
-
} else {
|
|
132
|
-
console.log(result.error); // 'Token expired', 'Invalid signature', etc.
|
|
133
|
-
console.log(result.expired); // true jika token expired
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Dengan cookie:
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
const result = await jwt.verify(ctx, { cookieName: 'auth_token' });
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
#### `verifyToken(token)`
|
|
144
|
-
|
|
145
|
-
Verify token string langsung.
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
const result = await jwt.verifyToken('eyJhbGciOiJIUzI1NiIs...');
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
#### `decode(token)`
|
|
152
|
-
|
|
153
|
-
Decode token tanpa verifikasi (untuk debugging).
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
const payload = jwt.decode(token);
|
|
157
|
-
console.log(payload);
|
|
158
|
-
// { id: 'user_123', email: '...', iat: 1234567890, exp: 1234571490 }
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
#### `refresh(token, options?)`
|
|
162
|
-
|
|
163
|
-
Generate token baru dengan data yang sama tapi expiry baru.
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
const newToken = await jwt.refresh(oldToken);
|
|
167
|
-
|
|
168
|
-
if (newToken) {
|
|
169
|
-
// Token berhasil di-refresh
|
|
170
|
-
} else {
|
|
171
|
-
// Token invalid, user harus login ulang
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
#### `middleware(options?)`
|
|
176
|
-
|
|
177
|
-
Get middleware untuk protect route.
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
// Dengan functional routes
|
|
181
|
-
app.get('/protected', jwt.middleware(), async (ctx) => {
|
|
182
|
-
return { user: ctx.user };
|
|
183
|
-
});
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
#### `hasRole(user, role)`
|
|
187
|
-
|
|
188
|
-
Check apakah user punya role tertentu.
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
if (jwt.hasRole(user, 'admin')) {
|
|
192
|
-
// User adalah admin
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Multiple roles (OR)
|
|
196
|
-
if (jwt.hasRole(user, ['admin', 'moderator'])) {
|
|
197
|
-
// User adalah admin ATAU moderator
|
|
198
|
-
}
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
#### `hasPermission(user, permission)`
|
|
202
|
-
|
|
203
|
-
Check apakah user punya permission tertentu.
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
if (jwt.hasPermission(user, 'delete:posts')) {
|
|
207
|
-
// User bisa delete posts
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Multiple permissions (OR)
|
|
211
|
-
if (jwt.hasPermission(user, ['create:posts', 'edit:posts'])) {
|
|
212
|
-
// User bisa create ATAU edit posts
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## JWT Plugin API
|
|
217
|
-
|
|
218
|
-
### Plugin Configuration
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
interface JWTPluginConfig {
|
|
222
|
-
secret: string; // JWT secret (wajib)
|
|
223
|
-
expiresIn?: string | number; // Token expiry
|
|
224
|
-
issuer?: string; // Token issuer
|
|
225
|
-
audience?: string; // Token audience
|
|
226
|
-
autoProtect?: boolean; // Auto-protect semua route (default: false)
|
|
227
|
-
publicPaths?: string[]; // Path yang tidak perlu auth
|
|
228
|
-
cookieName?: string; // Nama cookie untuk token
|
|
229
|
-
onUnauthorized?: (ctx, error) => any; // Custom unauthorized handler
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Plugin Exports
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
interface JWTPluginExports {
|
|
237
|
-
provider: JWTProvider;
|
|
238
|
-
sign: (payload) => Promise<string>;
|
|
239
|
-
verify: (ctx) => Promise<VerifyResult>;
|
|
240
|
-
verifyToken: (token) => Promise<VerifyResult>;
|
|
241
|
-
decode: (token) => any;
|
|
242
|
-
refresh: (token) => Promise<string | null>;
|
|
243
|
-
hasRole: (user, role) => boolean;
|
|
244
|
-
hasPermission: (user, permission) => boolean;
|
|
245
|
-
middleware: () => MiddlewareFunction;
|
|
246
|
-
}
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
### Akses Plugin Exports
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
// Via app
|
|
253
|
-
const jwt = app.getPluginExports<JWTPluginExports>('jwt');
|
|
254
|
-
const token = await jwt.sign({ id: user.id });
|
|
255
|
-
|
|
256
|
-
// Via context (jika plugin sudah decorate)
|
|
257
|
-
app.get('/test', async (ctx) => {
|
|
258
|
-
const result = await ctx.jwt.verify(ctx);
|
|
259
|
-
return { user: result.user };
|
|
260
|
-
});
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
## Penggunaan dengan Class-Based Routes
|
|
264
|
-
|
|
265
|
-
```typescript
|
|
266
|
-
import { Route, Context } from 'nexus';
|
|
267
|
-
import { JWTProvider } from 'nexus/security';
|
|
268
|
-
|
|
269
|
-
// Buat JWT provider global
|
|
270
|
-
export const jwt = new JWTProvider({
|
|
271
|
-
secret: process.env.JWT_SECRET!,
|
|
272
|
-
expiresIn: '1h'
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// Login Route
|
|
276
|
-
export class LoginRoute extends Route {
|
|
277
|
-
pathName = '/api/auth/login';
|
|
278
|
-
method = 'POST' as const;
|
|
279
|
-
|
|
280
|
-
schema() {
|
|
281
|
-
return {
|
|
282
|
-
body: z.object({
|
|
283
|
-
email: z.string().email(),
|
|
284
|
-
password: z.string().min(6)
|
|
285
|
-
})
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async handler(ctx: Context) {
|
|
290
|
-
const { email, password } = ctx.body;
|
|
291
|
-
|
|
292
|
-
// Validasi credentials...
|
|
293
|
-
const user = await this.validateUser(email, password);
|
|
294
|
-
|
|
295
|
-
if (!user) {
|
|
296
|
-
return ctx.response.status(401).json({
|
|
297
|
-
error: 'Invalid credentials'
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const token = await jwt.sign({
|
|
302
|
-
id: user.id,
|
|
303
|
-
email: user.email,
|
|
304
|
-
roles: user.roles
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
return { success: true, token };
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
private async validateUser(email: string, password: string) {
|
|
311
|
-
// Implement your validation logic
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Protected Route
|
|
316
|
-
export class ProfileRoute extends Route {
|
|
317
|
-
pathName = '/api/user/profile';
|
|
318
|
-
method = 'GET' as const;
|
|
319
|
-
|
|
320
|
-
middlewares() {
|
|
321
|
-
return [jwt.middleware()];
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
async handler(ctx: Context) {
|
|
325
|
-
const user = (ctx as any).user;
|
|
326
|
-
return { user };
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Role-Protected Route
|
|
331
|
-
export class AdminRoute extends Route {
|
|
332
|
-
pathName = '/api/admin/dashboard';
|
|
333
|
-
method = 'GET' as const;
|
|
334
|
-
|
|
335
|
-
middlewares() {
|
|
336
|
-
return [
|
|
337
|
-
jwt.middleware(),
|
|
338
|
-
// Custom role check middleware
|
|
339
|
-
async (ctx: Context, next: any) => {
|
|
340
|
-
const user = (ctx as any).user;
|
|
341
|
-
if (!jwt.hasRole(user, 'admin')) {
|
|
342
|
-
return ctx.response.status(403).json({
|
|
343
|
-
error: 'Forbidden',
|
|
344
|
-
message: 'Admin access required'
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
return next(ctx);
|
|
348
|
-
}
|
|
349
|
-
];
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async handler(ctx: Context) {
|
|
353
|
-
return { message: 'Welcome to admin dashboard!' };
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
## Token Expiry Format
|
|
359
|
-
|
|
360
|
-
```typescript
|
|
361
|
-
// Detik
|
|
362
|
-
expiresIn: 3600 // 1 jam
|
|
363
|
-
|
|
364
|
-
// String format
|
|
365
|
-
expiresIn: '30s' // 30 detik
|
|
366
|
-
expiresIn: '15m' // 15 menit
|
|
367
|
-
expiresIn: '1h' // 1 jam
|
|
368
|
-
expiresIn: '7d' // 7 hari
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
## Token Extraction
|
|
372
|
-
|
|
373
|
-
JWT Provider otomatis mengekstrak token dari:
|
|
374
|
-
|
|
375
|
-
1. **Authorization Header** (prioritas pertama)
|
|
376
|
-
```
|
|
377
|
-
Authorization: Bearer <token>
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
2. **Cookie** (jika `cookieName` di-set)
|
|
381
|
-
```
|
|
382
|
-
Cookie: auth_token=<token>
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
3. **Query Parameter** (untuk WebSocket atau special cases)
|
|
386
|
-
```
|
|
387
|
-
GET /api/profile?token=<token>
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
## Auto-Protect Mode (Plugin)
|
|
391
|
-
|
|
392
|
-
Dengan `autoProtect: true`, semua route otomatis dilindungi kecuali yang ada di `publicPaths`:
|
|
393
|
-
|
|
394
|
-
```typescript
|
|
395
|
-
app.plugin(jwtPlugin, {
|
|
396
|
-
secret: process.env.JWT_SECRET!,
|
|
397
|
-
autoProtect: true,
|
|
398
|
-
publicPaths: [
|
|
399
|
-
'/health',
|
|
400
|
-
'/api/auth/login',
|
|
401
|
-
'/api/auth/register',
|
|
402
|
-
'/api/public/*' // Wildcard support
|
|
403
|
-
]
|
|
404
|
-
});
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
## Custom Unauthorized Handler
|
|
408
|
-
|
|
409
|
-
```typescript
|
|
410
|
-
app.plugin(jwtPlugin, {
|
|
411
|
-
secret: process.env.JWT_SECRET!,
|
|
412
|
-
autoProtect: true,
|
|
413
|
-
onUnauthorized: (ctx, error) => {
|
|
414
|
-
// Custom response
|
|
415
|
-
return ctx.response.status(401).json({
|
|
416
|
-
success: false,
|
|
417
|
-
error: 'Authentication Required',
|
|
418
|
-
message: error,
|
|
419
|
-
loginUrl: '/api/auth/login'
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
## Verify Result Type
|
|
426
|
-
|
|
427
|
-
```typescript
|
|
428
|
-
interface VerifyResult {
|
|
429
|
-
valid: boolean; // true jika token valid
|
|
430
|
-
user: User | null; // User data dari token
|
|
431
|
-
error?: string; // Error message jika invalid
|
|
432
|
-
expired?: boolean; // true jika token expired
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
interface User {
|
|
436
|
-
id: string | number;
|
|
437
|
-
email?: string;
|
|
438
|
-
username?: string;
|
|
439
|
-
roles?: string[];
|
|
440
|
-
permissions?: string[];
|
|
441
|
-
}
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
## Best Practices
|
|
445
|
-
|
|
446
|
-
### 1. Gunakan Environment Variable untuk Secret
|
|
447
|
-
|
|
448
|
-
```typescript
|
|
449
|
-
// ❌ Jangan hardcode
|
|
450
|
-
const jwt = new JWTProvider({ secret: 'my-secret' });
|
|
451
|
-
|
|
452
|
-
// ✅ Gunakan env variable
|
|
453
|
-
const jwt = new JWTProvider({
|
|
454
|
-
secret: process.env.JWT_SECRET!
|
|
455
|
-
});
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
### 2. Secret Minimal 32 Karakter
|
|
459
|
-
|
|
460
|
-
```bash
|
|
461
|
-
# Generate random secret
|
|
462
|
-
openssl rand -base64 32
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
### 3. Gunakan Expiry yang Sesuai
|
|
466
|
-
|
|
467
|
-
```typescript
|
|
468
|
-
// Access token: pendek (1 jam)
|
|
469
|
-
const accessToken = await jwt.sign(user, { expiresIn: '1h' });
|
|
470
|
-
|
|
471
|
-
// Refresh token: panjang (7 hari)
|
|
472
|
-
const refreshToken = await jwt.sign(
|
|
473
|
-
{ id: user.id, type: 'refresh' },
|
|
474
|
-
{ expiresIn: '7d' }
|
|
475
|
-
);
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
### 4. Handle Token Expired
|
|
479
|
-
|
|
480
|
-
```typescript
|
|
481
|
-
app.get('/api/data', async (ctx, { jwt }) => {
|
|
482
|
-
const result = await jwt.verify(ctx);
|
|
483
|
-
|
|
484
|
-
if (!result.valid) {
|
|
485
|
-
if (result.expired) {
|
|
486
|
-
return ctx.response.status(401).json({
|
|
487
|
-
error: 'Token Expired',
|
|
488
|
-
code: 'TOKEN_EXPIRED' // Client bisa refresh token
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
return ctx.response.status(401).json({
|
|
492
|
-
error: 'Invalid Token',
|
|
493
|
-
code: 'INVALID_TOKEN' // Client harus login ulang
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
return { data: 'secret data' };
|
|
498
|
-
});
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
### 5. Implement Refresh Token Flow
|
|
502
|
-
|
|
503
|
-
```typescript
|
|
504
|
-
// Endpoint untuk refresh token
|
|
505
|
-
app.post('/api/auth/refresh', async (ctx, { jwt }) => {
|
|
506
|
-
const { refreshToken } = ctx.body;
|
|
507
|
-
|
|
508
|
-
const result = await jwt.verifyToken(refreshToken);
|
|
509
|
-
|
|
510
|
-
if (!result.valid || result.user?.type !== 'refresh') {
|
|
511
|
-
return ctx.response.status(401).json({
|
|
512
|
-
error: 'Invalid refresh token'
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Generate new access token
|
|
517
|
-
const accessToken = await jwt.sign({
|
|
518
|
-
id: result.user.id,
|
|
519
|
-
email: result.user.email,
|
|
520
|
-
roles: result.user.roles
|
|
521
|
-
}, { expiresIn: '1h' });
|
|
522
|
-
|
|
523
|
-
return { accessToken };
|
|
524
|
-
});
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
## Testing
|
|
528
|
-
|
|
529
|
-
```typescript
|
|
530
|
-
import { TestClient } from 'nexus/testing';
|
|
531
|
-
|
|
532
|
-
describe('JWT Authentication', () => {
|
|
533
|
-
const jwt = new JWTProvider({ secret: 'test-secret-min-32-characters!!' });
|
|
534
|
-
|
|
535
|
-
test('should generate valid token', async () => {
|
|
536
|
-
const token = await jwt.sign({ id: 1, email: 'test@test.com' });
|
|
537
|
-
expect(token).toBeDefined();
|
|
538
|
-
expect(token.split('.')).toHaveLength(3);
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
test('should verify valid token', async () => {
|
|
542
|
-
const token = await jwt.sign({ id: 1, email: 'test@test.com' });
|
|
543
|
-
const result = await jwt.verifyToken(token);
|
|
544
|
-
|
|
545
|
-
expect(result.valid).toBe(true);
|
|
546
|
-
expect(result.user?.id).toBe(1);
|
|
547
|
-
expect(result.user?.email).toBe('test@test.com');
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
test('should reject expired token', async () => {
|
|
551
|
-
const token = await jwt.sign(
|
|
552
|
-
{ id: 1 },
|
|
553
|
-
{ expiresIn: '1s' }
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
// Wait for token to expire
|
|
557
|
-
await new Promise(r => setTimeout(r, 1100));
|
|
558
|
-
|
|
559
|
-
const result = await jwt.verifyToken(token);
|
|
560
|
-
expect(result.valid).toBe(false);
|
|
561
|
-
expect(result.expired).toBe(true);
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
test('protected route should require token', async () => {
|
|
565
|
-
const app = createApp().provide({ jwt });
|
|
566
|
-
|
|
567
|
-
app.get('/protected', async (ctx, { jwt }) => {
|
|
568
|
-
const result = await jwt.verify(ctx);
|
|
569
|
-
if (!result.valid) {
|
|
570
|
-
return ctx.response.status(401).json({ error: 'Unauthorized' });
|
|
571
|
-
}
|
|
572
|
-
return { user: result.user };
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
const client = new TestClient(app);
|
|
576
|
-
|
|
577
|
-
// Without token
|
|
578
|
-
const res1 = await client.get('/protected');
|
|
579
|
-
expect(res1.status).toBe(401);
|
|
580
|
-
|
|
581
|
-
// With valid token
|
|
582
|
-
const token = await jwt.sign({ id: 1, email: 'test@test.com' });
|
|
583
|
-
const res2 = await client.get('/protected', {
|
|
584
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
585
|
-
});
|
|
586
|
-
expect(res2.status).toBe(200);
|
|
587
|
-
expect(res2.body.user.id).toBe(1);
|
|
588
|
-
});
|
|
589
|
-
});
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
## See Also
|
|
593
|
-
|
|
594
|
-
- [Dependency Injection](./27-dependency-injection.md) - DI system untuk inject JWT Provider
|
|
595
|
-
- [Plugin System](./30-plugin-system.md) - Plugin system untuk JWT Plugin
|
|
596
|
-
- [Class-Based Routing](./19-class-based-routing.md) - Penggunaan dengan class routes
|
|
597
|
-
- [Middleware](./04-middleware.md) - Custom middleware
|