@tiledesk/tiledesk-server 2.22.7 → 2.23.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/CHANGELOG.md +9 -1
- package/app.js +1 -1
- package/middleware/has-role.js +28 -7
- package/middleware/passport.js +1 -1
- package/package.json +1 -1
- package/routes/authtestWithRoleCheck.js +6 -0
- package/routes/integration.js +7 -7
- package/routes/jwt.js +30 -41
- package/test/authenticationJwt.js +155 -1
- package/test/hasRole.js +127 -0
- package/test/jwtRoute.js +16 -117
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,15 @@
|
|
|
5
5
|
🚀 IN PRODUCTION 🚀
|
|
6
6
|
(https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
|
|
7
7
|
|
|
8
|
-
# 2.
|
|
8
|
+
# 2.23.1
|
|
9
|
+
- Restored cross-project access for global subscriptions (`id_project: '*'` or `global: true`); bots and project-bound subscriptions remain scoped to their own project
|
|
10
|
+
|
|
11
|
+
# 2.23.0
|
|
12
|
+
- Deprecated JWT helper routes (`/jwt/decode`, `/jwt/generatetestjwt`); they now return 410
|
|
13
|
+
- Restricted bot and subscription tokens to their own project (cross-project access denied)
|
|
14
|
+
- Improved error response sanitization for integration routes
|
|
15
|
+
|
|
16
|
+
# 2.22.7
|
|
9
17
|
- Addressed and resolved a security vulnerability
|
|
10
18
|
- Updated tybot-connector to 2.1.11
|
|
11
19
|
|
package/app.js
CHANGED
|
@@ -622,7 +622,7 @@ app.use('/:projectid/publicanalytics', publicAnalytics);
|
|
|
622
622
|
|
|
623
623
|
app.use('/:projectid/keys', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], key);
|
|
624
624
|
|
|
625
|
-
//
|
|
625
|
+
// Deprecated helper routes (/jwt/decode, /jwt/generatetestjwt) — respond with 410.
|
|
626
626
|
app.use('/:projectid/jwt', jwtroute);
|
|
627
627
|
|
|
628
628
|
|
package/middleware/has-role.js
CHANGED
|
@@ -36,14 +36,35 @@ class RoleChecker {
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Bot and subscription tokens must only authorize the project they belong to.
|
|
41
|
+
* Fail closed if id_project is missing.
|
|
42
|
+
*/
|
|
43
|
+
belongsToProject(user, projectid) {
|
|
44
|
+
if (!user || user.id_project == null || projectid == null) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (!(user instanceof Faq_kb) && (String(user.id_project) === '*' || user.global === true)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return String(user.id_project) === String(projectid);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
isTypeAsFunction(type, user, projectid) {
|
|
40
54
|
winston.debug("isType:"+type);
|
|
41
55
|
winston.debug("user", user);
|
|
42
|
-
//TODO Check if belongs to project
|
|
43
56
|
if (type=='subscription' && user instanceof Subscription){
|
|
57
|
+
if (!this.belongsToProject(user, projectid)) {
|
|
58
|
+
winston.warn("Subscription " + (user && user._id) + " denied cross-project access to " + projectid);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
44
61
|
winston.debug("isTypeAsFunction is subscription");
|
|
45
62
|
return true
|
|
46
63
|
} else if (type=='bot' && user instanceof Faq_kb){
|
|
64
|
+
if (!this.belongsToProject(user, projectid)) {
|
|
65
|
+
winston.warn("Bot " + (user && user._id) + " denied cross-project access to " + projectid);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
47
68
|
winston.debug("isTypeAsFunction is bot");
|
|
48
69
|
return true
|
|
49
70
|
} else {
|
|
@@ -51,7 +72,7 @@ class RoleChecker {
|
|
|
51
72
|
|
|
52
73
|
var adminEmail = process.env.ADMIN_EMAIL || "admin@tiledesk.com";
|
|
53
74
|
|
|
54
|
-
if (user && user.email && user.email === adminEmail) { //skip has role check
|
|
75
|
+
if (user && user.email && user.email === adminEmail) { //skip has role check
|
|
55
76
|
return true;
|
|
56
77
|
}
|
|
57
78
|
return false;
|
|
@@ -59,7 +80,7 @@ class RoleChecker {
|
|
|
59
80
|
}
|
|
60
81
|
|
|
61
82
|
|
|
62
|
-
isTypesAsFunction(types, user) {
|
|
83
|
+
isTypesAsFunction(types, user, projectid) {
|
|
63
84
|
winston.debug("isTypes:"+types);
|
|
64
85
|
winston.debug("user", user);
|
|
65
86
|
var isType = false;
|
|
@@ -69,7 +90,7 @@ class RoleChecker {
|
|
|
69
90
|
try {
|
|
70
91
|
types.forEach(type => {
|
|
71
92
|
winston.debug("type:"+type);
|
|
72
|
-
isType = this.isTypeAsFunction(type, user);
|
|
93
|
+
isType = this.isTypeAsFunction(type, user, projectid);
|
|
73
94
|
winston.debug("isType:"+ isType);
|
|
74
95
|
if (isType==true) {
|
|
75
96
|
throw BreakException; //https://stackoverflow.com/questions/2641347/short-circuit-array-foreach-like-calling-break
|
|
@@ -191,12 +212,12 @@ class RoleChecker {
|
|
|
191
212
|
// console.log("QUIIIIIIIIIIIIIIIIIIIIIII",type);
|
|
192
213
|
if (types && types.length>0) {
|
|
193
214
|
// console.log("QUIIIIIIIIIIIIIIIIIIIIIII");
|
|
194
|
-
var checkRes = that.isTypesAsFunction(types, req.user);
|
|
215
|
+
var checkRes = that.isTypesAsFunction(types, req.user, projectid);
|
|
195
216
|
winston.debug("checkRes: " + checkRes);
|
|
196
217
|
|
|
197
218
|
if (checkRes) {
|
|
198
219
|
return next();
|
|
199
|
-
}
|
|
220
|
+
}
|
|
200
221
|
// return that.isType(type)(req,res,next);
|
|
201
222
|
// console.log("typers",typers);
|
|
202
223
|
}
|
package/middleware/passport.js
CHANGED
|
@@ -321,7 +321,7 @@ module.exports = function(passport) {
|
|
|
321
321
|
|
|
322
322
|
} else if (subject=="subscription") {
|
|
323
323
|
|
|
324
|
-
Subscription.findOne({_id: identifier}
|
|
324
|
+
Subscription.findOne({_id: identifier}).select('+global').exec(function(err, subscription) {
|
|
325
325
|
if (err) {
|
|
326
326
|
winston.error("Passport JWT subscription err", err);
|
|
327
327
|
return done(err, false);
|
package/package.json
CHANGED
|
@@ -15,6 +15,12 @@ router.get('/', function (req, res) {
|
|
|
15
15
|
function (req, res) {
|
|
16
16
|
res.send('{"success":true}');
|
|
17
17
|
});
|
|
18
|
+
|
|
19
|
+
router.get('/subscription', [
|
|
20
|
+
roleChecker.hasRoleOrTypes(null,['subscription'])],
|
|
21
|
+
function (req, res) {
|
|
22
|
+
res.send('{"success":true}');
|
|
23
|
+
});
|
|
18
24
|
|
|
19
25
|
router.get('/noentitycheck',
|
|
20
26
|
[noentitycheck,
|
package/routes/integration.js
CHANGED
|
@@ -52,7 +52,7 @@ router.get('/:integration_id', async (req, res) => {
|
|
|
52
52
|
Integration.findOne({ _id: integration_id, id_project: id_project }, (err, integration) => {
|
|
53
53
|
if (err) {
|
|
54
54
|
winston.error("Error find integration by id: ", err);
|
|
55
|
-
return res.status(
|
|
55
|
+
return res.status(500).send({ success: false, message: "Error getting integration" });
|
|
56
56
|
}
|
|
57
57
|
if (!integration) {
|
|
58
58
|
return res.status(404).send({ success: false, message: "Integration not found" });
|
|
@@ -73,7 +73,7 @@ router.get('/name/:integration_name', async (req, res) => {
|
|
|
73
73
|
Integration.findOne({ id_project: id_project, name: integration_name }, (err, integration) => {
|
|
74
74
|
if (err) {
|
|
75
75
|
winston.error("Error find integration by name: ", err);
|
|
76
|
-
return res.status(
|
|
76
|
+
return res.status(500).send({ success: false, message: "Error getting integration" });
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
if (!integration) {
|
|
@@ -96,7 +96,7 @@ router.post('/', async (req, res) => {
|
|
|
96
96
|
existingIntegration = await Integration.findOne({ id_project: id_project, name: req.body.name });
|
|
97
97
|
} catch (err) {
|
|
98
98
|
winston.error("Error finding existing integration ", err);
|
|
99
|
-
return res.status(500).send({ success: false,
|
|
99
|
+
return res.status(500).send({ success: false, message: "Error creating integration" });
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
let newIntegration = {
|
|
@@ -112,7 +112,7 @@ router.post('/', async (req, res) => {
|
|
|
112
112
|
Integration.findOneAndUpdate({ id_project: id_project, name: req.body.name }, newIntegration, { new: true, upsert: true, setDefaultsOnInsert: false}, (err, savedIntegration) => {
|
|
113
113
|
if (err) {
|
|
114
114
|
winston.error("Error creating new integration ", err);
|
|
115
|
-
return res.status(
|
|
115
|
+
return res.status(500).send({ success: false, message: "Error creating integration" });
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
winston.debug("New integration created: ", savedIntegration);
|
|
@@ -168,7 +168,7 @@ router.put('/:integration_id', async (req, res) => {
|
|
|
168
168
|
existingIntegration = await Integration.findOne({ _id: integration_id, id_project: id_project });
|
|
169
169
|
} catch (err) {
|
|
170
170
|
winston.error("Error finding integration to update ", err);
|
|
171
|
-
return res.status(500).send({ success: false,
|
|
171
|
+
return res.status(500).send({ success: false, message: "Error updating integration" });
|
|
172
172
|
}
|
|
173
173
|
if (!existingIntegration) {
|
|
174
174
|
return res.status(404).send({ success: false, message: "Integration not found" });
|
|
@@ -187,7 +187,7 @@ router.put('/:integration_id', async (req, res) => {
|
|
|
187
187
|
Integration.findOneAndUpdate({ _id: integration_id, id_project: id_project }, update, { new: true }, (err, savedIntegration) => {
|
|
188
188
|
if (err) {
|
|
189
189
|
winston.error("Error find by id and update integration: ", err);
|
|
190
|
-
return res.status(500).send({ success: false,
|
|
190
|
+
return res.status(500).send({ success: false, message: "Error updating integration" });
|
|
191
191
|
}
|
|
192
192
|
if (!savedIntegration) {
|
|
193
193
|
return res.status(404).send({ success: false, message: "Integration not found" });
|
|
@@ -213,7 +213,7 @@ router.delete('/:integration_id', async (req, res) => {
|
|
|
213
213
|
Integration.findOneAndDelete({ _id: integration_id, id_project: id_project }, (err, result) => {
|
|
214
214
|
if (err) {
|
|
215
215
|
winston.error("Error find by id and delete integration: ", err);
|
|
216
|
-
return res.status(500).send({ success: false,
|
|
216
|
+
return res.status(500).send({ success: false, message: "Error deleting integration" });
|
|
217
217
|
}
|
|
218
218
|
if (!result) {
|
|
219
219
|
return res.status(404).send({ success: false, message: "Integration not found" });
|
package/routes/jwt.js
CHANGED
|
@@ -4,32 +4,35 @@
|
|
|
4
4
|
// Modules imports
|
|
5
5
|
const express = require('express');
|
|
6
6
|
var router = express.Router();
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
// Deprecated test/helper endpoints. Kept mounted so callers get an explicit
|
|
9
|
+
// deprecation response instead of a silent 404. Do not re-enable without
|
|
10
|
+
// proper authentication (passport + role checks).
|
|
11
|
+
function deprecated(req, res) {
|
|
12
|
+
return res.status(410).send({
|
|
13
|
+
success: false,
|
|
14
|
+
msg: 'deprecated'
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
router.post('/decode', deprecated);
|
|
19
|
+
router.post('/generatetestjwt', deprecated);
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
var config = require('../config/database');
|
|
8
23
|
var jwt = require('jsonwebtoken');
|
|
9
24
|
var validtoken = require('../middleware/valid-token');
|
|
10
25
|
var requestUtil = require('../utils/requestUtil');
|
|
11
26
|
var Project = require('../models/project');
|
|
12
27
|
var winston = require('../config/winston');
|
|
13
28
|
|
|
14
|
-
// deprecated??
|
|
15
29
|
router.post('/decode', validtoken, function (req, res) {
|
|
16
|
-
|
|
17
|
-
// var project_id = req.query.project_id;
|
|
18
|
-
|
|
19
|
-
// winston.debug("project_id", project_id);
|
|
20
|
-
|
|
21
|
-
// if (!project_id) {
|
|
22
|
-
// winston.error("project_id parameter is required");
|
|
23
|
-
// res.status(400).send({ success: false, msg: "project_id parameter is required" });
|
|
24
|
-
// }
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return Project.findOne({_id: req.projectid, status: 100}, '+jwtSecret',function(err, project) {
|
|
30
|
+
return Project.findOne({_id: req.projectid, status: 100}, '+jwtSecret',function(err, project) {
|
|
28
31
|
if (err) {
|
|
29
32
|
winston.error('Error finding project', err);
|
|
30
33
|
return res.status(500).send({ success: false, msg: 'Error finding project.' });
|
|
31
34
|
}
|
|
32
|
-
|
|
35
|
+
|
|
33
36
|
winston.debug('project', project);
|
|
34
37
|
|
|
35
38
|
if (!project) {
|
|
@@ -40,74 +43,60 @@ router.post('/decode', validtoken, function (req, res) {
|
|
|
40
43
|
return res.status(404).send({ success: false, msg: 'Project hasnt a jwtSecret' });
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
|
|
44
46
|
try {
|
|
45
47
|
winston.debug("requestUtil", requestUtil);
|
|
46
48
|
var token = requestUtil.getToken(req.headers);
|
|
47
49
|
|
|
48
|
-
|
|
49
50
|
winston.debug("token", token);
|
|
50
|
-
// verify a token symmetric - synchronous
|
|
51
51
|
var decoded = jwt.verify(token, project.jwtSecret);
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
winston.debug("decoded", decoded);
|
|
54
|
-
|
|
55
54
|
|
|
56
|
-
if(!decoded.iat ) {
|
|
55
|
+
if(!decoded.iat ) {
|
|
57
56
|
winston.debug("token.iat is required");
|
|
58
57
|
return res.status(401).send({ success: false, msg: 'Authentication failed. Token iat is required'});
|
|
59
58
|
}
|
|
60
|
-
if(!decoded.exp ) {
|
|
59
|
+
if(!decoded.exp ) {
|
|
61
60
|
winston.debug("token.exp is required");
|
|
62
61
|
return res.status(401).send({ success: false, msg: 'Authentication failed. Token exp is required'});
|
|
63
62
|
}
|
|
64
|
-
if(decoded.exp - decoded.iat > 600 ) {
|
|
63
|
+
if(decoded.exp - decoded.iat > 600 ) {
|
|
65
64
|
winston.debug("The value of exp is permitted to be up to a maximum of 10 minutes from the iat value");
|
|
66
65
|
return res.status(401).send({ success: false, msg: 'Authentication failed. The value of exp is permitted to be up to a maximum of 10 minutes from the iat value'});
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
|
|
70
68
|
return res.json({decoded: decoded});
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
|
|
73
70
|
} catch(err) {
|
|
74
71
|
winston.error("Authentication failed", err);
|
|
75
72
|
return res.status(401).send({ success: false, msg: 'Authentication failed', err: err });
|
|
76
|
-
}
|
|
73
|
+
}
|
|
77
74
|
|
|
78
75
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
76
|
});
|
|
82
77
|
|
|
83
78
|
router.post('/generatetestjwt', validtoken, function (req, res) {
|
|
84
|
-
|
|
85
79
|
winston.debug("req.body", req.body);
|
|
86
|
-
return
|
|
80
|
+
return Project.findOne({_id: req.projectid, status: 100}, '+jwtSecret',function(err, project) {
|
|
87
81
|
if (err) {
|
|
88
82
|
winston.error('Error finding project', err);
|
|
89
83
|
return res.status(500).send({ success: false, msg: 'Error finding project.' });
|
|
90
84
|
}
|
|
91
|
-
|
|
85
|
+
|
|
92
86
|
winston.debug('project', project);
|
|
93
|
-
|
|
87
|
+
|
|
94
88
|
if (!project) {
|
|
95
89
|
return res.status(404).send({ success: false, msg: 'Project not found' });
|
|
96
90
|
}
|
|
97
|
-
|
|
91
|
+
|
|
98
92
|
if (!project.jwtSecret) {
|
|
99
93
|
return res.status(404).send({ success: false, msg: 'Project hasnt a jwtSecret' });
|
|
100
94
|
}
|
|
101
95
|
|
|
102
96
|
var token = jwt.sign(req.body, project.jwtSecret,{ expiresIn: 300 });
|
|
103
|
-
// var token = jwt.sign(req.body, project.jwtSecret);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
97
|
res.json({ success: true, token: 'JWT ' + token });
|
|
108
98
|
});
|
|
109
|
-
|
|
110
99
|
});
|
|
100
|
+
*/
|
|
111
101
|
|
|
112
|
-
|
|
113
|
-
module.exports = router;
|
|
102
|
+
module.exports = router;
|
|
@@ -20,6 +20,7 @@ var assert = chai.assert;
|
|
|
20
20
|
var jwt = require('jsonwebtoken');
|
|
21
21
|
var config = require('../config/database'); // get db config file
|
|
22
22
|
var faqService = require('../services/faqService');
|
|
23
|
+
var Subscription = require('../models/subscription');
|
|
23
24
|
|
|
24
25
|
let log = false;
|
|
25
26
|
|
|
@@ -401,7 +402,160 @@ describe('AuthenticationJWT', () => {
|
|
|
401
402
|
});
|
|
402
403
|
});
|
|
403
404
|
});
|
|
404
|
-
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('subscriptionJwt-wildcard-id-project-can-access-specific-project', (done) => {
|
|
408
|
+
|
|
409
|
+
var email = "test-signup-" + Date.now() + "@email.com";
|
|
410
|
+
var pwd = "pwd";
|
|
411
|
+
|
|
412
|
+
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
|
413
|
+
projectService.create("test-sub-wildcard-project", savedUser._id).then(function (savedProject) {
|
|
414
|
+
var subscription = new Subscription({
|
|
415
|
+
event: "queues.request.create." + Date.now(),
|
|
416
|
+
target: "https://queues.example.com/hook",
|
|
417
|
+
id_project: "*",
|
|
418
|
+
createdBy: savedUser._id
|
|
419
|
+
});
|
|
420
|
+
subscription.save(function (err, savedSub) {
|
|
421
|
+
if (err) { console.error("err: ", err); return done(err); }
|
|
422
|
+
|
|
423
|
+
var signOptions = {
|
|
424
|
+
subject: 'subscription',
|
|
425
|
+
audience: 'https://tiledesk.com/subscriptions/' + savedSub._id,
|
|
426
|
+
};
|
|
427
|
+
var jwtToken = jwt.sign(savedSub.toObject(), savedSub.secret, signOptions);
|
|
428
|
+
|
|
429
|
+
chai.request(server)
|
|
430
|
+
.get('/' + savedProject._id + '/authtestWithRoleCheck/subscription')
|
|
431
|
+
.set('Authorization', 'JWT ' + jwtToken)
|
|
432
|
+
.send()
|
|
433
|
+
.end((err, res) => {
|
|
434
|
+
if (err) { console.error("err: ", err); }
|
|
435
|
+
if (log) { console.log("res.body", res.body); }
|
|
436
|
+
|
|
437
|
+
res.should.have.status(200);
|
|
438
|
+
done();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}).timeout(20000);
|
|
444
|
+
|
|
445
|
+
it('subscriptionJwt-global-true-can-access-specific-project', (done) => {
|
|
446
|
+
|
|
447
|
+
var email = "test-signup-" + Date.now() + "@email.com";
|
|
448
|
+
var pwd = "pwd";
|
|
449
|
+
|
|
450
|
+
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
|
451
|
+
projectService.create("test-sub-global-a", savedUser._id).then(function (projectA) {
|
|
452
|
+
projectService.create("test-sub-global-b", savedUser._id).then(function (projectB) {
|
|
453
|
+
var subscription = new Subscription({
|
|
454
|
+
event: "queues.global." + Date.now(),
|
|
455
|
+
target: "https://queues.example.com/hook",
|
|
456
|
+
id_project: projectA._id,
|
|
457
|
+
global: true,
|
|
458
|
+
createdBy: savedUser._id
|
|
459
|
+
});
|
|
460
|
+
subscription.save(function (err, savedSub) {
|
|
461
|
+
if (err) { console.error("err: ", err); return done(err); }
|
|
462
|
+
|
|
463
|
+
var signOptions = {
|
|
464
|
+
subject: 'subscription',
|
|
465
|
+
audience: 'https://tiledesk.com/subscriptions/' + savedSub._id,
|
|
466
|
+
};
|
|
467
|
+
var jwtToken = jwt.sign(savedSub.toObject(), savedSub.secret, signOptions);
|
|
468
|
+
|
|
469
|
+
chai.request(server)
|
|
470
|
+
.get('/' + projectB._id + '/authtestWithRoleCheck/subscription')
|
|
471
|
+
.set('Authorization', 'JWT ' + jwtToken)
|
|
472
|
+
.send()
|
|
473
|
+
.end((err, res) => {
|
|
474
|
+
if (err) { console.error("err: ", err); }
|
|
475
|
+
if (log) { console.log("res.body", res.body); }
|
|
476
|
+
|
|
477
|
+
res.should.have.status(200);
|
|
478
|
+
done();
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
}).timeout(20000);
|
|
485
|
+
|
|
486
|
+
it('subscriptionJwt-denied-cross-project', (done) => {
|
|
487
|
+
|
|
488
|
+
var email = "test-signup-" + Date.now() + "@email.com";
|
|
489
|
+
var pwd = "pwd";
|
|
490
|
+
|
|
491
|
+
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
|
492
|
+
projectService.create("test-sub-project-a", savedUser._id).then(function (projectA) {
|
|
493
|
+
projectService.create("test-sub-project-b", savedUser._id).then(function (projectB) {
|
|
494
|
+
var subscription = new Subscription({
|
|
495
|
+
event: "webhook.request.create." + Date.now(),
|
|
496
|
+
target: "https://hooks.example.com/hook",
|
|
497
|
+
id_project: projectA._id,
|
|
498
|
+
createdBy: savedUser._id
|
|
499
|
+
});
|
|
500
|
+
subscription.save(function (err, savedSub) {
|
|
501
|
+
if (err) { console.error("err: ", err); return done(err); }
|
|
502
|
+
|
|
503
|
+
var signOptions = {
|
|
504
|
+
subject: 'subscription',
|
|
505
|
+
audience: 'https://tiledesk.com/subscriptions/' + savedSub._id,
|
|
506
|
+
};
|
|
507
|
+
var jwtToken = jwt.sign(savedSub.toObject(), savedSub.secret, signOptions);
|
|
508
|
+
|
|
509
|
+
chai.request(server)
|
|
510
|
+
.get('/' + projectB._id + '/authtestWithRoleCheck/subscription')
|
|
511
|
+
.set('Authorization', 'JWT ' + jwtToken)
|
|
512
|
+
.send()
|
|
513
|
+
.end((err, res) => {
|
|
514
|
+
if (err) { console.error("err: ", err); }
|
|
515
|
+
if (log) { console.log("res.body", res.body); }
|
|
516
|
+
|
|
517
|
+
res.should.have.status(403);
|
|
518
|
+
done();
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
}).timeout(20000);
|
|
525
|
+
|
|
526
|
+
it('botJwt-denied-cross-project', (done) => {
|
|
527
|
+
|
|
528
|
+
var email = "test-signup-" + Date.now() + "@email.com";
|
|
529
|
+
var pwd = "pwd";
|
|
530
|
+
|
|
531
|
+
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
|
532
|
+
projectService.create("test-bot-project-a", savedUser._id).then(function (projectA) {
|
|
533
|
+
projectService.create("test-bot-project-b", savedUser._id).then(function (projectB) {
|
|
534
|
+
faqService.create(projectA._id, savedUser._id, { name: "testbot-cross" }).then(function (savedBot) {
|
|
535
|
+
|
|
536
|
+
var savedBotObj = savedBot.toObject();
|
|
537
|
+
var signOptions = {
|
|
538
|
+
subject: 'bot',
|
|
539
|
+
audience: 'https://tiledesk.com/bots/' + savedBot._id,
|
|
540
|
+
};
|
|
541
|
+
var jwtToken = jwt.sign(savedBotObj, savedBot.secret, signOptions);
|
|
542
|
+
|
|
543
|
+
chai.request(server)
|
|
544
|
+
.get('/' + projectB._id + '/authtestWithRoleCheck/bot')
|
|
545
|
+
.set('Authorization', 'JWT ' + jwtToken)
|
|
546
|
+
.send()
|
|
547
|
+
.end((err, res) => {
|
|
548
|
+
if (err) { console.error("err: ", err); }
|
|
549
|
+
if (log) { console.log("res.body", res.body); }
|
|
550
|
+
|
|
551
|
+
res.should.have.status(403);
|
|
552
|
+
done();
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
});
|
|
405
559
|
|
|
406
560
|
});
|
|
407
561
|
|
package/test/hasRole.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
process.env.NODE_ENV = 'test';
|
|
2
|
+
process.env.LOG_LEVEL = 'critical';
|
|
3
|
+
|
|
4
|
+
const { expect } = require('chai');
|
|
5
|
+
const roleChecker = require('../middleware/has-role');
|
|
6
|
+
const Subscription = require('../models/subscription');
|
|
7
|
+
const Faq_kb = require('../models/faq_kb');
|
|
8
|
+
|
|
9
|
+
describe('RoleChecker.belongsToProject', () => {
|
|
10
|
+
|
|
11
|
+
const projectA = '6565047fdd64fd001323f37c';
|
|
12
|
+
const projectB = '507f1f77bcf86cd799439011';
|
|
13
|
+
|
|
14
|
+
it('fails closed when user, id_project or projectid is missing', () => {
|
|
15
|
+
expect(roleChecker.belongsToProject(null, projectA)).to.equal(false);
|
|
16
|
+
expect(roleChecker.belongsToProject({}, projectA)).to.equal(false);
|
|
17
|
+
expect(roleChecker.belongsToProject(new Subscription({ event: 'e', target: 't', createdBy: 'u' }), projectA)).to.equal(false);
|
|
18
|
+
expect(roleChecker.belongsToProject(new Subscription({ event: 'e', target: 't', id_project: projectA, createdBy: 'u' }), null)).to.equal(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('allows a subscription with id_project "*" to access a specific projectid', () => {
|
|
22
|
+
const subscription = new Subscription({
|
|
23
|
+
event: 'request.create',
|
|
24
|
+
target: 'https://queues.example.com',
|
|
25
|
+
id_project: '*',
|
|
26
|
+
createdBy: 'queues'
|
|
27
|
+
});
|
|
28
|
+
expect(roleChecker.belongsToProject(subscription, projectA)).to.equal(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('allows a subscription with global: true to access a specific projectid', () => {
|
|
32
|
+
const subscription = new Subscription({
|
|
33
|
+
event: 'request.create',
|
|
34
|
+
target: 'https://queues.example.com',
|
|
35
|
+
id_project: projectA,
|
|
36
|
+
global: true,
|
|
37
|
+
createdBy: 'queues'
|
|
38
|
+
});
|
|
39
|
+
expect(roleChecker.belongsToProject(subscription, projectB)).to.equal(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('denies a subscription bound to project A from accessing project B', () => {
|
|
43
|
+
const subscription = new Subscription({
|
|
44
|
+
event: 'request.create',
|
|
45
|
+
target: 'https://hooks.example.com',
|
|
46
|
+
id_project: projectA,
|
|
47
|
+
createdBy: 'user1'
|
|
48
|
+
});
|
|
49
|
+
expect(roleChecker.belongsToProject(subscription, projectB)).to.equal(false);
|
|
50
|
+
expect(roleChecker.belongsToProject(subscription, projectA)).to.equal(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('denies a bot for project A from accessing project B', () => {
|
|
54
|
+
const bot = new Faq_kb({
|
|
55
|
+
name: 'testbot',
|
|
56
|
+
id_project: projectA,
|
|
57
|
+
createdBy: 'user1'
|
|
58
|
+
});
|
|
59
|
+
expect(roleChecker.belongsToProject(bot, projectB)).to.equal(false);
|
|
60
|
+
expect(roleChecker.belongsToProject(bot, projectA)).to.equal(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('does not give bots a wildcard even if id_project is "*" or global is true', () => {
|
|
64
|
+
const wildcardBot = new Faq_kb({
|
|
65
|
+
name: 'wildcard-bot',
|
|
66
|
+
id_project: '*',
|
|
67
|
+
createdBy: 'user1'
|
|
68
|
+
});
|
|
69
|
+
expect(roleChecker.belongsToProject(wildcardBot, projectA)).to.equal(false);
|
|
70
|
+
|
|
71
|
+
const globalBot = new Faq_kb({
|
|
72
|
+
name: 'global-bot',
|
|
73
|
+
id_project: projectA,
|
|
74
|
+
createdBy: 'user1'
|
|
75
|
+
});
|
|
76
|
+
globalBot.set('global', true);
|
|
77
|
+
expect(roleChecker.belongsToProject(globalBot, projectB)).to.equal(false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('RoleChecker.isTypeAsFunction', () => {
|
|
82
|
+
|
|
83
|
+
const projectA = '6565047fdd64fd001323f37c';
|
|
84
|
+
const projectB = '507f1f77bcf86cd799439011';
|
|
85
|
+
|
|
86
|
+
it('allows a subscription with id_project "*" to access a specific projectid', () => {
|
|
87
|
+
const subscription = new Subscription({
|
|
88
|
+
event: 'request.create',
|
|
89
|
+
target: 'https://queues.example.com',
|
|
90
|
+
id_project: '*',
|
|
91
|
+
createdBy: 'queues'
|
|
92
|
+
});
|
|
93
|
+
expect(roleChecker.isTypeAsFunction('subscription', subscription, projectA)).to.equal(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('allows a subscription with global: true to access a specific projectid', () => {
|
|
97
|
+
const subscription = new Subscription({
|
|
98
|
+
event: 'request.create',
|
|
99
|
+
target: 'https://queues.example.com',
|
|
100
|
+
id_project: projectA,
|
|
101
|
+
global: true,
|
|
102
|
+
createdBy: 'queues'
|
|
103
|
+
});
|
|
104
|
+
expect(roleChecker.isTypeAsFunction('subscription', subscription, projectB)).to.equal(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('denies a subscription bound to project A from accessing project B', () => {
|
|
108
|
+
const subscription = new Subscription({
|
|
109
|
+
event: 'request.create',
|
|
110
|
+
target: 'https://hooks.example.com',
|
|
111
|
+
id_project: projectA,
|
|
112
|
+
createdBy: 'user1'
|
|
113
|
+
});
|
|
114
|
+
expect(roleChecker.isTypeAsFunction('subscription', subscription, projectB)).to.equal(false);
|
|
115
|
+
expect(roleChecker.isTypeAsFunction('subscription', subscription, projectA)).to.equal(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('denies a bot for project A from accessing project B', () => {
|
|
119
|
+
const bot = new Faq_kb({
|
|
120
|
+
name: 'testbot',
|
|
121
|
+
id_project: projectA,
|
|
122
|
+
createdBy: 'user1'
|
|
123
|
+
});
|
|
124
|
+
expect(roleChecker.isTypeAsFunction('bot', bot, projectB)).to.equal(false);
|
|
125
|
+
expect(roleChecker.isTypeAsFunction('bot', bot, projectA)).to.equal(true);
|
|
126
|
+
});
|
|
127
|
+
});
|
package/test/jwtRoute.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
//During the test the env variable is set to test
|
|
2
2
|
process.env.NODE_ENV = 'test';
|
|
3
3
|
|
|
4
|
-
var User = require('../models/user');
|
|
5
4
|
var projectService = require('../services/projectService');
|
|
6
5
|
var userService = require('../services/userService');
|
|
7
6
|
|
|
@@ -10,158 +9,60 @@ let chai = require('chai');
|
|
|
10
9
|
let chaiHttp = require('chai-http');
|
|
11
10
|
let server = require('../app');
|
|
12
11
|
let should = chai.should();
|
|
13
|
-
var jwt = require('jsonwebtoken');
|
|
14
12
|
|
|
15
13
|
let log = false;
|
|
16
14
|
|
|
17
|
-
// chai.config.includeStack = true;
|
|
18
|
-
|
|
19
15
|
var expect = chai.expect;
|
|
20
|
-
var assert = chai.assert;
|
|
21
16
|
|
|
22
17
|
chai.use(chaiHttp);
|
|
23
18
|
|
|
24
19
|
describe('JWTRoute', () => {
|
|
25
20
|
|
|
26
|
-
describe('
|
|
27
|
-
|
|
28
|
-
it('decodeWithIatExp', (done) => {
|
|
21
|
+
describe('deprecated endpoints', () => {
|
|
29
22
|
|
|
23
|
+
it('generatetestjwt returns deprecated', (done) => {
|
|
30
24
|
var email = "test-signup-" + Date.now() + "@email.com";
|
|
31
25
|
var pwd = "pwd";
|
|
32
26
|
|
|
33
27
|
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
|
34
|
-
projectService.create("test-
|
|
28
|
+
projectService.create("test-jwt-deprecated", savedUser._id).then(function (savedProject) {
|
|
35
29
|
chai.request(server)
|
|
36
|
-
.post('/' + savedProject._id + '/
|
|
30
|
+
.post('/' + savedProject._id + '/jwt/generatetestjwt')
|
|
37
31
|
.auth(email, pwd)
|
|
38
|
-
.send()
|
|
32
|
+
.send({ "name": "andrea", "surname": "leo" })
|
|
39
33
|
.end((err, res) => {
|
|
40
|
-
|
|
41
34
|
if (err) { console.error("err: ", err); }
|
|
42
35
|
if (log) { console.log("res.body", res.body); }
|
|
43
36
|
|
|
44
|
-
res.should.have.status(
|
|
37
|
+
res.should.have.status(410);
|
|
45
38
|
res.body.should.be.a('object');
|
|
46
|
-
expect(res.body.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.post('/' + savedProject._id + '/jwt/generatetestjwt')
|
|
50
|
-
.auth(email, pwd)
|
|
51
|
-
.send({ "name": "andrea", "surname": "leo" })
|
|
52
|
-
.end((err, res) => {
|
|
53
|
-
|
|
54
|
-
if (err) { console.error("err: ", err); }
|
|
55
|
-
if (log) { console.log("res.body", res.body); }
|
|
56
|
-
|
|
57
|
-
res.should.have.status(200);
|
|
58
|
-
res.body.should.be.a('object');
|
|
59
|
-
expect(res.body.token).to.not.equal(null);
|
|
60
|
-
|
|
61
|
-
var jwtToken = res.body.token;
|
|
62
|
-
if (log) { console.log("jwtToken", jwtToken); }
|
|
63
|
-
|
|
64
|
-
chai.request(server)
|
|
65
|
-
.post('/' + savedProject._id + '/jwt/decode')
|
|
66
|
-
.set('Authorization', jwtToken)
|
|
67
|
-
.send()
|
|
68
|
-
.end((err, res) => {
|
|
69
|
-
|
|
70
|
-
res.should.have.status(200);
|
|
71
|
-
res.body.should.be.a('object');
|
|
72
|
-
|
|
73
|
-
expect(res.body.name).to.not.equal("andrea");
|
|
74
|
-
expect(res.body.surname).to.not.equal("leo");
|
|
75
|
-
expect(res.body.iat).to.not.equal(null);
|
|
76
|
-
expect(res.body.exp).to.not.equal(null);
|
|
77
|
-
|
|
78
|
-
done();
|
|
79
|
-
});
|
|
80
|
-
});
|
|
39
|
+
expect(res.body.success).to.equal(false);
|
|
40
|
+
expect(res.body.msg).to.equal('deprecated');
|
|
41
|
+
done();
|
|
81
42
|
});
|
|
82
43
|
});
|
|
83
44
|
});
|
|
84
45
|
}).timeout(20000);
|
|
85
46
|
|
|
86
|
-
|
|
87
|
-
it('decodeWithIatNoExp', (done) => {
|
|
88
|
-
|
|
47
|
+
it('decode returns deprecated', (done) => {
|
|
89
48
|
var email = "test-signup-" + Date.now() + "@email.com";
|
|
90
49
|
var pwd = "pwd";
|
|
91
50
|
|
|
92
51
|
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
|
93
|
-
projectService.create("test-
|
|
52
|
+
projectService.create("test-jwt-deprecated-decode", savedUser._id).then(function (savedProject) {
|
|
94
53
|
chai.request(server)
|
|
95
|
-
.post('/' + savedProject._id + '/
|
|
54
|
+
.post('/' + savedProject._id + '/jwt/decode')
|
|
96
55
|
.auth(email, pwd)
|
|
97
56
|
.send()
|
|
98
57
|
.end((err, res) => {
|
|
99
|
-
|
|
100
58
|
if (err) { console.error("err: ", err); }
|
|
101
59
|
if (log) { console.log("res.body", res.body); }
|
|
102
60
|
|
|
103
|
-
res.should.have.status(
|
|
61
|
+
res.should.have.status(410);
|
|
104
62
|
res.body.should.be.a('object');
|
|
105
|
-
expect(res.body.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (log) { console.log("jwtToken", jwtToken); }
|
|
109
|
-
|
|
110
|
-
chai.request(server)
|
|
111
|
-
.post('/' + savedProject._id + '/jwt/decode')
|
|
112
|
-
.set('Authorization', 'JWT ' + jwtToken)
|
|
113
|
-
.send()
|
|
114
|
-
.end((err, res) => {
|
|
115
|
-
|
|
116
|
-
if (err) { console.error("err: ", err); }
|
|
117
|
-
if (log) { console.log("res.body", res.body); }
|
|
118
|
-
|
|
119
|
-
res.should.have.status(401);
|
|
120
|
-
|
|
121
|
-
done();
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
}).timeout(20000);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
it('decodeWithIatTooHightExp', (done) => {
|
|
130
|
-
|
|
131
|
-
var email = "test-signup-" + Date.now() + "@email.com";
|
|
132
|
-
var pwd = "pwd";
|
|
133
|
-
|
|
134
|
-
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
|
135
|
-
projectService.create("test-join-member", savedUser._id).then(function (savedProject) {
|
|
136
|
-
chai.request(server)
|
|
137
|
-
.post('/' + savedProject._id + '/keys/generate')
|
|
138
|
-
.auth(email, pwd)
|
|
139
|
-
.send()
|
|
140
|
-
.end((err, res) => {
|
|
141
|
-
|
|
142
|
-
if (err) { console.error("err: ", err); }
|
|
143
|
-
if (log) { console.log("res.body", res.body); }
|
|
144
|
-
|
|
145
|
-
res.should.have.status(200);
|
|
146
|
-
res.body.should.be.a('object');
|
|
147
|
-
expect(res.body.jwtSecret).to.not.equal(null);
|
|
148
|
-
|
|
149
|
-
var jwtToken = jwt.sign({ "name": "andrea", "surname": "leo" }, res.body.jwtSecret, { expiresIn: 800 });
|
|
150
|
-
if (log) { console.log("jwtToken", jwtToken); }
|
|
151
|
-
|
|
152
|
-
chai.request(server)
|
|
153
|
-
.post('/' + savedProject._id + '/jwt/decode')
|
|
154
|
-
.set('Authorization', 'JWT ' + jwtToken)
|
|
155
|
-
.send()
|
|
156
|
-
.end((err, res) => {
|
|
157
|
-
|
|
158
|
-
if (err) { console.error("err: ", err); }
|
|
159
|
-
if (log) { console.log("res.body", res.body); }
|
|
160
|
-
|
|
161
|
-
res.should.have.status(401);
|
|
162
|
-
|
|
163
|
-
done();
|
|
164
|
-
});
|
|
63
|
+
expect(res.body.success).to.equal(false);
|
|
64
|
+
expect(res.body.msg).to.equal('deprecated');
|
|
65
|
+
done();
|
|
165
66
|
});
|
|
166
67
|
});
|
|
167
68
|
});
|
|
@@ -170,5 +71,3 @@ describe('JWTRoute', () => {
|
|
|
170
71
|
});
|
|
171
72
|
|
|
172
73
|
});
|
|
173
|
-
|
|
174
|
-
|