wingbot-mongodb 2.18.0 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nyc_output/8e3ceac5-95c5-4bf9-a069-da51cccc2525.json +1 -0
- package/.nyc_output/processinfo/8e3ceac5-95c5-4bf9-a069-da51cccc2525.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -1
- package/package.json +5 -2
- package/src/AuditLogStorage.js +364 -0
- package/src/BaseStorage.js +49 -4
- package/src/BotTokenStorage.js +1 -1
- package/src/ChatLogStorage.js +59 -28
- package/src/NotificationsStorage.js +7 -7
- package/src/StateStorage.js +1 -1
- package/src/main.js +3 -1
- package/.nyc_output/77359fd4-c391-4d0e-bcba-614b1cf4709e.json +0 -1
- package/.nyc_output/processinfo/77359fd4-c391-4d0e-bcba-614b1cf4709e.json +0 -1
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author wingbot.ai
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const jsonwebtoken = require('jsonwebtoken');
|
|
7
|
+
const BaseStorage = require('./BaseStorage');
|
|
8
|
+
|
|
9
|
+
/** @typedef {import('mongodb/lib/db')} Db */
|
|
10
|
+
|
|
11
|
+
const LEVEL_CRITICAL = 'Critical';
|
|
12
|
+
const LEVEL_IMPORTANT = 'Important';
|
|
13
|
+
const LEVEL_DEBUG = 'Debug';
|
|
14
|
+
|
|
15
|
+
const TYPE_ERROR = 'Error';
|
|
16
|
+
const TYPE_WARN = 'Warn';
|
|
17
|
+
const TYPE_INFO = 'Info';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} TrackingEvent
|
|
21
|
+
* @prop {string} [type='audit']
|
|
22
|
+
* @prop {string} category
|
|
23
|
+
* @prop {string} action
|
|
24
|
+
* @prop {string} [label]
|
|
25
|
+
* @prop {object} [payload]
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} User
|
|
30
|
+
* @prop {string} [id]
|
|
31
|
+
* @prop {string} [senderId]
|
|
32
|
+
* @prop {string} [pageId]
|
|
33
|
+
* @prop {string} [jwt] - jwt to check the authorship
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {object} Meta
|
|
38
|
+
* @prop {string} [ip]
|
|
39
|
+
* @prop {string} [ua]
|
|
40
|
+
* @prop {string} [ro] - referrer || origin
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @typedef {object} LogEntry
|
|
45
|
+
* @prop {string} date - ISO date
|
|
46
|
+
* @prop {number} delta - time skew in ms if there was a write conflict
|
|
47
|
+
* @prop {string} [eventType='audit']
|
|
48
|
+
* @prop {string} category
|
|
49
|
+
* @prop {string} action
|
|
50
|
+
* @prop {string} [label]
|
|
51
|
+
* @prop {object} [payload]
|
|
52
|
+
* @prop {string} level - (Critical|Important|Debug)
|
|
53
|
+
* @prop {boolean} ok - signature matches
|
|
54
|
+
* @prop {number} seq - sequence number
|
|
55
|
+
* @prop {string} type - (Error|Warn|Info)
|
|
56
|
+
* @prop {User} user
|
|
57
|
+
* @prop {string} wid - workspace id
|
|
58
|
+
* @prop {Meta} meta
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* JWT Verifier
|
|
63
|
+
*
|
|
64
|
+
* @callback JwtVerifier
|
|
65
|
+
* @param {string} token
|
|
66
|
+
* @param {string} userId
|
|
67
|
+
* @param {User} [user]
|
|
68
|
+
* @returns {Promise<boolean>}
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @typedef {object} AuditLogEntry
|
|
73
|
+
* @prop {string} date - ISO date
|
|
74
|
+
* @prop {string} [eventType='audit']
|
|
75
|
+
* @prop {string} category
|
|
76
|
+
* @prop {string} action
|
|
77
|
+
* @prop {string} [label]
|
|
78
|
+
* @prop {object} [payload]
|
|
79
|
+
* @prop {string} level - (Critical|Important|Debug)
|
|
80
|
+
* @prop {string} type - (Error|Warn|Info)
|
|
81
|
+
* @prop {User} user
|
|
82
|
+
* @prop {string} wid - workspace id
|
|
83
|
+
* @prop {Meta} meta
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Audit Log Callback
|
|
88
|
+
*
|
|
89
|
+
* @callback AuditLogCallback
|
|
90
|
+
* @param {AuditLogEntry} entry
|
|
91
|
+
* @returns {Promise}
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Storage for audit logs with signatures chain
|
|
96
|
+
*/
|
|
97
|
+
class AuditLogStorage extends BaseStorage {
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
*
|
|
101
|
+
* @param {Db|{():Promise<Db>}} mongoDb
|
|
102
|
+
* @param {string} collectionName
|
|
103
|
+
* @param {{error:Function,log:Function}} [log] - console like logger
|
|
104
|
+
* @param {boolean} [isCosmo]
|
|
105
|
+
* @param {string|Promise<string>} [secret]
|
|
106
|
+
* @param {string|Promise<string>} [jwtVerifier]
|
|
107
|
+
*/
|
|
108
|
+
constructor (mongoDb, collectionName = 'auditlog', log = console, isCosmo = false, secret = null, jwtVerifier = null) {
|
|
109
|
+
super(mongoDb, collectionName, log, isCosmo);
|
|
110
|
+
|
|
111
|
+
this.addIndex({
|
|
112
|
+
wid: 1,
|
|
113
|
+
seq: -1
|
|
114
|
+
}, {
|
|
115
|
+
unique: true,
|
|
116
|
+
name: 'wid_1_seq_-1'
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (isCosmo) {
|
|
120
|
+
this.addIndex({
|
|
121
|
+
wid: 1
|
|
122
|
+
}, {
|
|
123
|
+
name: 'wid_1'
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
this.addIndex({
|
|
127
|
+
seq: -1
|
|
128
|
+
}, {
|
|
129
|
+
name: 'seq_-1'
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
this.addIndex({
|
|
133
|
+
wid: 1, date: -1
|
|
134
|
+
}, {
|
|
135
|
+
name: 'wid_1_date_-1'
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.defaultWid = '0';
|
|
140
|
+
|
|
141
|
+
this.muteErrors = true;
|
|
142
|
+
this.maxRetries = 4;
|
|
143
|
+
this._secret = secret;
|
|
144
|
+
|
|
145
|
+
this.LEVEL_CRITICAL = LEVEL_CRITICAL;
|
|
146
|
+
this.LEVEL_IMPORTANT = LEVEL_IMPORTANT;
|
|
147
|
+
this.LEVEL_DEBUG = LEVEL_DEBUG;
|
|
148
|
+
|
|
149
|
+
this.TYPE_ERROR = TYPE_ERROR;
|
|
150
|
+
this.TYPE_WARN = TYPE_WARN;
|
|
151
|
+
this.TYPE_INFO = TYPE_INFO;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @type {JwtVerifier}
|
|
155
|
+
*/
|
|
156
|
+
// @ts-ignore
|
|
157
|
+
this._jwtVerify = typeof jwtVerifier === 'function' || jwtVerifier === null
|
|
158
|
+
? jwtVerifier
|
|
159
|
+
: async (token, userId) => {
|
|
160
|
+
const jwtSec = await Promise.resolve(jwtVerifier);
|
|
161
|
+
const decoded = await new Promise((resolve) => {
|
|
162
|
+
jsonwebtoken.verify(token, jwtSec, (err, res) => {
|
|
163
|
+
resolve(res || {});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
return decoded.id === userId;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** @type {AuditLogCallback} */
|
|
170
|
+
this.callback = null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Add a log
|
|
175
|
+
*
|
|
176
|
+
* @param {TrackingEvent} event
|
|
177
|
+
* @param {User} user
|
|
178
|
+
* @param {Meta} [meta]
|
|
179
|
+
* @param {string} [wid] - workspace ID
|
|
180
|
+
* @param {string} [type]
|
|
181
|
+
* @param {string} [level]
|
|
182
|
+
* @param {Date} [date]
|
|
183
|
+
* @returns {Promise}
|
|
184
|
+
*/
|
|
185
|
+
async log (
|
|
186
|
+
event,
|
|
187
|
+
user = {},
|
|
188
|
+
meta = {},
|
|
189
|
+
wid = this.defaultWid,
|
|
190
|
+
type = TYPE_INFO,
|
|
191
|
+
level = LEVEL_IMPORTANT,
|
|
192
|
+
date = new Date()
|
|
193
|
+
) {
|
|
194
|
+
const {
|
|
195
|
+
type: eventType = 'audit',
|
|
196
|
+
...rest
|
|
197
|
+
} = event;
|
|
198
|
+
const entry = {
|
|
199
|
+
date,
|
|
200
|
+
eventType,
|
|
201
|
+
...rest,
|
|
202
|
+
level,
|
|
203
|
+
meta,
|
|
204
|
+
type,
|
|
205
|
+
user,
|
|
206
|
+
wid
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const secret = await Promise.resolve(this._secret);
|
|
210
|
+
const stored = await this._storeWithRetry(secret, entry);
|
|
211
|
+
if (!this.callback) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
await this.callback(stored);
|
|
216
|
+
} catch (e) {
|
|
217
|
+
this._log.error('Failed to send AuditLog', e, entry);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// logEvent(level, type, workflowType, workflowInstance, eventData, account)
|
|
221
|
+
// level is the criticality of the event ('Critical','Important','Debug').
|
|
222
|
+
// type is the type of the event ('Error','Warn','Info').
|
|
223
|
+
/**
|
|
224
|
+
* - log (N/A)
|
|
225
|
+
- report (Debug)
|
|
226
|
+
- conversation (N/A)
|
|
227
|
+
- audit (Important)
|
|
228
|
+
- user (N/A)
|
|
229
|
+
*/
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
*
|
|
234
|
+
* @param {string} [wid] - workspace id
|
|
235
|
+
* @param {number} [fromSeq] - for paging
|
|
236
|
+
* @param {number} [limit]
|
|
237
|
+
* @returns {Promise<LogEntry[]>}
|
|
238
|
+
*/
|
|
239
|
+
async list (wid = this.defaultWid, fromSeq = 0, limit = 40) {
|
|
240
|
+
const c = await this._getCollection();
|
|
241
|
+
|
|
242
|
+
const cond = { wid };
|
|
243
|
+
|
|
244
|
+
if (fromSeq) {
|
|
245
|
+
cond.seq = { $lt: fromSeq };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const data = await c.find({ wid })
|
|
249
|
+
.limit(limit + 1)
|
|
250
|
+
.sort({ seq: -1 })
|
|
251
|
+
.project({ _id: 0 })
|
|
252
|
+
.toArray();
|
|
253
|
+
|
|
254
|
+
const secret = await Promise.resolve(this._secret);
|
|
255
|
+
|
|
256
|
+
const len = data.length === limit + 1
|
|
257
|
+
? data.length - 1
|
|
258
|
+
: data.length;
|
|
259
|
+
const ret = new Array(len);
|
|
260
|
+
|
|
261
|
+
let verifyUser = false;
|
|
262
|
+
for (let i = 0; i < len; i++) {
|
|
263
|
+
const {
|
|
264
|
+
sign,
|
|
265
|
+
...log
|
|
266
|
+
} = data[i];
|
|
267
|
+
|
|
268
|
+
verifyUser = verifyUser || (log.user.id && log.user.jwt);
|
|
269
|
+
|
|
270
|
+
if (secret) {
|
|
271
|
+
const previous = data[i + 1] || { sign: null };
|
|
272
|
+
const objToSign = this._objectToSign(log);
|
|
273
|
+
const compare = this._signWithSecret(objToSign, secret, previous.sign);
|
|
274
|
+
log.ok = compare === sign;
|
|
275
|
+
if (!log.ok) {
|
|
276
|
+
this._log.error(`AuditLog: found wrong signature at wid: "${log.wid}", seq: "${log.seq}"`, log);
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
log.ok = null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
ret[i] = log;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (verifyUser && this._jwtVerify) {
|
|
286
|
+
return Promise.all(
|
|
287
|
+
ret.map(async (log) => {
|
|
288
|
+
if (!log.user.id || !log.user.jwt) {
|
|
289
|
+
return log;
|
|
290
|
+
}
|
|
291
|
+
const userChecked = await this._jwtVerify(log.user.jwt, log.user.id, log.user);
|
|
292
|
+
return Object.assign(log, {
|
|
293
|
+
// it's ok, when there was null
|
|
294
|
+
ok: log.ok !== false && userChecked
|
|
295
|
+
});
|
|
296
|
+
})
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return ret;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
_wait (ms) {
|
|
304
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async _storeWithRetry (secret, entry, delta = 0, i = 1) {
|
|
308
|
+
const start = Date.now();
|
|
309
|
+
Object.assign(entry, { delta });
|
|
310
|
+
try {
|
|
311
|
+
await this._store(entry, secret);
|
|
312
|
+
return entry;
|
|
313
|
+
} catch (e) {
|
|
314
|
+
if (e.code === 11000) {
|
|
315
|
+
// duplicate key
|
|
316
|
+
if (i >= this.maxRetries) {
|
|
317
|
+
throw new Error('AuditLog: cannot store log due to max-retries');
|
|
318
|
+
} else {
|
|
319
|
+
await this._wait((i * 50) + (Math.random() * 100));
|
|
320
|
+
const add = Date.now() - start;
|
|
321
|
+
return this._storeWithRetry(secret, entry, delta + add, i + 1);
|
|
322
|
+
}
|
|
323
|
+
} else if (this.muteErrors) {
|
|
324
|
+
this._log.error('Audit log store error', e, entry);
|
|
325
|
+
return entry;
|
|
326
|
+
} else {
|
|
327
|
+
throw e;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async _store (entry, secret) {
|
|
333
|
+
const c = await this._getCollection();
|
|
334
|
+
|
|
335
|
+
const previous = await c.findOne({
|
|
336
|
+
wid: entry.wid
|
|
337
|
+
}, {
|
|
338
|
+
sort: { seq: -1 },
|
|
339
|
+
projection: { seq: 1, _id: 0, sign: 1 }
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
Object.assign(entry, {
|
|
343
|
+
seq: previous ? previous.seq + 1 : 0
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
let insert;
|
|
347
|
+
if (secret) {
|
|
348
|
+
insert = this._objectToSign(entry);
|
|
349
|
+
const sign = this._signWithSecret(insert, secret, previous ? previous.sign : null);
|
|
350
|
+
Object.assign(insert, { sign });
|
|
351
|
+
} else {
|
|
352
|
+
insert = {
|
|
353
|
+
...entry,
|
|
354
|
+
sign: null
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// @ts-ignore
|
|
359
|
+
await c.insertOne(insert);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
module.exports = AuditLogStorage;
|
package/src/BaseStorage.js
CHANGED
|
@@ -4,15 +4,19 @@
|
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
6
|
const mongodb = require('mongodb'); // eslint-disable-line no-unused-vars
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
/** @typedef {import('mongodb/lib/db')} Db */
|
|
10
|
+
/** @typedef {import('mongodb/lib/collection')} Collection */
|
|
7
11
|
|
|
8
12
|
class BaseStorage {
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
*
|
|
12
|
-
* @param {
|
|
16
|
+
* @param {Db|{():Promise<Db>}} mongoDb
|
|
13
17
|
* @param {string} collectionName
|
|
14
18
|
* @param {{error:Function,log:Function}} [log] - console like logger
|
|
15
|
-
* @param {boolean} isCosmo
|
|
19
|
+
* @param {boolean} [isCosmo]
|
|
16
20
|
* @example
|
|
17
21
|
*
|
|
18
22
|
* const { BaseStorage } = require('winbot-mongodb');
|
|
@@ -45,11 +49,14 @@ class BaseStorage {
|
|
|
45
49
|
this._log = log;
|
|
46
50
|
|
|
47
51
|
/**
|
|
48
|
-
* @type {Promise<
|
|
52
|
+
* @type {Collection|Promise<Collection>}
|
|
49
53
|
*/
|
|
50
54
|
this._collection = null;
|
|
51
55
|
|
|
52
56
|
this._indexes = [];
|
|
57
|
+
|
|
58
|
+
this.ignoredSignatureKeys = ['_id', 'sign'];
|
|
59
|
+
this._secret = null;
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
/**
|
|
@@ -101,7 +108,7 @@ class BaseStorage {
|
|
|
101
108
|
/**
|
|
102
109
|
* Returns the collection to operate with
|
|
103
110
|
*
|
|
104
|
-
* @returns {Promise<
|
|
111
|
+
* @returns {Promise<Collection>}
|
|
105
112
|
*/
|
|
106
113
|
async _getCollection () {
|
|
107
114
|
if (this._collection === null) {
|
|
@@ -152,6 +159,44 @@ class BaseStorage {
|
|
|
152
159
|
}, Promise.resolve());
|
|
153
160
|
}
|
|
154
161
|
|
|
162
|
+
async _sign (object) {
|
|
163
|
+
if (!this._secret) {
|
|
164
|
+
return object;
|
|
165
|
+
}
|
|
166
|
+
const secret = await Promise.resolve(this._secret);
|
|
167
|
+
const objToSign = this._objectToSign(object);
|
|
168
|
+
const sign = this._signWithSecret(objToSign, secret);
|
|
169
|
+
|
|
170
|
+
return Object.assign(objToSign, {
|
|
171
|
+
sign
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
_objectToSign (object) {
|
|
176
|
+
const entries = Object.keys(object)
|
|
177
|
+
.filter((key) => !this.ignoredSignatureKeys.includes(key));
|
|
178
|
+
|
|
179
|
+
entries.sort();
|
|
180
|
+
|
|
181
|
+
return entries.reduce((o, key) => {
|
|
182
|
+
let val = object[key];
|
|
183
|
+
if (val instanceof Date) {
|
|
184
|
+
val = val.toISOString();
|
|
185
|
+
}
|
|
186
|
+
return Object.assign(o, { [key]: val });
|
|
187
|
+
}, {});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_signWithSecret (objToSign, secret, previous = null) {
|
|
191
|
+
const h = crypto.createHmac('sha3-224', secret)
|
|
192
|
+
.update(JSON.stringify(objToSign));
|
|
193
|
+
|
|
194
|
+
if (previous) {
|
|
195
|
+
h.update(previous);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return h.digest('base64');
|
|
199
|
+
}
|
|
155
200
|
}
|
|
156
201
|
|
|
157
202
|
module.exports = BaseStorage;
|
package/src/BotTokenStorage.js
CHANGED
package/src/ChatLogStorage.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
|
-
const mongodb = require('mongodb'); // eslint-disable-line no-unused-vars
|
|
7
6
|
const BaseStorage = require('./BaseStorage');
|
|
8
7
|
|
|
9
8
|
const PAGE_SENDER_TIMESTAMP = 'pageId_1_senderId_1_timestamp_-1';
|
|
10
9
|
const TIMESTAMP = 'timestamp_1';
|
|
11
10
|
|
|
11
|
+
/** @typedef {import('mongodb/lib/db')} Db */
|
|
12
|
+
|
|
12
13
|
/**
|
|
13
14
|
* Storage for conversation logs
|
|
14
15
|
*
|
|
@@ -18,12 +19,13 @@ class ChatLogStorage extends BaseStorage {
|
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
*
|
|
21
|
-
* @param {
|
|
22
|
+
* @param {Db|{():Promise<Db>}} mongoDb
|
|
22
23
|
* @param {string} collectionName
|
|
23
24
|
* @param {{error:Function,log:Function}} [log] - console like logger
|
|
24
|
-
* @param {boolean} isCosmo
|
|
25
|
+
* @param {boolean} [isCosmo]
|
|
26
|
+
* @param {string|Promise<string>} [secret]
|
|
25
27
|
*/
|
|
26
|
-
constructor (mongoDb, collectionName = 'chatlogs', log = console, isCosmo = false) {
|
|
28
|
+
constructor (mongoDb, collectionName = 'chatlogs', log = console, isCosmo = false, secret = null) {
|
|
27
29
|
super(mongoDb, collectionName, log, isCosmo);
|
|
28
30
|
|
|
29
31
|
this.addIndex({
|
|
@@ -43,6 +45,7 @@ class ChatLogStorage extends BaseStorage {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
this.muteErrors = true;
|
|
48
|
+
this._secret = secret;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
/**
|
|
@@ -54,6 +57,7 @@ class ChatLogStorage extends BaseStorage {
|
|
|
54
57
|
* @param {number} [limit]
|
|
55
58
|
* @param {number} [endAt] - iterate backwards to history
|
|
56
59
|
* @param {number} [startAt] - iterate forward to last interaction
|
|
60
|
+
* @returns {Promise<object[]>}
|
|
57
61
|
*/
|
|
58
62
|
async getInteractions (senderId, pageId, limit = 10, endAt = null, startAt = null) {
|
|
59
63
|
const c = await this._getCollection();
|
|
@@ -80,14 +84,33 @@ class ChatLogStorage extends BaseStorage {
|
|
|
80
84
|
const res = await c.find(q)
|
|
81
85
|
.limit(limit)
|
|
82
86
|
.sort({ timestamp: orderBackwards ? 1 : -1 })
|
|
83
|
-
.project({ _id: 0
|
|
87
|
+
.project({ _id: 0 })
|
|
84
88
|
.toArray();
|
|
85
89
|
|
|
86
90
|
if (!orderBackwards) {
|
|
87
91
|
res.reverse();
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
if (!this._secret) {
|
|
95
|
+
return res.map((r) => Object.assign(r, { ok: null }));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const secret = await Promise.resolve(this._secret);
|
|
99
|
+
|
|
100
|
+
return res.map((r) => {
|
|
101
|
+
const {
|
|
102
|
+
sign,
|
|
103
|
+
...log
|
|
104
|
+
} = r;
|
|
105
|
+
const objToSign = this._objectToSign(log);
|
|
106
|
+
const compare = this._signWithSecret(objToSign, secret);
|
|
107
|
+
const ok = compare === sign;
|
|
108
|
+
if (!ok) {
|
|
109
|
+
this._log.error(`ChatLog: found wrong signature at pageId: "${r.pageId}", senderId: "${r.senderId}", at: ${r.timestamp}`, r);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return Object.assign(log, { ok });
|
|
113
|
+
});
|
|
91
114
|
}
|
|
92
115
|
|
|
93
116
|
/**
|
|
@@ -102,22 +125,38 @@ class ChatLogStorage extends BaseStorage {
|
|
|
102
125
|
log (senderId, responses = [], request = {}, metadata = {}) {
|
|
103
126
|
const log = {
|
|
104
127
|
senderId,
|
|
105
|
-
time: new Date(request.timestamp || Date.now()),
|
|
106
128
|
request,
|
|
107
|
-
responses
|
|
129
|
+
responses,
|
|
130
|
+
...metadata
|
|
108
131
|
};
|
|
109
132
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return this._getCollection()
|
|
113
|
-
.then((c) => c.insertOne(log))
|
|
114
|
-
.catch((err) => {
|
|
115
|
-
this._log.error('Failed to store chat log', err, log);
|
|
133
|
+
return this._storeLog(log);
|
|
134
|
+
}
|
|
116
135
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
136
|
+
async _storeLog (event) {
|
|
137
|
+
let log = event;
|
|
138
|
+
if (!event.timestamp) {
|
|
139
|
+
Object.assign(event, {
|
|
140
|
+
timestamp: event.request.timestamp || Date.now()
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (typeof event.pageId === 'undefined') {
|
|
144
|
+
Object.assign(event, {
|
|
145
|
+
pageId: null
|
|
120
146
|
});
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const c = await this._getCollection();
|
|
150
|
+
log = await this._sign(log);
|
|
151
|
+
// @ts-ignore
|
|
152
|
+
await c.insertOne(log);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
this._log.error('Failed to store chat log', e, log);
|
|
155
|
+
|
|
156
|
+
if (!this.muteErrors) {
|
|
157
|
+
throw e;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
121
160
|
}
|
|
122
161
|
|
|
123
162
|
/**
|
|
@@ -135,23 +174,15 @@ class ChatLogStorage extends BaseStorage {
|
|
|
135
174
|
error (err, senderId, responses = [], request = {}, metadata = {}) {
|
|
136
175
|
const log = {
|
|
137
176
|
senderId,
|
|
138
|
-
time: new Date(request.timestamp || Date.now()),
|
|
139
177
|
request,
|
|
140
178
|
responses,
|
|
141
|
-
err: `${err}
|
|
179
|
+
err: `${err}`,
|
|
180
|
+
...metadata
|
|
142
181
|
};
|
|
143
182
|
|
|
144
183
|
Object.assign(log, metadata);
|
|
145
184
|
|
|
146
|
-
return this.
|
|
147
|
-
.then((c) => c.insertOne(log))
|
|
148
|
-
.catch((storeError) => {
|
|
149
|
-
this._log.error('Failed to store chat log', storeError, log);
|
|
150
|
-
|
|
151
|
-
if (!this.muteErrors) {
|
|
152
|
-
throw storeError;
|
|
153
|
-
}
|
|
154
|
-
});
|
|
185
|
+
return this._storeLog(log);
|
|
155
186
|
}
|
|
156
187
|
|
|
157
188
|
}
|
|
@@ -381,7 +381,7 @@ class NotificationsStorage {
|
|
|
381
381
|
}
|
|
382
382
|
}, {
|
|
383
383
|
sort: { enqueue: 1 },
|
|
384
|
-
|
|
384
|
+
returnDocument: 'after'
|
|
385
385
|
});
|
|
386
386
|
if (found.value) {
|
|
387
387
|
pop.push(this._mapGenericObject(found.value));
|
|
@@ -475,7 +475,7 @@ class NotificationsStorage {
|
|
|
475
475
|
}, {
|
|
476
476
|
$set: data
|
|
477
477
|
}, {
|
|
478
|
-
|
|
478
|
+
returnDocument: 'after'
|
|
479
479
|
});
|
|
480
480
|
|
|
481
481
|
return this._mapGenericObject(res.value);
|
|
@@ -565,7 +565,7 @@ class NotificationsStorage {
|
|
|
565
565
|
[eventType]: ts
|
|
566
566
|
}
|
|
567
567
|
}, {
|
|
568
|
-
|
|
568
|
+
returnDocument: 'after'
|
|
569
569
|
}))
|
|
570
570
|
);
|
|
571
571
|
|
|
@@ -602,7 +602,7 @@ class NotificationsStorage {
|
|
|
602
602
|
id: campaign.id
|
|
603
603
|
}, update, {
|
|
604
604
|
upsert: true,
|
|
605
|
-
|
|
605
|
+
returnDocument: 'after'
|
|
606
606
|
});
|
|
607
607
|
ret = this._mapCampaign(res.value);
|
|
608
608
|
} else {
|
|
@@ -661,7 +661,7 @@ class NotificationsStorage {
|
|
|
661
661
|
}, {
|
|
662
662
|
$set: data
|
|
663
663
|
}, {
|
|
664
|
-
|
|
664
|
+
returnDocument: 'after'
|
|
665
665
|
});
|
|
666
666
|
|
|
667
667
|
return this._mapCampaign(res.value);
|
|
@@ -681,7 +681,7 @@ class NotificationsStorage {
|
|
|
681
681
|
}, {
|
|
682
682
|
$set: { startAt: null }
|
|
683
683
|
}, {
|
|
684
|
-
|
|
684
|
+
returnDocument: 'before'
|
|
685
685
|
});
|
|
686
686
|
|
|
687
687
|
return this._mapCampaign(res.value);
|
|
@@ -807,7 +807,7 @@ class NotificationsStorage {
|
|
|
807
807
|
}, {
|
|
808
808
|
$pull: { subs: tag }
|
|
809
809
|
}, {
|
|
810
|
-
|
|
810
|
+
returnDocument: 'after'
|
|
811
811
|
});
|
|
812
812
|
|
|
813
813
|
if (res.value) {
|
package/src/StateStorage.js
CHANGED
package/src/main.js
CHANGED
|
@@ -9,6 +9,7 @@ const BotTokenStorage = require('./BotTokenStorage');
|
|
|
9
9
|
const ChatLogStorage = require('./ChatLogStorage');
|
|
10
10
|
const BotConfigStorage = require('./BotConfigStorage');
|
|
11
11
|
const AttachmentCache = require('./AttachmentCache');
|
|
12
|
+
const AuditLogStorage = require('./AuditLogStorage');
|
|
12
13
|
const NotificationsStorage = require('./NotificationsStorage');
|
|
13
14
|
|
|
14
15
|
module.exports = {
|
|
@@ -18,5 +19,6 @@ module.exports = {
|
|
|
18
19
|
ChatLogStorage,
|
|
19
20
|
BotConfigStorage,
|
|
20
21
|
AttachmentCache,
|
|
21
|
-
NotificationsStorage
|
|
22
|
+
NotificationsStorage,
|
|
23
|
+
AuditLogStorage
|
|
22
24
|
};
|