@hitchy/plugin-auth 0.4.6 → 0.5.1
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/LICENSE +1 -1
- package/api/controller/user.js +83 -89
- package/api/model/authorization/rule.js +2 -4
- package/api/model/role.js +3 -10
- package/api/model/user-to-role.js +10 -19
- package/api/model/user.js +8 -15
- package/api/policy/authentication.js +7 -10
- package/api/policy/authorization.js +24 -28
- package/api/policy/user.js +3 -5
- package/api/service/auth/manager.js +3 -5
- package/api/service/authentication/passport.js +4 -6
- package/api/service/authentication/strategies.js +10 -12
- package/api/service/authorization/node.js +7 -9
- package/api/service/authorization/policy-generator.js +7 -9
- package/api/service/authorization/tree.js +2 -4
- package/api/service/authorization.js +4 -6
- package/api/service/session.js +4 -6
- package/config/auth.js +13 -19
- package/hash-password.js +8 -33
- package/index.js +8 -8
- package/package.json +17 -11
package/LICENSE
CHANGED
package/api/controller/user.js
CHANGED
|
@@ -1,96 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Implements request handlers for common requests related to authenticating as
|
|
3
|
+
* a user.
|
|
4
|
+
*/
|
|
5
|
+
export default class UserController {
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* Responds on request for changing authenticated user's password.
|
|
8
|
+
*
|
|
9
|
+
* This handler assumes to follow one or more policy handler(s) which
|
|
10
|
+
* actually manage the process of changing the password.
|
|
11
|
+
*
|
|
12
|
+
* @param {Hitchy.Core.IncomingMessage} req request descriptor
|
|
13
|
+
* @param {Hitchy.Core.ServerResponse} res response manager
|
|
14
|
+
* @returns {void}
|
|
8
15
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* @returns {void}
|
|
19
|
-
*/
|
|
20
|
-
static changePassword( req, res ) {
|
|
21
|
-
res.format( {
|
|
22
|
-
default() {
|
|
23
|
-
res.json( {
|
|
24
|
-
success: true
|
|
25
|
-
} );
|
|
26
|
-
},
|
|
27
|
-
} );
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Responds on success after authenticating as a user.
|
|
32
|
-
*
|
|
33
|
-
* This request handler assumes to follow one or more policy handler(s)
|
|
34
|
-
* which actually manage authentication.
|
|
35
|
-
*
|
|
36
|
-
* @param {Hitchy.Core.IncomingMessage} req request descriptor
|
|
37
|
-
* @param {Hitchy.Core.ServerResponse} res response manager
|
|
38
|
-
* @returns {void}
|
|
39
|
-
*/
|
|
40
|
-
static authenticate( req, res ) {
|
|
41
|
-
res.format( {
|
|
42
|
-
default() {
|
|
43
|
-
res.json( {
|
|
44
|
-
success: true,
|
|
45
|
-
authenticated: Boolean( req.user ),
|
|
46
|
-
} );
|
|
47
|
-
},
|
|
48
|
-
} );
|
|
49
|
-
}
|
|
16
|
+
static changePassword( req, res ) {
|
|
17
|
+
res.format( {
|
|
18
|
+
default() {
|
|
19
|
+
res.json( {
|
|
20
|
+
success: true
|
|
21
|
+
} );
|
|
22
|
+
},
|
|
23
|
+
} );
|
|
24
|
+
}
|
|
50
25
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} );
|
|
72
|
-
}
|
|
26
|
+
/**
|
|
27
|
+
* Responds on success after authenticating as a user.
|
|
28
|
+
*
|
|
29
|
+
* This request handler assumes to follow one or more policy handler(s)
|
|
30
|
+
* which actually manage authentication.
|
|
31
|
+
*
|
|
32
|
+
* @param {Hitchy.Core.IncomingMessage} req request descriptor
|
|
33
|
+
* @param {Hitchy.Core.ServerResponse} res response manager
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
static authenticate( req, res ) {
|
|
37
|
+
res.format( {
|
|
38
|
+
default() {
|
|
39
|
+
res.json( {
|
|
40
|
+
success: true,
|
|
41
|
+
authenticated: Boolean( req.user ),
|
|
42
|
+
} );
|
|
43
|
+
},
|
|
44
|
+
} );
|
|
45
|
+
}
|
|
73
46
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Provides status of current request's user being authenticated or not.
|
|
49
|
+
*
|
|
50
|
+
* @param {Hitchy.Core.IncomingMessage} req request descriptor
|
|
51
|
+
* @param {Hitchy.Core.ServerResponse} res response manager
|
|
52
|
+
* @returns {void}
|
|
53
|
+
*/
|
|
54
|
+
static getCurrent( req, res ) {
|
|
55
|
+
res.format( {
|
|
56
|
+
default() {
|
|
57
|
+
res.json( {
|
|
58
|
+
success: true,
|
|
59
|
+
authenticated: req.user ? {
|
|
60
|
+
uuid: req.user.uuid,
|
|
61
|
+
name: req.user.name,
|
|
62
|
+
strategy: req.user.strategy || "local",
|
|
63
|
+
roles: req.user.roles.map( role => role.name ),
|
|
64
|
+
} : false,
|
|
65
|
+
} );
|
|
66
|
+
},
|
|
67
|
+
} );
|
|
92
68
|
}
|
|
93
69
|
|
|
94
|
-
|
|
95
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Drops authentication of current request's user. This request handler
|
|
72
|
+
* is assuming to be a follow-up handler to some policy actually dropping
|
|
73
|
+
* the user's authentication.
|
|
74
|
+
*
|
|
75
|
+
* @param {Hitchy.Core.IncomingMessage} req request descriptor
|
|
76
|
+
* @param {Hitchy.Core.ServerResponse} res response manager
|
|
77
|
+
* @returns {void}
|
|
78
|
+
*/
|
|
79
|
+
static unauthenticate( req, res ) {
|
|
80
|
+
res.format( {
|
|
81
|
+
default() {
|
|
82
|
+
res.json( {
|
|
83
|
+
success: true,
|
|
84
|
+
} );
|
|
85
|
+
},
|
|
86
|
+
} );
|
|
87
|
+
}
|
|
96
88
|
|
|
89
|
+
static useCMP = false;
|
|
90
|
+
}
|
package/api/model/role.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Defines role model.
|
|
5
|
-
*
|
|
6
|
-
* @returns {Hitchy.Plugin.Odem.ModelSchema} definition of role model
|
|
7
|
-
*/
|
|
8
|
-
module.exports = function() {
|
|
1
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
9
2
|
const api = this;
|
|
10
3
|
const { models } = api;
|
|
11
4
|
|
|
@@ -15,7 +8,7 @@ module.exports = function() {
|
|
|
15
8
|
* @property {string} name unique name of user
|
|
16
9
|
*
|
|
17
10
|
* @name Hitchy.Plugin.Auth.Role
|
|
18
|
-
* @type Hitchy.Plugin.Odem.Model
|
|
11
|
+
* @type {Hitchy.Plugin.Odem.Model}
|
|
19
12
|
*/
|
|
20
13
|
return {
|
|
21
14
|
props: {
|
|
@@ -37,4 +30,4 @@ module.exports = function() {
|
|
|
37
30
|
},
|
|
38
31
|
},
|
|
39
32
|
};
|
|
40
|
-
}
|
|
33
|
+
}
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Defines model associating users with roles.
|
|
5
|
-
*
|
|
6
|
-
* @returns {Hitchy.Plugin.Odem.ModelSchema} definition of model associating users with roles
|
|
7
|
-
*/
|
|
8
|
-
module.exports = function() {
|
|
9
|
-
/**
|
|
10
2
|
* Implements model of a user suitable for authenticating as.
|
|
11
3
|
*
|
|
12
4
|
* @property {Buffer} role UUID of associated role
|
|
@@ -14,16 +6,15 @@ module.exports = function() {
|
|
|
14
6
|
*
|
|
15
7
|
* @name Hitchy.Plugin.Auth.UserToRole
|
|
16
8
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
role: {
|
|
24
|
-
required: true,
|
|
25
|
-
type: "uuid",
|
|
26
|
-
},
|
|
9
|
+
export default {
|
|
10
|
+
props: {
|
|
11
|
+
user: {
|
|
12
|
+
required: true,
|
|
13
|
+
type: "uuid",
|
|
27
14
|
},
|
|
28
|
-
|
|
15
|
+
role: {
|
|
16
|
+
required: true,
|
|
17
|
+
type: "uuid",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
29
20
|
};
|
package/api/model/user.js
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import Crypto from "node:crypto";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Defines user model.
|
|
7
|
-
*
|
|
8
|
-
* @returns {Hitchy.Plugin.Odem.ModelSchema} definition of user model
|
|
9
|
-
*/
|
|
10
|
-
module.exports = function() {
|
|
3
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
11
4
|
const api = this;
|
|
12
5
|
const { services, models } = api;
|
|
13
6
|
|
|
@@ -62,7 +55,7 @@ module.exports = function() {
|
|
|
62
55
|
|
|
63
56
|
async beforeSave( existsAlready, record ) {
|
|
64
57
|
if ( record.password ) {
|
|
65
|
-
record.password = await this.hashPassword( record.password );
|
|
58
|
+
record.password = await this.hashPassword( record.password );
|
|
66
59
|
}
|
|
67
60
|
|
|
68
61
|
return record;
|
|
@@ -105,7 +98,7 @@ module.exports = function() {
|
|
|
105
98
|
|
|
106
99
|
switch ( algorithmName || "SCRYPT" ) {
|
|
107
100
|
case "SSHA512" : {
|
|
108
|
-
const hash =
|
|
101
|
+
const hash = Crypto.createHash( "SHA512" );
|
|
109
102
|
hash.update( normalized );
|
|
110
103
|
hash.update( salt );
|
|
111
104
|
|
|
@@ -113,7 +106,7 @@ module.exports = function() {
|
|
|
113
106
|
}
|
|
114
107
|
|
|
115
108
|
case "SCRYPT" :
|
|
116
|
-
return await new Promise( ( resolve, reject ) =>
|
|
109
|
+
return await new Promise( ( resolve, reject ) => Crypto.scrypt( normalized, salt, 64, {
|
|
117
110
|
cost: 16384,
|
|
118
111
|
blockSize: 8,
|
|
119
112
|
parallelization: 1,
|
|
@@ -173,7 +166,7 @@ module.exports = function() {
|
|
|
173
166
|
* @returns {Promise<number>} promise for random integer in selected range
|
|
174
167
|
*/
|
|
175
168
|
function randomNumber( min, max ) {
|
|
176
|
-
return new Promise( ( resolve, reject ) =>
|
|
169
|
+
return new Promise( ( resolve, reject ) => Crypto.randomInt( min, max, ( error, number ) => {
|
|
177
170
|
if ( error ) {
|
|
178
171
|
reject( error );
|
|
179
172
|
} else {
|
|
@@ -189,7 +182,7 @@ module.exports = function() {
|
|
|
189
182
|
* @returns {Promise<Buffer>} promise for selected number of random octets
|
|
190
183
|
*/
|
|
191
184
|
function randomOctets( size ) {
|
|
192
|
-
return new Promise( ( resolve, reject ) =>
|
|
185
|
+
return new Promise( ( resolve, reject ) => Crypto.randomBytes( size, ( error, octets ) => {
|
|
193
186
|
if ( error ) {
|
|
194
187
|
reject( error );
|
|
195
188
|
} else {
|
|
@@ -227,4 +220,4 @@ module.exports = function() {
|
|
|
227
220
|
},
|
|
228
221
|
},
|
|
229
222
|
};
|
|
230
|
-
}
|
|
223
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function() {
|
|
1
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
const { models, service } = api;
|
|
6
4
|
|
|
@@ -78,7 +76,7 @@ module.exports = function() {
|
|
|
78
76
|
|
|
79
77
|
service.AuthManager.checkAuthentication( parts[1], parts[2] )
|
|
80
78
|
.then( user => {
|
|
81
|
-
req.user = user;
|
|
79
|
+
req.user = user;
|
|
82
80
|
|
|
83
81
|
this.qualifyAuthenticated( req, res, next );
|
|
84
82
|
} )
|
|
@@ -101,7 +99,7 @@ module.exports = function() {
|
|
|
101
99
|
|
|
102
100
|
req.fetchBody()
|
|
103
101
|
.then( body => {
|
|
104
|
-
req.body = body;
|
|
102
|
+
req.body = body;
|
|
105
103
|
|
|
106
104
|
return new Promise( ( resolve, reject ) => {
|
|
107
105
|
AuthenticationPassport.authenticate( strategy || defaultStrategy )( req, res, err => {
|
|
@@ -148,7 +146,7 @@ module.exports = function() {
|
|
|
148
146
|
|
|
149
147
|
service.AuthManager.listRolesOfUser( new models.User( uuid ) )
|
|
150
148
|
.then( roles => {
|
|
151
|
-
req.user.roles = roles;
|
|
149
|
+
req.user.roles = roles;
|
|
152
150
|
|
|
153
151
|
logDebug( "authenticated as", req.user.name );
|
|
154
152
|
|
|
@@ -197,11 +195,11 @@ module.exports = function() {
|
|
|
197
195
|
} );
|
|
198
196
|
} else {
|
|
199
197
|
// re-generating session on change of user authentication mitigates session-related attacks
|
|
200
|
-
// -> passport's logout does not seem to exist, so we can
|
|
198
|
+
// -> passport's logout does not seem to exist, so we can not rely on passport regenerating the session
|
|
201
199
|
await new Promise( ( resolve, reject ) => req.session.regenerate( error => ( error ? reject( error ) : resolve() ) ) );
|
|
202
200
|
}
|
|
203
201
|
|
|
204
|
-
req.user = undefined;
|
|
202
|
+
req.user = undefined;
|
|
205
203
|
|
|
206
204
|
res.set( "X-Authenticated-As", undefined );
|
|
207
205
|
res.set( "X-Authorized-As", undefined );
|
|
@@ -260,5 +258,4 @@ module.exports = function() {
|
|
|
260
258
|
}
|
|
261
259
|
|
|
262
260
|
return AuthenticationPolicy;
|
|
263
|
-
}
|
|
264
|
-
|
|
261
|
+
}
|
|
@@ -1,34 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Implements commonly useful policy handlers for testing a request's
|
|
3
|
+
* authorizations.
|
|
4
|
+
*/
|
|
5
|
+
export default class AuthorizationPolicy {
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
+
* Ensures current request's user has administrative privileges.
|
|
8
|
+
*
|
|
9
|
+
* @param {Hitchy.Core.IncomingMessage} req request descriptor
|
|
10
|
+
* @param {Hitchy.Core.ServerResponse} res response manager
|
|
11
|
+
* @param {Hitchy.Core.ContinuationHandler} next invoke to continue request handling
|
|
12
|
+
* @returns {void}
|
|
7
13
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* Ensures current request's user has administrative privileges.
|
|
11
|
-
*
|
|
12
|
-
* @param {Hitchy.Core.IncomingMessage} req request descriptor
|
|
13
|
-
* @param {Hitchy.Core.ServerResponse} res response manager
|
|
14
|
-
* @param {Hitchy.Core.ContinuationHandler} next invoke to continue request handling
|
|
15
|
-
* @returns {void}
|
|
16
|
-
*/
|
|
17
|
-
static mustBeAdmin( req, res, next ) {
|
|
18
|
-
const { adminRole } = this.services.AuthManager;
|
|
19
|
-
|
|
20
|
-
if ( req.user && Array.isArray( req.user.roles ) && req.user.roles.some( role => role.name === adminRole ) ) {
|
|
21
|
-
next();
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
14
|
+
static mustBeAdmin( req, res, next ) {
|
|
15
|
+
const { adminRole } = this.services.AuthManager;
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
error: "access forbidden: must be admin",
|
|
29
|
-
} );
|
|
17
|
+
if ( req.user && Array.isArray( req.user.roles ) && req.user.roles.some( role => role.name === adminRole ) ) {
|
|
18
|
+
next();
|
|
19
|
+
return;
|
|
30
20
|
}
|
|
21
|
+
|
|
22
|
+
res
|
|
23
|
+
.status( 403 )
|
|
24
|
+
.json( {
|
|
25
|
+
error: "access forbidden: must be admin",
|
|
26
|
+
} );
|
|
31
27
|
}
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
}
|
|
29
|
+
static useCMP = false;
|
|
30
|
+
}
|
package/api/policy/user.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function() {
|
|
1
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
const { models } = api;
|
|
6
4
|
|
|
@@ -22,7 +20,7 @@ module.exports = function() {
|
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
res.status( 403 ).json( {
|
|
25
|
-
error: "access forbidden: this
|
|
23
|
+
error: "access forbidden: this is not you"
|
|
26
24
|
} );
|
|
27
25
|
},
|
|
28
26
|
|
|
@@ -130,4 +128,4 @@ module.exports = function() {
|
|
|
130
128
|
next();
|
|
131
129
|
}
|
|
132
130
|
};
|
|
133
|
-
}
|
|
131
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function() {
|
|
1
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
const { models, services } = api;
|
|
6
4
|
|
|
@@ -63,7 +61,7 @@ module.exports = function() {
|
|
|
63
61
|
const { Role } = models;
|
|
64
62
|
|
|
65
63
|
if ( !( role instanceof Role ) ) {
|
|
66
|
-
role = String( role );
|
|
64
|
+
role = String( role );
|
|
67
65
|
|
|
68
66
|
if ( !/^[a-z_]/i.test( role ) || /\s/.test( role ) ) {
|
|
69
67
|
throw new TypeError( "missing role information" );
|
|
@@ -290,4 +288,4 @@ module.exports = function() {
|
|
|
290
288
|
}
|
|
291
289
|
|
|
292
290
|
return AuthManager;
|
|
293
|
-
}
|
|
291
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import PassportLib from "passport";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
module.exports = function() {
|
|
3
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
6
4
|
const api = this;
|
|
7
5
|
|
|
8
6
|
const logAlert = api.log( "hitchy:auth:alert" );
|
|
@@ -28,7 +26,7 @@ module.exports = function() {
|
|
|
28
26
|
} );
|
|
29
27
|
|
|
30
28
|
passport.deserializeUser( ( uuid, done ) => {
|
|
31
|
-
/** @type Hitchy.Plugin.Auth.User */
|
|
29
|
+
/** @type {Hitchy.Plugin.Auth.User} */
|
|
32
30
|
const user = new User( uuid );
|
|
33
31
|
|
|
34
32
|
user.$exists
|
|
@@ -68,4 +66,4 @@ module.exports = function() {
|
|
|
68
66
|
};
|
|
69
67
|
|
|
70
68
|
return passport;
|
|
71
|
-
}
|
|
69
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const LocalStrategy = require( "passport-local" ).Strategy;
|
|
1
|
+
import { Strategy as LocalStrategy } from "passport-local";
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* Temporarily tracks additional session data per remotely authenticated user.
|
|
@@ -15,7 +13,7 @@ const LocalStrategy = require( "passport-local" ).Strategy;
|
|
|
15
13
|
*/
|
|
16
14
|
const RemoteAuthCustomData = new Map();
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
19
17
|
const api = this;
|
|
20
18
|
const { models, services } = api;
|
|
21
19
|
|
|
@@ -62,7 +60,7 @@ module.exports = function() {
|
|
|
62
60
|
*
|
|
63
61
|
* @param {string} username name of user to authenticate
|
|
64
62
|
* @param {string} password named user's password for authentication
|
|
65
|
-
* @param {function(Error?, object, object)} done invoked with optional error, authenticated user or some message as feedback
|
|
63
|
+
* @param {function(Error?, object, object):void} done invoked with optional error, authenticated user or some message as feedback
|
|
66
64
|
* @returns {void}
|
|
67
65
|
*/
|
|
68
66
|
static checkAuthentication( username, password, done ) {
|
|
@@ -136,7 +134,7 @@ module.exports = function() {
|
|
|
136
134
|
* @param {Hitchy.Plugin.Auth.SamlConfig} config SAML protocol configuration
|
|
137
135
|
* @returns {Strategy} generated strategy for use with passport.js
|
|
138
136
|
*/
|
|
139
|
-
static generateSaml( strategyName, config ) {
|
|
137
|
+
static async generateSaml( strategyName, config ) {
|
|
140
138
|
const verifyLocalProfileOnLogin = ( req, userInfo, done ) => {
|
|
141
139
|
RemoteAuthCustomData.set( `${strategyName}:${userInfo.nameID}`, { ...userInfo } );
|
|
142
140
|
|
|
@@ -147,7 +145,7 @@ module.exports = function() {
|
|
|
147
145
|
getLocalProfile( strategyName, userInfo.nameID, false, done );
|
|
148
146
|
};
|
|
149
147
|
|
|
150
|
-
const { Strategy } =
|
|
148
|
+
const { Strategy } = await import( "passport-saml" );
|
|
151
149
|
const strategy = new Strategy( {
|
|
152
150
|
...config,
|
|
153
151
|
passReqToCallback: true,
|
|
@@ -224,15 +222,15 @@ module.exports = function() {
|
|
|
224
222
|
* supporting OpenID Connect with Authorization Code Flow.
|
|
225
223
|
*
|
|
226
224
|
* @param {string} strategyName name of resulting strategy in context of your application
|
|
227
|
-
* @param {
|
|
225
|
+
* @param {object} config OpenID Connect client configuration
|
|
228
226
|
* @returns {Promise<Strategy>} promises generated strategy for use with passport.js
|
|
229
227
|
*/
|
|
230
|
-
static async generateOpenIdConnect( strategyName, config ) {
|
|
228
|
+
static async generateOpenIdConnect( strategyName, config ) {
|
|
231
229
|
const verifyLocalProfileOnLogin = ( req, tokens, userInfo, done ) => {
|
|
232
230
|
getLocalProfile( strategyName, userInfo.preferred_username, true, done );
|
|
233
231
|
};
|
|
234
232
|
|
|
235
|
-
const { Issuer, Strategy, generators } =
|
|
233
|
+
const { Issuer, Strategy, generators } = await import( "openid-client" );
|
|
236
234
|
const issuer = await Issuer.discover( config.discovery_url );
|
|
237
235
|
const client = new issuer.Client( config );
|
|
238
236
|
|
|
@@ -248,7 +246,7 @@ module.exports = function() {
|
|
|
248
246
|
return Promise.resolve( false );
|
|
249
247
|
}
|
|
250
248
|
|
|
251
|
-
const state = req.session[key] = generators.state( 32 );
|
|
249
|
+
const state = req.session[key] = generators.state( 32 );
|
|
252
250
|
|
|
253
251
|
// redirect user to discovered end_session_url of IdP
|
|
254
252
|
req.context.response.redirect( 302, client.endSessionUrl( { state } ) );
|
|
@@ -280,4 +278,4 @@ module.exports = function() {
|
|
|
280
278
|
}
|
|
281
279
|
|
|
282
280
|
return AuthenticationStrategies;
|
|
283
|
-
}
|
|
281
|
+
}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function() {
|
|
1
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
4
2
|
/**
|
|
5
3
|
* Implements behavior of a single node in a hierarchy of authorization
|
|
6
4
|
* rules.
|
|
7
5
|
*
|
|
8
6
|
* @property {?AuthorizationNode} parent refers to superordinated node
|
|
9
7
|
* @property {?string} name name of segment addressing this node in context of its parent
|
|
10
|
-
* @property {{accept:
|
|
11
|
-
* @property {{accept:
|
|
12
|
-
* @property {
|
|
8
|
+
* @property {{accept: Object<string, number>, reject: Object<string, number>}} roles maps names of roles into number of rules requesting to accept/reject
|
|
9
|
+
* @property {{accept: Object<string, number>, reject: Object<string, number>}} users lists UUIDs of users to accept/reject in context of node
|
|
10
|
+
* @property {Object<string,AuthorizationNode>} children maps relative names into subordinated nodes
|
|
13
11
|
*/
|
|
14
12
|
class AuthorizationNode {
|
|
15
13
|
/**
|
|
@@ -193,7 +191,7 @@ module.exports = function() {
|
|
|
193
191
|
/**
|
|
194
192
|
* Indicates if current node lacks any viable information.
|
|
195
193
|
*
|
|
196
|
-
* @returns {boolean} true if node
|
|
194
|
+
* @returns {boolean} true if node has not any viable information affecting authorization checks
|
|
197
195
|
*/
|
|
198
196
|
isSpare() {
|
|
199
197
|
for ( const list of [ this.users.accept, this.users.reject, this.roles.accept, this.roles.reject ] ) {
|
|
@@ -242,7 +240,7 @@ module.exports = function() {
|
|
|
242
240
|
* Transfer every valuable information to a clone of current node and
|
|
243
241
|
* return it.
|
|
244
242
|
*
|
|
245
|
-
* @returns {AuthorizationNode} clone of current node limited to currently valuable data, undefined if node
|
|
243
|
+
* @returns {AuthorizationNode} clone of current node limited to currently valuable data, undefined if node is not required anymore
|
|
246
244
|
*/
|
|
247
245
|
gc() {
|
|
248
246
|
const children = this.children;
|
|
@@ -293,4 +291,4 @@ module.exports = function() {
|
|
|
293
291
|
}
|
|
294
292
|
|
|
295
293
|
return AuthorizationNode;
|
|
296
|
-
}
|
|
294
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function() {
|
|
1
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
|
|
6
4
|
const logError = api.log( "hitchy:plugin:auth:error" );
|
|
@@ -12,11 +10,11 @@ module.exports = function() {
|
|
|
12
10
|
class AuthorizationPolicyGenerator {
|
|
13
11
|
/**
|
|
14
12
|
* Generates policy handler testing a request's user for having any of
|
|
15
|
-
* the listed roles and instantly rejecting requests of users who
|
|
13
|
+
* the listed roles and instantly rejecting requests of users who do not.
|
|
16
14
|
* Request is also rejected of request's user is unknown.
|
|
17
15
|
*
|
|
18
16
|
* @param {string|string[]} roles lists roles to accept
|
|
19
|
-
* @returns {Hitchy.Core.RequestPolicyHandler} policy handler rejecting requests of users who
|
|
17
|
+
* @returns {Hitchy.Core.RequestPolicyHandler} policy handler rejecting requests of users who does not have any of the listed roles
|
|
20
18
|
*/
|
|
21
19
|
static hasRole( roles ) {
|
|
22
20
|
if ( !roles ) {
|
|
@@ -35,7 +33,7 @@ module.exports = function() {
|
|
|
35
33
|
|
|
36
34
|
return ( req, res, next ) => {
|
|
37
35
|
if ( req.user && Array.isArray( req.user.roles ) ) {
|
|
38
|
-
const { adminRole } = req.hitchy.
|
|
36
|
+
const { adminRole } = req.hitchy.services.AuthManager;
|
|
39
37
|
const userRoles = req.user.roles;
|
|
40
38
|
const numUserRoles = userRoles.length;
|
|
41
39
|
|
|
@@ -60,10 +58,10 @@ module.exports = function() {
|
|
|
60
58
|
/**
|
|
61
59
|
* Generates policy handler testing a request's user for being
|
|
62
60
|
* authorized to access at least one of the listed resources and
|
|
63
|
-
* instantly rejecting requests of users who
|
|
61
|
+
* instantly rejecting requests of users who do not.
|
|
64
62
|
*
|
|
65
63
|
* @param {string|string[]} resource name(s) of resource(s)
|
|
66
|
-
* @returns {Hitchy.Core.RequestPolicyHandler} policy handler rejecting requests of users who
|
|
64
|
+
* @returns {Hitchy.Core.RequestPolicyHandler} policy handler rejecting requests of users who must not access any of the named resources
|
|
67
65
|
*/
|
|
68
66
|
static mayAccess( resource ) {
|
|
69
67
|
if ( !resource ) {
|
|
@@ -92,4 +90,4 @@ module.exports = function() {
|
|
|
92
90
|
}
|
|
93
91
|
|
|
94
92
|
return AuthorizationPolicyGenerator;
|
|
95
|
-
}
|
|
93
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function() {
|
|
1
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
const { services, models } = api;
|
|
6
4
|
|
|
@@ -300,4 +298,4 @@ module.exports = function() {
|
|
|
300
298
|
}
|
|
301
299
|
|
|
302
300
|
return AuthorizationTree;
|
|
303
|
-
}
|
|
301
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function() {
|
|
1
|
+
export default function() { // eslint-disable-line jsdoc/require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
const { service } = api;
|
|
6
4
|
|
|
@@ -22,8 +20,8 @@ module.exports = function() {
|
|
|
22
20
|
const adminRole = service.AuthManager.adminRole;
|
|
23
21
|
|
|
24
22
|
if ( adminRole && Array.isArray( userInfo?.roles ) ) {
|
|
25
|
-
for (
|
|
26
|
-
if (
|
|
23
|
+
for ( let i = 0; i < userInfo.roles.length; i++ ) {
|
|
24
|
+
if ( userInfo.roles[i]?.name === adminRole ) {
|
|
27
25
|
return true;
|
|
28
26
|
}
|
|
29
27
|
}
|
|
@@ -70,4 +68,4 @@ module.exports = function() {
|
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
return AuthorizationService;
|
|
73
|
-
}
|
|
71
|
+
}
|
package/api/service/session.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function( options, HitchyPluginSession ) {
|
|
1
|
+
export default function( options, HitchyPluginSession ) { // eslint-disable-line jsdoc/require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
|
|
6
4
|
const logDebug = api.log( "hitchy:auth:debug" );
|
|
@@ -93,7 +91,7 @@ module.exports = function( options, HitchyPluginSession ) {
|
|
|
93
91
|
*
|
|
94
92
|
* This method is used by passport.
|
|
95
93
|
*
|
|
96
|
-
* @param {function(
|
|
94
|
+
* @param {function((Error|undefined)): void} doneFn callback invoked when session has been re-generated or some error occurred
|
|
97
95
|
* @returns {void}
|
|
98
96
|
*/
|
|
99
97
|
regenerate( doneFn ) {
|
|
@@ -134,7 +132,7 @@ module.exports = function( options, HitchyPluginSession ) {
|
|
|
134
132
|
*
|
|
135
133
|
* This method is used by passport.
|
|
136
134
|
*
|
|
137
|
-
* @param {function(
|
|
135
|
+
* @param {function((Error|undefined)):void} doneFn callback invoked when session has been saved or some error occurred
|
|
138
136
|
* @returns {void}
|
|
139
137
|
*/
|
|
140
138
|
save( doneFn ) {
|
|
@@ -147,4 +145,4 @@ module.exports = function( options, HitchyPluginSession ) {
|
|
|
147
145
|
}
|
|
148
146
|
|
|
149
147
|
return PassportCompatibleSession;
|
|
150
|
-
}
|
|
148
|
+
}
|
package/config/auth.js
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
roles: [
|
|
15
|
-
// "customers",
|
|
16
|
-
// "testers",
|
|
17
|
-
],
|
|
18
|
-
}
|
|
19
|
-
};
|
|
1
|
+
export const auth = {
|
|
2
|
+
prefix: "/api/auth",
|
|
3
|
+
strategies: {
|
|
4
|
+
// local: this.services.AuthStrategies.generateLocal()
|
|
5
|
+
},
|
|
6
|
+
authorizations: {
|
|
7
|
+
// "feature.export.pdf": "@customers"
|
|
8
|
+
// "feature.export.text": "@customers,@testers"
|
|
9
|
+
},
|
|
10
|
+
roles: [
|
|
11
|
+
// "customers",
|
|
12
|
+
// "testers",
|
|
13
|
+
],
|
|
20
14
|
};
|
package/hash-password.js
CHANGED
|
@@ -1,41 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* The MIT License (MIT)
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) 2020 cepharum GmbH
|
|
7
|
-
*
|
|
8
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
-
* in the Software without restriction, including without limitation the rights
|
|
11
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
-
* furnished to do so, subject to the following conditions:
|
|
14
|
-
*
|
|
15
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
-
* copies or substantial portions of the Software.
|
|
17
|
-
*
|
|
18
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
-
* SOFTWARE.
|
|
25
|
-
*
|
|
26
|
-
* @author: cepharum
|
|
27
|
-
*/
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import UserModel from "./api/model/user.js";
|
|
28
3
|
|
|
29
|
-
"use strict";
|
|
30
|
-
|
|
31
|
-
const { hashPassword } = require( "./api/model/user" )().methods;
|
|
32
4
|
const chunks = [];
|
|
33
5
|
|
|
34
6
|
process.stdin.on( "data", chunk => chunks.push( chunk ) );
|
|
35
|
-
process.stdin.
|
|
36
|
-
|
|
7
|
+
process.stdin.once( "end", () => {
|
|
8
|
+
const input = Buffer.concat( chunks );
|
|
9
|
+
|
|
10
|
+
UserModel.call( { services: {}, models: {} } )
|
|
11
|
+
.methods.hashPassword( input )
|
|
37
12
|
.then( password => {
|
|
38
|
-
console.log( password );
|
|
13
|
+
console.log( password ); // eslint-disable-line no-console
|
|
39
14
|
} )
|
|
40
15
|
.catch( console.error );
|
|
41
16
|
} );
|
package/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function( options, plugins ) {
|
|
1
|
+
export default function( options, plugins ) { // eslint-disable-line jsdoc/require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
|
|
6
4
|
const logWarning = api.log( "hitchy:session:warning" );
|
|
@@ -32,7 +30,9 @@ module.exports = function( options, plugins ) {
|
|
|
32
30
|
const roles = api.config.auth.roles;
|
|
33
31
|
|
|
34
32
|
if ( Array.isArray( roles ) && roles.length > 0 ) {
|
|
35
|
-
for (
|
|
33
|
+
for ( let i = 0; i < roles.length; i++ ) {
|
|
34
|
+
const roleName = roles[i];
|
|
35
|
+
|
|
36
36
|
if ( typeof roleName === "string" && roleName && !/\s/.test( roleName.trim() ) ) {
|
|
37
37
|
await AuthManager.asRole( roleName.trim(), true ); // eslint-disable-line no-await-in-loop
|
|
38
38
|
} else {
|
|
@@ -53,7 +53,7 @@ module.exports = function( options, plugins ) {
|
|
|
53
53
|
const policies = {};
|
|
54
54
|
|
|
55
55
|
if ( session.disable ) {
|
|
56
|
-
logWarning( "server-side sessions are disabled, thus passport
|
|
56
|
+
logWarning( "server-side sessions are disabled, thus passport is not integrated for commonly handling all requests automatically" );
|
|
57
57
|
|
|
58
58
|
policies["/"] = [
|
|
59
59
|
"authentication.handleBasicAuth",
|
|
@@ -67,7 +67,7 @@ module.exports = function( options, plugins ) {
|
|
|
67
67
|
|
|
68
68
|
return prefix === false ? policies : {
|
|
69
69
|
...policies,
|
|
70
|
-
[`POST ${prefix}/login/:strategy
|
|
70
|
+
[`POST ${prefix}/login{/:strategy}`]: ["authentication.login"],
|
|
71
71
|
[`GET ${prefix}/login/:strategy`]: ["authentication.login"],
|
|
72
72
|
[`GET ${prefix}/logout`]: ["authentication.logout"],
|
|
73
73
|
[`POST ${prefix}/password`]: ["user.changePassword"],
|
|
@@ -80,7 +80,7 @@ module.exports = function( options, plugins ) {
|
|
|
80
80
|
|
|
81
81
|
return prefix === false ? {} : {
|
|
82
82
|
after: {
|
|
83
|
-
[`POST ${prefix}/login/:strategy
|
|
83
|
+
[`POST ${prefix}/login{/:strategy}`]: "user.authenticate",
|
|
84
84
|
[`GET ${prefix}/login/:strategy`]: "user.authenticate",
|
|
85
85
|
[`GET ${prefix}/current`]: "user.getCurrent",
|
|
86
86
|
[`GET ${prefix}/logout`]: "user.unauthenticate",
|
|
@@ -98,4 +98,4 @@ module.exports = function( options, plugins ) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
return pluginApi;
|
|
101
|
-
}
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hitchy/plugin-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "user authentication and authorization for Hitchy",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "index.js",
|
|
6
7
|
"types": "index.d.ts",
|
|
7
8
|
"scripts": {
|
|
@@ -11,6 +12,9 @@
|
|
|
11
12
|
"docs:dev": "vitepress dev docs",
|
|
12
13
|
"docs:build": "vitepress build docs"
|
|
13
14
|
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"hash-password": "./hash-password.js"
|
|
17
|
+
},
|
|
14
18
|
"repository": {
|
|
15
19
|
"type": "git",
|
|
16
20
|
"url": "https://gitlab.com/hitchy/plugin-auth.git"
|
|
@@ -22,24 +26,26 @@
|
|
|
22
26
|
},
|
|
23
27
|
"homepage": "https://auth.hitchy.org",
|
|
24
28
|
"peerDependencies": {
|
|
25
|
-
"@hitchy/core": "
|
|
26
|
-
"@hitchy/plugin-odem": "0.9.x",
|
|
29
|
+
"@hitchy/core": "1.x",
|
|
27
30
|
"@hitchy/plugin-cookies": "0.1.x",
|
|
31
|
+
"@hitchy/plugin-odem": "0.9.x",
|
|
28
32
|
"@hitchy/plugin-session": "0.4.x"
|
|
29
33
|
},
|
|
30
34
|
"devDependencies": {
|
|
31
|
-
"@hitchy/
|
|
35
|
+
"@hitchy/core": "^1.0.1",
|
|
36
|
+
"@hitchy/server-dev-tools": "^0.8.5",
|
|
32
37
|
"@hitchy/types": "^0.1.3",
|
|
33
38
|
"c8": "^10.1.2",
|
|
34
|
-
"eslint": "^
|
|
35
|
-
"eslint-config-cepharum": "^
|
|
36
|
-
"
|
|
37
|
-
"mocha": "^10.
|
|
38
|
-
"openid-client": "^5.
|
|
39
|
+
"eslint": "^9.15.0",
|
|
40
|
+
"eslint-config-cepharum": "^2.0.2",
|
|
41
|
+
"mermaid": "^11.4.0",
|
|
42
|
+
"mocha": "^10.8.2",
|
|
43
|
+
"openid-client": "^5.7.0",
|
|
39
44
|
"passport-saml": "^3.2.4",
|
|
40
45
|
"should": "^13.2.3",
|
|
41
46
|
"should-http": "^0.1.1",
|
|
42
|
-
"vitepress": "^1.
|
|
47
|
+
"vitepress": "^1.5.0",
|
|
48
|
+
"vitepress-plugin-mermaid": "^2.0.17"
|
|
43
49
|
},
|
|
44
50
|
"dependencies": {
|
|
45
51
|
"passport": "^0.7.0",
|
|
@@ -51,6 +57,6 @@
|
|
|
51
57
|
"hash-password.js",
|
|
52
58
|
"hitchy.json",
|
|
53
59
|
"index.d.ts",
|
|
54
|
-
"index.
|
|
60
|
+
"index.cjs"
|
|
55
61
|
]
|
|
56
62
|
}
|