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 +11 -7
- package/libs/dip.js +9 -7
- package/libs/elabase.js +34 -4
- package/libs/features.js +13 -10
- package/libs/messaging.js +7 -2
- package/libs/privileges.js +93 -0
- package/libs/session.js +4 -2
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
61
|
-
await
|
|
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
|
-
//
|
|
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
|
-
|
|
21
|
+
meta: undefined,
|
|
22
|
+
new: (async function (meta, txn, blank) {
|
|
22
23
|
this.txn = txn
|
|
23
|
-
|
|
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 (
|
|
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 = { ...
|
|
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}`,
|
|
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
|
|
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
|
-
|
|
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) {
|