corebasic 1.0.102 → 1.0.104

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/libs/auth.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import axios from 'axios'
2
2
  import otpGenerator from 'otp-generator'
3
3
 
4
- import * as Elabase from './elabase.js'
4
+ import * as Dip from './dip.js'
5
5
  import * as Utils from './utils.js'
6
6
  import * as Session from './session.js'
7
7
 
@@ -34,6 +34,8 @@ export const start = (app, successCallback) => {
34
34
  }
35
35
 
36
36
  async function attemptLogin(req, res) {
37
+ let meta = {company: "GLOBAL", outlet: "GLOBAL"}
38
+
37
39
  let expiry = 300000
38
40
  let phone = req.body.phone.trim()
39
41
  let time = new Date().getTime()
@@ -45,9 +47,9 @@ async function attemptLogin(req, res) {
45
47
  throw { ...errMessage, mode: otpValid ? 'verify' : 'login', info: "Invalid Client ID" }
46
48
 
47
49
  if (otpValid) { // verify login
48
- let res = await Elabase.query(collection, { _id: phone, otp: req.body.otp, clientId, time: { $gt: time - expiry } })
50
+ let res = await Dip.query(meta, collection, { _id: phone, otp: req.body.otp, clientId, time: { $gt: time - expiry } })
49
51
  if (res.length) {
50
- try {await Elabase.update(collection, { _id: phone }, { $set: { otp: '', clientId: '' } }) } catch (err) { throw {...errMessage, mode: 'verify', info: 'Cleanup Failed'} }
52
+ try {await Dip.update(meta, collection, { _id: phone }, { $set: { otp: '', clientId: '' } }) } catch (err) { throw {...errMessage, mode: 'verify', info: 'Cleanup Failed'} }
51
53
  return {...res[0], mode: 'verify', success: true, userId: res[0].userId, phone: res[0]._id}
52
54
  }
53
55
  throw {...errMessage, mode: 'verify', info: "Invalid/Expired OTP"}
@@ -57,8 +59,9 @@ async function attemptLogin(req, res) {
57
59
  let userId = req.body.userId ?? Utils.uid()
58
60
  let now = Utils.now().toISOString()
59
61
  let data = req.body.data ?? {}
60
- let meta = { clientId, created: now, updated: now, ...data}
61
- await Elabase.update(collection, { _id: phone }, { $set: { otp, clientId, time, updated: now }, $setOnInsert: { _id: phone, otp, time, userId, ...meta } }, { upsert: true })
62
+ let info = { clientId, created: now, updated: now, ...data}
63
+ await Dip.update(meta, collection, { _id: phone }, { $set: { otp, clientId, time, updated: now }, $setOnInsert: { _id: phone, otp, time, userId, ...info } }, { upsert: true })
64
+
62
65
  return {mode: 'login', success: true, userId, expiry, phone}
63
66
  }
64
67
  throw {...errMessage, mode: 'login', info: 'Generating Info Failed'}
@@ -85,9 +88,10 @@ async function sendOtp(phone, otp, app) {
85
88
 
86
89
  // Usage
87
90
  // Auth.validate((req) => ["Phone1", "Phone2"].includes(req.body.phone.trim()))
88
- // Auth.start(app, async (req, res, data) => {
91
+ // Auth.start(app, async (req, res, data) => {
89
92
  // try {
90
- // await Elabase.insert("users", { }, "asdsds")
93
+ // let meta = {company: "GLOBAL", outlet: "GLOBAL"}
94
+ // await Dip.insert(meta, "users", { }, "asdsds")
91
95
  // await Kafka.send("users", {event: "userLogin", ...data, time: Utils.now()})
92
96
  // res.json(data)
93
97
  // } catch {
package/libs/dip.js CHANGED
@@ -18,28 +18,30 @@ export let config = Elabase.config
18
18
  export const IdempotentDip = () => {
19
19
  return {
20
20
  txn: '',
21
- new: (async function (txn, blank) {
21
+ meta: undefined,
22
+ new: (async function (meta, txn, blank) {
22
23
  this.txn = txn
23
- return blank ? this : (await Elabase.query("txns",{_id: this.txn}).length ? false : this)
24
+ this.meta = meta
25
+ return blank ? this : (await Elabase.query(this.meta, "txns",{_id: this.txn}).length ? false : this)
24
26
  }),
25
27
  insert: (async function (collection, query, data) {
26
- await Elabase.update(collection, query, { $setOnInsert: data }, {upsert: true})
28
+ await Elabase.update(this.meta, collection, query, { $setOnInsert: data }, {upsert: true})
27
29
  }),
28
30
  update: (async function (collection, query, data, rollback) {
29
31
  let token = { [this.txn]: true }
30
32
  let idempotent = { [this.txn]: { $exists: rollback ?? false } }
31
- let result = await Elabase.update(collection, {...query, ...idempotent}, {...data, $setOnInsert: undefined, $set: {...(data.$set ?? {}), ...token} })
32
- return result.count ? true : await Elabase.query(collection, {...query, ...idempotent}).length
33
+ let result = await Elabase.update(this.meta, collection, {...query, ...idempotent}, {...data, $setOnInsert: undefined, $set: {...(data.$set ?? {}), ...token} })
34
+ return result.count ? true : await Elabase.query(this.meta, collection, {...query, ...idempotent}).length
33
35
  }),
34
36
  upsert: (async function (collection, query, data, rollback) {
35
37
  await this.insert(collection, query, data.$setOnInsert)
36
38
  return await this.update(collection, query, data, rollback)
37
39
  }),
38
40
  cleanup: (async function (collection, query) {
39
- await Elabase.update(collection, query, { $unset: { [this.txn]: true } })
41
+ await Elabase.update(this.meta, collection, query, { $unset: { [this.txn]: true } })
40
42
  }),
41
43
  finish: (async function () {
42
- return await Elabase.update("txns",{_id: this.txn}, {}, {upsert: true})
44
+ return await Elabase.update(this.meta, "txns",{_id: this.txn}, {}, {upsert: true})
43
45
  })
44
46
  }
45
47
  }
package/libs/elabase.js CHANGED
@@ -83,7 +83,15 @@ export function transactionCommit(arg) {
83
83
  })
84
84
  }
85
85
 
86
- export const insert = async (collection, value, _id) => {
86
+ export const insert = async (meta, collection, value, _id) => {
87
+ // Multi Company Support
88
+ collection = `${meta.company}.${collection}`
89
+ if (meta.company !== "GLOBAL") {
90
+ if (typeof value === "object" && !Array.isArray(value))
91
+ value.company = meta.company
92
+ }
93
+ // ---------------------
94
+
87
95
  var arg = {
88
96
  collection: collection,
89
97
  insert: value
@@ -104,7 +112,14 @@ export const insert = async (collection, value, _id) => {
104
112
  })
105
113
  }
106
114
 
107
- export const query = async (collection, query, options) => {
115
+ export const query = async (meta, collection, query, options) => {
116
+ // Multi Company Support
117
+ collection = `${meta.company}.${collection}`
118
+ if (meta.company !== "GLOBAL")
119
+ query.company = meta.company
120
+ // ---------------------
121
+
122
+
108
123
  var arg = {
109
124
  collection: collection,
110
125
  query: query,
@@ -137,7 +152,16 @@ function collectionArray(col) {
137
152
  }
138
153
 
139
154
 
140
- export const update = async (collection, query, update, options) => {
155
+ export const update = async (meta, collection, query, update, options) => {
156
+ // Multi Company Support
157
+ collection = `${meta.company}.${collection}`
158
+ if (meta.company !== "GLOBAL") {
159
+ query.company = meta.company
160
+ if (update?.$setOnInsert)
161
+ update.$setOnInsert.company = meta.company
162
+ }
163
+ // ---------------------
164
+
141
165
  var arg = {
142
166
  collection: collection,
143
167
  query: query,
@@ -157,7 +181,13 @@ export const update = async (collection, query, update, options) => {
157
181
  })
158
182
  }
159
183
 
160
- export const remove = async (collection, query) => {
184
+ export const remove = async (meta, collection, query) => {
185
+ // Multi Company Support
186
+ collection = `${meta.company}.${collection}`
187
+ if (meta.company !== "GLOBAL")
188
+ query.company = meta.company
189
+ // ---------------------
190
+
161
191
  var arg = {
162
192
  collection: collection,
163
193
  query: query,
package/libs/features.js CHANGED
@@ -39,7 +39,7 @@ let appId = Utils.uid()
39
39
  let SERVICE_ADDRESS = process.env.APP_ENDPOINT || 'http://127.0.0.1:3000'
40
40
 
41
41
 
42
- export const send = async (feature, params, payload, headers) => {
42
+ export const send = async (meta, feature, data, params) => {
43
43
  const throwError = () => {throw {message: `Feature ${feature} not found in internal or external list during inter feature call`}}
44
44
  let {api, service = SERVICE_ADDRESS} = getFeature(feature) ?? SLYP_FEATURES_LIST[feature] ?? throwError()
45
45
  let method = getFeatureMethod(api)
@@ -52,10 +52,10 @@ export const send = async (feature, params, payload, headers) => {
52
52
  throw {feature, when: 'api/params/substitution', message: "Internal Feature Call" }
53
53
  }
54
54
 
55
- payload = { ...payload, feature, txn: Utils.uid() }
55
+ let payload = { ...meta, data, feature, txn: Utils.uid() }
56
56
 
57
57
  // return (await axios[method](`${service}${url}`, { data: payload, headers: {jwt: headers.jwt}, timeout: 1000 })).data
58
- return (await axios[method](`${service}${url}`, { data: payload, headers: {jwt: SERVICE_ACCESS_TOKEN, service: true}, timeout: 1000 })).data
58
+ return (await axios[method](`${service}${url}`, payload, {headers: {jwt: SERVICE_ACCESS_TOKEN, service: true}, timeout: 1000 })).data
59
59
  }
60
60
 
61
61
  let appids = {}
@@ -123,6 +123,8 @@ export const start = async (app, url, file) => {
123
123
  if (!req.params[param])
124
124
  throw { status: 404, message: "Resource not found. One or more url parameter not specified." }
125
125
 
126
+ let meta = {...req.body, data: undefined}
127
+ req.meta = meta
126
128
 
127
129
  if (method === "get") {
128
130
  try {
@@ -164,9 +166,9 @@ export const start = async (app, url, file) => {
164
166
  topic = topic.replace(/^.*Features./,'')
165
167
  while (true) {
166
168
  try {
167
- await Dip.update(`users.txns.${message.user}`, {_id: message.txn}, { $setOnInsert: { user: message.user, feature: message.feature, date: message.date, created: message.date, updated: message.date, status: "Queued" } }, {upsert: true}) // Can always update the status later
169
+ await Dip.update(message.meta, `users.txns.${message.user}`, {_id: message.txn}, { $setOnInsert: { user: message.user, feature: message.feature, date: message.date, created: message.date, updated: message.date, status: "Queued" } }, {upsert: true}) // Can always update the status later
168
170
  await features[message.feature].handler(topic, message)
169
- await Dip.insert("Features.txns", {_id: message.txn, status: "Processed"})
171
+ await Dip.insert(message.meta, "Features.txns", {_id: message.txn, status: "Processed"})
170
172
  break
171
173
  } catch {
172
174
  console.warn(`Feature: ${message.feature}, error in executing handler during Kafka.receive(topic: ${topic})`)
@@ -177,9 +179,9 @@ export const start = async (app, url, file) => {
177
179
  }
178
180
 
179
181
  // Transaction status api
180
- app.get('/transactions/:id', async (req, res) => {
182
+ app.get('/transactions/:id', async (req, res) => { // Front end calls this as a regular feature call. so `req.meta` exists
181
183
  try {
182
- let items = await Dip.query("Features.txns", {_id: req.params.id})
184
+ let items = await Dip.query(req.meta, "Features.txns", {_id: req.params.id})
183
185
  if (items.length)
184
186
  res.json({ data: { ...items[0], txn: items._id } })
185
187
  else
@@ -190,7 +192,7 @@ export const start = async (app, url, file) => {
190
192
  })
191
193
  app.get('/users/:id/transactions', async (req, res) => {
192
194
  try {
193
- res.json({ data: await Dip.query(`users.txns.${req.params.id}`, {}, { $orderby: { date: -1 } }) })
195
+ res.json({ data: await Dip.query(req.meta, `users.txns.${req.params.id}`, {}, { $orderby: { date: -1 } }) })
194
196
  } catch {
195
197
  res.status(500).json({ message: "Failed to retreive data" })
196
198
  }
@@ -201,10 +203,11 @@ export const start = async (app, url, file) => {
201
203
 
202
204
  function prepareMessage(req) {
203
205
  let txn = req.body.txn ?? Utils.uid()
204
- let _id = req.body.data._id ?? txn
206
+ let _id = req.body.data?._id ?? txn
205
207
  let {data, feature, app, user, outlet, company, client, version} = req.body
208
+ let meta = req.meta
206
209
  let params = req.params
207
- return { data, params, feature, app, user, outlet, company, client, version, txn, id: _id, date: new Date().getTime() }
210
+ return { data, params, meta, feature, app, user, outlet, company, client, version, txn, id: _id, date: new Date().getTime() }
208
211
  }
209
212
  const commandAction = async (req, res, topic) => {
210
213
  let message = prepareMessage(req)
package/libs/messaging.js CHANGED
@@ -74,8 +74,13 @@ export async function start(app) {
74
74
 
75
75
 
76
76
  export async function publish(channel, message) {
77
- if (publisher)
78
- await publisher.publish(channel, typeof message === "object" ? JSON.stringify(message) : message);
77
+ if (publisher && !Utils.isEmpty(channel)) {
78
+ try {
79
+ await publisher.publish(channel, typeof message === "object" ? JSON.stringify(message) : message)
80
+ } catch {
81
+ console.log('Error: Publishing on redis channel', channel)
82
+ }
83
+ }
79
84
  }
80
85
 
81
86
 
@@ -0,0 +1,93 @@
1
+
2
+
3
+ import * as Dip from './dip.js'
4
+
5
+
6
+ let PRIVILEGES_CACHE = {
7
+
8
+ // "<company>": {
9
+ // "Sales Man": {
10
+ // "products.query.list": { type: "Feature" }
11
+ // },
12
+ // "Accountant": {
13
+ // "Sales Man": { type: "Role" },
14
+ // "accounts.query.list": { type: "Feature" },
15
+ // },
16
+ // "Cashier": {
17
+ // "Sales Man": { type: "Role" },
18
+ // "cash.query.list": { type: "Feature" },
19
+ // },
20
+ // }
21
+ }
22
+
23
+
24
+ const get = async (company) => {
25
+ PRIVILEGES_CACHE[company] = {}
26
+ let meta = {company, outlet: "GLOBAL"}
27
+ let privileges = await Dip.query(meta, "privileges", { })
28
+ privileges.forEach(privilege => PRIVILEGES_CACHE[company][privilege.name] = privilege)
29
+
30
+ // Format
31
+ for (let role in PRIVILEGES_CACHE[company])
32
+ PRIVILEGES_CACHE[company][role] = PRIVILEGES_CACHE[company][role].items.reduce( (obj, item) => ({...obj, [item.name]: {type: item.type} }), {})
33
+
34
+ return PRIVILEGES_CACHE[company]
35
+ }
36
+
37
+ export const start = async () => {
38
+ let meta = {company: "GLOBAL", outlet: "GLOBAL"}
39
+ let companies = await Dip.query(meta, "companies", { })
40
+ for (let company of companies)
41
+ await get(company._id)
42
+ }
43
+
44
+
45
+ function rolesToFeatures(company, roles) {
46
+ let features = []
47
+
48
+ let privileges = PRIVILEGES_CACHE[company]
49
+
50
+ roles.forEach(role => {
51
+ for (let key in privileges[role]) {
52
+ let isFeature = privileges[role][key].type === "Feature"
53
+ let isRole = privileges[role][key].type === "Role"
54
+ if (isFeature)
55
+ features.push(key)
56
+ else if (isRole)
57
+ features.push(...(rolesToFeatures(company, [key])))
58
+ }
59
+ })
60
+ return features
61
+ }
62
+
63
+
64
+
65
+
66
+
67
+
68
+ const getAllowedFeatures = (company, roles) => {
69
+ return [...new Set(rolesToFeatures(company, roles))]
70
+ }
71
+
72
+ const check = async (company, user, feature, req) => {
73
+ let roles = await getRoles(company, user, req)
74
+ return roles.includes(feature)
75
+ }
76
+
77
+ export const checkRequest = async (req) => {
78
+ return await check(req.body.company, req.body.user, req.body.feature, req)
79
+ }
80
+
81
+
82
+
83
+
84
+ const getRoles = async (company, user, req) => {
85
+ let staff = (await Dip.query(req.meta, "staff", { user: user }))[0]
86
+ let roles = (await Dip.query(req.meta, "privileges.staff", { _id: staff._id }))[0].items.map(item => item._id)
87
+ return getAllowedFeatures(company, roles)
88
+ }
89
+
90
+
91
+
92
+
93
+
package/libs/session.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import jwt from 'jsonwebtoken'
2
+ import * as Privilege from './privileges.js'
2
3
  let app
3
4
 
4
5
 
@@ -15,8 +16,9 @@ export const start = (expressApp, allowedUrls) => {
15
16
  urlsAllowed = ["/refreshToken", "/login"].concat(allowedUrls ?? [])
16
17
  app = expressApp
17
18
 
19
+ Privilege.start()
18
20
 
19
- app.use((req, res, next) => {
21
+ app.use(async (req, res, next) => {
20
22
 
21
23
  // return next() // Disable session
22
24
 
@@ -28,7 +30,7 @@ export const start = (expressApp, allowedUrls) => {
28
30
  const service = req.header('SERVICE'); // Case insensitive search
29
31
  if (service && jwt.verify(token, DEPLOY_TOKEN_SECRET))
30
32
  return next()
31
- else if (jwt.verify(token, ACCESS_TOKEN_SECRET))
33
+ else if (jwt.verify(token, ACCESS_TOKEN_SECRET) && (process.env.GRANT_FULL_ACCESS || await Privilege.checkRequest(req)))
32
34
  return next()
33
35
  throw null;
34
36
  } catch (error) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "corebasic",
3
3
  "type": "module",
4
- "version": "1.0.102",
4
+ "version": "1.0.104",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "scripts": {