wingbot-mongodb 2.17.0 → 2.20.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/README.md CHANGED
@@ -32,17 +32,17 @@ Contains storage for tokens, chat states, bot config and chat logs.
32
32
  ## Typedefs
33
33
 
34
34
  <dl>
35
- <dt><a href="#State">State</a> : <code>Object</code></dt>
35
+ <dt><a href="#State">State</a> : <code>object</code></dt>
36
36
  <dd></dd>
37
- <dt><a href="#StateCondition">StateCondition</a> : <code>Object</code></dt>
37
+ <dt><a href="#StateCondition">StateCondition</a> : <code>object</code></dt>
38
38
  <dd></dd>
39
- <dt><a href="#Token">Token</a> : <code>Object</code></dt>
39
+ <dt><a href="#Token">Token</a> : <code>object</code></dt>
40
40
  <dd></dd>
41
41
  <dt><a href="#Target">Target</a> : <code>Object</code></dt>
42
42
  <dd></dd>
43
43
  <dt><a href="#Subscribtion">Subscribtion</a> : <code>Object</code></dt>
44
44
  <dd></dd>
45
- <dt><a href="#Campaign">Campaign</a> : <code>Object</code></dt>
45
+ <dt><a href="#Campaign">Campaign</a> : <code>object</code></dt>
46
46
  <dd></dd>
47
47
  <dt><a href="#Task">Task</a> : <code>Object</code></dt>
48
48
  <dd></dd>
@@ -57,13 +57,11 @@ Storage for chat states
57
57
 
58
58
  * [StateStorage](#StateStorage)
59
59
  * [new StateStorage(mongoDb, collectionName, [log], isCosmo)](#new_StateStorage_new)
60
- * [._collection](#StateStorage+_collection) : <code>Promise.&lt;mongodb.Collection&gt;</code>
61
- * [.addCustomIndex(index, options)](#StateStorage+addCustomIndex)
62
- * [._getCollection()](#StateStorage+_getCollection) ⇒ <code>Promise.&lt;mongodb.Collection&gt;</code>
60
+ * ~~[.addCustomIndex(index, options)](#StateStorage+addCustomIndex)~~
63
61
  * [.getState(senderId, pageId)](#StateStorage+getState) ⇒ <code>Promise.&lt;(State\|null)&gt;</code>
64
- * [.getOrCreateAndLock(senderId, pageId, [defaultState], [timeout])](#StateStorage+getOrCreateAndLock) ⇒ <code>Promise.&lt;Object&gt;</code>
62
+ * [.getOrCreateAndLock(senderId, pageId, [defaultState], [timeout])](#StateStorage+getOrCreateAndLock) ⇒ <code>Promise.&lt;object&gt;</code>
65
63
  * [.getStates(condition, limit, lastKey)](#StateStorage+getStates) ⇒ <code>Promise.&lt;{Array.&lt;data:State&gt;, lastKey:string}&gt;</code>
66
- * [.saveState(state)](#StateStorage+saveState) ⇒ <code>Promise.&lt;Object&gt;</code>
64
+ * [.saveState(state)](#StateStorage+saveState) ⇒ <code>Promise.&lt;object&gt;</code>
67
65
 
68
66
  <a name="new_StateStorage_new"></a>
69
67
 
@@ -72,31 +70,25 @@ Storage for chat states
72
70
  | Param | Type | Default | Description |
73
71
  | --- | --- | --- | --- |
74
72
  | mongoDb | <code>mongodb.Db</code> \| <code>Object</code> | | |
75
- | collectionName | <code>string</code> | <code>&quot;states&quot;</code> | |
73
+ | collectionName | <code>string</code> | <code>&quot;chatlogs&quot;</code> | |
76
74
  | [log] | <code>Object</code> | | console like logger |
77
75
  | isCosmo | <code>boolean</code> | <code>false</code> | |
78
76
 
79
- <a name="StateStorage+_collection"></a>
80
-
81
- ### stateStorage.\_collection : <code>Promise.&lt;mongodb.Collection&gt;</code>
82
- **Kind**: instance property of [<code>StateStorage</code>](#StateStorage)
83
77
  <a name="StateStorage+addCustomIndex"></a>
84
78
 
85
- ### stateStorage.addCustomIndex(index, options)
79
+ ### ~~stateStorage.addCustomIndex(index, options)~~
80
+ ***Deprecated***
81
+
86
82
  Add custom indexing rule
87
83
 
88
84
  **Kind**: instance method of [<code>StateStorage</code>](#StateStorage)
89
85
 
90
86
  | Param | Type |
91
87
  | --- | --- |
92
- | index | <code>Object</code> |
93
- | options | <code>Object</code> |
88
+ | index | <code>object</code> |
89
+ | options | <code>object</code> |
94
90
  | options.name | <code>string</code> |
95
91
 
96
- <a name="StateStorage+_getCollection"></a>
97
-
98
- ### stateStorage.\_getCollection() ⇒ <code>Promise.&lt;mongodb.Collection&gt;</code>
99
- **Kind**: instance method of [<code>StateStorage</code>](#StateStorage)
100
92
  <a name="StateStorage+getState"></a>
101
93
 
102
94
  ### stateStorage.getState(senderId, pageId) ⇒ <code>Promise.&lt;(State\|null)&gt;</code>
@@ -109,17 +101,17 @@ Add custom indexing rule
109
101
 
110
102
  <a name="StateStorage+getOrCreateAndLock"></a>
111
103
 
112
- ### stateStorage.getOrCreateAndLock(senderId, pageId, [defaultState], [timeout]) ⇒ <code>Promise.&lt;Object&gt;</code>
104
+ ### stateStorage.getOrCreateAndLock(senderId, pageId, [defaultState], [timeout]) ⇒ <code>Promise.&lt;object&gt;</code>
113
105
  Load state from database and lock it to prevent another reads
114
106
 
115
107
  **Kind**: instance method of [<code>StateStorage</code>](#StateStorage)
116
- **Returns**: <code>Promise.&lt;Object&gt;</code> - - conversation state
108
+ **Returns**: <code>Promise.&lt;object&gt;</code> - - conversation state
117
109
 
118
110
  | Param | Type | Default | Description |
119
111
  | --- | --- | --- | --- |
120
112
  | senderId | <code>string</code> | | sender identifier |
121
113
  | pageId | <code>string</code> | | page identifier |
122
- | [defaultState] | <code>Object</code> | | default state of the conversation |
114
+ | [defaultState] | <code>object</code> | | default state of the conversation |
123
115
  | [timeout] | <code>number</code> | <code>300</code> | given default state |
124
116
 
125
117
  <a name="StateStorage+getStates"></a>
@@ -135,14 +127,14 @@ Load state from database and lock it to prevent another reads
135
127
 
136
128
  <a name="StateStorage+saveState"></a>
137
129
 
138
- ### stateStorage.saveState(state) ⇒ <code>Promise.&lt;Object&gt;</code>
130
+ ### stateStorage.saveState(state) ⇒ <code>Promise.&lt;object&gt;</code>
139
131
  Save the state to database
140
132
 
141
133
  **Kind**: instance method of [<code>StateStorage</code>](#StateStorage)
142
134
 
143
135
  | Param | Type | Description |
144
136
  | --- | --- | --- |
145
- | state | <code>Object</code> | conversation state |
137
+ | state | <code>object</code> | conversation state |
146
138
 
147
139
  <a name="BotTokenStorage"></a>
148
140
 
@@ -244,9 +236,9 @@ Log single event
244
236
  | Param | Type | Description |
245
237
  | --- | --- | --- |
246
238
  | senderId | <code>string</code> | |
247
- | responses | <code>Array.&lt;Object&gt;</code> | list of sent responses |
248
- | request | <code>Object</code> | event request |
249
- | [metadata] | <code>Object</code> | request metadata |
239
+ | responses | <code>Array.&lt;object&gt;</code> | list of sent responses |
240
+ | request | <code>object</code> | event request |
241
+ | [metadata] | <code>object</code> | request metadata |
250
242
 
251
243
  <a name="BotConfigStorage"></a>
252
244
 
@@ -262,8 +254,9 @@ Storage for wingbot.ai conversation config
262
254
  * [.api([onUpdate], [acl])](#BotConfigStorage+api) ⇒ <code>Object</code>
263
255
  * [.invalidateConfig()](#BotConfigStorage+invalidateConfig) ⇒ <code>Promise</code>
264
256
  * [.getConfigTimestamp()](#BotConfigStorage+getConfigTimestamp) ⇒ <code>Promise.&lt;number&gt;</code>
265
- * [.updateConfig(newConfig)](#BotConfigStorage+updateConfig) ⇒ <code>Promise.&lt;T&gt;</code>
266
- * [.getConfig()](#BotConfigStorage+getConfig) ⇒ <code>Promise.&lt;(Object\|null)&gt;</code>
257
+ * [.updateConfig(newConfig, [id])](#BotConfigStorage+updateConfig) ⇒ <code>Promise.&lt;T&gt;</code>
258
+ * [.setConfig(id, newConfig)](#BotConfigStorage+setConfig)
259
+ * [.getConfig([id])](#BotConfigStorage+getConfig) ⇒ <code>Promise.&lt;(object\|null)&gt;</code>
267
260
 
268
261
  <a name="new_BotConfigStorage_new"></a>
269
262
 
@@ -306,17 +299,33 @@ Invalidates current configuration
306
299
  **Kind**: instance method of [<code>BotConfigStorage</code>](#BotConfigStorage)
307
300
  <a name="BotConfigStorage+updateConfig"></a>
308
301
 
309
- ### botConfigStorage.updateConfig(newConfig) ⇒ <code>Promise.&lt;T&gt;</code>
302
+ ### botConfigStorage.updateConfig(newConfig, [id]) ⇒ <code>Promise.&lt;T&gt;</code>
310
303
  **Kind**: instance method of [<code>BotConfigStorage</code>](#BotConfigStorage)
311
304
 
312
305
  | Param | Type |
313
306
  | --- | --- |
314
307
  | newConfig | <code>T</code> |
308
+ | [id] | <code>string</code> |
309
+
310
+ <a name="BotConfigStorage+setConfig"></a>
311
+
312
+ ### botConfigStorage.setConfig(id, newConfig)
313
+ **Kind**: instance method of [<code>BotConfigStorage</code>](#BotConfigStorage)
314
+
315
+ | Param | Type |
316
+ | --- | --- |
317
+ | id | <code>string</code> |
318
+ | newConfig | <code>object</code> |
315
319
 
316
320
  <a name="BotConfigStorage+getConfig"></a>
317
321
 
318
- ### botConfigStorage.getConfig() ⇒ <code>Promise.&lt;(Object\|null)&gt;</code>
322
+ ### botConfigStorage.getConfig([id]) ⇒ <code>Promise.&lt;(object\|null)&gt;</code>
319
323
  **Kind**: instance method of [<code>BotConfigStorage</code>](#BotConfigStorage)
324
+
325
+ | Param | Type |
326
+ | --- | --- |
327
+ | [id] | <code>string</code> |
328
+
320
329
  <a name="AttachmentCache"></a>
321
330
 
322
331
  ## AttachmentCache
@@ -428,7 +437,7 @@ Cache storage for Facebook attachments
428
437
 
429
438
  | Param | Type |
430
439
  | --- | --- |
431
- | tasks | <code>Object</code> |
440
+ | tasks | <code>object</code> |
432
441
 
433
442
  <a name="NotificationsStorage+getUnsuccessfulSubscribersByCampaign"></a>
434
443
 
@@ -460,7 +469,7 @@ Return Task By Id
460
469
  | Param | Type |
461
470
  | --- | --- |
462
471
  | taskId | <code>string</code> |
463
- | data | <code>Object</code> |
472
+ | data | <code>object</code> |
464
473
 
465
474
  <a name="NotificationsStorage+getSentTask"></a>
466
475
 
@@ -506,8 +515,8 @@ Get last sent task from campaign
506
515
 
507
516
  | Param | Type | Default |
508
517
  | --- | --- | --- |
509
- | campaign | <code>Object</code> | |
510
- | [updateCampaign] | <code>Object</code> | <code></code> |
518
+ | campaign | <code>object</code> | |
519
+ | [updateCampaign] | <code>object</code> | <code></code> |
511
520
 
512
521
  <a name="NotificationsStorage+removeCampaign"></a>
513
522
 
@@ -526,7 +535,7 @@ Get last sent task from campaign
526
535
  | Param | Type |
527
536
  | --- | --- |
528
537
  | campaignId | <code>string</code> |
529
- | increment | <code>Object</code> |
538
+ | increment | <code>object</code> |
530
539
 
531
540
  <a name="NotificationsStorage+updateCampaign"></a>
532
541
 
@@ -536,7 +545,7 @@ Get last sent task from campaign
536
545
  | Param | Type |
537
546
  | --- | --- |
538
547
  | campaignId | <code>string</code> |
539
- | data | <code>Object</code> |
548
+ | data | <code>object</code> |
540
549
 
541
550
  <a name="NotificationsStorage+popCampaign"></a>
542
551
 
@@ -572,9 +581,9 @@ Get last sent task from campaign
572
581
 
573
582
  | Param | Type | Default |
574
583
  | --- | --- | --- |
575
- | condition | <code>Object</code> | |
584
+ | condition | <code>object</code> | |
576
585
  | [limit] | <code>number</code> | <code></code> |
577
- | [lastKey] | <code>Object</code> | <code></code> |
586
+ | [lastKey] | <code>object</code> | <code></code> |
578
587
 
579
588
  <a name="NotificationsStorage+subscribe"></a>
580
589
 
@@ -692,9 +701,8 @@ Add custom indexing rule
692
701
 
693
702
  | Param | Type |
694
703
  | --- | --- |
695
- | index | <code>Object</code> |
696
- | options | <code>Object</code> |
697
- | options.name | <code>string</code> |
704
+ | index | <code>object</code> |
705
+ | options | <code>mongodb.IndexOptions</code> |
698
706
 
699
707
  <a name="BaseStorage+_getCollection"></a>
700
708
 
@@ -704,7 +712,7 @@ Returns the collection to operate with
704
712
  **Kind**: instance method of [<code>BaseStorage</code>](#BaseStorage)
705
713
  <a name="State"></a>
706
714
 
707
- ## State : <code>Object</code>
715
+ ## State : <code>object</code>
708
716
  **Kind**: global typedef
709
717
  **Properties**
710
718
 
@@ -712,11 +720,11 @@ Returns the collection to operate with
712
720
  | --- | --- |
713
721
  | senderId | <code>string</code> |
714
722
  | pageId | <code>string</code> |
715
- | state | <code>Object</code> |
723
+ | state | <code>object</code> |
716
724
 
717
725
  <a name="StateCondition"></a>
718
726
 
719
- ## StateCondition : <code>Object</code>
727
+ ## StateCondition : <code>object</code>
720
728
  **Kind**: global typedef
721
729
  **Properties**
722
730
 
@@ -726,7 +734,7 @@ Returns the collection to operate with
726
734
 
727
735
  <a name="Token"></a>
728
736
 
729
- ## Token : <code>Object</code>
737
+ ## Token : <code>object</code>
730
738
  **Kind**: global typedef
731
739
  **Properties**
732
740
 
@@ -761,7 +769,7 @@ Returns the collection to operate with
761
769
 
762
770
  <a name="Campaign"></a>
763
771
 
764
- ## Campaign : <code>Object</code>
772
+ ## Campaign : <code>object</code>
765
773
  **Kind**: global typedef
766
774
  **Properties**
767
775
 
@@ -781,7 +789,7 @@ Returns the collection to operate with
781
789
  | leaved | <code>number</code> | |
782
790
  | queued | <code>number</code> | Interaction |
783
791
  | action | <code>string</code> | |
784
- | [data] | <code>Object</code> | Setup |
792
+ | [data] | <code>object</code> | Setup |
785
793
  | sliding | <code>boolean</code> | |
786
794
  | slide | <code>number</code> | |
787
795
  | slideRound | <code>number</code> | |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot-mongodb",
3
- "version": "2.17.0",
3
+ "version": "2.20.0",
4
4
  "description": "MongoDB storage for wingbot.ai",
5
5
  "main": "src/main.js",
6
6
  "scripts": {
@@ -46,10 +46,15 @@
46
46
  "mocha": "^9.1.3",
47
47
  "mongodb": "^3.7.3",
48
48
  "nyc": "^15.1.0",
49
- "wingbot": "^3.22.4"
49
+ "wingbot": "^3.25.0"
50
50
  },
51
51
  "peerDependencies": {
52
- "mongodb": "^3.0.0",
52
+ "mongodb": "^3.0.0"
53
+ },
54
+ "optionalDependencies": {
53
55
  "wingbot": "^3.0.0"
56
+ },
57
+ "dependencies": {
58
+ "jsonwebtoken": "^8.5.1"
54
59
  }
55
60
  }
@@ -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;