@everneed/worker 1.1.0 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/worker/rabbitmq.js +228 -69
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everneed/worker",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -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(["SVC_NAME", "RABBIT_HOST"])
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 mqReq = pipe(message)
51
- .then(message=> JSON.parse(message.content))
52
- .then(mqReq=> nullCleanser(mqReq))
53
- .result
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
- const result = await main.default[functName](mqReq.payload)
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(JSON.stringify(result)), {
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, userName, trip = "passby", data})=>{
135
+ publish: function({topic, auth=null, trip="passby", data}){
73
136
  /* Usage */
74
137
  // publish({
75
138
  // topic: <topic start at svc :String>,
76
- // userName: <userName or guest :String>,
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/get-all
83
- // ex: res/cd6f01UjkB7E/rolling-tempat-duduk/class/get-all
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
- const channel = connection.createChannel((error1, channel)=>{
92
- if (error1){ throw error1 }
93
-
94
- return channel
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
- const prep = {
100
- topic: `req/${topic}`,
101
- message:{
102
- trip: trip,
103
- senderAddress: `${createHmac("sha256", userName).digest("hex").slice(0, 6)}${moment().utc().format("HHmmssSSS")}`,
104
- payload: data
105
- }
106
- }
107
-
108
- if(trip == "returning"){
109
- /* Reshape topic into response shape */
110
- let resTopic = `res/${topic}`
111
-
112
- /* Creates consumer for returning messages */
113
- channel.assertQueue(resTopic, {
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
- if(message.properties.correlationId == prep.message.senderAddress){
120
- resolve(mqRes)
121
- channel.ack(message)
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
  }