@everneed/worker 1.0.2 → 1.2.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/index.js +19 -0
- package/package.json +1 -1
- package/worker/rabbitmq.js +228 -69
package/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import postgres from "postgres"
|
|
2
2
|
import mongoose from "mongoose"
|
|
3
3
|
import { createClient } from "redis"
|
|
4
|
+
import { io } from "socket.io-client"
|
|
4
5
|
|
|
5
6
|
import rabbitmq from "./worker/rabbitmq.js"
|
|
6
7
|
import pseudoEnv from "./pseudoenv.js"
|
|
@@ -68,8 +69,26 @@ import pseudoEnv from "./pseudoenv.js"
|
|
|
68
69
|
.on("error", err => console.log("Redis Client Error", err))
|
|
69
70
|
// .connect()
|
|
70
71
|
}
|
|
72
|
+
/* Socketio */
|
|
73
|
+
let socketioInstance = ()=>{}
|
|
74
|
+
socketioInstance.config = (object)=>{
|
|
75
|
+
// config({
|
|
76
|
+
// ENV: value,
|
|
77
|
+
// ...
|
|
78
|
+
// })
|
|
79
|
+
/* Param Validation */
|
|
80
|
+
const allowed = new Set(["WEBSOCKET_HOST", "WEBSOCKET_PORT"])
|
|
81
|
+
if(new Set(Object.keys(object)).difference(allowed).size){
|
|
82
|
+
throw `illegal key on socketio.config(): Only ${Array.from(allowed).join(", ")} are allowed`
|
|
83
|
+
}
|
|
84
|
+
/* Env Add Process */
|
|
85
|
+
pseudoEnv.config(object)
|
|
86
|
+
/* Refresh Instance (force re-export) */
|
|
87
|
+
socketioInstance = io(`ws://${pseudoEnv.process.env.WEBSOCKET_HOST}:${pseudoEnv.process.env.WEBSOCKET_PORT}/`)
|
|
88
|
+
}
|
|
71
89
|
|
|
72
90
|
export { rabbitmq } // preserve seperate-dir for example
|
|
73
91
|
export { postgresInstance as postgres }
|
|
74
92
|
// export { mongooseInstance as mongoose }
|
|
75
93
|
export { redisInstance as redis }
|
|
94
|
+
export { socketioInstance as socketio }
|
package/package.json
CHANGED
package/worker/rabbitmq.js
CHANGED
|
@@ -1,26 +1,40 @@
|
|
|
1
1
|
import path from "path"
|
|
2
2
|
import fs from "fs"
|
|
3
|
+
import crypto from "crypto"
|
|
3
4
|
import amqp from "amqplib/callback_api.js"
|
|
4
5
|
import moment from "moment"
|
|
5
6
|
|
|
6
7
|
import pseudoEnv from "@everneed/worker/pseudoenv.js"
|
|
7
8
|
import { createHmac } from "crypto"
|
|
9
|
+
import { redis } from "@everneed/worker"
|
|
10
|
+
import { ResponseCode } from "@everneed/responsecode"
|
|
11
|
+
import { pipe, switchPourStr, randomStr } from "@everneed/helper"
|
|
8
12
|
|
|
9
13
|
export default {
|
|
10
|
-
config: (object)
|
|
14
|
+
config: function(object){
|
|
11
15
|
// config({
|
|
12
16
|
// ENV: value,
|
|
13
17
|
// ...
|
|
14
18
|
// })
|
|
15
19
|
/* Param Validation */
|
|
16
|
-
const allowed = new Set([
|
|
20
|
+
const allowed = new Set([
|
|
21
|
+
"SVC_NAME",
|
|
22
|
+
"SVC_DISPLAY",
|
|
23
|
+
"RABBIT_HOST",
|
|
24
|
+
"RABBIT_MESSAGE_KEY",
|
|
25
|
+
"RABBIT_MESSAGE_IV",
|
|
26
|
+
"RABBIT_MESSAGE_SALT",
|
|
27
|
+
"TOKEN_UA_SALT",
|
|
28
|
+
"TOKEN_KEY",
|
|
29
|
+
"TOKEN_IV"
|
|
30
|
+
])
|
|
17
31
|
if(new Set(Object.keys(object)).difference(allowed).size){
|
|
18
32
|
throw `illegal key on rabbitmq.config(): Only ${Array.from(allowed).join(", ")} are allowed`
|
|
19
33
|
}
|
|
20
34
|
/* Env Add Process */
|
|
21
35
|
pseudoEnv.config(object)
|
|
22
36
|
},
|
|
23
|
-
start: ()
|
|
37
|
+
start: function(){
|
|
24
38
|
/* RabbitMQ Engine */
|
|
25
39
|
amqp.connect(pseudoEnv.process.env.RABBIT_HOST, async (error0, connection)=>{
|
|
26
40
|
if(error0) throw error0
|
|
@@ -47,15 +61,64 @@ export default {
|
|
|
47
61
|
durable: true
|
|
48
62
|
})
|
|
49
63
|
channel.consume(`req/${pseudoEnv.process.env.SVC_NAME}/${moduleSpace}/${functName}`, async (message)=>{
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
.
|
|
53
|
-
.
|
|
64
|
+
const mqHeader = message.properties.headers
|
|
65
|
+
const mqReq = await this.decryptMessage({
|
|
66
|
+
message: message.content.toString(),
|
|
67
|
+
...(mqHeader.auth && { token: mqHeader.auth.token }),
|
|
68
|
+
...(mqHeader.auth && { userAgent: mqHeader.auth.userAgent })
|
|
69
|
+
})
|
|
70
|
+
const prep = {
|
|
71
|
+
message: null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* With Auth */
|
|
75
|
+
if(main.default[functName].authentication){
|
|
76
|
+
if(!mqHeader.auth){
|
|
77
|
+
const response = new ResponseCode()
|
|
78
|
+
response.pushCode("NOT_AUTHENTICATED")
|
|
79
|
+
this.encryptMessage({
|
|
80
|
+
message: response
|
|
81
|
+
})
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { token, userAgent } = mqHeader.auth
|
|
86
|
+
const auth = await this.verifyToken({
|
|
87
|
+
token: token,
|
|
88
|
+
userAgent: userAgent
|
|
89
|
+
})
|
|
54
90
|
|
|
55
|
-
|
|
91
|
+
if(auth.checkError()){ // NOT_AUTHENTICATED || SESSION_EXPIRED
|
|
92
|
+
prep["message"] = await this.encryptMessage({
|
|
93
|
+
message: auth.result,
|
|
94
|
+
token: token,
|
|
95
|
+
userAgent: userAgent
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
else{ // AUTHENTICATED || REFRESH_SESSION
|
|
99
|
+
const result = await main.default[functName](mqHeader.auth, mqReq.payload)
|
|
100
|
+
prep["message"] = await this.encryptMessage({
|
|
101
|
+
message: {
|
|
102
|
+
...result,
|
|
103
|
+
...(auth.hasCode("REFRESH_SESSION") && auth.result.data)
|
|
104
|
+
},
|
|
105
|
+
token: token,
|
|
106
|
+
userAgent: userAgent
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|
|
56
110
|
|
|
111
|
+
/* Without Auth */
|
|
112
|
+
else{
|
|
113
|
+
const result = await main.default[functName](false, mqReq.payload)
|
|
114
|
+
prep["message"] = await this.encryptMessage({
|
|
115
|
+
message: result,
|
|
116
|
+
...(mqHeader.auth && { token: mqHeader.auth.token }),
|
|
117
|
+
...(mqHeader.auth && { userAgent: mqHeader.auth.userAgent })
|
|
118
|
+
})
|
|
119
|
+
}
|
|
57
120
|
if(mqReq.trip == "returning"){
|
|
58
|
-
channel.sendToQueue(message.properties.replyTo, Buffer.from(
|
|
121
|
+
channel.sendToQueue(message.properties.replyTo, Buffer.from(prep.message), {
|
|
59
122
|
correlationId: message.properties.correlationId
|
|
60
123
|
})
|
|
61
124
|
channel.ack(message)
|
|
@@ -69,91 +132,187 @@ export default {
|
|
|
69
132
|
// connection.close()
|
|
70
133
|
})
|
|
71
134
|
},
|
|
72
|
-
publish: ({topic,
|
|
135
|
+
publish: function({topic, auth=null, trip="passby", data}){
|
|
73
136
|
/* Usage */
|
|
74
137
|
// publish({
|
|
75
138
|
// topic: <topic start at svc :String>,
|
|
76
|
-
//
|
|
139
|
+
// auth:{
|
|
140
|
+
// token: <auth token :String>,
|
|
141
|
+
// userAgent: <ua string :String>,
|
|
142
|
+
// }
|
|
77
143
|
// trip: <"returning"|"passby" :String>,
|
|
78
144
|
// data: <message :String>
|
|
79
145
|
// })
|
|
80
146
|
|
|
81
147
|
// topic: <req|res>/<short sha username + random string>/<svc>/<endpoint>
|
|
82
|
-
// ex: req/cd6f01UjkB7E/rolling-tempat-duduk/class/
|
|
83
|
-
// ex: res/cd6f01UjkB7E/rolling-tempat-duduk/class/
|
|
148
|
+
// ex: req/cd6f01UjkB7E/rolling-tempat-duduk/class/getAll
|
|
149
|
+
// ex: res/cd6f01UjkB7E/rolling-tempat-duduk/class/getAll
|
|
84
150
|
|
|
85
151
|
return new Promise(async (resolve, reject)=>{
|
|
86
152
|
/* RabbitMQ Engine */
|
|
87
|
-
amqp.connect(pseudoEnv.process.env.RABBIT_HOST, (error0, connection)=>{
|
|
153
|
+
amqp.connect(pseudoEnv.process.env.RABBIT_HOST, async (error0, connection)=>{
|
|
88
154
|
if(error0){ throw error0 }
|
|
89
155
|
|
|
90
156
|
/* Creates connection & channel */
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
})
|
|
157
|
+
const channel = connection.createChannel((error1, channel)=>{
|
|
158
|
+
if (error1){ throw error1 }
|
|
159
|
+
return channel
|
|
160
|
+
})
|
|
96
161
|
|
|
97
162
|
/* Build payload structure */
|
|
98
163
|
// like topic, address, etc.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
durable: true
|
|
115
|
-
})
|
|
116
|
-
channel.consume(resTopic, (message)=>{
|
|
117
|
-
const mqRes = JSON.parse(message.content.toString())
|
|
164
|
+
const userName = auth ? this.getUserInfo({tokenBody: auth.token.split(".")[0]}).userName : `guest${randomStr({length: 12})}`
|
|
165
|
+
const prep = {
|
|
166
|
+
topic: `req/${topic}`,
|
|
167
|
+
message:{
|
|
168
|
+
trip: trip,
|
|
169
|
+
senderAddress: `${createHmac("sha256", userName).digest("hex").slice(0, 12)}${moment().utc().format("HHmmssSSS")}`,
|
|
170
|
+
payload: data
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/* Main publisher */
|
|
174
|
+
const mqReq = await this.encryptMessage({
|
|
175
|
+
message: prep.message,
|
|
176
|
+
...(auth && { token: auth.token }),
|
|
177
|
+
...(auth && { userAgent: auth.userAgent })
|
|
178
|
+
})
|
|
118
179
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
180
|
+
channel.assertQueue(prep.topic, { durable: true })
|
|
181
|
+
channel.sendToQueue(prep.topic, Buffer.from(mqReq), {
|
|
182
|
+
...(trip == "returning" && { correlationId: prep.message.senderAddress }),
|
|
183
|
+
...(trip == "returning" && { replyTo: `res/${topic}` }),
|
|
184
|
+
...(auth && { headers:{auth: auth} })
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
/* Resolvation According to Trip */
|
|
188
|
+
if(trip == "returning"){
|
|
189
|
+
/* Creates consumer for returning messages */
|
|
190
|
+
channel.assertQueue(`res/${topic}`, { durable: true })
|
|
191
|
+
channel.consume(`res/${topic}`, async (message)=>{
|
|
192
|
+
const mqRes = await this.decryptMessage({
|
|
193
|
+
message: message.content.toString(),
|
|
194
|
+
...(auth && { token: auth.token }),
|
|
195
|
+
...(auth && { userAgent: auth.userAgent })
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
if(message.properties.correlationId == prep.message.senderAddress){
|
|
199
|
+
resolve(mqRes)
|
|
200
|
+
channel.ack(message)
|
|
201
|
+
setTimeout(()=>{
|
|
202
|
+
connection.close()
|
|
203
|
+
}, 500)
|
|
204
|
+
}
|
|
205
|
+
}, {
|
|
206
|
+
noAck: false
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
else if(trip == "passby"){
|
|
210
|
+
resolve("sent")
|
|
211
|
+
|
|
122
212
|
setTimeout(()=>{
|
|
123
213
|
connection.close()
|
|
124
214
|
}, 500)
|
|
125
215
|
}
|
|
126
|
-
}, {
|
|
127
|
-
noAck: false
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
/* Main publisher (with correlation id) */
|
|
131
|
-
channel.assertQueue(prep.topic,{
|
|
132
|
-
durable: true
|
|
133
|
-
})
|
|
134
|
-
channel.sendToQueue(prep.topic, Buffer.from(JSON.stringify(prep.message)), {
|
|
135
|
-
correlationId: prep.message.senderAddress,
|
|
136
|
-
replyTo: resTopic
|
|
137
|
-
})
|
|
138
|
-
}
|
|
139
|
-
else if(trip == "passby"){
|
|
140
|
-
/* Main publisher */
|
|
141
|
-
// pushes without senderAddress
|
|
142
|
-
prep.message["senderAddress"] = null
|
|
143
|
-
|
|
144
|
-
channel.assertQueue(prep.topic,{
|
|
145
|
-
durable: true
|
|
146
|
-
})
|
|
147
|
-
channel.sendToQueue(prep.topic, Buffer.from(JSON.stringify(prep.message)))
|
|
148
|
-
resolve("sent")
|
|
149
|
-
|
|
150
|
-
setTimeout(()=>{
|
|
151
|
-
connection.close()
|
|
152
|
-
}, 500)
|
|
153
|
-
}
|
|
154
216
|
|
|
155
217
|
// connection.close()
|
|
156
218
|
})
|
|
157
219
|
})
|
|
158
220
|
},
|
|
221
|
+
encryptMessage: function({message, token=null, userAgent=null}){
|
|
222
|
+
return new Promise(async (resolve, reject)=>{
|
|
223
|
+
/* Active Auth Session */
|
|
224
|
+
if(token && userAgent){
|
|
225
|
+
const { userId } = this.getUserInfo({ tokenBody: token.split(".")[0] })
|
|
226
|
+
const uaHash = crypto.hash("sha256", switchPourStr(userAgent, pseudoEnv.process.env.TOKEN_UA_SALT))
|
|
227
|
+
await redis.connect().catch(error => {})
|
|
228
|
+
const authKey = await redis.get(`auth:session:long:${userId}:${uaHash}`)
|
|
229
|
+
await redis.quit()
|
|
230
|
+
const hash = crypto.hash("sha256", switchPourStr(authKey, pseudoEnv.process.env.RABBIT_MESSAGE_SALT))
|
|
231
|
+
const key = hash.substring(0, 16)
|
|
232
|
+
const iv = hash.substring(48)
|
|
233
|
+
|
|
234
|
+
const cipher = crypto.createCipheriv("aes-128-cbc", Buffer.from(key), Buffer.from(iv))
|
|
235
|
+
let encrypted = cipher.update(JSON.stringify(nullCleanser(message)), "utf-8", "base64")
|
|
236
|
+
encrypted += cipher.final("base64")
|
|
237
|
+
return resolve(encrypted)
|
|
238
|
+
}
|
|
239
|
+
/* No Auth */
|
|
240
|
+
else{
|
|
241
|
+
const cipher = crypto.createCipheriv("aes-128-cbc", Buffer.from(pseudoEnv.process.env.RABBIT_MESSAGE_KEY), Buffer.from(pseudoEnv.process.env.RABBIT_MESSAGE_IV))
|
|
242
|
+
let encrypted = cipher.update(JSON.stringify(nullCleanser(message)), "utf-8", "base64")
|
|
243
|
+
encrypted += cipher.final("base64")
|
|
244
|
+
return resolve(encrypted)
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
},
|
|
248
|
+
decryptMessage: function({message, token=null, userAgent=null}){
|
|
249
|
+
return new Promise(async (resolve, reject)=>{
|
|
250
|
+
/* Active Auth Session */
|
|
251
|
+
if(token && userAgent){
|
|
252
|
+
const { userId } = this.getUserInfo({ tokenBody: token.split(".")[0] })
|
|
253
|
+
const uaHash = crypto.hash("sha256", switchPourStr(userAgent, pseudoEnv.process.env.TOKEN_UA_SALT))
|
|
254
|
+
await redis.connect().catch(error => {})
|
|
255
|
+
const authKey = await redis.get(`auth:session:long:${userId}:${uaHash}`)
|
|
256
|
+
await redis.quit()
|
|
257
|
+
const hash = crypto.hash("sha256", switchPourStr(authKey, pseudoEnv.process.env.RABBIT_MESSAGE_SALT))
|
|
258
|
+
const key = hash.substring(0, 16)
|
|
259
|
+
const iv = hash.substring(48)
|
|
260
|
+
|
|
261
|
+
const decipher = crypto.createDecipheriv("aes-128-cbc", Buffer.from(key), Buffer.from(iv))
|
|
262
|
+
let decrypted = decipher.update(message, "base64", "utf-8")
|
|
263
|
+
decrypted += decipher.final("utf-8")
|
|
264
|
+
return resolve(nullCleanser(JSON.parse(decrypted)))
|
|
265
|
+
}
|
|
266
|
+
/* No Auth */
|
|
267
|
+
else{
|
|
268
|
+
const decipher = crypto.createDecipheriv("aes-128-cbc", Buffer.from(pseudoEnv.process.env.RABBIT_MESSAGE_KEY), Buffer.from(pseudoEnv.process.env.RABBIT_MESSAGE_IV))
|
|
269
|
+
let decrypted = decipher.update(message, "base64", "utf-8")
|
|
270
|
+
decrypted += decipher.final("utf-8")
|
|
271
|
+
return resolve(nullCleanser(JSON.parse(decrypted)))
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
},
|
|
275
|
+
verifyToken: function({token, userAgent}){
|
|
276
|
+
const response = new ResponseCode()
|
|
277
|
+
return new Promise(async (resolve, reject)=>{
|
|
278
|
+
try{
|
|
279
|
+
await this.publish({
|
|
280
|
+
topic: "authentor/session/svVerifyToken",
|
|
281
|
+
trip: "returning",
|
|
282
|
+
auth:{
|
|
283
|
+
token: token,
|
|
284
|
+
userAgent: userAgent,
|
|
285
|
+
},
|
|
286
|
+
data:{
|
|
287
|
+
token: token,
|
|
288
|
+
userAgent: userAgent
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
.then(apiRes=>{
|
|
292
|
+
response.createNew(apiRes)
|
|
293
|
+
throw "abort"
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
catch(something){
|
|
297
|
+
if(something != "abort"){
|
|
298
|
+
// err: other error
|
|
299
|
+
response.pushCode("LOGIC_ERROR")
|
|
300
|
+
response.pushTrace({code: "LOGIC_ERROR", trace: `Unexpected error on ${pseudoEnv.process.env.SVC_DISPLAY} svCreateToken() function`})
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
resolve(response)
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
},
|
|
307
|
+
getUserInfo: function({tokenBody}){
|
|
308
|
+
return pipe(tokenBody)
|
|
309
|
+
.then(aes=>{
|
|
310
|
+
const decipher = crypto.createDecipheriv("aes-128-cbc", Buffer.from(pseudoEnv.process.env.TOKEN_KEY), Buffer.from(pseudoEnv.process.env.TOKEN_IV))
|
|
311
|
+
let decrypted = decipher.update(aes, "base64", "utf-8")
|
|
312
|
+
return decrypted += decipher.final("utf-8")
|
|
313
|
+
})
|
|
314
|
+
.then(json=> JSON.parse(json))
|
|
315
|
+
.then(circular=> circular.user)
|
|
316
|
+
.result // { userId, userName, displayName }
|
|
317
|
+
},
|
|
159
318
|
}
|