@flink-app/jwt-auth-plugin 0.12.1-alpha.33 → 0.12.1-alpha.35
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/FlinkJwtAuthPlugin.d.ts +1 -1
- package/dist/FlinkJwtAuthPlugin.js +8 -10
- package/package.json +3 -3
- package/src/FlinkJwtAuthPlugin.ts +101 -131
|
@@ -3,7 +3,7 @@ import jwtSimple from "jwt-simple";
|
|
|
3
3
|
export interface JwtAuthPluginOptions {
|
|
4
4
|
secret: string;
|
|
5
5
|
algo?: jwtSimple.TAlgorithm;
|
|
6
|
-
getUser: (tokenData: any) => Promise<FlinkAuthUser>;
|
|
6
|
+
getUser: (tokenData: any) => Promise<FlinkAuthUser | null | undefined>;
|
|
7
7
|
passwordPolicy?: RegExp;
|
|
8
8
|
tokenTTL?: number;
|
|
9
9
|
rolePermissions: {
|
|
@@ -76,12 +76,8 @@ function jwtAuthPlugin(_a) {
|
|
|
76
76
|
})];
|
|
77
77
|
});
|
|
78
78
|
}); },
|
|
79
|
-
createToken: function (payload, roles) {
|
|
80
|
-
|
|
81
|
-
},
|
|
82
|
-
createPasswordHashAndSalt: function (password) {
|
|
83
|
-
return createPasswordHashAndSalt(password, passwordPolicy);
|
|
84
|
-
},
|
|
79
|
+
createToken: function (payload, roles) { return createToken(__assign(__assign({}, payload), { roles: roles }), { algo: algo, secret: secret, tokenTTL: tokenTTL }); },
|
|
80
|
+
createPasswordHashAndSalt: function (password) { return createPasswordHashAndSalt(password, passwordPolicy); },
|
|
85
81
|
validatePassword: validatePassword,
|
|
86
82
|
};
|
|
87
83
|
}
|
|
@@ -100,13 +96,11 @@ function authenticateRequest(req_1, routePermissions_1, rolePermissions_1, _a) {
|
|
|
100
96
|
decodedToken = jwt_simple_1.default.decode(token, secret, false, algo);
|
|
101
97
|
}
|
|
102
98
|
catch (err) {
|
|
103
|
-
flink_1.log.debug("Failed to decode token: ".concat(err));
|
|
99
|
+
flink_1.log.debug("[JWT AUTH PLUGIN] Failed to decode token: ".concat(err));
|
|
104
100
|
decodedToken = null;
|
|
105
101
|
}
|
|
106
102
|
if (!decodedToken) return [3 /*break*/, 2];
|
|
107
|
-
permissionsArr = Array.isArray(routePermissions)
|
|
108
|
-
? routePermissions
|
|
109
|
-
: [routePermissions];
|
|
103
|
+
permissionsArr = Array.isArray(routePermissions) ? routePermissions : [routePermissions];
|
|
110
104
|
if (permissionsArr && permissionsArr.length > 0) {
|
|
111
105
|
validPerms = (0, PermissionValidator_1.hasValidPermissions)(decodedToken.roles || [], rolePermissions, permissionsArr);
|
|
112
106
|
if (!validPerms) {
|
|
@@ -116,6 +110,10 @@ function authenticateRequest(req_1, routePermissions_1, rolePermissions_1, _a) {
|
|
|
116
110
|
return [4 /*yield*/, getUser(decodedToken)];
|
|
117
111
|
case 1:
|
|
118
112
|
user = _c.sent();
|
|
113
|
+
if (!user) {
|
|
114
|
+
flink_1.log.debug("[JWT AUTH PLUGIN] User not returned from getUser callback");
|
|
115
|
+
return [2 /*return*/, false];
|
|
116
|
+
}
|
|
119
117
|
req.user = user;
|
|
120
118
|
return [2 /*return*/, true];
|
|
121
119
|
case 2: return [2 /*return*/, false];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flink-app/jwt-auth-plugin",
|
|
3
|
-
"version": "0.12.1-alpha.
|
|
3
|
+
"version": "0.12.1-alpha.35",
|
|
4
4
|
"description": "Flink plugin for JWT auth",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --preserve-symlinks -r ts-node/register -- node_modules/jasmine/bin/jasmine --config=./spec/support/jasmine.json",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"jwt-simple": "^0.5.6"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@flink-app/flink": "^0.12.1-alpha.
|
|
23
|
+
"@flink-app/flink": "^0.12.1-alpha.35",
|
|
24
24
|
"@types/bcrypt": "^5.0.0",
|
|
25
25
|
"@types/jasmine": "^3.7.1",
|
|
26
26
|
"@types/node": "22.13.10",
|
|
@@ -31,5 +31,5 @@
|
|
|
31
31
|
"tsc-watch": "^4.2.9",
|
|
32
32
|
"typescript": "5.4.5"
|
|
33
33
|
},
|
|
34
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "f8e8c6565a9ca1dd3e5fdb4c2a791c99ae3ba51a"
|
|
35
35
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FlinkAuthPlugin,
|
|
3
|
-
FlinkAuthUser,
|
|
4
|
-
FlinkRequest,
|
|
5
|
-
log,
|
|
6
|
-
} from "@flink-app/flink";
|
|
1
|
+
import { FlinkAuthPlugin, FlinkAuthUser, FlinkRequest, log } from "@flink-app/flink";
|
|
7
2
|
import jwtSimple from "jwt-simple";
|
|
8
3
|
import { encrypt, genSalt } from "./BcryptUtils";
|
|
9
4
|
import { hasValidPermissions } from "./PermissionValidator";
|
|
@@ -15,166 +10,141 @@ import { hasValidPermissions } from "./PermissionValidator";
|
|
|
15
10
|
const defaultPasswordPolicy = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
|
|
16
11
|
|
|
17
12
|
export interface JwtAuthPluginOptions {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
secret: string;
|
|
14
|
+
algo?: jwtSimple.TAlgorithm;
|
|
15
|
+
getUser: (tokenData: any) => Promise<FlinkAuthUser | null | undefined>;
|
|
16
|
+
passwordPolicy?: RegExp;
|
|
17
|
+
tokenTTL?: number;
|
|
18
|
+
rolePermissions: {
|
|
19
|
+
[role: string]: string[];
|
|
20
|
+
};
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
export interface JwtAuthPlugin extends FlinkAuthPlugin {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*/
|
|
52
|
-
validatePassword: (
|
|
53
|
-
password: string,
|
|
54
|
-
passwordHash: string,
|
|
55
|
-
salt: string
|
|
56
|
-
) => Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Encodes and returns JWT token that includes provided payload.
|
|
26
|
+
*
|
|
27
|
+
* The payload can by anything but should in most cases be and object that
|
|
28
|
+
* holds user information including an identifier such as the username or id.
|
|
29
|
+
*/
|
|
30
|
+
createToken: (payload: any, roles: string[]) => Promise<string>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Generates new password hash and salt for provided password.
|
|
34
|
+
*
|
|
35
|
+
* This method should be used when setting a new password. Both hash and salt needs
|
|
36
|
+
* to be saved in database as both are needed to validate the password.
|
|
37
|
+
*
|
|
38
|
+
* Returns null if password does not match configured `passwordPolicy`.
|
|
39
|
+
*/
|
|
40
|
+
createPasswordHashAndSalt: (password: string) => Promise<{ hash: string; salt: string } | null>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validates that provided `password` is same as provided `hash`.
|
|
44
|
+
*/
|
|
45
|
+
validatePassword: (password: string, passwordHash: string, salt: string) => Promise<boolean>;
|
|
57
46
|
}
|
|
58
47
|
|
|
59
48
|
/**
|
|
60
49
|
* Configures and creates authentication plugin.
|
|
61
50
|
*/
|
|
62
51
|
export function jwtAuthPlugin({
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
secret,
|
|
53
|
+
getUser,
|
|
54
|
+
rolePermissions,
|
|
55
|
+
algo = "HS256",
|
|
56
|
+
passwordPolicy = defaultPasswordPolicy,
|
|
57
|
+
tokenTTL = 1000 * 60 * 60 * 24 * 365 * 100, //Defaults to hundred year
|
|
69
58
|
}: JwtAuthPluginOptions): JwtAuthPlugin {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
validatePassword,
|
|
82
|
-
};
|
|
59
|
+
return {
|
|
60
|
+
authenticateRequest: async (req, permissions) =>
|
|
61
|
+
authenticateRequest(req, permissions, rolePermissions, {
|
|
62
|
+
algo,
|
|
63
|
+
secret,
|
|
64
|
+
getUser,
|
|
65
|
+
}),
|
|
66
|
+
createToken: (payload, roles) => createToken({ ...payload, roles }, { algo, secret, tokenTTL }),
|
|
67
|
+
createPasswordHashAndSalt: (password: string) => createPasswordHashAndSalt(password, passwordPolicy),
|
|
68
|
+
validatePassword,
|
|
69
|
+
};
|
|
83
70
|
}
|
|
84
71
|
|
|
85
72
|
async function authenticateRequest(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
secret,
|
|
91
|
-
algo,
|
|
92
|
-
getUser,
|
|
93
|
-
}: Pick<JwtAuthPluginOptions, "algo" | "secret" | "getUser">
|
|
73
|
+
req: FlinkRequest,
|
|
74
|
+
routePermissions: string | string[],
|
|
75
|
+
rolePermissions: { [x: string]: string[] },
|
|
76
|
+
{ secret, algo, getUser }: Pick<JwtAuthPluginOptions, "algo" | "secret" | "getUser">
|
|
94
77
|
) {
|
|
95
|
-
|
|
78
|
+
const token = getTokenFromReq(req);
|
|
96
79
|
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
if (token) {
|
|
81
|
+
let decodedToken;
|
|
99
82
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
83
|
+
try {
|
|
84
|
+
decodedToken = jwtSimple.decode(token, secret, false, algo);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
log.debug(`[JWT AUTH PLUGIN] Failed to decode token: ${err}`);
|
|
87
|
+
decodedToken = null;
|
|
88
|
+
}
|
|
106
89
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
? routePermissions
|
|
110
|
-
: [routePermissions];
|
|
90
|
+
if (decodedToken) {
|
|
91
|
+
const permissionsArr = Array.isArray(routePermissions) ? routePermissions : [routePermissions];
|
|
111
92
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
decodedToken.roles || [],
|
|
115
|
-
rolePermissions,
|
|
116
|
-
permissionsArr
|
|
117
|
-
);
|
|
93
|
+
if (permissionsArr && permissionsArr.length > 0) {
|
|
94
|
+
const validPerms = hasValidPermissions(decodedToken.roles || [], rolePermissions, permissionsArr);
|
|
118
95
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
96
|
+
if (!validPerms) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
123
100
|
|
|
124
|
-
|
|
101
|
+
const user = await getUser(decodedToken);
|
|
125
102
|
|
|
103
|
+
if (!user) {
|
|
104
|
+
log.debug("[JWT AUTH PLUGIN] User not returned from getUser callback");
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
126
107
|
|
|
127
|
-
|
|
128
|
-
|
|
108
|
+
req.user = user;
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
129
111
|
}
|
|
130
|
-
|
|
131
|
-
return false;
|
|
112
|
+
return false;
|
|
132
113
|
}
|
|
133
114
|
|
|
134
115
|
function getTokenFromReq(req: FlinkRequest) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
116
|
+
const authHeader = req.headers.authorization;
|
|
117
|
+
if (authHeader) {
|
|
118
|
+
const [, token] = authHeader.split("Bearer ");
|
|
119
|
+
return token;
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
141
122
|
}
|
|
142
123
|
|
|
143
|
-
async function createToken(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (!payload) {
|
|
148
|
-
throw new Error("Cannot create token - payload is missing");
|
|
149
|
-
}
|
|
124
|
+
async function createToken(payload: any, { secret, algo, tokenTTL }: Pick<JwtAuthPluginOptions, "algo" | "secret" | "tokenTTL">) {
|
|
125
|
+
if (!payload) {
|
|
126
|
+
throw new Error("Cannot create token - payload is missing");
|
|
127
|
+
}
|
|
150
128
|
|
|
151
|
-
|
|
129
|
+
return jwtSimple.encode({ exp: _calculateExpiration(tokenTTL || 1000 * 60 * 60 * 24 * 365 * 100), ...payload }, secret, algo);
|
|
152
130
|
}
|
|
153
131
|
|
|
154
|
-
function _calculateExpiration(expiresInMs
|
|
132
|
+
function _calculateExpiration(expiresInMs: number) {
|
|
155
133
|
return Math.floor((Date.now() + expiresInMs) / 1000);
|
|
156
134
|
}
|
|
157
135
|
|
|
136
|
+
async function createPasswordHashAndSalt(password: string, passwordPolicy: RegExp) {
|
|
137
|
+
if (!passwordPolicy.test(password)) {
|
|
138
|
+
log.debug(`Password does not match password policy '${passwordPolicy}'`);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
158
141
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
) {
|
|
163
|
-
if (!passwordPolicy.test(password)) {
|
|
164
|
-
log.debug(`Password does not match password policy '${passwordPolicy}'`);
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const salt = await genSalt(10);
|
|
169
|
-
const hash = await encrypt(password, salt);
|
|
170
|
-
return { salt, hash };
|
|
142
|
+
const salt = await genSalt(10);
|
|
143
|
+
const hash = await encrypt(password, salt);
|
|
144
|
+
return { salt, hash };
|
|
171
145
|
}
|
|
172
146
|
|
|
173
|
-
async function validatePassword(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
salt: string
|
|
177
|
-
) {
|
|
178
|
-
const hashCandidate = await encrypt(password, salt);
|
|
179
|
-
return hashCandidate === passwordHash;
|
|
147
|
+
async function validatePassword(password: string, passwordHash: string, salt: string) {
|
|
148
|
+
const hashCandidate = await encrypt(password, salt);
|
|
149
|
+
return hashCandidate === passwordHash;
|
|
180
150
|
}
|