@tiledesk/tiledesk-server 2.23.0 → 2.24.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.
@@ -22,10 +22,12 @@ const projectService = require('./projectService');
22
22
  const axios = require("axios").default;
23
23
 
24
24
  let port = process.env.PORT || '3000';
25
- let TILEBOT_ENDPOINT = "http://localhost:" + port + "/modules/tilebot/ext/";;
25
+ let TILEBOT_ENDPOINT = process.env.API_ENDPOINT ? process.env.API_ENDPOINT + "/modules/tilebot/ext/" : ("http://localhost:" + port + "/modules/tilebot/ext/");
26
+
26
27
  if (process.env.TILEBOT_ENDPOINT) {
27
- TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + "/ext/"
28
+ TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + "/ext/";
28
29
  }
30
+ winston.info("[RequestService] TILEBOT_ENDPOINT: " + TILEBOT_ENDPOINT);
29
31
 
30
32
  let tdCache = new TdCache({
31
33
  host: process.env.CACHE_REDIS_HOST,
@@ -7,11 +7,12 @@ const errorCodes = require("../errorCodes");
7
7
  var ObjectId = require('mongoose').Types.ObjectId;
8
8
 
9
9
  const port = process.env.PORT || '3000';
10
- let TILEBOT_ENDPOINT = "http://localhost:" + port + "/modules/tilebot/";;
10
+ let TILEBOT_ENDPOINT = process.env.API_ENDPOINT ? process.env.API_ENDPOINT + "/modules/tilebot/ext/" : ("http://localhost:" + port + "/modules/tilebot/ext/");
11
+
11
12
  if (process.env.TILEBOT_ENDPOINT) {
12
- TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + "/"
13
+ TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + "/ext/";
13
14
  }
14
- winston.debug("TILEBOT_ENDPOINT: " + TILEBOT_ENDPOINT);
15
+ winston.info("[RequestService] TILEBOT_ENDPOINT: " + TILEBOT_ENDPOINT);
15
16
 
16
17
  class WebhookService {
17
18
 
@@ -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";
@@ -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
+ });
@@ -0,0 +1,167 @@
1
+ //During the test the env variable is set to test
2
+ process.env.NODE_ENV = 'test';
3
+
4
+ const projectService = require('../services/projectService');
5
+ const userService = require('../services/userService');
6
+
7
+ //Require the dev-dependencies
8
+ let chai = require('chai');
9
+ let chaiHttp = require('chai-http');
10
+ let server = require('../app');
11
+ let should = chai.should();
12
+ const expect = chai.expect;
13
+
14
+ let log = false;
15
+
16
+ chai.use(chaiHttp);
17
+
18
+ function createProjectUser(done, callback) {
19
+ const email = "test-signup-" + Date.now() + "@email.com";
20
+ const pwd = "pwd";
21
+
22
+ userService.signup(email, pwd, "Test Firstname", "Test Lastname").then((savedUser) => {
23
+ projectService.create("test-native-mcp", savedUser._id).then((savedProject) => {
24
+ callback(savedProject, email, pwd, done);
25
+ });
26
+ });
27
+ }
28
+
29
+ describe('NativeMCP', () => {
30
+
31
+ it('get-native-mcp-servers-list', (done) => {
32
+ createProjectUser(done, (savedProject, email, pwd, done) => {
33
+ chai.request(server)
34
+ .get('/' + savedProject._id + '/mcp/native')
35
+ .auth(email, pwd)
36
+ .end((err, res) => {
37
+
38
+ if (err) { console.error("err: ", err); }
39
+ if (log) { console.log("res.body: ", res.body); }
40
+
41
+ res.should.have.status(200);
42
+ res.body.should.be.an('array');
43
+ res.body.should.have.length(2);
44
+
45
+ expect(res.body[0].id).to.equal("tiledesk-communicator");
46
+ expect(res.body[0].name).to.equal("Tiledesk Communicator");
47
+ expect(res.body[0].native).to.equal(true);
48
+ expect(res.body[0].transport).to.equal("streamable_http");
49
+ expect(res.body[0].url).to.not.exist;
50
+ expect(res.body[1].id).to.equal("tiledesk-data-table");
51
+ expect(res.body[1].name).to.equal("Tiledesk Data Table");
52
+ expect(res.body[1].native).to.equal(true);
53
+ expect(res.body[1].transport).to.equal("streamable_http");
54
+ expect(res.body[1].url).to.not.exist;
55
+
56
+ done();
57
+ });
58
+ });
59
+ });
60
+
61
+ it('connect-native-mcp-server-not-found', (done) => {
62
+ createProjectUser(done, (savedProject, email, pwd, done) => {
63
+ chai.request(server)
64
+ .post('/' + savedProject._id + '/mcp/native/unknown-server/connect')
65
+ .auth(email, pwd)
66
+ .end((err, res) => {
67
+
68
+ if (err) { console.error("err: ", err); }
69
+ if (log) { console.log("connect res.body: ", res.body); }
70
+
71
+ res.should.have.status(404);
72
+ res.body.should.have.property('success', false);
73
+ done();
74
+ });
75
+ });
76
+ });
77
+
78
+ it('connect-native-mcp-server', (done) => {
79
+ createProjectUser(done, (savedProject, email, pwd, done) => {
80
+ const serverId = 'tiledesk-communicator';
81
+
82
+ chai.request(server)
83
+ .post('/' + savedProject._id + '/mcp/native/' + serverId + '/connect')
84
+ .auth(email, pwd)
85
+ .end((err, connectRes) => {
86
+
87
+ if (err) { console.error("err: ", err); }
88
+ if (log) { console.log("connect res.body: ", connectRes.body); }
89
+
90
+ connectRes.should.have.status(200);
91
+ expect(connectRes.body).to.be.an('object');
92
+ expect(connectRes.body.success).to.equal(true);
93
+ expect(connectRes.body.message).to.equal('Native MCP server connected successfully');
94
+ expect(connectRes.body.nativeId).to.equal(serverId);
95
+ connectRes.body.tools.should.be.an('array');
96
+ connectRes.body.tools.length.should.be.greaterThan(0);
97
+ connectRes.body.tools[0].should.have.property('name');
98
+ connectRes.body.tools.map((tool) => tool.name).should.include('reply_to_user');
99
+
100
+ done();
101
+ });
102
+ });
103
+ });
104
+
105
+ it('save-native-mcp-server-to-integration', (done) => {
106
+ createProjectUser(done, (savedProject, email, pwd, done) => {
107
+
108
+ let serverId = 'tiledesk-communicator';
109
+ chai.request(server)
110
+ .post('/' + savedProject._id + '/mcp/native/' + serverId + '/tools')
111
+ .auth(email, pwd)
112
+ .end((err, toolsRes) => {
113
+
114
+ if (err) { console.error("err: ", err); }
115
+ if (log) { console.log("tools res.body: ", toolsRes.body); }
116
+
117
+ toolsRes.should.have.status(200);
118
+ expect(toolsRes.body).to.be.an('array');
119
+ const totalTools = toolsRes.body.length;
120
+ const tools = toolsRes.body.slice(0, totalTools - 2);
121
+
122
+ chai.request(server)
123
+ .post('/' + savedProject._id + '/mcp/servers')
124
+ .auth(email, pwd)
125
+ .send({
126
+ type: 'native',
127
+ nativeId: 'tiledesk-communicator',
128
+ tools,
129
+ selectedTools: [{ name: 'reply_to_user' }]
130
+ })
131
+ .end((err, res) => {
132
+
133
+ if (err) { console.error("err: ", err); }
134
+ if (log) { console.log("save res.body: ", res.body); }
135
+
136
+ res.should.have.status(200);
137
+ res.body.should.have.property('success', true);
138
+ res.body.server.should.have.property('type', 'native');
139
+ res.body.server.should.have.property('nativeId', 'tiledesk-communicator');
140
+ res.body.server.should.have.property('name', 'Tiledesk Communicator');
141
+ res.body.server.should.not.have.property('url');
142
+ res.body.server.tools.should.have.length(totalTools - 2);
143
+ res.body.server.selectedTools.should.deep.equal([{ name: 'reply_to_user' }]);
144
+
145
+ chai.request(server)
146
+ .get('/' + savedProject._id + '/integration/name/mcp')
147
+ .auth(email, pwd)
148
+ .end((err, integrationRes) => {
149
+
150
+ if (err) { console.error("err: ", err); }
151
+ if (log) { console.log("integration res.body: ", integrationRes.body); }
152
+
153
+ integrationRes.should.have.status(200);
154
+ integrationRes.body.should.have.property('name', 'mcp');
155
+ integrationRes.body.value.servers.should.be.an('array');
156
+ integrationRes.body.value.servers.should.have.length(1);
157
+ integrationRes.body.value.servers[0].should.have.property('nativeId', 'tiledesk-communicator');
158
+ integrationRes.body.value.servers[0].should.not.have.property('url');
159
+
160
+ done();
161
+ });
162
+ });
163
+ });
164
+ });
165
+ });
166
+
167
+ });