@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.
Files changed (205) hide show
  1. package/package.json +1 -1
  2. package/BENCHMARK_REPORT.md +0 -343
  3. package/documentation/01-getting-started.md +0 -240
  4. package/documentation/02-context.md +0 -335
  5. package/documentation/03-routing.md +0 -397
  6. package/documentation/04-middleware.md +0 -483
  7. package/documentation/05-validation.md +0 -514
  8. package/documentation/06-error-handling.md +0 -465
  9. package/documentation/07-performance.md +0 -364
  10. package/documentation/08-adapters.md +0 -470
  11. package/documentation/09-api-reference.md +0 -548
  12. package/documentation/10-examples.md +0 -582
  13. package/documentation/11-deployment.md +0 -477
  14. package/documentation/12-sentry.md +0 -620
  15. package/documentation/13-sentry-data-storage.md +0 -996
  16. package/documentation/14-sentry-data-reference.md +0 -457
  17. package/documentation/15-sentry-summary.md +0 -409
  18. package/documentation/16-alerts-system.md +0 -745
  19. package/documentation/17-alert-adapters.md +0 -696
  20. package/documentation/18-alerts-implementation-summary.md +0 -385
  21. package/documentation/19-class-based-routing.md +0 -840
  22. package/documentation/20-websocket-realtime.md +0 -813
  23. package/documentation/21-cache-system.md +0 -510
  24. package/documentation/22-job-queue.md +0 -772
  25. package/documentation/23-sentry-plugin.md +0 -551
  26. package/documentation/24-testing-utilities.md +0 -1287
  27. package/documentation/25-api-versioning.md +0 -533
  28. package/documentation/26-context-store.md +0 -607
  29. package/documentation/27-dependency-injection.md +0 -329
  30. package/documentation/28-lifecycle-hooks.md +0 -521
  31. package/documentation/29-package-structure.md +0 -196
  32. package/documentation/30-plugin-system.md +0 -414
  33. package/documentation/31-jwt-authentication.md +0 -597
  34. package/documentation/32-cli.md +0 -268
  35. package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
  36. package/documentation/ALERTS-INDEX.md +0 -330
  37. package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
  38. package/documentation/README.md +0 -178
  39. package/documentation/index.html +0 -34
  40. package/modern_framework_paper.md +0 -1870
  41. package/public/css/style.css +0 -87
  42. package/public/index.html +0 -34
  43. package/public/js/app.js +0 -27
  44. package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
  45. package/src/advanced/cache/MultiTierCache.ts +0 -194
  46. package/src/advanced/cache/RedisCacheStore.ts +0 -341
  47. package/src/advanced/cache/index.ts +0 -5
  48. package/src/advanced/cache/types.ts +0 -40
  49. package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
  50. package/src/advanced/graphql/index.ts +0 -22
  51. package/src/advanced/graphql/server.ts +0 -252
  52. package/src/advanced/graphql/types.ts +0 -42
  53. package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
  54. package/src/advanced/jobs/JobQueue.ts +0 -556
  55. package/src/advanced/jobs/RedisQueueStore.ts +0 -367
  56. package/src/advanced/jobs/index.ts +0 -5
  57. package/src/advanced/jobs/types.ts +0 -70
  58. package/src/advanced/observability/APMManager.ts +0 -163
  59. package/src/advanced/observability/AlertManager.ts +0 -109
  60. package/src/advanced/observability/MetricRegistry.ts +0 -151
  61. package/src/advanced/observability/ObservabilityCenter.ts +0 -304
  62. package/src/advanced/observability/StructuredLogger.ts +0 -154
  63. package/src/advanced/observability/TracingManager.ts +0 -117
  64. package/src/advanced/observability/adapters.ts +0 -304
  65. package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
  66. package/src/advanced/observability/index.ts +0 -11
  67. package/src/advanced/observability/types.ts +0 -174
  68. package/src/advanced/playground/extractPathParams.ts +0 -6
  69. package/src/advanced/playground/generateFieldExample.ts +0 -31
  70. package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1956
  71. package/src/advanced/playground/generateSummary.ts +0 -19
  72. package/src/advanced/playground/getTagFromPath.ts +0 -9
  73. package/src/advanced/playground/index.ts +0 -8
  74. package/src/advanced/playground/playground.ts +0 -250
  75. package/src/advanced/playground/types.ts +0 -49
  76. package/src/advanced/playground/zodToExample.ts +0 -16
  77. package/src/advanced/playground/zodToParams.ts +0 -15
  78. package/src/advanced/postman/buildAuth.ts +0 -31
  79. package/src/advanced/postman/buildBody.ts +0 -15
  80. package/src/advanced/postman/buildQueryParams.ts +0 -27
  81. package/src/advanced/postman/buildRequestItem.ts +0 -36
  82. package/src/advanced/postman/buildResponses.ts +0 -11
  83. package/src/advanced/postman/buildUrl.ts +0 -33
  84. package/src/advanced/postman/capitalize.ts +0 -4
  85. package/src/advanced/postman/generateCollection.ts +0 -59
  86. package/src/advanced/postman/generateEnvironment.ts +0 -34
  87. package/src/advanced/postman/generateExampleFromZod.ts +0 -21
  88. package/src/advanced/postman/generateFieldExample.ts +0 -45
  89. package/src/advanced/postman/generateName.ts +0 -20
  90. package/src/advanced/postman/generateUUID.ts +0 -11
  91. package/src/advanced/postman/getTagFromPath.ts +0 -10
  92. package/src/advanced/postman/index.ts +0 -28
  93. package/src/advanced/postman/postman.ts +0 -156
  94. package/src/advanced/postman/slugify.ts +0 -7
  95. package/src/advanced/postman/types.ts +0 -140
  96. package/src/advanced/realtime/index.ts +0 -18
  97. package/src/advanced/realtime/websocket.ts +0 -231
  98. package/src/advanced/sentry/index.ts +0 -1236
  99. package/src/advanced/sentry/types.ts +0 -355
  100. package/src/advanced/static/generateDirectoryListing.ts +0 -47
  101. package/src/advanced/static/generateETag.ts +0 -7
  102. package/src/advanced/static/getMimeType.ts +0 -9
  103. package/src/advanced/static/index.ts +0 -32
  104. package/src/advanced/static/isSafePath.ts +0 -13
  105. package/src/advanced/static/publicDir.ts +0 -21
  106. package/src/advanced/static/serveStatic.ts +0 -225
  107. package/src/advanced/static/spa.ts +0 -24
  108. package/src/advanced/static/types.ts +0 -159
  109. package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
  110. package/src/advanced/swagger/buildOperation.ts +0 -61
  111. package/src/advanced/swagger/buildParameters.ts +0 -61
  112. package/src/advanced/swagger/buildRequestBody.ts +0 -21
  113. package/src/advanced/swagger/buildResponses.ts +0 -54
  114. package/src/advanced/swagger/capitalize.ts +0 -5
  115. package/src/advanced/swagger/convertPath.ts +0 -9
  116. package/src/advanced/swagger/createSwagger.ts +0 -12
  117. package/src/advanced/swagger/generateOperationId.ts +0 -21
  118. package/src/advanced/swagger/generateSpec.ts +0 -105
  119. package/src/advanced/swagger/generateSummary.ts +0 -24
  120. package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
  121. package/src/advanced/swagger/generateThemeCss.ts +0 -53
  122. package/src/advanced/swagger/index.ts +0 -25
  123. package/src/advanced/swagger/swagger.ts +0 -237
  124. package/src/advanced/swagger/types.ts +0 -206
  125. package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
  126. package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
  127. package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
  128. package/src/advanced/testing/factory.ts +0 -509
  129. package/src/advanced/testing/harness.ts +0 -612
  130. package/src/advanced/testing/index.ts +0 -430
  131. package/src/advanced/testing/load-test.ts +0 -618
  132. package/src/advanced/testing/mock-server.ts +0 -498
  133. package/src/advanced/testing/mock.ts +0 -670
  134. package/src/cli/bin.ts +0 -9
  135. package/src/cli/cli.ts +0 -158
  136. package/src/cli/commands/add.ts +0 -178
  137. package/src/cli/commands/build.ts +0 -73
  138. package/src/cli/commands/create.ts +0 -166
  139. package/src/cli/commands/dev.ts +0 -85
  140. package/src/cli/commands/generate.ts +0 -99
  141. package/src/cli/commands/help.ts +0 -95
  142. package/src/cli/commands/init.ts +0 -91
  143. package/src/cli/commands/version.ts +0 -38
  144. package/src/cli/index.ts +0 -6
  145. package/src/cli/templates/generators.ts +0 -359
  146. package/src/cli/templates/index.ts +0 -680
  147. package/src/cli/utils/exec.ts +0 -52
  148. package/src/cli/utils/file-system.ts +0 -78
  149. package/src/cli/utils/logger.ts +0 -111
  150. package/src/core/adapter.ts +0 -88
  151. package/src/core/application.ts +0 -1453
  152. package/src/core/context-pool.ts +0 -79
  153. package/src/core/context.ts +0 -856
  154. package/src/core/index.ts +0 -94
  155. package/src/core/middleware.ts +0 -272
  156. package/src/core/performance/buffer-pool.ts +0 -108
  157. package/src/core/performance/middleware-optimizer.ts +0 -162
  158. package/src/core/plugin/PluginManager.ts +0 -435
  159. package/src/core/plugin/builder.ts +0 -358
  160. package/src/core/plugin/index.ts +0 -50
  161. package/src/core/plugin/types.ts +0 -214
  162. package/src/core/router/file-router.ts +0 -623
  163. package/src/core/router/index.ts +0 -260
  164. package/src/core/router/radix-tree.ts +0 -242
  165. package/src/core/serializer.ts +0 -397
  166. package/src/core/store/index.ts +0 -30
  167. package/src/core/store/registry.ts +0 -178
  168. package/src/core/store/request-store.ts +0 -240
  169. package/src/core/store/types.ts +0 -233
  170. package/src/core/types.ts +0 -616
  171. package/src/database/adapter.ts +0 -35
  172. package/src/database/adapters/index.ts +0 -1
  173. package/src/database/adapters/mysql.ts +0 -669
  174. package/src/database/database.ts +0 -70
  175. package/src/database/dialect.ts +0 -388
  176. package/src/database/index.ts +0 -12
  177. package/src/database/migrations.ts +0 -86
  178. package/src/database/optimizer.ts +0 -125
  179. package/src/database/query-builder.ts +0 -404
  180. package/src/database/realtime.ts +0 -53
  181. package/src/database/schema.ts +0 -71
  182. package/src/database/transactions.ts +0 -56
  183. package/src/database/types.ts +0 -87
  184. package/src/deployment/cluster.ts +0 -471
  185. package/src/deployment/config.ts +0 -454
  186. package/src/deployment/docker.ts +0 -599
  187. package/src/deployment/graceful-shutdown.ts +0 -373
  188. package/src/deployment/index.ts +0 -56
  189. package/src/index.ts +0 -281
  190. package/src/security/adapter.ts +0 -318
  191. package/src/security/auth/JWTPlugin.ts +0 -234
  192. package/src/security/auth/JWTProvider.ts +0 -316
  193. package/src/security/auth/adapter.ts +0 -12
  194. package/src/security/auth/jwt.ts +0 -234
  195. package/src/security/auth/middleware.ts +0 -188
  196. package/src/security/csrf.ts +0 -220
  197. package/src/security/headers.ts +0 -108
  198. package/src/security/index.ts +0 -60
  199. package/src/security/rate-limit/adapter.ts +0 -7
  200. package/src/security/rate-limit/memory.ts +0 -108
  201. package/src/security/rate-limit/middleware.ts +0 -181
  202. package/src/security/sanitization.ts +0 -75
  203. package/src/security/types.ts +0 -240
  204. package/src/security/utils.ts +0 -52
  205. 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