@live-change/security-service 0.2.5 → 0.2.19
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/ban.js +167 -35
- package/definition.js +1 -2
- package/event.js +214 -56
- package/package.json +2 -2
- package/utils.js +31 -8
package/ban.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const app = require("@live-change/framework").app()
|
|
2
2
|
const definition = require('./definition.js')
|
|
3
|
-
const { getClientKeysStrings } = require('./utils.js')
|
|
3
|
+
const { getClientKeysStrings, multiKeyIndexQuery, fastMultiKeyIndexQuery } = require('./utils.js')
|
|
4
4
|
const lcp = require('@live-change/pattern')
|
|
5
5
|
|
|
6
6
|
const banProperties = {
|
|
@@ -49,7 +49,10 @@ const Ban = definition.model({
|
|
|
49
49
|
},
|
|
50
50
|
actionBans: {
|
|
51
51
|
function: async function(input, output) {
|
|
52
|
-
|
|
52
|
+
function prefixes(ban) {
|
|
53
|
+
//output.debug("BAN", ban)
|
|
54
|
+
if(!ban.keys) return []
|
|
55
|
+
if(!ban.actions) return []
|
|
53
56
|
const v = ban.keys.length
|
|
54
57
|
const w = ban.actions.length
|
|
55
58
|
let res = new Array(v * w)
|
|
@@ -59,26 +62,89 @@ const Ban = definition.model({
|
|
|
59
62
|
res[i * v + j] = `${ban.actions[j]}:${key.key}:${key.value}`
|
|
60
63
|
}
|
|
61
64
|
}
|
|
65
|
+
//output.debug("BAN ACTIONS", res)
|
|
62
66
|
return res
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
function indexObject(prefix, obj) {
|
|
69
|
+
//output.debug("BAN", obj)
|
|
70
|
+
return {
|
|
71
|
+
id: prefix+'_'+obj.id,
|
|
72
|
+
to: obj.id,
|
|
73
|
+
type: obj.type,
|
|
74
|
+
expire: obj.expire,
|
|
75
|
+
actions: obj.actions
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
await input.table("security_Ban").onChange((obj, oldObj) => {
|
|
65
79
|
if(obj && oldObj) {
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
//output.debug("CHANGE!", obj, oldObj)
|
|
81
|
+
const pointers = obj && new Set(prefixes(obj))
|
|
82
|
+
const oldPointers = oldObj && new Set(prefixes(oldObj))
|
|
68
83
|
for(let pointer of pointers) {
|
|
69
|
-
if(!!oldPointers.has(pointer)) output.change(
|
|
84
|
+
if(!!oldPointers.has(pointer)) output.change(indexObject(pointer, obj), null)
|
|
70
85
|
}
|
|
71
86
|
for(let pointer of oldPointers) {
|
|
72
|
-
if(!!pointers.has(pointer)) output.change(null,
|
|
87
|
+
if(!!pointers.has(pointer)) output.change(null, indexObject(pointer, oldObj))
|
|
73
88
|
}
|
|
74
89
|
} else if(obj) {
|
|
75
|
-
|
|
90
|
+
//output.debug("CREATE!", obj, oldObj)
|
|
91
|
+
prefixes(obj).forEach(v => output.change(indexObject(v, obj), null))
|
|
76
92
|
} else if(oldObj) {
|
|
77
|
-
|
|
93
|
+
//output.debug("DELETE!", obj, oldObj)
|
|
94
|
+
prefixes(oldObj).forEach(v => output.change(null, indexObject(v, oldObj)))
|
|
78
95
|
}
|
|
79
96
|
})
|
|
80
97
|
}
|
|
81
|
-
}
|
|
98
|
+
},
|
|
99
|
+
actionBansByType: {
|
|
100
|
+
function: async function(input, output) {
|
|
101
|
+
function prefixes(ban) {
|
|
102
|
+
//output.debug("BAN", ban)
|
|
103
|
+
if(!ban.keys) return []
|
|
104
|
+
if(!ban.actions) return []
|
|
105
|
+
const v = ban.keys.length
|
|
106
|
+
const w = ban.actions.length
|
|
107
|
+
let res = new Array(v * w)
|
|
108
|
+
for(let i = 0; i < v; i++) {
|
|
109
|
+
for(let j = 0; j < w; j++) {
|
|
110
|
+
const key = ban.keys[i]
|
|
111
|
+
res[i * v + j] = `${ban.actions[j]}:${key.key}:${key.value}:${ban.type}:${ban.expire}`
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
output.debug("BAN PREFIXES", res)
|
|
115
|
+
return res
|
|
116
|
+
}
|
|
117
|
+
function indexObject(prefix, obj) {
|
|
118
|
+
//output.debug("BAN", obj)
|
|
119
|
+
return {
|
|
120
|
+
id: prefix+'_'+obj.id,
|
|
121
|
+
to: obj.id,
|
|
122
|
+
type: obj.type,
|
|
123
|
+
expire: obj.expire,
|
|
124
|
+
actions: obj.actions
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
await input.table("security_Ban").onChange((obj, oldObj) => {
|
|
128
|
+
if(obj && oldObj) {
|
|
129
|
+
//output.debug("CHANGE!", obj, oldObj)
|
|
130
|
+
let pointers = obj && new Set(prefixes(obj))
|
|
131
|
+
let oldPointers = oldObj && new Set(prefixes(oldObj))
|
|
132
|
+
for(let pointer of pointers) {
|
|
133
|
+
if(!!oldPointers.has(pointer)) output.change(indexObject(pointer, obj), null)
|
|
134
|
+
}
|
|
135
|
+
for(let pointer of oldPointers) {
|
|
136
|
+
if(!!pointers.has(pointer)) output.change(null, indexObject(pointer, oldObj))
|
|
137
|
+
}
|
|
138
|
+
} else if(obj) {
|
|
139
|
+
//output.debug("CREATE!", obj, oldObj)
|
|
140
|
+
prefixes(obj).forEach(v => output.change(indexObject(v, obj), null))
|
|
141
|
+
} else if(oldObj) {
|
|
142
|
+
//output.debug("DELETE!", obj, oldObj)
|
|
143
|
+
prefixes(oldObj).forEach(v => output.change(null, indexObject(v, oldObj)))
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
},
|
|
82
148
|
}
|
|
83
149
|
})
|
|
84
150
|
|
|
@@ -101,23 +167,87 @@ definition.view({
|
|
|
101
167
|
properties: {},
|
|
102
168
|
daoPath(params, { client, service }) {
|
|
103
169
|
const keys = getClientKeysStrings(client)
|
|
104
|
-
return multiKeyIndexQuery(keys, '
|
|
170
|
+
return multiKeyIndexQuery(keys, 'security_Ban_bans', Ban.tableName)
|
|
105
171
|
},
|
|
106
172
|
})
|
|
107
173
|
|
|
108
174
|
definition.view({
|
|
109
|
-
name: "
|
|
175
|
+
name: "myActionsBans",
|
|
110
176
|
properties: {
|
|
111
|
-
|
|
177
|
+
actions: {
|
|
112
178
|
type: String
|
|
113
179
|
}
|
|
114
180
|
},
|
|
115
|
-
daoPath({
|
|
116
|
-
const keys =
|
|
117
|
-
|
|
181
|
+
daoPath({ actions }, { client, service }) {
|
|
182
|
+
const keys = []
|
|
183
|
+
for(const action of actions) {
|
|
184
|
+
keys.push(...getClientKeysStrings(client, action + ':'))
|
|
185
|
+
}
|
|
186
|
+
return fastMultiKeyIndexQuery(keys, 'security_Ban_actionBans', Ban.tableName)
|
|
118
187
|
},
|
|
119
188
|
})
|
|
120
189
|
|
|
190
|
+
definition.view({
|
|
191
|
+
name: "myActionsBansByTypes",
|
|
192
|
+
properties: {
|
|
193
|
+
actions: {
|
|
194
|
+
type: Array,
|
|
195
|
+
of: {
|
|
196
|
+
type: String
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
types: {
|
|
200
|
+
type: Array,
|
|
201
|
+
of: {
|
|
202
|
+
type: String
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
daoPath({ actions, types }, { client, service }) {
|
|
207
|
+
const keys = []
|
|
208
|
+
for(const type of types) {
|
|
209
|
+
for (const action of actions) {
|
|
210
|
+
keys.push(...getClientKeysStrings(client, action + ':',':' + type))
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
console.log("BAN KEYS", keys)
|
|
214
|
+
return ['database', 'query', app.databaseName, `(${
|
|
215
|
+
async (input, output, { keys, indexName }) => {
|
|
216
|
+
function mapper(obj) {
|
|
217
|
+
if(!obj) return null
|
|
218
|
+
const { id, to, ...safeObj } = obj
|
|
219
|
+
return { ...safeObj, id: to }
|
|
220
|
+
}
|
|
221
|
+
function onIndexChange(obj, oldObj) {
|
|
222
|
+
output.change(mapper(obj), mapper(oldObj))
|
|
223
|
+
}
|
|
224
|
+
await Promise.all(keys.map(async (encodedKey) => {
|
|
225
|
+
const range = {
|
|
226
|
+
gte: encodedKey,
|
|
227
|
+
lte: encodedKey + "\xFF",
|
|
228
|
+
reverse: true,
|
|
229
|
+
limit: 1 // only last expire
|
|
230
|
+
}
|
|
231
|
+
await (await input.index(indexName)).range(range).onChange(onIndexChange)
|
|
232
|
+
}))
|
|
233
|
+
}
|
|
234
|
+
})`, { keys, indexName: 'security_Ban_actionBansByType' }]
|
|
235
|
+
},
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
/*function getBanPrefixes(keys, actions) {
|
|
239
|
+
const v = keys.length
|
|
240
|
+
const w = actions.length
|
|
241
|
+
let res = new Array(v * w)
|
|
242
|
+
for(let i = 0; i < v; i++) {
|
|
243
|
+
for(let j = 0; j < w; j++) {
|
|
244
|
+
const key = keys[i]
|
|
245
|
+
res[i * v + j] = `${actions[j]}:${key.key}:${key.value}`
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return res
|
|
249
|
+
}*/
|
|
250
|
+
|
|
121
251
|
definition.trigger({
|
|
122
252
|
name: "securityActionBan",
|
|
123
253
|
properties: {
|
|
@@ -136,7 +266,6 @@ definition.trigger({
|
|
|
136
266
|
}
|
|
137
267
|
},
|
|
138
268
|
async execute({ keys, event, ban: { actions, expire, type } }, { service }, emit) {
|
|
139
|
-
const ban = app.generateUid()
|
|
140
269
|
|
|
141
270
|
console.log("SECURITY BAN!", arguments[0])
|
|
142
271
|
|
|
@@ -152,23 +281,25 @@ definition.trigger({
|
|
|
152
281
|
console.log("BAN KEYS", banKeys)
|
|
153
282
|
console.log("BAN EXPIRE", banExpire)
|
|
154
283
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
284
|
+
const ban = app.generateUid()
|
|
285
|
+
|
|
286
|
+
service.trigger({
|
|
287
|
+
type: 'createTimer',
|
|
288
|
+
timer: {
|
|
289
|
+
timestamp: banExpire.getTime() + 1000,
|
|
290
|
+
service: 'security',
|
|
291
|
+
trigger: {
|
|
292
|
+
type: 'removeExpiredBan',
|
|
293
|
+
ban
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
emit({
|
|
299
|
+
type: "banCreated",
|
|
300
|
+
ban,
|
|
301
|
+
data: { actions, keys: banKeys, expire: banExpire, type }
|
|
302
|
+
})
|
|
172
303
|
}
|
|
173
304
|
})
|
|
174
305
|
|
|
@@ -177,7 +308,8 @@ definition.trigger({
|
|
|
177
308
|
properties: {
|
|
178
309
|
...banProperties
|
|
179
310
|
},
|
|
180
|
-
async execute({ ban }, {client, service}, emit) {
|
|
311
|
+
async execute({ ban }, { client, service }, emit) {
|
|
312
|
+
console.log("REMOVE EXPIRED BAN", ban)
|
|
181
313
|
emit({
|
|
182
314
|
type: "banRemoved",
|
|
183
315
|
ban
|
package/definition.js
CHANGED
package/event.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
const crypto = require('crypto')
|
|
2
2
|
const app = require("@live-change/framework").app()
|
|
3
3
|
const definition = require('./definition.js')
|
|
4
|
-
const { getClientKeysObject } = require('./utils.js')
|
|
4
|
+
const { getClientKeysObject, getClientKeysStrings} = require('./utils.js')
|
|
5
5
|
|
|
6
6
|
const lcp = require('@live-change/pattern')
|
|
7
7
|
const lcpDb = require('@live-change/pattern-db')
|
|
8
8
|
const { request } = require('http')
|
|
9
9
|
|
|
10
|
-
const securityPatterns = definition.config
|
|
11
|
-
const relationsStore = lcpDb.relationsStore(app.dao, app.databaseName, '
|
|
10
|
+
const securityPatterns = definition.config?.patterns ?? []
|
|
11
|
+
const relationsStore = lcpDb.relationsStore(app.dao, app.databaseName, 'security_relations')
|
|
12
12
|
lcp.prepareModelForLive(securityPatterns)
|
|
13
13
|
//console.log("SECURITY PATTERNS", securityPatterns)
|
|
14
14
|
|
|
15
|
-
const securityCounters = definition.config
|
|
15
|
+
const securityCounters = definition.config?.counters ?? []
|
|
16
16
|
|
|
17
17
|
definition.beforeStart(service => {
|
|
18
18
|
relationsStore.createTable()
|
|
@@ -23,22 +23,10 @@ const eventProperties = {
|
|
|
23
23
|
type: String
|
|
24
24
|
},
|
|
25
25
|
keys: {
|
|
26
|
-
type:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
key: {
|
|
31
|
-
type: {
|
|
32
|
-
String
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
value: {
|
|
36
|
-
type: {
|
|
37
|
-
String
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
26
|
+
type: Object
|
|
27
|
+
},
|
|
28
|
+
properties: {
|
|
29
|
+
type: Object
|
|
42
30
|
},
|
|
43
31
|
timestamp: {
|
|
44
32
|
type: Date
|
|
@@ -51,8 +39,21 @@ const Event = definition.model({
|
|
|
51
39
|
...eventProperties
|
|
52
40
|
},
|
|
53
41
|
indexes: {
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
byKeyTypeAndTimestamp: {
|
|
43
|
+
function: async function(input, output) {
|
|
44
|
+
async function spread(event) {
|
|
45
|
+
for(const key in event.keys) {
|
|
46
|
+
output.put({
|
|
47
|
+
id: `${key}:${JSON.stringify(event.keys[key])}:${event.type}:${event.timestamp}_${event.id}`,
|
|
48
|
+
type: event.type,
|
|
49
|
+
timestamp: event.timestamp
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
await input.table('security_Event').onChange(
|
|
54
|
+
(obj, oldObj) => spread(obj)
|
|
55
|
+
)
|
|
56
|
+
}
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
})
|
|
@@ -140,30 +141,42 @@ async function processSecurityCounters(event, timestamp, service) {
|
|
|
140
141
|
for(const counter of counters) {
|
|
141
142
|
const duration = lcp.parseDuration(counter.duration)
|
|
142
143
|
for(const eventType of counter.match) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
for(const key of counter.keys) {
|
|
145
|
+
const request = counterEventsRequests.find(req => req.type == eventType && req.key == key)
|
|
146
|
+
if(request) {
|
|
147
|
+
request.max = Math.max(request.max, counter.max)
|
|
148
|
+
request.duration = Math.max(request.duration, duration)
|
|
149
|
+
} else {
|
|
150
|
+
counterEventsRequests.push({
|
|
151
|
+
type: eventType,
|
|
152
|
+
key,
|
|
153
|
+
max: counter.max,
|
|
154
|
+
duration
|
|
155
|
+
})
|
|
156
|
+
}
|
|
153
157
|
}
|
|
154
158
|
}
|
|
155
159
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
const indexName = Event.tableName+'_byKeyTypeAndTimestamp'
|
|
161
|
+
//console.log("ALL COUNTER EVENTS REQUESTS", counterEventsRequests)
|
|
162
|
+
const counterEvents = await Promise.all(counterEventsRequests.map(async request => {
|
|
163
|
+
const back = (new Date(now - request.duration - 1000)).toISOString()
|
|
164
|
+
const prefix = `${request.key}:${JSON.stringify(event.keys[request.key])}:${request.type}:`
|
|
165
|
+
console.log("{", prefix)
|
|
166
|
+
const range = {
|
|
167
|
+
gt: prefix + back, // time limited
|
|
168
|
+
lt: prefix + '\xFF',
|
|
162
169
|
reverse: true,
|
|
163
170
|
limit: request.max
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
171
|
+
}
|
|
172
|
+
console.log("R", range)
|
|
173
|
+
const events = await app.dao.get(['database', 'indexRange', service.databaseName, indexName, range])
|
|
174
|
+
console.log("RR", events)
|
|
175
|
+
return {
|
|
176
|
+
type: request.type,
|
|
177
|
+
events
|
|
178
|
+
}
|
|
179
|
+
}))
|
|
167
180
|
for(const counter of counters) {
|
|
168
181
|
const duration = lcp.parseDuration(counter.duration)
|
|
169
182
|
const fromTime = (new Date(now - duration)).toISOString()
|
|
@@ -177,7 +190,7 @@ async function processSecurityCounters(event, timestamp, service) {
|
|
|
177
190
|
}
|
|
178
191
|
}
|
|
179
192
|
if(count + 1 > counter.max) { // +1 for the new event, not added yet
|
|
180
|
-
console.log("COUNTER FIRE", counter)
|
|
193
|
+
//console.log("COUNTER FIRE", counter)
|
|
181
194
|
actions.push(...counter.actions)
|
|
182
195
|
}
|
|
183
196
|
}
|
|
@@ -189,24 +202,14 @@ definition.trigger({
|
|
|
189
202
|
properties: {
|
|
190
203
|
event: {
|
|
191
204
|
type: Object,
|
|
192
|
-
properties:
|
|
193
|
-
type: {
|
|
194
|
-
type: String
|
|
195
|
-
},
|
|
196
|
-
keys: {
|
|
197
|
-
type: Object
|
|
198
|
-
},
|
|
199
|
-
properties: {
|
|
200
|
-
type: Object
|
|
201
|
-
}
|
|
202
|
-
}
|
|
205
|
+
properties: eventProperties
|
|
203
206
|
},
|
|
204
207
|
client: {
|
|
205
208
|
type: Object
|
|
206
209
|
}
|
|
207
210
|
},
|
|
208
211
|
async execute({ event, client, timestamp }, { service }, emit) {
|
|
209
|
-
console.log("SECURITY EVENT TRIGGERED", arguments[0])
|
|
212
|
+
//console.log("SECURITY EVENT TRIGGERED", arguments[0])
|
|
210
213
|
event.id = event.id || app.generateUid()
|
|
211
214
|
event.time = event.time || timestamp
|
|
212
215
|
const time = (typeof event.time == 'number') ? event.time : new Date(event.time).getTime()
|
|
@@ -215,7 +218,7 @@ definition.trigger({
|
|
|
215
218
|
event.keys = { ...getClientKeysObject(client), ...event.keys }
|
|
216
219
|
}
|
|
217
220
|
|
|
218
|
-
console.log("PROCESS EVENT", event)
|
|
221
|
+
//console.log("PROCESS EVENT", event)
|
|
219
222
|
let [
|
|
220
223
|
{ newRelations, canceledRelations, actions: patternsActions },
|
|
221
224
|
{ actions: countersActions }
|
|
@@ -229,7 +232,7 @@ definition.trigger({
|
|
|
229
232
|
|
|
230
233
|
for(const action of actions) {
|
|
231
234
|
const actionTypeUpperCase = action.type[0].toUpperCase() + action.type.slice(1)
|
|
232
|
-
console.log("ACTION", JSON.stringify(action, null, ' '))
|
|
235
|
+
//console.log("ACTION", JSON.stringify(action, null, ' '))
|
|
233
236
|
promises.push(service.trigger({
|
|
234
237
|
...action,
|
|
235
238
|
event,
|
|
@@ -252,3 +255,158 @@ definition.trigger({
|
|
|
252
255
|
}
|
|
253
256
|
})
|
|
254
257
|
|
|
258
|
+
definition.view({
|
|
259
|
+
name: 'myCountersState',
|
|
260
|
+
properties: {
|
|
261
|
+
events: {
|
|
262
|
+
type: String
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
daoPath({ events }, { client, service }) {
|
|
266
|
+
const keys = getClientKeysObject(client)
|
|
267
|
+
const eventRequests = []
|
|
268
|
+
for(const eventType of events) {
|
|
269
|
+
for(const counter of securityCounters) {
|
|
270
|
+
if(!counter.visible) continue
|
|
271
|
+
if(!counter.match.includes(eventType)) continue
|
|
272
|
+
const duration = lcp.parseDuration(counter.duration)
|
|
273
|
+
for(const keyName in keys) {
|
|
274
|
+
const keyValue = keys[keyName]
|
|
275
|
+
if(keyValue === undefined) continue
|
|
276
|
+
eventRequests.push({
|
|
277
|
+
eventType,
|
|
278
|
+
counter: counter.id,
|
|
279
|
+
duration,
|
|
280
|
+
keyName,
|
|
281
|
+
keyValue,
|
|
282
|
+
max: counter.max
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return ['database', 'query', service.databaseName, `(${
|
|
288
|
+
async (input, output, { eventRequests, indexName } ) => {
|
|
289
|
+
const index = await input.index(indexName)
|
|
290
|
+
|
|
291
|
+
class Request {
|
|
292
|
+
constructor(eventRequest) {
|
|
293
|
+
this.eventRequest = eventRequest
|
|
294
|
+
|
|
295
|
+
this.id = eventRequest.counter + ':' + eventRequest.keyName
|
|
296
|
+
this.count = 0
|
|
297
|
+
this.max = this.eventRequest.max
|
|
298
|
+
this.remaining = this.max + 1
|
|
299
|
+
|
|
300
|
+
this.prefix = `${eventRequest.keyName}:${JSON.stringify(eventRequest.keyValue)}:${eventRequest.eventType}:`
|
|
301
|
+
this.range = undefined
|
|
302
|
+
this.indexRange = undefined
|
|
303
|
+
this.fromTime = 0
|
|
304
|
+
this.value = undefined
|
|
305
|
+
this.observer = undefined
|
|
306
|
+
this.oldest = undefined
|
|
307
|
+
this.expire = Infinity
|
|
308
|
+
|
|
309
|
+
this.loading = false
|
|
310
|
+
}
|
|
311
|
+
async refresh() {
|
|
312
|
+
this.loading = true
|
|
313
|
+
const newFromTime = Date.now() - this.eventRequest.duration
|
|
314
|
+
//output.debug("REFRESH", newFromTime, this.fromTime, newFromTime - this.fromTime)
|
|
315
|
+
if(Math.abs(newFromTime - this.fromTime) > 5000) { // refresh only when time is different by 5s
|
|
316
|
+
//output.debug("OBSERVER REFRESH!")
|
|
317
|
+
this.fromTime = newFromTime
|
|
318
|
+
const newRange = {
|
|
319
|
+
gt: this.prefix + (new Date(this.fromTime)).toISOString(), // time limited
|
|
320
|
+
lt: this.prefix + '\xFF',
|
|
321
|
+
reverse: true,
|
|
322
|
+
limit: this.eventRequest.max
|
|
323
|
+
}
|
|
324
|
+
this.range = newRange
|
|
325
|
+
//output.debug("NEW RANGE", this.range)
|
|
326
|
+
if(this.observer) {
|
|
327
|
+
this.indexRange.unobserve(this.observer)
|
|
328
|
+
}
|
|
329
|
+
//output.debug("OBSERVE RANGE!", this.range)
|
|
330
|
+
this.indexRange = index.range(this.range)
|
|
331
|
+
|
|
332
|
+
this.observer = await this.indexRange.onChange(async (ind, oldInd) => {
|
|
333
|
+
//output.debug("RANGE CHANGE!", newRange, ind)
|
|
334
|
+
if(!this.loading) {
|
|
335
|
+
await this.refresh()
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
const newValue = await this.indexRange.get()
|
|
340
|
+
this.loading = false
|
|
341
|
+
if(JSON.stringify(this.value) != JSON.stringify(newValue)) {
|
|
342
|
+
this.value = newValue
|
|
343
|
+
if(this.value) {
|
|
344
|
+
this.count = this.value.length
|
|
345
|
+
this.oldest = this.value[this.count - 1]
|
|
346
|
+
this.remaining = this.max - this.count + 1
|
|
347
|
+
this.expire = this.oldest
|
|
348
|
+
? (new Date(this.oldest.timestamp)).getTime() + this.eventRequest.duration
|
|
349
|
+
: Infinity
|
|
350
|
+
}
|
|
351
|
+
await recompute()
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const requests = new Array(eventRequests.length)
|
|
357
|
+
|
|
358
|
+
let currentTimeout = null
|
|
359
|
+
let expire = Infinity
|
|
360
|
+
async function recompute() {
|
|
361
|
+
//output.debug("RECOMPUTE?")
|
|
362
|
+
for(const request of requests) {
|
|
363
|
+
if(request.value === undefined) return // no recompute until all readed
|
|
364
|
+
}
|
|
365
|
+
//output.debug("RECOMPUTE!")
|
|
366
|
+
let firstExpire = Infinity
|
|
367
|
+
let firstExpireRequest = null
|
|
368
|
+
for(const request of requests) {
|
|
369
|
+
//console.log("REQUEST EXPIRE", request.prefix, request.expire)
|
|
370
|
+
if(request.expire < firstExpire) {
|
|
371
|
+
firstExpire = request.expire
|
|
372
|
+
firstExpireRequest = request
|
|
373
|
+
}
|
|
374
|
+
const { id, count, remaining, max, oldest, value } = request
|
|
375
|
+
output.change({
|
|
376
|
+
id,
|
|
377
|
+
count, remaining, max,
|
|
378
|
+
oldest: oldest?.timestamp,
|
|
379
|
+
expire: request.expire ? new Date(request.expire) : null,
|
|
380
|
+
//value
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
//console.log("FIRST EXPIRE", firstExpire)
|
|
384
|
+
//console.log("LAST EXPIRE", expire)
|
|
385
|
+
if(firstExpire != expire) {
|
|
386
|
+
expire = firstExpire
|
|
387
|
+
if(currentTimeout) { // clears timeout
|
|
388
|
+
currentTimeout()
|
|
389
|
+
}
|
|
390
|
+
expire += 1000 // additional delay
|
|
391
|
+
//console.log("SET TIMEOUT", new Date(expire))
|
|
392
|
+
currentTimeout = output.timeout(expire, () => {
|
|
393
|
+
//output.debug("EXPIRE TIMEOUT!", expire)
|
|
394
|
+
firstExpireRequest.refresh()
|
|
395
|
+
}) // time parameter can be number or date or iso
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if(eventRequests.length == 0) return
|
|
400
|
+
for(const i in eventRequests) {
|
|
401
|
+
const eventRequest = eventRequests[i]
|
|
402
|
+
const request = new Request(eventRequest)
|
|
403
|
+
requests[i] = request
|
|
404
|
+
//output.debug("REQUEST", eventRequest, '=>', request)
|
|
405
|
+
}
|
|
406
|
+
await Promise.all(requests.map(async request => {
|
|
407
|
+
await request.refresh()
|
|
408
|
+
}))
|
|
409
|
+
}
|
|
410
|
+
})`, { eventRequests, indexName: Event.tableName + '_byKeyTypeAndTimestamp' }]
|
|
411
|
+
}
|
|
412
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/security-service",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
"@live-change/pattern-db": "^0.2.2",
|
|
27
27
|
"nodemailer": "^6.7.2"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "c2833a9660374ee17dd3def99fd1cdf47f6b1531"
|
|
30
30
|
}
|
package/utils.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
const app = require("@live-change/framework").app()
|
|
1
2
|
const definition = require('./definition.js')
|
|
2
3
|
|
|
3
|
-
const clientKeys = definition.config
|
|
4
|
+
const clientKeys = definition.config?.clientKeys ?? []
|
|
4
5
|
|
|
5
|
-
function multiKeyIndexQuery(keys, indexName) {
|
|
6
|
-
return ['database', 'query',
|
|
6
|
+
function multiKeyIndexQuery(keys, indexName, tableName) {
|
|
7
|
+
return ['database', 'query', app.databaseName, `(${
|
|
7
8
|
async (input, output, { keys, indexName, tableName }) => {
|
|
8
9
|
const objectStates = new Map()
|
|
9
10
|
async function mapper(res) {
|
|
@@ -55,16 +56,38 @@ function multiKeyIndexQuery(keys, indexName) {
|
|
|
55
56
|
await (await input.index(indexName)).range(range).onChange(onIndexChange)
|
|
56
57
|
}))
|
|
57
58
|
}
|
|
58
|
-
})`, { keys, indexName, tableName
|
|
59
|
+
})`, { keys, indexName, tableName }]
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
function
|
|
62
|
+
function fastMultiKeyIndexQuery(keys, indexName) {
|
|
63
|
+
return ['database', 'query', app.databaseName, `(${
|
|
64
|
+
async (input, output, { keys, indexName }) => {
|
|
65
|
+
function mapper(obj) {
|
|
66
|
+
if(!obj) return null
|
|
67
|
+
const { id, to, ...safeObj } = obj
|
|
68
|
+
return { ...safeObj, id: to }
|
|
69
|
+
}
|
|
70
|
+
function onIndexChange(obj, oldObj) {
|
|
71
|
+
output.change(mapper(obj), mapper(oldObj))
|
|
72
|
+
}
|
|
73
|
+
await Promise.all(keys.map(async (encodedKey) => {
|
|
74
|
+
const range = {
|
|
75
|
+
gte: encodedKey + '_',
|
|
76
|
+
lte: encodedKey + "_\xFF\xFF\xFF\xFF"
|
|
77
|
+
}
|
|
78
|
+
await (await input.index(indexName)).range(range).onChange(onIndexChange)
|
|
79
|
+
}))
|
|
80
|
+
}
|
|
81
|
+
})`, { keys, indexName}]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getClientKeysStrings(client, prefix = '', suffix = '') {
|
|
62
85
|
if(clientKeys) {
|
|
63
|
-
return clientKeys(client).map(k => prefix + k.key + ':' + k.value)
|
|
86
|
+
return clientKeys(client).filter(k => k.value).map(k => prefix + k.key + ':' + k.value + suffix)
|
|
64
87
|
} else {
|
|
65
88
|
const keys = []
|
|
66
89
|
for(let key in client) {
|
|
67
|
-
keys.push(prefix + key + ':' + client[key])
|
|
90
|
+
if(client[key]) keys.push(prefix + key + ':' + client[key] + suffix)
|
|
68
91
|
}
|
|
69
92
|
return keys
|
|
70
93
|
}
|
|
@@ -82,4 +105,4 @@ function getClientKeysObject(client, prefix = '') {
|
|
|
82
105
|
}
|
|
83
106
|
}
|
|
84
107
|
|
|
85
|
-
module.exports = { multiKeyIndexQuery, getClientKeysStrings, getClientKeysObject }
|
|
108
|
+
module.exports = { multiKeyIndexQuery, fastMultiKeyIndexQuery, getClientKeysStrings, getClientKeysObject }
|