@flink-app/jwt-auth-plugin 1.0.0 → 2.0.0-alpha.100
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/CHANGELOG.md +424 -0
- package/dist/FlinkJwtAuthPlugin.d.ts +36 -9
- package/dist/FlinkJwtAuthPlugin.js +64 -13
- package/dist/PermissionValidator.js +18 -1
- package/package.json +5 -2
- package/readme.md +314 -0
- package/spec/FlinkJwtAuthPlugin.spec.ts +162 -0
- package/spec/PermissionValidator.spec.ts +92 -0
- package/src/FlinkJwtAuthPlugin.ts +115 -15
- package/src/PermissionValidator.ts +18 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,429 @@
|
|
|
1
1
|
# @flink-app/jwt-auth-plugin
|
|
2
2
|
|
|
3
|
+
## 2.0.0-alpha.100
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [6e8166a]
|
|
8
|
+
- @flink-app/flink@2.0.0-alpha.100
|
|
9
|
+
|
|
10
|
+
## 2.0.0-alpha.99
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies [ed9cd2c]
|
|
15
|
+
- @flink-app/flink@2.0.0-alpha.99
|
|
16
|
+
|
|
17
|
+
## 2.0.0-alpha.98
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- 2f4a132: feat: expose verified token on the request
|
|
22
|
+
|
|
23
|
+
The JWT auth plugin now populates `req.token` (the decoded, signature-verified
|
|
24
|
+
payload) and `req.rawToken` (the original token string) after successful
|
|
25
|
+
authentication. Handlers can read token claims such as `jti` directly instead of
|
|
26
|
+
re-extracting and re-decoding the bearer token with an unverified decode.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// Before
|
|
30
|
+
const authHeader = req.headers.authorization;
|
|
31
|
+
const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : undefined;
|
|
32
|
+
const decoded = bearer ? (jsonwebtoken.decode(bearer) as { jti?: string }) : null;
|
|
33
|
+
const sessionId = decoded?.jti ?? legacySessionId(req.user._id);
|
|
34
|
+
|
|
35
|
+
// After
|
|
36
|
+
const { jti } = (req.token as { jti?: string }) ?? {};
|
|
37
|
+
const sessionId = jti ?? legacySessionId(req.user._id);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`token` / `rawToken` are added as optional fields on the core `FlinkRequest`
|
|
41
|
+
type, populated by auth plugins that have a token concept.
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- Updated dependencies [2f4a132]
|
|
46
|
+
- @flink-app/flink@2.0.0-alpha.98
|
|
47
|
+
|
|
48
|
+
## 2.0.0-alpha.97
|
|
49
|
+
|
|
50
|
+
### Minor Changes
|
|
51
|
+
|
|
52
|
+
- eed6bc1: Pass host application context to auth plugins.
|
|
53
|
+
|
|
54
|
+
`FlinkAuthPlugin.authenticateRequest` now receives the host app's context as an optional third argument. Flink core supplies it on every authenticated request, so auth plugins can access repos/services without the caller wiring up a lazy accessor or maintaining a module-level mutable reference.
|
|
55
|
+
|
|
56
|
+
For `@flink-app/jwt-auth-plugin`, this means `getUser` receives the context directly:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
jwtAuthPlugin({
|
|
60
|
+
getUser: async (tokenData, req, ctx) => {
|
|
61
|
+
const user = await ctx?.repos.userRepo.getById(tokenData._id);
|
|
62
|
+
return user ? { username: user.username, _id: user._id } : null;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The `getContext` option previously added in alpha.96 is no longer needed and has been removed — remove it from your `jwtAuthPlugin({ … })` call. The third `ctx` parameter on `getUser` is optional, so callbacks that ignore it are unaffected.
|
|
68
|
+
|
|
69
|
+
### Patch Changes
|
|
70
|
+
|
|
71
|
+
- Updated dependencies [eed6bc1]
|
|
72
|
+
- @flink-app/flink@2.0.0-alpha.97
|
|
73
|
+
|
|
74
|
+
## 2.0.0-alpha.96
|
|
75
|
+
|
|
76
|
+
### Patch Changes
|
|
77
|
+
|
|
78
|
+
- e431237: Add `iat` (issued-at) claim to generated JWTs.
|
|
79
|
+
|
|
80
|
+
`iat` is a standard JWT claim that records when the token was issued (Unix seconds). Adding it enables consumers to implement stateless force-logout / token-revocation patterns by storing a per-user "tokens-valid-from" timestamp and rejecting any token whose `iat` is older.
|
|
81
|
+
|
|
82
|
+
Backwards compatible — the claim is informational and does not affect verification of existing tokens. If a caller's payload already contains an `iat` field it takes precedence (useful for tests).
|
|
83
|
+
|
|
84
|
+
- Updated dependencies
|
|
85
|
+
- @flink-app/flink@2.0.0-alpha.96
|
|
86
|
+
|
|
87
|
+
## 2.0.0-alpha.95
|
|
88
|
+
|
|
89
|
+
### Patch Changes
|
|
90
|
+
|
|
91
|
+
- @flink-app/flink@2.0.0-alpha.95
|
|
92
|
+
|
|
93
|
+
## 2.0.0-alpha.94
|
|
94
|
+
|
|
95
|
+
### Patch Changes
|
|
96
|
+
|
|
97
|
+
- @flink-app/flink@2.0.0-alpha.94
|
|
98
|
+
|
|
99
|
+
## 2.0.0-alpha.93
|
|
100
|
+
|
|
101
|
+
### Patch Changes
|
|
102
|
+
|
|
103
|
+
- @flink-app/flink@2.0.0-alpha.93
|
|
104
|
+
|
|
105
|
+
## 2.0.0-alpha.92
|
|
106
|
+
|
|
107
|
+
### Patch Changes
|
|
108
|
+
|
|
109
|
+
- Updated dependencies [8d23d56]
|
|
110
|
+
- @flink-app/flink@2.0.0-alpha.92
|
|
111
|
+
|
|
112
|
+
## 2.0.0-alpha.91
|
|
113
|
+
|
|
114
|
+
### Patch Changes
|
|
115
|
+
|
|
116
|
+
- @flink-app/flink@2.0.0-alpha.91
|
|
117
|
+
|
|
118
|
+
## 2.0.0-alpha.90
|
|
119
|
+
|
|
120
|
+
### Patch Changes
|
|
121
|
+
|
|
122
|
+
- Updated dependencies [0d84b5f]
|
|
123
|
+
- @flink-app/flink@2.0.0-alpha.90
|
|
124
|
+
|
|
125
|
+
## 2.0.0-alpha.89
|
|
126
|
+
|
|
127
|
+
### Patch Changes
|
|
128
|
+
|
|
129
|
+
- @flink-app/flink@2.0.0-alpha.89
|
|
130
|
+
|
|
131
|
+
## 2.0.0-alpha.88
|
|
132
|
+
|
|
133
|
+
### Patch Changes
|
|
134
|
+
|
|
135
|
+
- @flink-app/flink@2.0.0-alpha.88
|
|
136
|
+
|
|
137
|
+
## 2.0.0-alpha.87
|
|
138
|
+
|
|
139
|
+
### Patch Changes
|
|
140
|
+
|
|
141
|
+
- Updated dependencies
|
|
142
|
+
- @flink-app/flink@2.0.0-alpha.87
|
|
143
|
+
|
|
144
|
+
## 2.0.0-alpha.86
|
|
145
|
+
|
|
146
|
+
### Patch Changes
|
|
147
|
+
|
|
148
|
+
- @flink-app/flink@2.0.0-alpha.86
|
|
149
|
+
|
|
150
|
+
## 2.0.0-alpha.85
|
|
151
|
+
|
|
152
|
+
### Minor Changes
|
|
153
|
+
|
|
154
|
+
- 179c085: Add support for trailing-segment wildcard scopes in `rolePermissions`. A role scope ending in `.*` (e.g. `"license.subscription.*"`) now matches any required route permission that starts with that prefix including the trailing dot — `"license.subscription.get"` and `"license.subscription.cancel"` match, while `"license.subscription"` and `"license.subscriptions.get"` do not. Wildcards are segment-scoped to prevent accidental privilege escalation, and only trailing `.*` is special. Existing behavior (route `"*"`, role `"*"`, exact-match scopes, empty permission arrays) is unchanged.
|
|
155
|
+
|
|
156
|
+
### Patch Changes
|
|
157
|
+
|
|
158
|
+
- @flink-app/flink@2.0.0-alpha.85
|
|
159
|
+
|
|
160
|
+
## 2.0.0-alpha.84
|
|
161
|
+
|
|
162
|
+
### Patch Changes
|
|
163
|
+
|
|
164
|
+
- @flink-app/flink@2.0.0-alpha.84
|
|
165
|
+
|
|
166
|
+
## 2.0.0-alpha.83
|
|
167
|
+
|
|
168
|
+
### Patch Changes
|
|
169
|
+
|
|
170
|
+
- Updated dependencies
|
|
171
|
+
- @flink-app/flink@2.0.0-alpha.83
|
|
172
|
+
|
|
173
|
+
## 2.0.0-alpha.82
|
|
174
|
+
|
|
175
|
+
### Patch Changes
|
|
176
|
+
|
|
177
|
+
- @flink-app/flink@2.0.0-alpha.82
|
|
178
|
+
|
|
179
|
+
## 2.0.0-alpha.81
|
|
180
|
+
|
|
181
|
+
### Patch Changes
|
|
182
|
+
|
|
183
|
+
- @flink-app/flink@2.0.0-alpha.81
|
|
184
|
+
|
|
185
|
+
## 2.0.0-alpha.80
|
|
186
|
+
|
|
187
|
+
### Patch Changes
|
|
188
|
+
|
|
189
|
+
- @flink-app/flink@2.0.0-alpha.80
|
|
190
|
+
|
|
191
|
+
## 2.0.0-alpha.79
|
|
192
|
+
|
|
193
|
+
### Patch Changes
|
|
194
|
+
|
|
195
|
+
- @flink-app/flink@2.0.0-alpha.79
|
|
196
|
+
|
|
197
|
+
## 2.0.0-alpha.78
|
|
198
|
+
|
|
199
|
+
### Patch Changes
|
|
200
|
+
|
|
201
|
+
- @flink-app/flink@2.0.0-alpha.78
|
|
202
|
+
|
|
203
|
+
## 2.0.0-alpha.77
|
|
204
|
+
|
|
205
|
+
### Patch Changes
|
|
206
|
+
|
|
207
|
+
- @flink-app/flink@2.0.0-alpha.77
|
|
208
|
+
|
|
209
|
+
## 2.0.0-alpha.76
|
|
210
|
+
|
|
211
|
+
### Minor Changes
|
|
212
|
+
|
|
213
|
+
- Add resolveTokenTTL callback to jwt-auth-plugin for dynamic token TTL based on role. Fix renames in email-plugin and inbound-email-plugin. Fix tsconfig in generic-auth-plugin.
|
|
214
|
+
|
|
215
|
+
### Patch Changes
|
|
216
|
+
|
|
217
|
+
- @flink-app/flink@2.0.0-alpha.76
|
|
218
|
+
|
|
219
|
+
## 2.0.0-alpha.75
|
|
220
|
+
|
|
221
|
+
### Patch Changes
|
|
222
|
+
|
|
223
|
+
- @flink-app/flink@2.0.0-alpha.75
|
|
224
|
+
|
|
225
|
+
## 2.0.0-alpha.74
|
|
226
|
+
|
|
227
|
+
### Patch Changes
|
|
228
|
+
|
|
229
|
+
- Updated dependencies
|
|
230
|
+
- Updated dependencies
|
|
231
|
+
- @flink-app/flink@2.0.0-alpha.74
|
|
232
|
+
|
|
233
|
+
## 2.0.0-alpha.73
|
|
234
|
+
|
|
235
|
+
### Patch Changes
|
|
236
|
+
|
|
237
|
+
- Updated dependencies
|
|
238
|
+
- @flink-app/flink@2.0.0-alpha.73
|
|
239
|
+
|
|
240
|
+
## 2.0.0-alpha.72
|
|
241
|
+
|
|
242
|
+
### Patch Changes
|
|
243
|
+
|
|
244
|
+
- Updated dependencies
|
|
245
|
+
- @flink-app/flink@2.0.0-alpha.72
|
|
246
|
+
|
|
247
|
+
## 2.0.0-alpha.71
|
|
248
|
+
|
|
249
|
+
### Patch Changes
|
|
250
|
+
|
|
251
|
+
- @flink-app/flink@2.0.0-alpha.71
|
|
252
|
+
|
|
253
|
+
## 2.0.0-alpha.70
|
|
254
|
+
|
|
255
|
+
### Patch Changes
|
|
256
|
+
|
|
257
|
+
- @flink-app/flink@2.0.0-alpha.70
|
|
258
|
+
|
|
259
|
+
## 2.0.0-alpha.69
|
|
260
|
+
|
|
261
|
+
### Patch Changes
|
|
262
|
+
|
|
263
|
+
- @flink-app/flink@2.0.0-alpha.69
|
|
264
|
+
|
|
265
|
+
## 2.0.0-alpha.68
|
|
266
|
+
|
|
267
|
+
### Patch Changes
|
|
268
|
+
|
|
269
|
+
- @flink-app/flink@2.0.0-alpha.68
|
|
270
|
+
|
|
271
|
+
## 2.0.0-alpha.67
|
|
272
|
+
|
|
273
|
+
### Patch Changes
|
|
274
|
+
|
|
275
|
+
- Updated dependencies [418cb54]
|
|
276
|
+
- Updated dependencies [70d574c]
|
|
277
|
+
- @flink-app/flink@2.0.0-alpha.67
|
|
278
|
+
|
|
279
|
+
## 2.0.0-alpha.66
|
|
280
|
+
|
|
281
|
+
### Patch Changes
|
|
282
|
+
|
|
283
|
+
- Updated dependencies [5593d26]
|
|
284
|
+
- @flink-app/flink@2.0.0-alpha.66
|
|
285
|
+
|
|
286
|
+
## 2.0.0-alpha.65
|
|
287
|
+
|
|
288
|
+
### Patch Changes
|
|
289
|
+
|
|
290
|
+
- @flink-app/flink@2.0.0-alpha.65
|
|
291
|
+
|
|
292
|
+
## 2.0.0-alpha.64
|
|
293
|
+
|
|
294
|
+
### Patch Changes
|
|
295
|
+
|
|
296
|
+
- Updated dependencies [95b99ee]
|
|
297
|
+
- @flink-app/flink@2.0.0-alpha.64
|
|
298
|
+
|
|
299
|
+
## 2.0.0-alpha.63
|
|
300
|
+
|
|
301
|
+
### Patch Changes
|
|
302
|
+
|
|
303
|
+
- Updated dependencies [810df2c]
|
|
304
|
+
- Updated dependencies [8dd0752]
|
|
305
|
+
- @flink-app/flink@2.0.0-alpha.63
|
|
306
|
+
|
|
307
|
+
## 2.0.0-alpha.62
|
|
308
|
+
|
|
309
|
+
### Patch Changes
|
|
310
|
+
|
|
311
|
+
- Updated dependencies
|
|
312
|
+
- @flink-app/flink@2.0.0-alpha.62
|
|
313
|
+
|
|
314
|
+
## 2.0.0-alpha.61
|
|
315
|
+
|
|
316
|
+
### Patch Changes
|
|
317
|
+
|
|
318
|
+
- @flink-app/flink@2.0.0-alpha.61
|
|
319
|
+
|
|
320
|
+
## 2.0.0-alpha.60
|
|
321
|
+
|
|
322
|
+
### Patch Changes
|
|
323
|
+
|
|
324
|
+
- @flink-app/flink@2.0.0-alpha.60
|
|
325
|
+
|
|
326
|
+
## 2.0.0-alpha.59
|
|
327
|
+
|
|
328
|
+
### Patch Changes
|
|
329
|
+
|
|
330
|
+
- Updated dependencies [dbc2119]
|
|
331
|
+
- @flink-app/flink@2.0.0-alpha.59
|
|
332
|
+
|
|
333
|
+
## 2.0.0-alpha.58
|
|
334
|
+
|
|
335
|
+
### Patch Changes
|
|
336
|
+
|
|
337
|
+
- Updated dependencies
|
|
338
|
+
- @flink-app/flink@2.0.0-alpha.58
|
|
339
|
+
|
|
340
|
+
## 2.0.0-alpha.57
|
|
341
|
+
|
|
342
|
+
### Patch Changes
|
|
343
|
+
|
|
344
|
+
- Updated dependencies [ef7f495]
|
|
345
|
+
- Updated dependencies [2d53aa4]
|
|
346
|
+
- Updated dependencies [b3cc5d1]
|
|
347
|
+
- Updated dependencies [d377fac]
|
|
348
|
+
- Updated dependencies [4ad44c0]
|
|
349
|
+
- Updated dependencies [90f80c7]
|
|
350
|
+
- Updated dependencies [f0887e7]
|
|
351
|
+
- Updated dependencies [68c46d3]
|
|
352
|
+
- Updated dependencies [4ad44c0]
|
|
353
|
+
- Updated dependencies
|
|
354
|
+
- @flink-app/flink@2.0.0-alpha.57
|
|
355
|
+
|
|
356
|
+
## 2.0.0-alpha.56
|
|
357
|
+
|
|
358
|
+
### Patch Changes
|
|
359
|
+
|
|
360
|
+
- Zod 3.x compat
|
|
361
|
+
- Updated dependencies
|
|
362
|
+
- Updated dependencies [0203256]
|
|
363
|
+
- @flink-app/flink@2.0.0-alpha.56
|
|
364
|
+
|
|
365
|
+
## 2.0.0-alpha.55
|
|
366
|
+
|
|
367
|
+
### Patch Changes
|
|
368
|
+
|
|
369
|
+
- Updated dependencies [3267206]
|
|
370
|
+
- Updated dependencies [497b7c2]
|
|
371
|
+
- Updated dependencies [3267206]
|
|
372
|
+
- @flink-app/flink@2.0.0-alpha.55
|
|
373
|
+
|
|
374
|
+
## 2.0.0-alpha.53
|
|
375
|
+
|
|
376
|
+
### Patch Changes
|
|
377
|
+
|
|
378
|
+
- Updated dependencies [669c806]
|
|
379
|
+
- @flink-app/flink@2.0.0-alpha.53
|
|
380
|
+
|
|
381
|
+
## 2.0.0-alpha.52
|
|
382
|
+
|
|
383
|
+
### Patch Changes
|
|
384
|
+
|
|
385
|
+
- Updated dependencies [fe9f8f2]
|
|
386
|
+
- @flink-app/flink@2.0.0-alpha.52
|
|
387
|
+
|
|
388
|
+
## 2.0.0-alpha.51
|
|
389
|
+
|
|
390
|
+
### Patch Changes
|
|
391
|
+
|
|
392
|
+
- fix: add peerDependencies to all plugins and adapters
|
|
393
|
+
|
|
394
|
+
**Breaking Change Prevention:**
|
|
395
|
+
|
|
396
|
+
- Added "@flink-app/flink": ">=2.0.0-alpha.0" as peerDependency to all plugins
|
|
397
|
+
- Fixed oidc-plugin peerDependencies from "workspace:\*" to proper version ranges
|
|
398
|
+
- Fixed oauth-plugin to include @flink-app/flink peerDependency
|
|
399
|
+
|
|
400
|
+
**JWT Auth Plugin Interface Fix:**
|
|
401
|
+
|
|
402
|
+
- Removed redundant createToken declaration from JwtAuthPlugin interface
|
|
403
|
+
- The method is already inherited from FlinkAuthPlugin, this fixes TypeScript type checking issues
|
|
404
|
+
|
|
405
|
+
**Why this matters:**
|
|
406
|
+
|
|
407
|
+
- Ensures only one version of @flink-app/flink exists in node_modules
|
|
408
|
+
- Prevents type conflicts when TypeScript resolves interfaces across packages
|
|
409
|
+
- Follows standard plugin architecture best practices (same pattern as React, Webpack, etc.)
|
|
410
|
+
- Package managers now warn if incompatible versions are installed
|
|
411
|
+
- @flink-app/flink@2.0.0-alpha.51
|
|
412
|
+
|
|
413
|
+
## 2.0.0-alpha.50
|
|
414
|
+
|
|
415
|
+
## 2.0.0-alpha.49
|
|
416
|
+
|
|
417
|
+
### Patch Changes
|
|
418
|
+
|
|
419
|
+
- Typescript include fix
|
|
420
|
+
|
|
421
|
+
## 2.0.0-alpha.48
|
|
422
|
+
|
|
423
|
+
### Minor Changes
|
|
424
|
+
|
|
425
|
+
- AI features
|
|
426
|
+
|
|
3
427
|
## 1.0.0
|
|
4
428
|
|
|
5
429
|
### Minor Changes
|
|
@@ -20,10 +20,28 @@ export type TokenExtractor = (req: FlinkRequest) => string | null | undefined;
|
|
|
20
20
|
* @returns true if user has required permissions, false otherwise
|
|
21
21
|
*/
|
|
22
22
|
export type PermissionChecker = (user: FlinkAuthUser, routePermissions: string[]) => Promise<boolean> | boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Custom token TTL resolver callback.
|
|
25
|
+
*
|
|
26
|
+
* Called during token creation to determine expiration dynamically based on roles.
|
|
27
|
+
*
|
|
28
|
+
* Return values:
|
|
29
|
+
* - `number`: TTL in milliseconds to use for this token
|
|
30
|
+
* - `undefined`: Fall back to static `tokenTTL` option (or default ~100 years)
|
|
31
|
+
*/
|
|
32
|
+
export type TokenTTLResolver = (roles: string[], payload: any) => number | undefined;
|
|
23
33
|
export interface JwtAuthPluginOptions {
|
|
24
34
|
secret: string;
|
|
25
35
|
algo?: jwtSimple.TAlgorithm;
|
|
26
|
-
|
|
36
|
+
/**
|
|
37
|
+
* @param ctx The host application's context, supplied by Flink core on
|
|
38
|
+
* each authenticated request. Lets the callback access repos/services
|
|
39
|
+
* that don't exist when the plugin is constructed (e.g. before
|
|
40
|
+
* `FlinkApp.start()` resolves) without forcing the consumer to keep a
|
|
41
|
+
* module-level mutable reference. Undefined only when invoked outside
|
|
42
|
+
* the normal request pipeline (e.g. unit tests).
|
|
43
|
+
*/
|
|
44
|
+
getUser: (tokenData: any, req: FlinkRequest, ctx?: any) => Promise<FlinkAuthUser | null | undefined>;
|
|
27
45
|
passwordPolicy?: RegExp;
|
|
28
46
|
tokenTTL?: number;
|
|
29
47
|
rolePermissions: {
|
|
@@ -73,15 +91,25 @@ export interface JwtAuthPluginOptions {
|
|
|
73
91
|
* ```
|
|
74
92
|
*/
|
|
75
93
|
useDynamicRoles?: boolean;
|
|
76
|
-
}
|
|
77
|
-
export interface JwtAuthPlugin extends FlinkAuthPlugin {
|
|
78
94
|
/**
|
|
79
|
-
*
|
|
95
|
+
* Optional dynamic token TTL resolver.
|
|
96
|
+
*
|
|
97
|
+
* When provided, called during token creation with the user's roles and payload.
|
|
98
|
+
* Return a TTL in milliseconds to override the static `tokenTTL`, or `undefined`
|
|
99
|
+
* to fall back to `tokenTTL` (or the default).
|
|
80
100
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
101
|
+
* Example:
|
|
102
|
+
* ```typescript
|
|
103
|
+
* resolveTokenTTL: (roles) => {
|
|
104
|
+
* if (roles.includes("service-account")) return 1000 * 60 * 60 * 24 * 365; // 1 year
|
|
105
|
+
* if (roles.includes("admin")) return 1000 * 60 * 60 * 8; // 8 hours
|
|
106
|
+
* return 1000 * 60 * 60; // 1 hour default
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
83
109
|
*/
|
|
84
|
-
|
|
110
|
+
resolveTokenTTL?: TokenTTLResolver;
|
|
111
|
+
}
|
|
112
|
+
export interface JwtAuthPlugin extends FlinkAuthPlugin {
|
|
85
113
|
/**
|
|
86
114
|
* Generates new password hash and salt for provided password.
|
|
87
115
|
*
|
|
@@ -102,5 +130,4 @@ export interface JwtAuthPlugin extends FlinkAuthPlugin {
|
|
|
102
130
|
/**
|
|
103
131
|
* Configures and creates authentication plugin.
|
|
104
132
|
*/
|
|
105
|
-
export declare function jwtAuthPlugin({ secret, getUser, rolePermissions, algo, passwordPolicy, tokenTTL,
|
|
106
|
-
tokenExtractor, checkPermissions, useDynamicRoles, }: JwtAuthPluginOptions): JwtAuthPlugin;
|
|
133
|
+
export declare function jwtAuthPlugin({ secret, getUser, rolePermissions, algo, passwordPolicy, tokenTTL, tokenExtractor, checkPermissions, useDynamicRoles, resolveTokenTTL, }: JwtAuthPluginOptions): JwtAuthPlugin;
|
|
@@ -65,12 +65,11 @@ var defaultPasswordPolicy = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
|
|
|
65
65
|
*/
|
|
66
66
|
function jwtAuthPlugin(_a) {
|
|
67
67
|
var _this = this;
|
|
68
|
-
var secret = _a.secret, getUser = _a.getUser, rolePermissions = _a.rolePermissions, _b = _a.algo, algo = _b === void 0 ? "HS256" : _b, _c = _a.passwordPolicy, passwordPolicy = _c === void 0 ? defaultPasswordPolicy : _c,
|
|
69
|
-
tokenExtractor = _a.tokenExtractor, checkPermissions = _a.checkPermissions, _e = _a.useDynamicRoles, useDynamicRoles = _e === void 0 ? false : _e;
|
|
68
|
+
var secret = _a.secret, getUser = _a.getUser, rolePermissions = _a.rolePermissions, _b = _a.algo, algo = _b === void 0 ? "HS256" : _b, _c = _a.passwordPolicy, passwordPolicy = _c === void 0 ? defaultPasswordPolicy : _c, tokenTTL = _a.tokenTTL, tokenExtractor = _a.tokenExtractor, checkPermissions = _a.checkPermissions, _d = _a.useDynamicRoles, useDynamicRoles = _d === void 0 ? false : _d, resolveTokenTTL = _a.resolveTokenTTL;
|
|
70
69
|
return {
|
|
71
|
-
authenticateRequest: function (req, permissions) { return __awaiter(_this, void 0, void 0, function () {
|
|
70
|
+
authenticateRequest: function (req, permissions, ctx) { return __awaiter(_this, void 0, void 0, function () {
|
|
72
71
|
return __generator(this, function (_a) {
|
|
73
|
-
return [2 /*return*/, authenticateRequest(req, permissions, rolePermissions, {
|
|
72
|
+
return [2 /*return*/, authenticateRequest(req, permissions, rolePermissions, ctx, {
|
|
74
73
|
algo: algo,
|
|
75
74
|
secret: secret,
|
|
76
75
|
getUser: getUser,
|
|
@@ -80,13 +79,13 @@ function jwtAuthPlugin(_a) {
|
|
|
80
79
|
})];
|
|
81
80
|
});
|
|
82
81
|
}); },
|
|
83
|
-
createToken: function (payload, roles) { return createToken(__assign(__assign({}, payload), { roles: roles }), { algo: algo, secret: secret, tokenTTL: tokenTTL }); },
|
|
82
|
+
createToken: function (payload, roles) { return createToken(__assign(__assign({}, payload), { roles: roles }), { algo: algo, secret: secret, tokenTTL: tokenTTL, resolveTokenTTL: resolveTokenTTL }, roles); },
|
|
84
83
|
createPasswordHashAndSalt: function (password) { return createPasswordHashAndSalt(password, passwordPolicy); },
|
|
85
84
|
validatePassword: validatePassword,
|
|
86
85
|
};
|
|
87
86
|
}
|
|
88
|
-
function authenticateRequest(req_1, routePermissions_1, rolePermissions_1, _a) {
|
|
89
|
-
return __awaiter(this, arguments, void 0, function (req, routePermissions, rolePermissions, _b) {
|
|
87
|
+
function authenticateRequest(req_1, routePermissions_1, rolePermissions_1, ctx_1, _a) {
|
|
88
|
+
return __awaiter(this, arguments, void 0, function (req, routePermissions, rolePermissions, ctx, _b) {
|
|
90
89
|
var token, decodedToken, permissionsArr, validPerms, user, validPerms, hasPermission;
|
|
91
90
|
var secret = _b.secret, algo = _b.algo, getUser = _b.getUser, tokenExtractor = _b.tokenExtractor, checkPermissions = _b.checkPermissions, useDynamicRoles = _b.useDynamicRoles;
|
|
92
91
|
return __generator(this, function (_c) {
|
|
@@ -123,7 +122,7 @@ function authenticateRequest(req_1, routePermissions_1, rolePermissions_1, _a) {
|
|
|
123
122
|
return [2 /*return*/, false];
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
|
-
return [4 /*yield*/, getUser(decodedToken, req)];
|
|
125
|
+
return [4 /*yield*/, getUser(decodedToken, req, ctx)];
|
|
127
126
|
case 1:
|
|
128
127
|
user = _c.sent();
|
|
129
128
|
if (!user) {
|
|
@@ -149,6 +148,16 @@ function authenticateRequest(req_1, routePermissions_1, rolePermissions_1, _a) {
|
|
|
149
148
|
_c.label = 3;
|
|
150
149
|
case 3:
|
|
151
150
|
req.user = user;
|
|
151
|
+
// Expose the verified token so handlers can read claims (e.g. jti)
|
|
152
|
+
// without re-extracting and re-decoding (and without an unverified decode).
|
|
153
|
+
req.token = decodedToken;
|
|
154
|
+
req.rawToken = token;
|
|
155
|
+
// Populate userPermissions from resolved permissions
|
|
156
|
+
req.userPermissions = computeUserPermissions(user, decodedToken, {
|
|
157
|
+
useDynamicRoles: useDynamicRoles,
|
|
158
|
+
rolePermissions: rolePermissions,
|
|
159
|
+
checkPermissions: checkPermissions,
|
|
160
|
+
});
|
|
152
161
|
return [2 /*return*/, true];
|
|
153
162
|
case 4: return [2 /*return*/, false];
|
|
154
163
|
}
|
|
@@ -163,14 +172,19 @@ function getTokenFromReq(req) {
|
|
|
163
172
|
}
|
|
164
173
|
return;
|
|
165
174
|
}
|
|
166
|
-
function createToken(payload_1, _a) {
|
|
167
|
-
return __awaiter(this, arguments, void 0, function (payload, _b) {
|
|
168
|
-
var
|
|
169
|
-
|
|
175
|
+
function createToken(payload_1, _a, roles_1) {
|
|
176
|
+
return __awaiter(this, arguments, void 0, function (payload, _b, roles) {
|
|
177
|
+
var resolvedTTL, effectiveTTL, iat;
|
|
178
|
+
var _c;
|
|
179
|
+
var secret = _b.secret, algo = _b.algo, tokenTTL = _b.tokenTTL, resolveTokenTTL = _b.resolveTokenTTL;
|
|
180
|
+
return __generator(this, function (_d) {
|
|
170
181
|
if (!payload) {
|
|
171
182
|
throw new Error("Cannot create token - payload is missing");
|
|
172
183
|
}
|
|
173
|
-
|
|
184
|
+
resolvedTTL = resolveTokenTTL === null || resolveTokenTTL === void 0 ? void 0 : resolveTokenTTL(roles, payload);
|
|
185
|
+
effectiveTTL = (_c = resolvedTTL !== null && resolvedTTL !== void 0 ? resolvedTTL : tokenTTL) !== null && _c !== void 0 ? _c : 1000 * 60 * 60 * 24 * 365 * 100;
|
|
186
|
+
iat = Math.floor(Date.now() / 1000);
|
|
187
|
+
return [2 /*return*/, jwt_simple_1.default.encode(__assign({ iat: iat, exp: _calculateExpiration(effectiveTTL) }, payload), secret, algo)];
|
|
174
188
|
});
|
|
175
189
|
});
|
|
176
190
|
}
|
|
@@ -211,3 +225,40 @@ function validatePassword(password, passwordHash, salt) {
|
|
|
211
225
|
});
|
|
212
226
|
});
|
|
213
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Compute user permissions based on auth plugin configuration
|
|
230
|
+
*
|
|
231
|
+
* Strategies (in order):
|
|
232
|
+
* 1. User object has permissions directly (from getUser callback)
|
|
233
|
+
* 2. Dynamic roles - map user.roles to permissions
|
|
234
|
+
* 3. Static roles from token - map token.roles to permissions
|
|
235
|
+
* 4. No permissions available - return empty array
|
|
236
|
+
*/
|
|
237
|
+
function computeUserPermissions(user, decodedToken, config) {
|
|
238
|
+
// Strategy 1: User object has permissions directly (from getUser callback)
|
|
239
|
+
if (user.permissions && Array.isArray(user.permissions)) {
|
|
240
|
+
return user.permissions;
|
|
241
|
+
}
|
|
242
|
+
// Strategy 2: Dynamic roles - map user.roles to permissions
|
|
243
|
+
if (config.useDynamicRoles && user.roles && Array.isArray(user.roles)) {
|
|
244
|
+
return flattenRolesToPermissions(user.roles, config.rolePermissions);
|
|
245
|
+
}
|
|
246
|
+
// Strategy 3: Static roles from token - map token.roles to permissions
|
|
247
|
+
if (decodedToken.roles && Array.isArray(decodedToken.roles)) {
|
|
248
|
+
return flattenRolesToPermissions(decodedToken.roles, config.rolePermissions);
|
|
249
|
+
}
|
|
250
|
+
// Strategy 4: No permissions available
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Convert roles to permissions using rolePermissions mapping
|
|
255
|
+
*/
|
|
256
|
+
function flattenRolesToPermissions(roles, roleMap) {
|
|
257
|
+
var perms = new Set();
|
|
258
|
+
for (var _i = 0, roles_1 = roles; _i < roles_1.length; _i++) {
|
|
259
|
+
var role = roles_1[_i];
|
|
260
|
+
var rolePerms = roleMap[role] || [];
|
|
261
|
+
rolePerms.forEach(function (p) { return perms.add(p); });
|
|
262
|
+
}
|
|
263
|
+
return Array.from(perms);
|
|
264
|
+
}
|
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.hasValidPermissions = hasValidPermissions;
|
|
4
4
|
var flink_1 = require("@flink-app/flink");
|
|
5
|
+
/**
|
|
6
|
+
* Checks whether a single role scope entry matches a required route
|
|
7
|
+
* permission. Supports exact match and trailing-segment wildcards of the
|
|
8
|
+
* form `"foo.bar.*"`, which match any route permission that starts with
|
|
9
|
+
* `"foo.bar."` (segment-scoped — does NOT match `"foo.bar"` itself, and
|
|
10
|
+
* does NOT match `"foo.barbaz.x"`).
|
|
11
|
+
*/
|
|
12
|
+
function scopeMatches(scope, routePerm) {
|
|
13
|
+
if (scope === routePerm)
|
|
14
|
+
return true;
|
|
15
|
+
if (scope.endsWith(".*")) {
|
|
16
|
+
// Keep the trailing "." so "a.*" does not match "a" or "ab.c".
|
|
17
|
+
var prefix = scope.slice(0, -1);
|
|
18
|
+
return routePerm.startsWith(prefix);
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
5
22
|
/**
|
|
6
23
|
* Checks if provided role has permission to access route
|
|
7
24
|
* based on its permissions.
|
|
@@ -28,7 +45,7 @@ function hasValidPermissions(roles, rolePermissions, routePermissions) {
|
|
|
28
45
|
return { value: true };
|
|
29
46
|
}
|
|
30
47
|
if (routePermissions.every(function (routePerm) {
|
|
31
|
-
return thisRolesPermissions.
|
|
48
|
+
return thisRolesPermissions.some(function (scope) { return scopeMatches(scope, routePerm); });
|
|
32
49
|
})) {
|
|
33
50
|
return { value: true };
|
|
34
51
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flink-app/jwt-auth-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-alpha.100",
|
|
4
4
|
"description": "Flink plugin for JWT auth",
|
|
5
5
|
"author": "joel@frost.se",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
"@types/node": "22.13.10",
|
|
20
20
|
"ts-node": "^10.9.2",
|
|
21
21
|
"tsc-watch": "^4.2.9",
|
|
22
|
-
"@flink-app/flink": "
|
|
22
|
+
"@flink-app/flink": "2.0.0-alpha.100"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@flink-app/flink": ">=2.0.0-alpha.100"
|
|
23
26
|
},
|
|
24
27
|
"gitHead": "4243e3b3cd6d4e1ca001a61baa8436bf2bbe4113",
|
|
25
28
|
"scripts": {
|