@tiledesk/tiledesk-server 2.20.0 → 2.20.2
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 +10 -0
- package/app.js +4 -3
- package/package.json +2 -2
- package/routes/integration.js +50 -13
- package/routes/kbsettings.js +181 -211
- package/routes/llm.js +5 -4
- package/routes/openai.js +87 -137
- package/routes/urlPreview.js +1 -1
- package/services/webhookService.js +1 -1
- package/test/maskIntegrationSecrets.test.js +95 -0
- package/utils/assignmentContextUtil.js +1 -1
- package/utils/maskIntegrationSecrets.js +132 -0
- package/utils/sanitizeAxiosError.js +46 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
🚀 IN PRODUCTION 🚀
|
|
6
6
|
(https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
|
|
7
7
|
|
|
8
|
+
# 2.21.0
|
|
9
|
+
- Addressed and resolved a security vulnerability
|
|
10
|
+
|
|
11
|
+
# 2.20.1
|
|
12
|
+
- Fixed a security vulnerability on AI-related routes
|
|
13
|
+
- Added authentication to `/openai` and `/url-preview` routes
|
|
14
|
+
- Improved error response sanitization for LLM routes
|
|
15
|
+
- Fixed cross-tenant access checks on integration routes
|
|
16
|
+
- Disabled unused `/kbsettings` route
|
|
17
|
+
|
|
8
18
|
# 2.20.0
|
|
9
19
|
- Updated tybot-connector to 2.1.8
|
|
10
20
|
- Improved flow attributes management
|
package/app.js
CHANGED
|
@@ -639,15 +639,16 @@ app.use('/:projectid/segments',[passport.authenticate(['basic', 'jwt'], { sessio
|
|
|
639
639
|
app.use('/:projectid/tables', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], dataTable);
|
|
640
640
|
|
|
641
641
|
app.use('/:projectid/llm', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], llm);
|
|
642
|
-
app.use('/:projectid/openai', openai);
|
|
643
|
-
app.use('/:projectid/url-preview', urlPreview);
|
|
642
|
+
app.use('/:projectid/openai', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], openai);
|
|
643
|
+
app.use('/:projectid/url-preview', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('guest', ['bot','subscription'])], urlPreview);
|
|
644
644
|
app.use('/:projectid/quotes', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], quotes)
|
|
645
645
|
|
|
646
646
|
app.use('/:projectid/integration', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], integration )
|
|
647
647
|
|
|
648
648
|
app.use('/:projectid/mcp', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], mcp);
|
|
649
649
|
|
|
650
|
-
|
|
650
|
+
// Disabled: superseded by /kb routes. Kept commented with gptkey stripped from responses.
|
|
651
|
+
// app.use('/:projectid/kbsettings', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], kbsettings);
|
|
651
652
|
app.use('/:projectid/kb/unanswered', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], unanswered);
|
|
652
653
|
app.use('/:projectid/kb/answered', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], answered);
|
|
653
654
|
app.use('/:projectid/kb', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], kb);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiledesk/tiledesk-server",
|
|
3
3
|
"description": "The Tiledesk server module",
|
|
4
|
-
"version": "2.20.
|
|
4
|
+
"version": "2.20.2",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"start": "node ./bin/www",
|
|
7
7
|
"pretest": "mongodb-runner start",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@tiledesk/tiledesk-rasa-connector": "^1.0.10",
|
|
50
50
|
"@tiledesk/tiledesk-sms-connector": "^0.1.13",
|
|
51
51
|
"@tiledesk/tiledesk-telegram-connector": "^0.1.14",
|
|
52
|
-
"@tiledesk/tiledesk-tybot-connector": "^2.1.
|
|
52
|
+
"@tiledesk/tiledesk-tybot-connector": "^2.1.10",
|
|
53
53
|
"@tiledesk/tiledesk-voice-twilio-connector": "^0.3.2",
|
|
54
54
|
"@tiledesk/tiledesk-vxml-connector": "^0.1.91",
|
|
55
55
|
"@tiledesk/tiledesk-whatsapp-connector": "1.0.26",
|
package/routes/integration.js
CHANGED
|
@@ -5,6 +5,11 @@ let Integration = require('../models/integrations');
|
|
|
5
5
|
const cacheEnabler = require('../services/cacheEnabler');
|
|
6
6
|
var cacheUtil = require('../utils/cacheUtil');
|
|
7
7
|
const integrationEvent = require('../event/integrationEvent');
|
|
8
|
+
const {
|
|
9
|
+
mergeIntegrationValue,
|
|
10
|
+
toPublicIntegration,
|
|
11
|
+
toPublicIntegrations
|
|
12
|
+
} = require('../utils/maskIntegrationSecrets');
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
// Get all integration for a project id
|
|
@@ -24,7 +29,7 @@ router.get('/', async (req, res) => {
|
|
|
24
29
|
winston.error("Error getting integrations: ", err);
|
|
25
30
|
return res.status(500).send({ success: false, message: "Error getting integrations "});
|
|
26
31
|
}
|
|
27
|
-
res.status(200).send(integrations);
|
|
32
|
+
res.status(200).send(toPublicIntegrations(integrations));
|
|
28
33
|
})
|
|
29
34
|
// without cache
|
|
30
35
|
// Integration.find({ id_project: id_project }, (err, integrations) => {
|
|
@@ -41,14 +46,18 @@ router.get('/', async (req, res) => {
|
|
|
41
46
|
router.get('/:integration_id', async (req, res) => {
|
|
42
47
|
|
|
43
48
|
let integration_id = req.params.integration_id;
|
|
49
|
+
let id_project = req.projectid;
|
|
44
50
|
winston.debug("Get integration with id " + integration_id);
|
|
45
51
|
|
|
46
|
-
Integration.
|
|
52
|
+
Integration.findOne({ _id: integration_id, id_project: id_project }, (err, integration) => {
|
|
47
53
|
if (err) {
|
|
48
54
|
winston.error("Error find integration by id: ", err);
|
|
49
55
|
return res.status(404).send({ success: false, err: err });
|
|
50
56
|
}
|
|
51
|
-
|
|
57
|
+
if (!integration) {
|
|
58
|
+
return res.status(404).send({ success: false, message: "Integration not found" });
|
|
59
|
+
}
|
|
60
|
+
res.status(200).send(toPublicIntegration(integration));
|
|
52
61
|
})
|
|
53
62
|
|
|
54
63
|
})
|
|
@@ -72,7 +81,7 @@ router.get('/name/:integration_name', async (req, res) => {
|
|
|
72
81
|
return res.status(200).send("Integration not found");
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
res.status(200).send(integration);
|
|
84
|
+
res.status(200).send(toPublicIntegration(integration));
|
|
76
85
|
})
|
|
77
86
|
})
|
|
78
87
|
|
|
@@ -82,13 +91,22 @@ router.post('/', async (req, res) => {
|
|
|
82
91
|
let id_project = req.projectid;
|
|
83
92
|
winston.debug("Add new integration ", req.body);
|
|
84
93
|
|
|
94
|
+
let existingIntegration;
|
|
95
|
+
try {
|
|
96
|
+
existingIntegration = await Integration.findOne({ id_project: id_project, name: req.body.name });
|
|
97
|
+
} catch (err) {
|
|
98
|
+
winston.error("Error finding existing integration ", err);
|
|
99
|
+
return res.status(500).send({ success: false, err: err });
|
|
100
|
+
}
|
|
85
101
|
|
|
86
102
|
let newIntegration = {
|
|
87
103
|
id_project: id_project,
|
|
88
104
|
name: req.body.name
|
|
89
105
|
}
|
|
90
|
-
if (req.body.value) {
|
|
91
|
-
newIntegration.value =
|
|
106
|
+
if (req.body.value !== undefined) {
|
|
107
|
+
newIntegration.value = existingIntegration?.value
|
|
108
|
+
? mergeIntegrationValue(req.body.value, existingIntegration.value)
|
|
109
|
+
: req.body.value;
|
|
92
110
|
}
|
|
93
111
|
|
|
94
112
|
Integration.findOneAndUpdate({ id_project: id_project, name: req.body.name }, newIntegration, { new: true, upsert: true, setDefaultsOnInsert: false}, (err, savedIntegration) => {
|
|
@@ -107,7 +125,7 @@ router.post('/', async (req, res) => {
|
|
|
107
125
|
}
|
|
108
126
|
})
|
|
109
127
|
|
|
110
|
-
res.status(200).send(savedIntegration);
|
|
128
|
+
res.status(200).send(toPublicIntegration(savedIntegration));
|
|
111
129
|
})
|
|
112
130
|
|
|
113
131
|
// let newIntegration = new Integration({
|
|
@@ -144,19 +162,35 @@ router.put('/:integration_id', async (req, res) => {
|
|
|
144
162
|
|
|
145
163
|
let id_project = req.projectid;
|
|
146
164
|
let integration_id = req.params.integration_id;
|
|
165
|
+
|
|
166
|
+
let existingIntegration;
|
|
167
|
+
try {
|
|
168
|
+
existingIntegration = await Integration.findOne({ _id: integration_id, id_project: id_project });
|
|
169
|
+
} catch (err) {
|
|
170
|
+
winston.error("Error finding integration to update ", err);
|
|
171
|
+
return res.status(500).send({ success: false, error: err });
|
|
172
|
+
}
|
|
173
|
+
if (!existingIntegration) {
|
|
174
|
+
return res.status(404).send({ success: false, message: "Integration not found" });
|
|
175
|
+
}
|
|
147
176
|
|
|
148
177
|
let update = {};
|
|
149
178
|
if (req.body.name != undefined) {
|
|
150
179
|
update.name = req.body.name;
|
|
151
180
|
}
|
|
152
181
|
if (req.body.value != undefined) {
|
|
153
|
-
update.value =
|
|
182
|
+
update.value = existingIntegration.value
|
|
183
|
+
? mergeIntegrationValue(req.body.value, existingIntegration.value)
|
|
184
|
+
: req.body.value;
|
|
154
185
|
}
|
|
155
186
|
|
|
156
|
-
Integration.
|
|
187
|
+
Integration.findOneAndUpdate({ _id: integration_id, id_project: id_project }, update, { new: true }, (err, savedIntegration) => {
|
|
157
188
|
if (err) {
|
|
158
189
|
winston.error("Error find by id and update integration: ", err);
|
|
159
|
-
return res.status({ success: false, error: err })
|
|
190
|
+
return res.status(500).send({ success: false, error: err })
|
|
191
|
+
}
|
|
192
|
+
if (!savedIntegration) {
|
|
193
|
+
return res.status(404).send({ success: false, message: "Integration not found" });
|
|
160
194
|
}
|
|
161
195
|
|
|
162
196
|
Integration.find({ id_project: id_project }, (err, integrations) => {
|
|
@@ -167,7 +201,7 @@ router.put('/:integration_id', async (req, res) => {
|
|
|
167
201
|
}
|
|
168
202
|
})
|
|
169
203
|
|
|
170
|
-
res.status(200).send(savedIntegration);
|
|
204
|
+
res.status(200).send(toPublicIntegration(savedIntegration));
|
|
171
205
|
})
|
|
172
206
|
})
|
|
173
207
|
|
|
@@ -176,10 +210,13 @@ router.delete('/:integration_id', async (req, res) => {
|
|
|
176
210
|
let id_project = req.projectid;
|
|
177
211
|
let integration_id = req.params.integration_id;
|
|
178
212
|
|
|
179
|
-
Integration.
|
|
213
|
+
Integration.findOneAndDelete({ _id: integration_id, id_project: id_project }, (err, result) => {
|
|
180
214
|
if (err) {
|
|
181
215
|
winston.error("Error find by id and delete integration: ", err);
|
|
182
|
-
return res.status({ success: false, error: err })
|
|
216
|
+
return res.status(500).send({ success: false, error: err })
|
|
217
|
+
}
|
|
218
|
+
if (!result) {
|
|
219
|
+
return res.status(404).send({ success: false, message: "Integration not found" });
|
|
183
220
|
}
|
|
184
221
|
|
|
185
222
|
Integration.find({ id_project: id_project }, (err, integrations) => {
|
package/routes/kbsettings.js
CHANGED
|
@@ -1,215 +1,185 @@
|
|
|
1
1
|
var express = require('express');
|
|
2
|
-
var { KBSettings } = require('../models/kb_setting');
|
|
3
|
-
// var { KB } = require('../models/kb_setting');
|
|
4
|
-
// var KB = require('../models/kb_setting')
|
|
5
2
|
var router = express.Router();
|
|
6
|
-
var winston = require('../config/winston');
|
|
7
|
-
const aiService = require('../services/aiService');
|
|
8
3
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
winston.error("find knoledge base settings error: ", err);
|
|
15
|
-
return res.status(500).send({ success: false, error: err });
|
|
16
|
-
}
|
|
17
|
-
else if (!kb_setting) {
|
|
18
|
-
// Automatically creates the kb settings object if it does not exist
|
|
19
|
-
let new_settings = new KBSettings({
|
|
20
|
-
id_project: req.projectid
|
|
21
|
-
})
|
|
22
|
-
new_settings.save(function (err, savedKbSettings) {
|
|
23
|
-
if (err) {
|
|
24
|
-
winston.error("save new kbs error: ", err);
|
|
25
|
-
return res.status(500).send({ success: false, error: err});
|
|
26
|
-
} else {
|
|
27
|
-
return res.status(200).send(savedKbSettings);
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
return res.status(200).send(kb_setting)
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// Never used?
|
|
39
|
-
router.post('/', async (req, res) => {
|
|
40
|
-
|
|
41
|
-
let body = req.body;
|
|
42
|
-
|
|
43
|
-
let new_settings = new KBSettings({
|
|
44
|
-
id_project: req.projectid,
|
|
45
|
-
// gptkey: body.gptkey
|
|
46
|
-
// others params are set with default values
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
new_settings.save(function (err, savedKbSettings) {
|
|
50
|
-
if (err) {
|
|
51
|
-
winston.error("save new kbs error: ", err);
|
|
52
|
-
return res.status(500).send({ success: false, error: err});
|
|
53
|
-
} else {
|
|
54
|
-
return res.status(200).send(savedKbSettings);
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
router.put('/:settings_id', async (req, res) => {
|
|
60
|
-
|
|
61
|
-
let settings_id = req.params.settings_id;
|
|
62
|
-
let update = {};
|
|
63
|
-
|
|
64
|
-
if (req.body.gptkey != undefined) {
|
|
65
|
-
update.gptkey = req.body.gptkey
|
|
4
|
+
// Route disabled — see app.js (/kbsettings superseded by /kb).
|
|
5
|
+
// Never expose gptkey in HTTP responses.
|
|
6
|
+
function stripGptkey(kb_setting) {
|
|
7
|
+
if (!kb_setting) {
|
|
8
|
+
return kb_setting;
|
|
66
9
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
router.post('/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
router.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
10
|
+
const obj = typeof kb_setting.toObject === 'function' ? kb_setting.toObject() : { ...kb_setting };
|
|
11
|
+
delete obj.gptkey;
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// var { KBSettings } = require('../models/kb_setting');
|
|
16
|
+
// var winston = require('../config/winston');
|
|
17
|
+
// const aiService = require('../services/aiService');
|
|
18
|
+
// const { sanitizeAxiosError, axiosErrorStatus } = require('../utils/sanitizeAxiosError');
|
|
19
|
+
//
|
|
20
|
+
// router.get('/', async (req, res) => {
|
|
21
|
+
// let project_id = req.projectid;
|
|
22
|
+
//
|
|
23
|
+
// KBSettings.findOne({ id_project: project_id }, (err, kb_setting) => {
|
|
24
|
+
// if (err) {
|
|
25
|
+
// winston.error("find knoledge base settings error: ", err);
|
|
26
|
+
// return res.status(500).send({ success: false, error: err });
|
|
27
|
+
// }
|
|
28
|
+
// else if (!kb_setting) {
|
|
29
|
+
// let new_settings = new KBSettings({
|
|
30
|
+
// id_project: req.projectid
|
|
31
|
+
// })
|
|
32
|
+
// new_settings.save(function (err, savedKbSettings) {
|
|
33
|
+
// if (err) {
|
|
34
|
+
// winston.error("save new kbs error: ", err);
|
|
35
|
+
// return res.status(500).send({ success: false, error: err});
|
|
36
|
+
// } else {
|
|
37
|
+
// return res.status(200).send(stripGptkey(savedKbSettings));
|
|
38
|
+
// }
|
|
39
|
+
// })
|
|
40
|
+
// }
|
|
41
|
+
// else {
|
|
42
|
+
// return res.status(200).send(stripGptkey(kb_setting))
|
|
43
|
+
// }
|
|
44
|
+
// })
|
|
45
|
+
// })
|
|
46
|
+
//
|
|
47
|
+
// router.post('/', async (req, res) => {
|
|
48
|
+
// let new_settings = new KBSettings({
|
|
49
|
+
// id_project: req.projectid
|
|
50
|
+
// })
|
|
51
|
+
//
|
|
52
|
+
// new_settings.save(function (err, savedKbSettings) {
|
|
53
|
+
// if (err) {
|
|
54
|
+
// winston.error("save new kbs error: ", err);
|
|
55
|
+
// return res.status(500).send({ success: false, error: err});
|
|
56
|
+
// } else {
|
|
57
|
+
// return res.status(200).send(stripGptkey(savedKbSettings));
|
|
58
|
+
// }
|
|
59
|
+
// })
|
|
60
|
+
// })
|
|
61
|
+
//
|
|
62
|
+
// router.put('/:settings_id', async (req, res) => {
|
|
63
|
+
// let settings_id = req.params.settings_id;
|
|
64
|
+
// let update = {};
|
|
65
|
+
//
|
|
66
|
+
// if (req.body.gptkey != undefined) {
|
|
67
|
+
// update.gptkey = req.body.gptkey
|
|
68
|
+
// }
|
|
69
|
+
//
|
|
70
|
+
// winston.debug("update: ", update);
|
|
71
|
+
// KBSettings.findByIdAndUpdate(settings_id, update, { new: true }, (err, settings) => {
|
|
72
|
+
// if (err) {
|
|
73
|
+
// winston.error("findByIdAndUpdate error: ", err);
|
|
74
|
+
// res.status(500).send({ success: false, error: err });
|
|
75
|
+
// } else {
|
|
76
|
+
// res.status(200).send(stripGptkey(settings));
|
|
77
|
+
// }
|
|
78
|
+
// })
|
|
79
|
+
// })
|
|
80
|
+
//
|
|
81
|
+
// router.delete('/:settings_id/:kb_id', async (req, res) => {
|
|
82
|
+
// let settings_id = req.params.settings_id;
|
|
83
|
+
// let kb_id = req.params.kb_id;
|
|
84
|
+
//
|
|
85
|
+
// KBSettings.findByIdAndUpdate(settings_id, { $pull: { kbs: { _id: kb_id }}}, { new: true}, (err, settings) => {
|
|
86
|
+
// if (err) {
|
|
87
|
+
// winston.error("delete kb from list error: ", err);
|
|
88
|
+
// res.status(500).send({ success: false, error: err });
|
|
89
|
+
// } else {
|
|
90
|
+
// res.status(200).send(stripGptkey(settings));
|
|
91
|
+
// }
|
|
92
|
+
// })
|
|
93
|
+
// })
|
|
94
|
+
//
|
|
95
|
+
// router.post('/qa', async (req, res) => {
|
|
96
|
+
// let data = req.body;
|
|
97
|
+
// winston.debug("/qa data: ", data);
|
|
98
|
+
//
|
|
99
|
+
// aiService.ask(data).then((resp) => {
|
|
100
|
+
// winston.debug("qa resp: ", resp.data);
|
|
101
|
+
// res.status(200).send(resp.data);
|
|
102
|
+
// }).catch((err) => {
|
|
103
|
+
// winston.error("qa err: ", sanitizeAxiosError(err));
|
|
104
|
+
// res.status(axiosErrorStatus(err, 400)).send({ success: false, error: sanitizeAxiosError(err) });
|
|
105
|
+
// })
|
|
106
|
+
// })
|
|
107
|
+
//
|
|
108
|
+
// router.post('/startscrape', async (req, res) => {
|
|
109
|
+
// let data = req.body;
|
|
110
|
+
// winston.debug("/startscrape data: ", data);
|
|
111
|
+
//
|
|
112
|
+
// aiService.startScrape(data).then((resp) => {
|
|
113
|
+
// winston.debug("startScrape resp: ", resp.data);
|
|
114
|
+
// res.status(200).send(resp.data);
|
|
115
|
+
// }).catch((err) => {
|
|
116
|
+
// winston.error("startScrape err: ", sanitizeAxiosError(err));
|
|
117
|
+
// res.status(axiosErrorStatus(err, 400)).send({ success: false, error: sanitizeAxiosError(err) });
|
|
118
|
+
// })
|
|
119
|
+
// })
|
|
120
|
+
//
|
|
121
|
+
// router.post('/checkstatus', async (req, res) => {
|
|
122
|
+
// winston.debug("/checkstatus req.body: ", req.body);
|
|
123
|
+
//
|
|
124
|
+
// let full_url = req.body.full_url;
|
|
125
|
+
// let data = {
|
|
126
|
+
// url_list: [ req.body.full_url ]
|
|
127
|
+
// }
|
|
128
|
+
//
|
|
129
|
+
// aiService.checkStatus(data).then((resp) => {
|
|
130
|
+
// let response = resp.data[full_url];
|
|
131
|
+
//
|
|
132
|
+
// let return_data = {
|
|
133
|
+
// status_message: response.status_message
|
|
134
|
+
// }
|
|
135
|
+
//
|
|
136
|
+
// if (response.status_code === 3) {
|
|
137
|
+
// return_data.status_code = 2;
|
|
138
|
+
// }
|
|
139
|
+
//
|
|
140
|
+
// if (response.status_code === 1 || response.status_code === 2 ) {
|
|
141
|
+
// return_data.status_code = 1;
|
|
142
|
+
// }
|
|
143
|
+
//
|
|
144
|
+
// if (response.status_code === 0) {
|
|
145
|
+
// return_data.status_code = 0;
|
|
146
|
+
// }
|
|
147
|
+
//
|
|
148
|
+
// if (!response.status_code) {
|
|
149
|
+
// return_data.status_code = 0;
|
|
150
|
+
// }
|
|
151
|
+
//
|
|
152
|
+
// res.status(200).send(return_data);
|
|
153
|
+
// }).catch((err) => {
|
|
154
|
+
// res.status(axiosErrorStatus(err, 400)).send({ success: false, error: sanitizeAxiosError(err) });
|
|
155
|
+
// })
|
|
156
|
+
// })
|
|
157
|
+
//
|
|
158
|
+
// router.post('/:settings_id', async (req, res) => {
|
|
159
|
+
// let settings_id = req.params.settings_id;
|
|
160
|
+
// let body = req.body;
|
|
161
|
+
//
|
|
162
|
+
// KBSettings.findById(settings_id, (err, settings) => {
|
|
163
|
+
// if (err) {
|
|
164
|
+
// winston.error("find knoledge base error: ", err);
|
|
165
|
+
// return res.status(500).send({ success: false, error: err});
|
|
166
|
+
// } else {
|
|
167
|
+
// let new_kb = {
|
|
168
|
+
// name: body.name,
|
|
169
|
+
// url: body.url
|
|
170
|
+
// }
|
|
171
|
+
// settings.kbs.push(new_kb);
|
|
172
|
+
//
|
|
173
|
+
// KBSettings.findByIdAndUpdate( settings_id, settings, { new: true }, (err, savedSettings) => {
|
|
174
|
+
// if (err) {
|
|
175
|
+
// winston.err("findByIdAndUpdate error: ", err);
|
|
176
|
+
// res.status(500).send({ success: false, error: err });
|
|
177
|
+
// } else {
|
|
178
|
+
// res.status(200).send(stripGptkey(savedSettings));
|
|
179
|
+
// }
|
|
180
|
+
// })
|
|
181
|
+
// }
|
|
182
|
+
// })
|
|
183
|
+
// })
|
|
184
|
+
|
|
185
|
+
module.exports = router;
|
package/routes/llm.js
CHANGED
|
@@ -7,6 +7,7 @@ const aiManager = require('../services/aiManager');
|
|
|
7
7
|
const multer = require('multer');
|
|
8
8
|
const fileUtils = require('../utils/fileUtils');
|
|
9
9
|
const { MODELS_MULTIPLIER } = require('../utils/aiUtils');
|
|
10
|
+
const { sanitizeAxiosError, axiosErrorStatus } = require('../utils/sanitizeAxiosError');
|
|
10
11
|
|
|
11
12
|
let MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;
|
|
12
13
|
let uploadlimits = undefined;
|
|
@@ -133,9 +134,9 @@ router.post('/preview', async (req, res) => {
|
|
|
133
134
|
} else if (err.response?.data?.detail?.answer) {
|
|
134
135
|
res.status(400).send({ success: false, error: err.response.data.detail.answer, detail: err.response.data.detail });
|
|
135
136
|
} else if (err.response?.data) {
|
|
136
|
-
res.status(
|
|
137
|
+
res.status(axiosErrorStatus(err)).send({ success: false, error: sanitizeAxiosError(err) });
|
|
137
138
|
} else {
|
|
138
|
-
res.status(
|
|
139
|
+
res.status(axiosErrorStatus(err)).send({ success: false, error: sanitizeAxiosError(err) });
|
|
139
140
|
}
|
|
140
141
|
})
|
|
141
142
|
|
|
@@ -174,8 +175,8 @@ router.post('/transcription', upload.single('uploadFile'), async (req, res) => {
|
|
|
174
175
|
winston.verbose("Transcript response: ", response.data);
|
|
175
176
|
res.status(200).send({ text: response.data.text});
|
|
176
177
|
}).catch((err) => {
|
|
177
|
-
winston.error("
|
|
178
|
-
res.status(
|
|
178
|
+
winston.error("transcription error: ", sanitizeAxiosError(err));
|
|
179
|
+
res.status(axiosErrorStatus(err)).send({ success: false, error: sanitizeAxiosError(err) });
|
|
179
180
|
})
|
|
180
181
|
|
|
181
182
|
})
|
package/routes/openai.js
CHANGED
|
@@ -1,90 +1,92 @@
|
|
|
1
1
|
var express = require('express');
|
|
2
2
|
var router = express.Router();
|
|
3
|
-
var { KBSettings } = require('../models/kb_setting');
|
|
4
|
-
var aiService = require('../services/aiService');
|
|
5
|
-
var winston = require('../config/winston');
|
|
6
|
-
const { QuoteManager } = require('../services/QuoteManager');
|
|
7
|
-
const { MODELS_MULTIPLIER } = require('../utils/aiUtils');
|
|
8
3
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
4
|
+
// Disabled: POST / was unused (only Design Studio GPT Task preview, now deprecated).
|
|
5
|
+
// Kept commented with sanitized error handling for potential future re-enablement.
|
|
6
|
+
// var { KBSettings } = require('../models/kb_setting');
|
|
7
|
+
// var aiService = require('../services/aiService');
|
|
8
|
+
// const { MODELS_MULTIPLIER } = require('../utils/aiUtils');
|
|
9
|
+
// const { sanitizeAxiosError, axiosErrorStatus } = require('../utils/sanitizeAxiosError');
|
|
10
|
+
//
|
|
11
|
+
// router.post('/', async (req, res) => {
|
|
12
|
+
//
|
|
13
|
+
// let project_id = req.projectid;
|
|
14
|
+
// let body = req.body;
|
|
15
|
+
// let usePublicKey = false;
|
|
16
|
+
// let publicKey = process.env.GPTKEY;
|
|
17
|
+
// let gptkey = null;
|
|
18
|
+
// let obj = { createdAt: new Date() };
|
|
19
|
+
// let quoteManager = req.app.get('quote_manager');
|
|
20
|
+
//
|
|
21
|
+
// KBSettings.findOne({ id_project: project_id }, async (err, kbSettings) => {
|
|
22
|
+
//
|
|
23
|
+
// if (err) {
|
|
24
|
+
// usePublicKey = true;
|
|
25
|
+
// gptkey = publicKey;
|
|
26
|
+
// }
|
|
27
|
+
//
|
|
28
|
+
// if (kbSettings && kbSettings.gptkey) {
|
|
29
|
+
// gptkey = kbSettings.gptkey;
|
|
30
|
+
// } else {
|
|
31
|
+
// usePublicKey = true;
|
|
32
|
+
// gptkey = publicKey;
|
|
33
|
+
// }
|
|
34
|
+
//
|
|
35
|
+
// if (!gptkey) {
|
|
36
|
+
// return res.status(400).send({ success: false, message: "Missing gptkey parameter" });
|
|
37
|
+
// }
|
|
38
|
+
//
|
|
39
|
+
// if (usePublicKey === true) {
|
|
40
|
+
// let isAvailable = await quoteManager.checkQuote(req.project, obj, 'tokens');
|
|
41
|
+
// if (isAvailable === false) {
|
|
42
|
+
// return res.status(403).send({ success: false, message: "Tokens quota exceeded", error_code: 13001})
|
|
43
|
+
// }
|
|
44
|
+
// }
|
|
45
|
+
//
|
|
46
|
+
//
|
|
47
|
+
// let json = {
|
|
48
|
+
// model: body.model,
|
|
49
|
+
// messages: [
|
|
50
|
+
// { role: "user", content: body.question }
|
|
51
|
+
// ],
|
|
52
|
+
// max_tokens: body.max_tokens,
|
|
53
|
+
// temperature: body.temperature
|
|
54
|
+
// }
|
|
55
|
+
//
|
|
56
|
+
// let message = { role: "", content: "" };
|
|
57
|
+
// if (body.context) {
|
|
58
|
+
// message.role = "system";
|
|
59
|
+
// message.content = body.context;
|
|
60
|
+
// json.messages.unshift(message);
|
|
61
|
+
// }
|
|
62
|
+
//
|
|
63
|
+
// if (body.formatType && body.formatType !== 'none') {
|
|
64
|
+
// json.response_format = {
|
|
65
|
+
// type: body.formatType
|
|
66
|
+
// }
|
|
67
|
+
// }
|
|
68
|
+
//
|
|
69
|
+
// let multiplier = MODELS_MULTIPLIER[json.model];
|
|
70
|
+
// if (!multiplier) {
|
|
71
|
+
// multiplier = 1;
|
|
72
|
+
// winston.info("No multiplier found for AI model (openai) " + json.model)
|
|
73
|
+
// }
|
|
74
|
+
//
|
|
75
|
+
// aiService.completions(json, gptkey).then(async (response) => {
|
|
76
|
+
// let data = { createdAt: new Date(), tokens: response.data.usage.total_tokens, multiplier: multiplier }
|
|
77
|
+
// if (usePublicKey === true) {
|
|
78
|
+
// let incremented_key = await quoteManager.incrementTokenCount(req.project, data);
|
|
79
|
+
// winston.verbose("Tokens quota incremented for key " + incremented_key);
|
|
80
|
+
// }
|
|
81
|
+
//
|
|
82
|
+
// res.status(200).send(response.data);
|
|
83
|
+
//
|
|
84
|
+
// }).catch((err) => {
|
|
85
|
+
// winston.error("completions error: ", sanitizeAxiosError(err));
|
|
86
|
+
// res.status(axiosErrorStatus(err)).send({ success: false, error: sanitizeAxiosError(err) });
|
|
87
|
+
// })
|
|
88
|
+
// })
|
|
89
|
+
// })
|
|
88
90
|
|
|
89
91
|
router.post('/quotes', async (req, res) => {
|
|
90
92
|
|
|
@@ -106,56 +108,4 @@ router.post('/quotes', async (req, res) => {
|
|
|
106
108
|
res.status(200).send({ message: "value incremented for key " + incremented_key, key: incremented_key, currentQuote: quote });
|
|
107
109
|
})
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// let project_id = req.projectid;
|
|
112
|
-
|
|
113
|
-
// OpenaiKbs.find({ id_project: project_id }, (err, kbs) => {
|
|
114
|
-
// if (err) {
|
|
115
|
-
// console.error("find all kbs error: ", err);
|
|
116
|
-
// return res.status(500).send({ success: false, error: err });
|
|
117
|
-
// } else {
|
|
118
|
-
// return res.status(200).send(kbs);
|
|
119
|
-
// }
|
|
120
|
-
// })
|
|
121
|
-
// })
|
|
122
|
-
|
|
123
|
-
// router.post('/', async (req, res) => {
|
|
124
|
-
|
|
125
|
-
// let body = req.body;
|
|
126
|
-
|
|
127
|
-
// let new_kbs = new OpenaiKbs({
|
|
128
|
-
// name: body.name,
|
|
129
|
-
// url: body.url,
|
|
130
|
-
// id_project: req.projectid,
|
|
131
|
-
// gptkey: req.body.gptkey
|
|
132
|
-
// })
|
|
133
|
-
|
|
134
|
-
// new_kbs.save(function (err, savedKbs) {
|
|
135
|
-
// if (err) {
|
|
136
|
-
// console.error("save new kbs error: ", err);
|
|
137
|
-
// return res.status(500).send({ success: false, error: err});
|
|
138
|
-
// } else {
|
|
139
|
-
// return res.status(200).send(savedKbs);
|
|
140
|
-
// }
|
|
141
|
-
// })
|
|
142
|
-
// })
|
|
143
|
-
|
|
144
|
-
// router.put('/', async (req, res) => {
|
|
145
|
-
// // to be implemented
|
|
146
|
-
// })
|
|
147
|
-
|
|
148
|
-
// router.delete('/:kbs_id', async (req, res) => {
|
|
149
|
-
// let kbs_id = req.params.kbs_id;
|
|
150
|
-
|
|
151
|
-
// OpenaiKbs.findOneAndDelete( { _id: kbs_id }, (err, kbDeleted) => {
|
|
152
|
-
// if (err) {
|
|
153
|
-
// console.error("find one and delete kbs error: ", err);
|
|
154
|
-
// return res.status(500).send({ success: false, error: err});
|
|
155
|
-
// } else {
|
|
156
|
-
// return res.status(200).send({ success: true, message: 'Knowledge Base deleted successfully', openai_kb: kbDeleted });
|
|
157
|
-
// }
|
|
158
|
-
// })
|
|
159
|
-
// })
|
|
160
|
-
|
|
161
|
-
module.exports = router;
|
|
111
|
+
module.exports = router;
|
package/routes/urlPreview.js
CHANGED
|
@@ -17,7 +17,7 @@ function isSafeUrl(rawUrl) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// POST /:projectid/url-preview —
|
|
20
|
+
// POST /:projectid/url-preview — requires JWT (widget sends tiledeskToken as guest)
|
|
21
21
|
// Body: { "urls": ["https://example.com", "https://another.com"] }
|
|
22
22
|
router.post('/', async (req, res) => {
|
|
23
23
|
const urls = req.body.urls;
|
|
@@ -53,7 +53,7 @@ class WebhookService {
|
|
|
53
53
|
let token = await this.generateChatbotToken(chatbot);
|
|
54
54
|
|
|
55
55
|
let url = TILEBOT_ENDPOINT + 'block/' + webhook.id_project + "/" + chatbot_id + "/" + webhook.block_id;
|
|
56
|
-
winston.
|
|
56
|
+
winston.verbose("Webhook chatbot URL: " + url);
|
|
57
57
|
|
|
58
58
|
payload.async = webhook.async;
|
|
59
59
|
payload.token = token;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const { expect } = require('chai');
|
|
2
|
+
const {
|
|
3
|
+
maskApiKey,
|
|
4
|
+
isMaskedSecret,
|
|
5
|
+
maskIntegrationValue,
|
|
6
|
+
mergeIntegrationValue,
|
|
7
|
+
toPublicIntegration
|
|
8
|
+
} = require('../utils/maskIntegrationSecrets');
|
|
9
|
+
|
|
10
|
+
describe('maskIntegrationSecrets', () => {
|
|
11
|
+
|
|
12
|
+
it('masks api keys keeping prefix and suffix', () => {
|
|
13
|
+
expect(maskApiKey('sk-abc123nz2bVU')).to.equal('sk-********2bVU');
|
|
14
|
+
expect(maskApiKey('sk-xxxxxxxxxxx2bVU')).to.equal('sk-***********2bVU');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('masks nested integration value fields', () => {
|
|
18
|
+
const value = {
|
|
19
|
+
apikey: 'sk-secretkey1234',
|
|
20
|
+
organization: 'MyCompany',
|
|
21
|
+
servers: [{
|
|
22
|
+
name: 'together',
|
|
23
|
+
url: 'https://api.together.xyz/v1/',
|
|
24
|
+
apikey: 'tgp_v1_abcdefgySDDG-Yw',
|
|
25
|
+
models: ['model-a']
|
|
26
|
+
}]
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const masked = maskIntegrationValue(value);
|
|
30
|
+
expect(masked.organization).to.equal('MyCompany');
|
|
31
|
+
expect(masked.apikey).to.equal('sk-*********1234');
|
|
32
|
+
expect(masked.servers[0].apikey).to.equal('tgp***************G-Yw');
|
|
33
|
+
expect(masked.servers[0].models).to.deep.equal(['model-a']);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('detects masked secrets', () => {
|
|
37
|
+
expect(isMaskedSecret('sk-***********2bVU')).to.be.true;
|
|
38
|
+
expect(isMaskedSecret('sk-real-key')).to.be.false;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('preserves existing secrets when incoming value is masked', () => {
|
|
42
|
+
const existing = {
|
|
43
|
+
apikey: 'sk-real-key',
|
|
44
|
+
organization: 'MyCompany',
|
|
45
|
+
servers: [{
|
|
46
|
+
name: 'together',
|
|
47
|
+
apikey: 'tgp_v1_realkey',
|
|
48
|
+
url: 'https://api.together.xyz/v1/'
|
|
49
|
+
}]
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const incoming = {
|
|
53
|
+
apikey: 'sk-***********2bVU',
|
|
54
|
+
organization: 'NewCompany',
|
|
55
|
+
servers: [{
|
|
56
|
+
name: 'together',
|
|
57
|
+
apikey: 'tgp*************D-Yw',
|
|
58
|
+
url: 'https://api.together.xyz/v1/'
|
|
59
|
+
}]
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const merged = mergeIntegrationValue(incoming, existing);
|
|
63
|
+
expect(merged.apikey).to.equal('sk-real-key');
|
|
64
|
+
expect(merged.organization).to.equal('NewCompany');
|
|
65
|
+
expect(merged.servers[0].apikey).to.equal('tgp_v1_realkey');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('replaces secrets when incoming value is not masked', () => {
|
|
69
|
+
const existing = {
|
|
70
|
+
apikey: 'sk-old-key'
|
|
71
|
+
};
|
|
72
|
+
const incoming = {
|
|
73
|
+
apikey: 'sk-new-key'
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const merged = mergeIntegrationValue(incoming, existing);
|
|
77
|
+
expect(merged.apikey).to.equal('sk-new-key');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('returns masked integration for public responses', () => {
|
|
81
|
+
const integration = {
|
|
82
|
+
_id: '656054000410fa00132e5dcc',
|
|
83
|
+
id_project: '656054000410fa00132e5dcc',
|
|
84
|
+
name: 'openai',
|
|
85
|
+
value: {
|
|
86
|
+
apikey: 'sk-secretkey1234',
|
|
87
|
+
organization: 'MyCompany'
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const publicIntegration = toPublicIntegration(integration);
|
|
92
|
+
expect(publicIntegration.value.apikey).to.equal('sk-*********1234');
|
|
93
|
+
expect(publicIntegration.value.organization).to.equal('MyCompany');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -38,7 +38,7 @@ function assignmentActorContext(req, defaultSource) {
|
|
|
38
38
|
url: req.originalUrl
|
|
39
39
|
});
|
|
40
40
|
} else {
|
|
41
|
-
winston.
|
|
41
|
+
winston.debug('assignmentActorContext actor resolution', {
|
|
42
42
|
actor: actor,
|
|
43
43
|
principal_id: projectUserUpdateContextUtil.resolveUserId(req.user),
|
|
44
44
|
principal_name: req.user && req.user.name,
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const SECRET_KEYS = new Set(['apikey', 'api_key']);
|
|
2
|
+
|
|
3
|
+
const UNMASKED_START = 3;
|
|
4
|
+
const UNMASKED_END = 4;
|
|
5
|
+
|
|
6
|
+
function maskApiKey(key) {
|
|
7
|
+
if (typeof key !== 'string' || key.length === 0) {
|
|
8
|
+
return key;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (key.length <= UNMASKED_START + UNMASKED_END) {
|
|
12
|
+
return '*'.repeat(key.length);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const start = key.slice(0, UNMASKED_START);
|
|
16
|
+
const end = key.slice(-UNMASKED_END);
|
|
17
|
+
const maskLength = key.length - UNMASKED_START - UNMASKED_END;
|
|
18
|
+
return start + '*'.repeat(maskLength) + end;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isMaskedSecret(value) {
|
|
22
|
+
return typeof value === 'string' && value.includes('*');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mergeSecretField(incoming, existing) {
|
|
26
|
+
if (incoming === undefined) {
|
|
27
|
+
return existing;
|
|
28
|
+
}
|
|
29
|
+
if (isMaskedSecret(incoming) && existing) {
|
|
30
|
+
return existing;
|
|
31
|
+
}
|
|
32
|
+
return incoming;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function maskIntegrationValue(value) {
|
|
36
|
+
if (value === null || value === undefined) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return value.map(maskIntegrationValue);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof value !== 'object') {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = {};
|
|
49
|
+
for (const [key, val] of Object.entries(value)) {
|
|
50
|
+
if (SECRET_KEYS.has(key) && typeof val === 'string') {
|
|
51
|
+
result[key] = maskApiKey(val);
|
|
52
|
+
} else if (val !== null && typeof val === 'object') {
|
|
53
|
+
result[key] = maskIntegrationValue(val);
|
|
54
|
+
} else {
|
|
55
|
+
result[key] = val;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function mergeIntegrationValue(incoming, existing) {
|
|
62
|
+
if (!incoming) {
|
|
63
|
+
return incoming;
|
|
64
|
+
}
|
|
65
|
+
if (!existing) {
|
|
66
|
+
return incoming;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const merged = { ...existing, ...incoming };
|
|
70
|
+
|
|
71
|
+
if (incoming.apikey !== undefined) {
|
|
72
|
+
merged.apikey = mergeSecretField(incoming.apikey, existing.apikey);
|
|
73
|
+
}
|
|
74
|
+
if (incoming.api_key !== undefined) {
|
|
75
|
+
merged.api_key = mergeSecretField(incoming.api_key, existing.api_key);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Array.isArray(incoming.servers)) {
|
|
79
|
+
merged.servers = incoming.servers.map((server) => {
|
|
80
|
+
const existingServer = Array.isArray(existing.servers)
|
|
81
|
+
? existing.servers.find((s) => s.name === server.name)
|
|
82
|
+
: undefined;
|
|
83
|
+
|
|
84
|
+
if (!existingServer) {
|
|
85
|
+
return server;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
...existingServer,
|
|
90
|
+
...server,
|
|
91
|
+
apikey: mergeSecretField(server.apikey, existingServer.apikey),
|
|
92
|
+
api_key: mergeSecretField(server.api_key, existingServer.api_key)
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return merged;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toIntegrationObject(integration) {
|
|
101
|
+
if (!integration) {
|
|
102
|
+
return integration;
|
|
103
|
+
}
|
|
104
|
+
return integration.toObject ? integration.toObject() : { ...integration };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function toPublicIntegration(integration) {
|
|
108
|
+
const obj = toIntegrationObject(integration);
|
|
109
|
+
if (!obj) {
|
|
110
|
+
return obj;
|
|
111
|
+
}
|
|
112
|
+
if (obj.value) {
|
|
113
|
+
obj.value = maskIntegrationValue(obj.value);
|
|
114
|
+
}
|
|
115
|
+
return obj;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function toPublicIntegrations(integrations) {
|
|
119
|
+
if (!Array.isArray(integrations)) {
|
|
120
|
+
return integrations;
|
|
121
|
+
}
|
|
122
|
+
return integrations.map(toPublicIntegration);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
maskApiKey,
|
|
127
|
+
isMaskedSecret,
|
|
128
|
+
maskIntegrationValue,
|
|
129
|
+
mergeIntegrationValue,
|
|
130
|
+
toPublicIntegration,
|
|
131
|
+
toPublicIntegrations
|
|
132
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a safe error payload for HTTP responses.
|
|
3
|
+
* Never pass raw AxiosError objects to res.send/res.json — toJSON() includes config.headers.
|
|
4
|
+
*/
|
|
5
|
+
function sanitizeAxiosError(err) {
|
|
6
|
+
if (!err) {
|
|
7
|
+
return { message: 'Request failed' };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const data = err.response?.data;
|
|
11
|
+
|
|
12
|
+
if (data?.error?.message) {
|
|
13
|
+
return { message: data.error.message };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (typeof data?.detail === 'string') {
|
|
17
|
+
return { message: data.detail };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (Array.isArray(data?.detail) && data.detail[0]?.msg) {
|
|
21
|
+
return { message: data.detail[0].msg };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (data?.detail?.answer) {
|
|
25
|
+
return { message: data.detail.answer };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof data?.message === 'string') {
|
|
29
|
+
return { message: data.message };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof err.message === 'string') {
|
|
33
|
+
return { message: err.message };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { message: 'Request failed' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function axiosErrorStatus(err, defaultStatus) {
|
|
40
|
+
return err?.response?.status || defaultStatus || 500;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
sanitizeAxiosError,
|
|
45
|
+
axiosErrorStatus
|
|
46
|
+
};
|