@tiledesk/tiledesk-server 2.23.0 → 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 +3 -0
- package/middleware/has-role.js +3 -0
- package/middleware/passport.js +1 -1
- package/package.json +1 -1
- package/routes/authtestWithRoleCheck.js +6 -0
- package/test/authenticationJwt.js +120 -0
- package/test/hasRole.js +127 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
(https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
|
|
7
7
|
|
|
8
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
|
|
9
12
|
- Deprecated JWT helper routes (`/jwt/decode`, `/jwt/generatetestjwt`); they now return 410
|
|
10
13
|
- Restricted bot and subscription tokens to their own project (cross-project access denied)
|
|
11
14
|
- Improved error response sanitization for integration routes
|
package/middleware/has-role.js
CHANGED
|
@@ -44,6 +44,9 @@ class RoleChecker {
|
|
|
44
44
|
if (!user || user.id_project == null || projectid == null) {
|
|
45
45
|
return false;
|
|
46
46
|
}
|
|
47
|
+
if (!(user instanceof Faq_kb) && (String(user.id_project) === '*' || user.global === true)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
47
50
|
return String(user.id_project) === String(projectid);
|
|
48
51
|
}
|
|
49
52
|
|
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,
|
|
@@ -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
|
|
|
@@ -403,6 +404,125 @@ describe('AuthenticationJWT', () => {
|
|
|
403
404
|
});
|
|
404
405
|
});
|
|
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
|
+
|
|
406
526
|
it('botJwt-denied-cross-project', (done) => {
|
|
407
527
|
|
|
408
528
|
var email = "test-signup-" + Date.now() + "@email.com";
|
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
|
+
});
|