wingbot-mongodb 2.18.0 → 2.20.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/.nyc_output/f60575da-064f-476a-a3be-be7e864d586d.json +1 -0
- package/.nyc_output/processinfo/f60575da-064f-476a-a3be-be7e864d586d.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -1
- package/README.md +257 -20
- package/package.json +9 -3
- package/src/AuditLogStorage.js +364 -0
- package/src/BaseStorage.js +86 -14
- package/src/BotConfigStorage.js +15 -5
- package/src/BotTokenStorage.js +1 -1
- package/src/ChatLogStorage.js +59 -28
- package/src/NotificationsStorage.js +7 -7
- package/src/StateStorage.js +2 -2
- 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,27 @@ 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;
|
|
60
|
+
|
|
61
|
+
this.systemIndexes = ['_id_', '_id'];
|
|
62
|
+
|
|
63
|
+
this._fixtures = [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Insert defalt document to DB
|
|
68
|
+
*
|
|
69
|
+
* @param {...any} objects
|
|
70
|
+
*/
|
|
71
|
+
addFixtureDoc (...objects) {
|
|
72
|
+
this._fixtures.push(...objects);
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
/**
|
|
@@ -76,6 +96,7 @@ class BaseStorage {
|
|
|
76
96
|
let collection;
|
|
77
97
|
|
|
78
98
|
if (this._isCosmo) {
|
|
99
|
+
// @ts-ignore
|
|
79
100
|
const collections = await db.collections();
|
|
80
101
|
|
|
81
102
|
collection = collections
|
|
@@ -101,7 +122,7 @@ class BaseStorage {
|
|
|
101
122
|
/**
|
|
102
123
|
* Returns the collection to operate with
|
|
103
124
|
*
|
|
104
|
-
* @returns {Promise<
|
|
125
|
+
* @returns {Promise<Collection>}
|
|
105
126
|
*/
|
|
106
127
|
async _getCollection () {
|
|
107
128
|
if (this._collection === null) {
|
|
@@ -127,31 +148,82 @@ class BaseStorage {
|
|
|
127
148
|
}
|
|
128
149
|
|
|
129
150
|
await existing
|
|
130
|
-
.filter((e) => !
|
|
151
|
+
.filter((e) => !this.systemIndexes.includes(e.name)
|
|
131
152
|
&& !indexes.some((i) => e.name === i.options.name))
|
|
132
153
|
.reduce((p, e) => {
|
|
133
|
-
|
|
134
|
-
this._log.log(`dropping index ${e.name}`);
|
|
154
|
+
this._log.log(`DB.${this._collectionName} dropping index ${e.name}`);
|
|
135
155
|
return p
|
|
136
156
|
.then(() => collection.dropIndex(e.name))
|
|
137
157
|
.catch((err) => {
|
|
138
|
-
|
|
139
|
-
this._log.error(`dropping index ${e.name} FAILED`, err);
|
|
158
|
+
this._log.error(`DB.${this._collectionName} dropping index ${e.name} FAILED`, err);
|
|
140
159
|
});
|
|
141
160
|
}, Promise.resolve());
|
|
142
161
|
|
|
143
|
-
await indexes
|
|
162
|
+
const updated = await indexes
|
|
144
163
|
.filter((i) => !existing.some((e) => e.name === i.options.name))
|
|
145
164
|
.reduce((p, i) => {
|
|
146
|
-
this._log.log(`creating index ${i.name}`);
|
|
165
|
+
this._log.log(`DB.${this._collectionName} creating index ${i.options.name}`);
|
|
147
166
|
return p
|
|
148
167
|
.then(() => collection.createIndex(i.index, i.options))
|
|
149
168
|
.catch((e) => {
|
|
150
|
-
this._log.error(`failed to create index ${i.options.name} on ${collection.collectionName}`, e);
|
|
151
|
-
})
|
|
152
|
-
|
|
169
|
+
this._log.error(`DB.${this._collectionName} failed to create index ${i.options.name} on ${collection.collectionName}`, e);
|
|
170
|
+
})
|
|
171
|
+
.then(() => true);
|
|
172
|
+
}, Promise.resolve(false));
|
|
173
|
+
|
|
174
|
+
if (updated || existing.every((i) => this.systemIndexes.includes(i.name))) {
|
|
175
|
+
// upsert fixtures
|
|
176
|
+
|
|
177
|
+
await this._fixtures.reduce((p, o) => p
|
|
178
|
+
.then(() => collection.insertOne(o))
|
|
179
|
+
.then(() => this._log.log(`DB.${this._collectionName} Inserted fixture doc to "${this._collectionName}"`))
|
|
180
|
+
.catch((e) => {
|
|
181
|
+
if (e.code !== 11000) {
|
|
182
|
+
this._log.error(`DB.${this._collectionName} failed to insert fixture doc to "${this._collectionName}"`, e);
|
|
183
|
+
}
|
|
184
|
+
}),
|
|
185
|
+
Promise.resolve());
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async _sign (object) {
|
|
190
|
+
if (!this._secret) {
|
|
191
|
+
return object;
|
|
192
|
+
}
|
|
193
|
+
const secret = await Promise.resolve(this._secret);
|
|
194
|
+
const objToSign = this._objectToSign(object);
|
|
195
|
+
const sign = this._signWithSecret(objToSign, secret);
|
|
196
|
+
|
|
197
|
+
return Object.assign(objToSign, {
|
|
198
|
+
sign
|
|
199
|
+
});
|
|
153
200
|
}
|
|
154
201
|
|
|
202
|
+
_objectToSign (object) {
|
|
203
|
+
const entries = Object.keys(object)
|
|
204
|
+
.filter((key) => !this.ignoredSignatureKeys.includes(key));
|
|
205
|
+
|
|
206
|
+
entries.sort();
|
|
207
|
+
|
|
208
|
+
return entries.reduce((o, key) => {
|
|
209
|
+
let val = object[key];
|
|
210
|
+
if (val instanceof Date) {
|
|
211
|
+
val = val.toISOString();
|
|
212
|
+
}
|
|
213
|
+
return Object.assign(o, { [key]: val });
|
|
214
|
+
}, {});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
_signWithSecret (objToSign, secret, previous = null) {
|
|
218
|
+
const h = crypto.createHmac('sha3-224', secret)
|
|
219
|
+
.update(JSON.stringify(objToSign));
|
|
220
|
+
|
|
221
|
+
if (previous) {
|
|
222
|
+
h.update(previous);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return h.digest('base64');
|
|
226
|
+
}
|
|
155
227
|
}
|
|
156
228
|
|
|
157
229
|
module.exports = BaseStorage;
|
package/src/BotConfigStorage.js
CHANGED
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
let apiAuthorizer = () => false;
|
|
7
|
+
try {
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
({ apiAuthorizer } = module.require('wingbot'));
|
|
10
|
+
} catch (e) {
|
|
11
|
+
// noop
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** @typedef {import('mongodb/lib/db')} Db */
|
|
15
|
+
/** @typedef {import('mongodb/lib/collection')} Collection */
|
|
8
16
|
|
|
9
17
|
const CONFIG_ID = 'config';
|
|
10
18
|
|
|
@@ -17,7 +25,7 @@ class BotConfigStorage {
|
|
|
17
25
|
|
|
18
26
|
/**
|
|
19
27
|
*
|
|
20
|
-
* @param {
|
|
28
|
+
* @param {Db|{():Promise<Db>}} mongoDb
|
|
21
29
|
* @param {string} collectionName
|
|
22
30
|
*/
|
|
23
31
|
constructor (mongoDb, collectionName = 'botconfig') {
|
|
@@ -25,13 +33,13 @@ class BotConfigStorage {
|
|
|
25
33
|
this._collectionName = collectionName;
|
|
26
34
|
|
|
27
35
|
/**
|
|
28
|
-
* @type {
|
|
36
|
+
* @type {Collection}
|
|
29
37
|
*/
|
|
30
38
|
this._collection = null;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
/**
|
|
34
|
-
* @returns {Promise<
|
|
42
|
+
* @returns {Promise<Collection>}
|
|
35
43
|
*/
|
|
36
44
|
async _getCollection () {
|
|
37
45
|
if (this._collection === null) {
|
|
@@ -56,6 +64,7 @@ class BotConfigStorage {
|
|
|
56
64
|
const storage = this;
|
|
57
65
|
return {
|
|
58
66
|
async updateBot (args, ctx) {
|
|
67
|
+
// @ts-ignore
|
|
59
68
|
if (!apiAuthorizer(args, ctx, acl)) {
|
|
60
69
|
return null;
|
|
61
70
|
}
|
|
@@ -74,6 +83,7 @@ class BotConfigStorage {
|
|
|
74
83
|
async invalidateConfig () {
|
|
75
84
|
const c = await this._getCollection();
|
|
76
85
|
|
|
86
|
+
// @ts-ignore
|
|
77
87
|
return c.deleteOne({ _id: CONFIG_ID });
|
|
78
88
|
}
|
|
79
89
|
|