@hitchy/plugin-auth 0.4.5 → 0.5.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/LICENSE +1 -1
- package/api/controller/user.js +83 -89
- package/api/model/authorization/rule.js +2 -4
- package/api/model/role.js +2 -9
- package/api/model/user-to-role.js +10 -19
- package/api/model/user.js +7 -14
- package/api/policy/authentication.js +3 -6
- package/api/policy/authorization.js +24 -28
- package/api/policy/user.js +3 -5
- package/api/service/auth/manager.js +2 -4
- package/api/service/authentication/passport.js +3 -5
- package/api/service/authentication/strategies.js +6 -8
- package/api/service/authorization/node.js +4 -6
- package/api/service/authorization/policy-generator.js +7 -9
- package/api/service/authorization/tree.js +2 -4
- package/api/service/authorization.js +23 -6
- package/api/service/session.js +2 -4
- package/config/auth.js +13 -19
- package/hash-password.js +7 -32
- package/index.js +8 -8
- package/package.json +16 -9
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 require-jsdoc
|
|
9
2
|
const api = this;
|
|
10
3
|
const { models } = api;
|
|
11
4
|
|
|
@@ -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 require-jsdoc
|
|
11
4
|
const api = this;
|
|
12
5
|
const { services, models } = api;
|
|
13
6
|
|
|
@@ -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 require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
const { models, service } = api;
|
|
6
4
|
|
|
@@ -197,7 +195,7 @@ 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
|
|
|
@@ -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 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 require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
const { models, services } = api;
|
|
6
4
|
|
|
@@ -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 require-jsdoc
|
|
6
4
|
const api = this;
|
|
7
5
|
|
|
8
6
|
const logAlert = api.log( "hitchy:auth:alert" );
|
|
@@ -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 require-jsdoc
|
|
19
17
|
const api = this;
|
|
20
18
|
const { models, services } = api;
|
|
21
19
|
|
|
@@ -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,
|
|
@@ -232,7 +230,7 @@ module.exports = function() {
|
|
|
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
|
|
|
@@ -280,4 +278,4 @@ module.exports = function() {
|
|
|
280
278
|
}
|
|
281
279
|
|
|
282
280
|
return AuthenticationStrategies;
|
|
283
|
-
}
|
|
281
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function() {
|
|
1
|
+
export default function() { // eslint-disable-line require-jsdoc
|
|
4
2
|
/**
|
|
5
3
|
* Implements behavior of a single node in a hierarchy of authorization
|
|
6
4
|
* rules.
|
|
@@ -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 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 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 require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
const { service } = api;
|
|
6
4
|
|
|
@@ -11,6 +9,27 @@ module.exports = function() {
|
|
|
11
9
|
* @alias Authorization
|
|
12
10
|
*/
|
|
13
11
|
class AuthorizationService {
|
|
12
|
+
/**
|
|
13
|
+
* Checks if described user is an administrator with privileged access.
|
|
14
|
+
*
|
|
15
|
+
* @param {User|string|undefined} user user to check, may be undefined if no user is known
|
|
16
|
+
* @returns {Promise<boolean>} promises result of check whether given user has privileged access or not
|
|
17
|
+
*/
|
|
18
|
+
static async isAdmin( user ) {
|
|
19
|
+
const userInfo = user ? await service.AuthManager.asUser( user ).catch( () => undefined ) : undefined;
|
|
20
|
+
const adminRole = service.AuthManager.adminRole;
|
|
21
|
+
|
|
22
|
+
if ( adminRole && Array.isArray( userInfo?.roles ) ) {
|
|
23
|
+
for ( let i = 0; i < userInfo.roles.length; i++ ) {
|
|
24
|
+
if ( userInfo.roles[i]?.name === adminRole ) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
/**
|
|
15
34
|
* Checks if given user may access named resource(s).
|
|
16
35
|
*
|
|
@@ -25,9 +44,7 @@ module.exports = function() {
|
|
|
25
44
|
}
|
|
26
45
|
|
|
27
46
|
const userInfo = user ? await service.AuthManager.asUser( user ).catch( () => undefined ) : undefined;
|
|
28
|
-
|
|
29
47
|
const resources = Array.isArray( resource ) ? resource : [resource];
|
|
30
|
-
|
|
31
48
|
const roleNames = Array.isArray( userInfo?.roles ) ? userInfo.roles.map( role => role.name ) : [];
|
|
32
49
|
|
|
33
50
|
if ( roleNames.indexOf( service.AuthManager.adminRole ) > -1 ) {
|
|
@@ -51,4 +68,4 @@ module.exports = function() {
|
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
return AuthorizationService;
|
|
54
|
-
}
|
|
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 require-jsdoc
|
|
4
2
|
const api = this;
|
|
5
3
|
|
|
6
4
|
const logDebug = api.log( "hitchy:auth:debug" );
|
|
@@ -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,39 +1,14 @@
|
|
|
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
13
|
console.log( password );
|
|
39
14
|
} )
|
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 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.0",
|
|
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,27 @@
|
|
|
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.4",
|
|
32
37
|
"@hitchy/types": "^0.1.3",
|
|
33
38
|
"c8": "^10.1.2",
|
|
34
|
-
"eslint": "^8.57.
|
|
39
|
+
"eslint": "^8.57.1",
|
|
35
40
|
"eslint-config-cepharum": "^1.0.14",
|
|
36
41
|
"eslint-plugin-promise": "^6.6.0",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
42
|
+
"mermaid": "^11.4.0",
|
|
43
|
+
"mocha": "^10.8.2",
|
|
44
|
+
"openid-client": "^5.7.0",
|
|
39
45
|
"passport-saml": "^3.2.4",
|
|
40
46
|
"should": "^13.2.3",
|
|
41
47
|
"should-http": "^0.1.1",
|
|
42
|
-
"vitepress": "^1.
|
|
48
|
+
"vitepress": "^1.5.0",
|
|
49
|
+
"vitepress-plugin-mermaid": "^2.0.17"
|
|
43
50
|
},
|
|
44
51
|
"dependencies": {
|
|
45
52
|
"passport": "^0.7.0",
|
|
@@ -51,6 +58,6 @@
|
|
|
51
58
|
"hash-password.js",
|
|
52
59
|
"hitchy.json",
|
|
53
60
|
"index.d.ts",
|
|
54
|
-
"index.
|
|
61
|
+
"index.cjs"
|
|
55
62
|
]
|
|
56
63
|
}
|