@flink-app/jwt-auth-plugin 0.12.1-alpha.40 → 0.12.1-alpha.43

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.
@@ -9,15 +9,84 @@ import { hasValidPermissions } from "./PermissionValidator";
9
9
  */
10
10
  const defaultPasswordPolicy = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
11
11
 
12
+ /**
13
+ * Custom token extraction callback.
14
+ *
15
+ * Return values:
16
+ * - `string`: Token found, use this token
17
+ * - `null`: No token found, authentication should fail
18
+ * - `undefined`: Skip custom extraction, use default Bearer token extraction
19
+ */
20
+ export type TokenExtractor = (req: FlinkRequest) => string | null | undefined;
21
+
22
+ /**
23
+ * Custom permission validation callback.
24
+ *
25
+ * Called after getUser to validate if the user has required permissions.
26
+ * Useful for dynamic permissions stored in database.
27
+ *
28
+ * @param user - The authenticated user object returned from getUser
29
+ * @param routePermissions - Array of permissions required by the route
30
+ * @returns true if user has required permissions, false otherwise
31
+ */
32
+ export type PermissionChecker = (
33
+ user: FlinkAuthUser,
34
+ routePermissions: string[]
35
+ ) => Promise<boolean> | boolean;
36
+
12
37
  export interface JwtAuthPluginOptions {
13
38
  secret: string;
14
39
  algo?: jwtSimple.TAlgorithm;
15
- getUser: (tokenData: any) => Promise<FlinkAuthUser | null | undefined>;
40
+ getUser: (tokenData: any, req: FlinkRequest) => Promise<FlinkAuthUser | null | undefined>;
16
41
  passwordPolicy?: RegExp;
17
42
  tokenTTL?: number;
18
43
  rolePermissions: {
19
44
  [role: string]: string[];
20
45
  };
46
+ /**
47
+ * Optional custom token extraction callback.
48
+ *
49
+ * Allows conditional token extraction based on request properties (path, method, headers, etc.).
50
+ * Return `undefined` to fall back to default Bearer token extraction.
51
+ */
52
+ tokenExtractor?: TokenExtractor;
53
+ /**
54
+ * Optional custom permission checker for dynamic permissions.
55
+ *
56
+ * When provided, replaces static rolePermissions checking.
57
+ * Called after getUser with the full user object.
58
+ *
59
+ * Example:
60
+ * ```typescript
61
+ * checkPermissions: async (user, routePermissions) => {
62
+ * return routePermissions.every(perm =>
63
+ * user.permissions?.includes(perm)
64
+ * );
65
+ * }
66
+ * ```
67
+ */
68
+ checkPermissions?: PermissionChecker;
69
+ /**
70
+ * When true, uses roles from the user object returned by getUser
71
+ * instead of roles from the decoded token for static permission checking.
72
+ *
73
+ * Useful for multi-tenant scenarios where user roles vary by organization context.
74
+ * The organization context can be determined from request headers, subdomain, path, etc.
75
+ *
76
+ * Example:
77
+ * ```typescript
78
+ * useDynamicRoles: true,
79
+ * getUser: async (tokenData, req) => {
80
+ * const orgId = req.headers['x-organization-id'];
81
+ * const membership = await getOrgMembership(tokenData.userId, orgId);
82
+ * return {
83
+ * id: tokenData.userId,
84
+ * roles: [membership.role], // Org-specific role
85
+ * };
86
+ * }
87
+ * ```
88
+ */
89
+ useDynamicRoles?: boolean;
21
90
  }
22
91
 
23
92
  export interface JwtAuthPlugin extends FlinkAuthPlugin {
@@ -55,6 +124,9 @@ export function jwtAuthPlugin({
55
124
  algo = "HS256",
56
125
  passwordPolicy = defaultPasswordPolicy,
57
126
  tokenTTL = 1000 * 60 * 60 * 24 * 365 * 100, //Defaults to hundred year
127
+ tokenExtractor,
128
+ checkPermissions,
129
+ useDynamicRoles = false,
58
130
  }: JwtAuthPluginOptions): JwtAuthPlugin {
59
131
  return {
60
132
  authenticateRequest: async (req, permissions) =>
@@ -62,6 +134,9 @@ export function jwtAuthPlugin({
62
134
  algo,
63
135
  secret,
64
136
  getUser,
137
+ tokenExtractor,
138
+ checkPermissions,
139
+ useDynamicRoles,
65
140
  }),
66
141
  createToken: (payload, roles) => createToken({ ...payload, roles }, { algo, secret, tokenTTL }),
67
142
  createPasswordHashAndSalt: (password: string) => createPasswordHashAndSalt(password, passwordPolicy),
@@ -73,9 +148,26 @@ async function authenticateRequest(
73
148
  req: FlinkRequest,
74
149
  routePermissions: string | string[],
75
150
  rolePermissions: { [x: string]: string[] },
76
- { secret, algo, getUser }: Pick<JwtAuthPluginOptions, "algo" | "secret" | "getUser">
151
+ { secret, algo, getUser, tokenExtractor, checkPermissions, useDynamicRoles }: Pick<
152
+ JwtAuthPluginOptions,
153
+ "algo" | "secret" | "getUser" | "tokenExtractor" | "checkPermissions" | "useDynamicRoles"
154
+ >
77
155
  ) {
78
- const token = getTokenFromReq(req);
156
+ let token: string | null | undefined;
157
+
158
+ if (tokenExtractor) {
159
+ token = tokenExtractor(req);
160
+
161
+ // If tokenExtractor returns undefined, fall back to default
162
+ if (token === undefined) {
163
+ token = getTokenFromReq(req);
164
+ }
165
+ // If it returns null, token stays null (no default fallback)
166
+ // If it returns string, token is the string
167
+ } else {
168
+ // No custom extractor, use default
169
+ token = getTokenFromReq(req);
170
+ }
79
171
 
80
172
  if (token) {
81
173
  let decodedToken;
@@ -90,7 +182,8 @@ async function authenticateRequest(
90
182
  if (decodedToken) {
91
183
  const permissionsArr = Array.isArray(routePermissions) ? routePermissions : [routePermissions];
92
184
 
93
- if (permissionsArr && permissionsArr.length > 0) {
185
+ // Static permission check - only if custom checker NOT provided AND not using dynamic roles
186
+ if (!checkPermissions && !useDynamicRoles && permissionsArr && permissionsArr.length > 0) {
94
187
  const validPerms = hasValidPermissions(decodedToken.roles || [], rolePermissions, permissionsArr);
95
188
 
96
189
  if (!validPerms) {
@@ -98,13 +191,33 @@ async function authenticateRequest(
98
191
  }
99
192
  }
100
193
 
101
- const user = await getUser(decodedToken);
194
+ const user = await getUser(decodedToken, req);
102
195
 
103
196
  if (!user) {
104
197
  log.debug("[JWT AUTH PLUGIN] User not returned from getUser callback");
105
198
  return false;
106
199
  }
107
200
 
201
+ // Dynamic roles: check permissions using roles from user object
202
+ if (!checkPermissions && useDynamicRoles && permissionsArr && permissionsArr.length > 0) {
203
+ const validPerms = hasValidPermissions(user.roles || [], rolePermissions, permissionsArr);
204
+
205
+ if (!validPerms) {
206
+ log.debug("[JWT AUTH PLUGIN] Dynamic role permission check failed");
207
+ return false;
208
+ }
209
+ }
210
+
211
+ // Custom permission check - only if provided
212
+ if (checkPermissions && permissionsArr && permissionsArr.length > 0) {
213
+ const hasPermission = await checkPermissions(user, permissionsArr);
214
+
215
+ if (!hasPermission) {
216
+ log.debug("[JWT AUTH PLUGIN] Custom permission check failed");
217
+ return false;
218
+ }
219
+ }
220
+
108
221
  req.user = user;
109
222
  return true;
110
223
  }