@flink-app/jwt-auth-plugin 0.12.1-alpha.9 → 0.13.0
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 +7 -0
- package/dist/BcryptUtils.js +2 -3
- package/dist/FlinkJwtAuthPlugin.d.ts +68 -3
- package/dist/FlinkJwtAuthPlugin.js +55 -23
- package/dist/PermissionValidator.js +1 -2
- package/package.json +31 -34
- package/readme.md +1301 -18
- package/spec/FlinkJwtAuthPlugin.spec.ts +692 -5
- package/src/FlinkJwtAuthPlugin.ts +213 -130
- package/tsconfig.json +1 -1
package/CHANGELOG.md
ADDED
package/dist/BcryptUtils.js
CHANGED
|
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.encrypt = encrypt;
|
|
7
|
+
exports.genSalt = genSalt;
|
|
7
8
|
var bcrypt_1 = __importDefault(require("bcrypt"));
|
|
8
9
|
function encrypt(password, salt) {
|
|
9
10
|
return new Promise(function (resolve, reject) {
|
|
@@ -14,7 +15,6 @@ function encrypt(password, salt) {
|
|
|
14
15
|
});
|
|
15
16
|
});
|
|
16
17
|
}
|
|
17
|
-
exports.encrypt = encrypt;
|
|
18
18
|
function genSalt(rounds) {
|
|
19
19
|
if (rounds === void 0) { rounds = 10; }
|
|
20
20
|
return new Promise(function (resolve, reject) {
|
|
@@ -25,4 +25,3 @@ function genSalt(rounds) {
|
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
-
exports.genSalt = genSalt;
|
|
@@ -1,14 +1,78 @@
|
|
|
1
|
-
import { FlinkAuthPlugin, FlinkAuthUser } from "@flink-app/flink";
|
|
1
|
+
import { FlinkAuthPlugin, FlinkAuthUser, FlinkRequest } from "@flink-app/flink";
|
|
2
2
|
import jwtSimple from "jwt-simple";
|
|
3
|
+
/**
|
|
4
|
+
* Custom token extraction callback.
|
|
5
|
+
*
|
|
6
|
+
* Return values:
|
|
7
|
+
* - `string`: Token found, use this token
|
|
8
|
+
* - `null`: No token found, authentication should fail
|
|
9
|
+
* - `undefined`: Skip custom extraction, use default Bearer token extraction
|
|
10
|
+
*/
|
|
11
|
+
export type TokenExtractor = (req: FlinkRequest) => string | null | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Custom permission validation callback.
|
|
14
|
+
*
|
|
15
|
+
* Called after getUser to validate if the user has required permissions.
|
|
16
|
+
* Useful for dynamic permissions stored in database.
|
|
17
|
+
*
|
|
18
|
+
* @param user - The authenticated user object returned from getUser
|
|
19
|
+
* @param routePermissions - Array of permissions required by the route
|
|
20
|
+
* @returns true if user has required permissions, false otherwise
|
|
21
|
+
*/
|
|
22
|
+
export type PermissionChecker = (user: FlinkAuthUser, routePermissions: string[]) => Promise<boolean> | boolean;
|
|
3
23
|
export interface JwtAuthPluginOptions {
|
|
4
24
|
secret: string;
|
|
5
25
|
algo?: jwtSimple.TAlgorithm;
|
|
6
|
-
getUser: (tokenData: any) => Promise<FlinkAuthUser>;
|
|
26
|
+
getUser: (tokenData: any, req: FlinkRequest) => Promise<FlinkAuthUser | null | undefined>;
|
|
7
27
|
passwordPolicy?: RegExp;
|
|
8
28
|
tokenTTL?: number;
|
|
9
29
|
rolePermissions: {
|
|
10
30
|
[role: string]: string[];
|
|
11
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* Optional custom token extraction callback.
|
|
34
|
+
*
|
|
35
|
+
* Allows conditional token extraction based on request properties (path, method, headers, etc.).
|
|
36
|
+
* Return `undefined` to fall back to default Bearer token extraction.
|
|
37
|
+
*/
|
|
38
|
+
tokenExtractor?: TokenExtractor;
|
|
39
|
+
/**
|
|
40
|
+
* Optional custom permission checker for dynamic permissions.
|
|
41
|
+
*
|
|
42
|
+
* When provided, replaces static rolePermissions checking.
|
|
43
|
+
* Called after getUser with the full user object.
|
|
44
|
+
*
|
|
45
|
+
* Example:
|
|
46
|
+
* ```typescript
|
|
47
|
+
* checkPermissions: async (user, routePermissions) => {
|
|
48
|
+
* return routePermissions.every(perm =>
|
|
49
|
+
* user.permissions?.includes(perm)
|
|
50
|
+
* );
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
checkPermissions?: PermissionChecker;
|
|
55
|
+
/**
|
|
56
|
+
* When true, uses roles from the user object returned by getUser
|
|
57
|
+
* instead of roles from the decoded token for static permission checking.
|
|
58
|
+
*
|
|
59
|
+
* Useful for multi-tenant scenarios where user roles vary by organization context.
|
|
60
|
+
* The organization context can be determined from request headers, subdomain, path, etc.
|
|
61
|
+
*
|
|
62
|
+
* Example:
|
|
63
|
+
* ```typescript
|
|
64
|
+
* useDynamicRoles: true,
|
|
65
|
+
* getUser: async (tokenData, req) => {
|
|
66
|
+
* const orgId = req.headers['x-organization-id'];
|
|
67
|
+
* const membership = await getOrgMembership(tokenData.userId, orgId);
|
|
68
|
+
* return {
|
|
69
|
+
* id: tokenData.userId,
|
|
70
|
+
* roles: [membership.role], // Org-specific role
|
|
71
|
+
* };
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
useDynamicRoles?: boolean;
|
|
12
76
|
}
|
|
13
77
|
export interface JwtAuthPlugin extends FlinkAuthPlugin {
|
|
14
78
|
/**
|
|
@@ -38,4 +102,5 @@ export interface JwtAuthPlugin extends FlinkAuthPlugin {
|
|
|
38
102
|
/**
|
|
39
103
|
* Configures and creates authentication plugin.
|
|
40
104
|
*/
|
|
41
|
-
export declare function jwtAuthPlugin({ secret, getUser, rolePermissions, algo, passwordPolicy, tokenTTL,
|
|
105
|
+
export declare function jwtAuthPlugin({ secret, getUser, rolePermissions, algo, passwordPolicy, tokenTTL, //Defaults to hundred year
|
|
106
|
+
tokenExtractor, checkPermissions, useDynamicRoles, }: JwtAuthPluginOptions): JwtAuthPlugin;
|
|
@@ -20,8 +20,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
20
20
|
});
|
|
21
21
|
};
|
|
22
22
|
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
24
|
-
return g =
|
|
23
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
24
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
25
|
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
26
|
function step(op) {
|
|
27
27
|
if (f) throw new TypeError("Generator is already executing.");
|
|
@@ -50,7 +50,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
50
50
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
51
|
};
|
|
52
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
-
exports.jwtAuthPlugin =
|
|
53
|
+
exports.jwtAuthPlugin = jwtAuthPlugin;
|
|
54
54
|
var flink_1 = require("@flink-app/flink");
|
|
55
55
|
var jwt_simple_1 = __importDefault(require("jwt-simple"));
|
|
56
56
|
var BcryptUtils_1 = require("./BcryptUtils");
|
|
@@ -65,7 +65,8 @@ 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, _d = _a.tokenTTL, tokenTTL = _d === void 0 ? 1000 * 60 * 60 * 24 * 365 * 100 : _d
|
|
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, _d = _a.tokenTTL, tokenTTL = _d === void 0 ? 1000 * 60 * 60 * 24 * 365 * 100 : _d, //Defaults to hundred year
|
|
69
|
+
tokenExtractor = _a.tokenExtractor, checkPermissions = _a.checkPermissions, _e = _a.useDynamicRoles, useDynamicRoles = _e === void 0 ? false : _e;
|
|
69
70
|
return {
|
|
70
71
|
authenticateRequest: function (req, permissions) { return __awaiter(_this, void 0, void 0, function () {
|
|
71
72
|
return __generator(this, function (_a) {
|
|
@@ -73,52 +74,83 @@ function jwtAuthPlugin(_a) {
|
|
|
73
74
|
algo: algo,
|
|
74
75
|
secret: secret,
|
|
75
76
|
getUser: getUser,
|
|
77
|
+
tokenExtractor: tokenExtractor,
|
|
78
|
+
checkPermissions: checkPermissions,
|
|
79
|
+
useDynamicRoles: useDynamicRoles,
|
|
76
80
|
})];
|
|
77
81
|
});
|
|
78
82
|
}); },
|
|
79
|
-
createToken: function (payload, roles) {
|
|
80
|
-
|
|
81
|
-
},
|
|
82
|
-
createPasswordHashAndSalt: function (password) {
|
|
83
|
-
return createPasswordHashAndSalt(password, passwordPolicy);
|
|
84
|
-
},
|
|
83
|
+
createToken: function (payload, roles) { return createToken(__assign(__assign({}, payload), { roles: roles }), { algo: algo, secret: secret, tokenTTL: tokenTTL }); },
|
|
84
|
+
createPasswordHashAndSalt: function (password) { return createPasswordHashAndSalt(password, passwordPolicy); },
|
|
85
85
|
validatePassword: validatePassword,
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
|
-
exports.jwtAuthPlugin = jwtAuthPlugin;
|
|
89
88
|
function authenticateRequest(req_1, routePermissions_1, rolePermissions_1, _a) {
|
|
90
89
|
return __awaiter(this, arguments, void 0, function (req, routePermissions, rolePermissions, _b) {
|
|
91
|
-
var token, decodedToken, permissionsArr, validPerms, user;
|
|
92
|
-
var secret = _b.secret, algo = _b.algo, getUser = _b.getUser;
|
|
90
|
+
var token, decodedToken, permissionsArr, validPerms, user, validPerms, hasPermission;
|
|
91
|
+
var secret = _b.secret, algo = _b.algo, getUser = _b.getUser, tokenExtractor = _b.tokenExtractor, checkPermissions = _b.checkPermissions, useDynamicRoles = _b.useDynamicRoles;
|
|
93
92
|
return __generator(this, function (_c) {
|
|
94
93
|
switch (_c.label) {
|
|
95
94
|
case 0:
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
if (tokenExtractor) {
|
|
96
|
+
token = tokenExtractor(req);
|
|
97
|
+
// If tokenExtractor returns undefined, fall back to default
|
|
98
|
+
if (token === undefined) {
|
|
99
|
+
token = getTokenFromReq(req);
|
|
100
|
+
}
|
|
101
|
+
// If it returns null, token stays null (no default fallback)
|
|
102
|
+
// If it returns string, token is the string
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// No custom extractor, use default
|
|
106
|
+
token = getTokenFromReq(req);
|
|
107
|
+
}
|
|
108
|
+
if (!token) return [3 /*break*/, 4];
|
|
98
109
|
decodedToken = void 0;
|
|
99
110
|
try {
|
|
100
111
|
decodedToken = jwt_simple_1.default.decode(token, secret, false, algo);
|
|
101
112
|
}
|
|
102
113
|
catch (err) {
|
|
103
|
-
flink_1.log.debug("Failed to decode token: ".concat(err));
|
|
114
|
+
flink_1.log.debug("[JWT AUTH PLUGIN] Failed to decode token: ".concat(err));
|
|
104
115
|
decodedToken = null;
|
|
105
116
|
}
|
|
106
|
-
if (!decodedToken) return [3 /*break*/,
|
|
107
|
-
permissionsArr = Array.isArray(routePermissions)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (permissionsArr && permissionsArr.length > 0) {
|
|
117
|
+
if (!decodedToken) return [3 /*break*/, 4];
|
|
118
|
+
permissionsArr = Array.isArray(routePermissions) ? routePermissions : [routePermissions];
|
|
119
|
+
// Static permission check - only if custom checker NOT provided AND not using dynamic roles
|
|
120
|
+
if (!checkPermissions && !useDynamicRoles && permissionsArr && permissionsArr.length > 0) {
|
|
111
121
|
validPerms = (0, PermissionValidator_1.hasValidPermissions)(decodedToken.roles || [], rolePermissions, permissionsArr);
|
|
112
122
|
if (!validPerms) {
|
|
113
123
|
return [2 /*return*/, false];
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
|
-
return [4 /*yield*/, getUser(decodedToken)];
|
|
126
|
+
return [4 /*yield*/, getUser(decodedToken, req)];
|
|
117
127
|
case 1:
|
|
118
128
|
user = _c.sent();
|
|
129
|
+
if (!user) {
|
|
130
|
+
flink_1.log.debug("[JWT AUTH PLUGIN] User not returned from getUser callback");
|
|
131
|
+
return [2 /*return*/, false];
|
|
132
|
+
}
|
|
133
|
+
// Dynamic roles: check permissions using roles from user object
|
|
134
|
+
if (!checkPermissions && useDynamicRoles && permissionsArr && permissionsArr.length > 0) {
|
|
135
|
+
validPerms = (0, PermissionValidator_1.hasValidPermissions)(user.roles || [], rolePermissions, permissionsArr);
|
|
136
|
+
if (!validPerms) {
|
|
137
|
+
flink_1.log.debug("[JWT AUTH PLUGIN] Dynamic role permission check failed");
|
|
138
|
+
return [2 /*return*/, false];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!(checkPermissions && permissionsArr && permissionsArr.length > 0)) return [3 /*break*/, 3];
|
|
142
|
+
return [4 /*yield*/, checkPermissions(user, permissionsArr)];
|
|
143
|
+
case 2:
|
|
144
|
+
hasPermission = _c.sent();
|
|
145
|
+
if (!hasPermission) {
|
|
146
|
+
flink_1.log.debug("[JWT AUTH PLUGIN] Custom permission check failed");
|
|
147
|
+
return [2 /*return*/, false];
|
|
148
|
+
}
|
|
149
|
+
_c.label = 3;
|
|
150
|
+
case 3:
|
|
119
151
|
req.user = user;
|
|
120
152
|
return [2 /*return*/, true];
|
|
121
|
-
case
|
|
153
|
+
case 4: return [2 /*return*/, false];
|
|
122
154
|
}
|
|
123
155
|
});
|
|
124
156
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.hasValidPermissions =
|
|
3
|
+
exports.hasValidPermissions = hasValidPermissions;
|
|
4
4
|
var flink_1 = require("@flink-app/flink");
|
|
5
5
|
/**
|
|
6
6
|
* Checks if provided role has permission to access route
|
|
@@ -41,4 +41,3 @@ function hasValidPermissions(roles, rolePermissions, routePermissions) {
|
|
|
41
41
|
}
|
|
42
42
|
return false;
|
|
43
43
|
}
|
|
44
|
-
exports.hasValidPermissions = hasValidPermissions;
|
package/package.json
CHANGED
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
"gitHead": "3007ad036607014176930446fde56203bde8d6f5"
|
|
35
|
-
}
|
|
2
|
+
"name": "@flink-app/jwt-auth-plugin",
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "Flink plugin for JWT auth",
|
|
5
|
+
"author": "joel@frost.se",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"bcrypt": "^5.0.1",
|
|
14
|
+
"jwt-simple": "^0.5.6"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/bcrypt": "^5.0.0",
|
|
18
|
+
"@types/jasmine": "^3.7.1",
|
|
19
|
+
"@types/node": "22.13.10",
|
|
20
|
+
"ts-node": "^10.9.2",
|
|
21
|
+
"tsc-watch": "^4.2.9",
|
|
22
|
+
"@flink-app/flink": "0.13.0"
|
|
23
|
+
},
|
|
24
|
+
"gitHead": "4243e3b3cd6d4e1ca001a61baa8436bf2bbe4113",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "jasmine-ts --config=./spec/support/jasmine.json",
|
|
27
|
+
"test:watch": "nodemon --ext ts --exec 'jasmine-ts --config=./spec/support/jasmine.json'",
|
|
28
|
+
"watch": "tsc-watch --project tsconfig.dist.json",
|
|
29
|
+
"build": "tsc --project tsconfig.dist.json",
|
|
30
|
+
"clean": "rimraf dist .flink"
|
|
31
|
+
}
|
|
32
|
+
}
|