@es-labs/jslib 0.0.1
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/CHANGELOG.md +4 -0
- package/README.md +42 -0
- package/__test__/services.test.js +32 -0
- package/auth/index.js +226 -0
- package/auth/keyv.js +23 -0
- package/auth/knex.js +29 -0
- package/auth/redis.js +23 -0
- package/comms/email.js +123 -0
- package/comms/nexmo.js +44 -0
- package/comms/telegram.js +43 -0
- package/comms/telegram2/inbound.js +314 -0
- package/comms/telegram2/outbound.js +574 -0
- package/comms/webpush.js +60 -0
- package/config.js +37 -0
- package/express/controller/auth/oauth.js +39 -0
- package/express/controller/auth/oidc.js +87 -0
- package/express/controller/auth/own.js +100 -0
- package/express/controller/auth/saml.js +74 -0
- package/express/upload.js +48 -0
- package/index.js +1 -0
- package/iso/README.md +4 -0
- package/iso/__tests__/csv-utils.spec.js +128 -0
- package/iso/__tests__/datetime.spec.js +101 -0
- package/iso/__tests__/fetch.spec.js +270 -0
- package/iso/csv-utils.js +206 -0
- package/iso/datetime.js +103 -0
- package/iso/fetch.js +129 -0
- package/iso/fetch2.js +180 -0
- package/iso/log-filter.js +17 -0
- package/iso/sleep.js +6 -0
- package/iso/ws.js +63 -0
- package/node/oss-files/oss-uploader-client-fetch.js +258 -0
- package/node/oss-files/oss-uploader-client-fetch.md +31 -0
- package/node/oss-files/oss-uploader-client.js +219 -0
- package/node/oss-files/oss-uploader-server.js +199 -0
- package/node/oss-files/oss-uploader-usage.js +121 -0
- package/node/oss-files/oss-uploader-usage.md +34 -0
- package/node/oss-files/s3-uploader-client.js +217 -0
- package/node/oss-files/s3-uploader-server.js +123 -0
- package/node/oss-files/s3-uploader-usage.js +77 -0
- package/node/oss-files/s3-uploader-usage.md +34 -0
- package/package.json +53 -0
- package/packageInfo.js +9 -0
- package/services/ali.js +279 -0
- package/services/aws.js +194 -0
- package/services/db/__tests__/keyv.spec.js +31 -0
- package/services/db/keyv.js +14 -0
- package/services/db/knex.js +67 -0
- package/services/db/redis.js +51 -0
- package/services/index.js +57 -0
- package/services/mq/README.md +8 -0
- package/services/websocket.js +139 -0
- package/t4t/README.md +1 -0
- package/traps.js +20 -0
- package/utils/__tests__/aes.spec.js +52 -0
- package/utils/aes.js +23 -0
- package/web/UI.md +71 -0
- package/web/bwc-autocomplete.js +211 -0
- package/web/bwc-combobox.js +343 -0
- package/web/bwc-fileupload.js +87 -0
- package/web/bwc-loading-overlay.js +54 -0
- package/web/bwc-t4t-form.js +511 -0
- package/web/bwc-table.js +756 -0
- package/web/fetch.js +129 -0
- package/web/i18n.js +24 -0
- package/web/idle.js +49 -0
- package/web/parse-jwt.js +15 -0
- package/web/pwa.js +84 -0
- package/web/sign-pad.js +164 -0
- package/web/t4t-fe.js +164 -0
- package/web/util.js +126 -0
- package/web/web-cam.js +182 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
## Description
|
|
2
|
+
|
|
3
|
+
Shared Javascript ES Module library for Javascript applications (node, express, web, isomorphic)
|
|
4
|
+
|
|
5
|
+
- web
|
|
6
|
+
- node
|
|
7
|
+
- iso
|
|
8
|
+
- express
|
|
9
|
+
- auth: authorization
|
|
10
|
+
- comms: for communications
|
|
11
|
+
- express: for express JS
|
|
12
|
+
- services: various services
|
|
13
|
+
- db:
|
|
14
|
+
- mysql
|
|
15
|
+
- postgres
|
|
16
|
+
- mq: TODO
|
|
17
|
+
- others:
|
|
18
|
+
- keyv
|
|
19
|
+
- redis/valkey
|
|
20
|
+
- webpush
|
|
21
|
+
- websocket
|
|
22
|
+
- cloud:
|
|
23
|
+
- aliyun:
|
|
24
|
+
- bucket (oss)
|
|
25
|
+
- aws:
|
|
26
|
+
- bucket (s3)
|
|
27
|
+
- t4t: table editor
|
|
28
|
+
- utils: various utilities
|
|
29
|
+
- aes: encrypt & decrypt
|
|
30
|
+
- config.js: config loader
|
|
31
|
+
- package.json
|
|
32
|
+
- packageInfo.js: some package information for now
|
|
33
|
+
- README.md
|
|
34
|
+
- traps.js: error trapping
|
|
35
|
+
|
|
36
|
+
Refer to [https://github.com/es-labs/express-template]() project for usage
|
|
37
|
+
|
|
38
|
+
## TODO
|
|
39
|
+
|
|
40
|
+
- setup github actions to auto increment package json version for `libs/node` & `libs/esm` when deploying to npm
|
|
41
|
+
- https://aboutbits.it/blog/2021-03-11-using-github-actions-to-perfom-npm-version-increment
|
|
42
|
+
- setup unit tests
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import '@express-template/common/env' // setup env vars
|
|
2
|
+
import { describe, it, before, after } from 'node:test';
|
|
3
|
+
import assert from 'node:assert';
|
|
4
|
+
import StoreKnex from '@es-labs/node/services/db/knex.js';
|
|
5
|
+
|
|
6
|
+
let sqldb;
|
|
7
|
+
|
|
8
|
+
const RUN_TEST = false;
|
|
9
|
+
if (RUN_TEST) {
|
|
10
|
+
before(async () => {
|
|
11
|
+
sqldb = new StoreKnex();
|
|
12
|
+
await sqldb.open();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
after(async () => {
|
|
16
|
+
await sqldb.close();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('Test Services', () => {
|
|
20
|
+
it.skip('Test Knex', async () => {
|
|
21
|
+
let knex = sqldb.get();
|
|
22
|
+
const rv = (await knex('users').where({ username: 'ais-one' }).first()).githubId;
|
|
23
|
+
assert.strictEqual(rv, 4284574);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('Services Test', () => {
|
|
28
|
+
it.skip('should pass', () => {
|
|
29
|
+
assert.strictEqual(true, true);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
package/auth/index.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import bcrypt from 'bcryptjs'
|
|
2
|
+
import jwt from 'jsonwebtoken'
|
|
3
|
+
import * as keyv from './keyv.js'
|
|
4
|
+
import * as knex from './knex.js'
|
|
5
|
+
import * as redis from './redis.js'
|
|
6
|
+
|
|
7
|
+
//NOSONAR import uuid from 'uuid/v4'
|
|
8
|
+
//NOSONAR import qrcode from 'qrcode'
|
|
9
|
+
|
|
10
|
+
let setRefreshToken, getRefreshToken, revokeRefreshToken,
|
|
11
|
+
setRefreshTokenStoreName, setTokenService, setUserService,
|
|
12
|
+
findUser, updateUser,
|
|
13
|
+
setAuthUserStoreName
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
COOKIE_HTTPONLY, COOKIE_SAMESITE, COOKIE_SECURE, COOKIE_MAXAGE, COOKIE_DOMAIN,
|
|
17
|
+
AUTH_REFRESH_URL, AUTH_USER_FIELD_LOGIN, AUTH_USER_FIELD_PASSWORD, AUTH_USER_FIELD_GAKEY, AUTH_USER_FIELD_ID_FOR_JWT, AUTH_USER_FIELDS_JWT_PAYLOAD = '',
|
|
18
|
+
JWT_REFRESH_STORE='keyv',
|
|
19
|
+
AUTH_USER_STORE,
|
|
20
|
+
AUTH_USER_STORE_NAME,
|
|
21
|
+
JWT_REFRESH_STORE_NAME,
|
|
22
|
+
|
|
23
|
+
USE_OTP,
|
|
24
|
+
JWT_ALG, JWT_EXPIRY, JWT_REFRESH_EXPIRY,
|
|
25
|
+
JWT_PRIVATE_KEY, JWT_CERTIFICATE, JWT_REFRESH_PRIVATE_KEY, JWT_REFRESH_CERTIFICATE, JWT_SECRET, JWT_REFRESH_SECRET,
|
|
26
|
+
JWT_ALLOW_INSECURE_KEY_SIZES
|
|
27
|
+
} = process.env
|
|
28
|
+
|
|
29
|
+
const authFns = { // rename to authFns
|
|
30
|
+
findUser: null,
|
|
31
|
+
updateUser: null,
|
|
32
|
+
revokeRefreshToken: null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// const services = (await import('@es-labs/node/services')).default;
|
|
36
|
+
// const authService = (await import('@es-labs/node/auth')).default;
|
|
37
|
+
|
|
38
|
+
const store = {
|
|
39
|
+
keyv,
|
|
40
|
+
knex,
|
|
41
|
+
redis
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const setup = (tokenService, userService) => {
|
|
45
|
+
//NOSONAR ({ } = process.env);
|
|
46
|
+
// ({ setRefreshToken, getRefreshToken, revokeRefreshToken, setRefreshTokenStoreName, setTokenService } = require('./' + JWT_REFRESH_STORE)); // keyv, redis, knex
|
|
47
|
+
// ({ findUser, updateUser, setAuthUserStoreName, setUserService } = require('./' + AUTH_USER_STORE)); // knex
|
|
48
|
+
({ setRefreshToken, getRefreshToken, revokeRefreshToken, setRefreshTokenStoreName, setTokenService } = store[JWT_REFRESH_STORE]); // keyv, redis, knex
|
|
49
|
+
({ findUser, updateUser, setAuthUserStoreName, setUserService } = store[AUTH_USER_STORE]); // knex
|
|
50
|
+
authFns.findUser = findUser
|
|
51
|
+
authFns.updateUser = updateUser
|
|
52
|
+
authFns.revokeRefreshToken = revokeRefreshToken
|
|
53
|
+
if (setTokenService) setTokenService(tokenService)
|
|
54
|
+
if (setUserService) setUserService(userService)
|
|
55
|
+
if (setRefreshTokenStoreName) setRefreshTokenStoreName(JWT_REFRESH_STORE_NAME)
|
|
56
|
+
if (setAuthUserStoreName) setAuthUserStoreName(AUTH_USER_STORE_NAME)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// SameSite=None; must use with Secure;
|
|
60
|
+
// may need to restart browser, TODO set Max-Age, ALTERNATE use res.cookie, Signed?
|
|
61
|
+
const httpOnlyCookie = () => `HttpOnly;SameSite=${COOKIE_SAMESITE};`
|
|
62
|
+
+ (COOKIE_SECURE ? 'Secure;':'')
|
|
63
|
+
+ (COOKIE_MAXAGE ? 'MaxAge='+COOKIE_MAXAGE+';':'')
|
|
64
|
+
+ (COOKIE_DOMAIN ? 'domain='+COOKIE_DOMAIN+';':'')
|
|
65
|
+
|
|
66
|
+
//NOSONAR algorithm
|
|
67
|
+
// expiresIn
|
|
68
|
+
// issuer = 'Mysoft corp'
|
|
69
|
+
// subject = 'some@user.com'
|
|
70
|
+
// audience = 'http://mysoftcorp.in'
|
|
71
|
+
// ip
|
|
72
|
+
// We implement stateful refresh_token not stateless
|
|
73
|
+
|
|
74
|
+
//NOSONAR
|
|
75
|
+
// mode: sign, verify
|
|
76
|
+
// type: access, refresh
|
|
77
|
+
const getSecret = (mode, type) => {
|
|
78
|
+
if (JWT_ALG.substring(0,2) === 'RS') {
|
|
79
|
+
if (mode === 'sign') {
|
|
80
|
+
return type === 'refresh' ? JWT_REFRESH_PRIVATE_KEY : JWT_PRIVATE_KEY
|
|
81
|
+
} else {
|
|
82
|
+
return type === 'refresh' ? JWT_REFRESH_CERTIFICATE : JWT_CERTIFICATE
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return type === 'refresh' ? JWT_REFRESH_SECRET : JWT_SECRET
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// should use:
|
|
89
|
+
// sub - for user id (access_token & refresh_token)
|
|
90
|
+
// groups - for user groups (access_token only)
|
|
91
|
+
// all other user related information sent on initial login and stored using local storage
|
|
92
|
+
// do not catch exception here, let functions above handle
|
|
93
|
+
const createToken = async (user) => { // Create a tokens & data from user
|
|
94
|
+
const user_meta = { }
|
|
95
|
+
const options = { }
|
|
96
|
+
|
|
97
|
+
const id = user[AUTH_USER_FIELD_ID_FOR_JWT]
|
|
98
|
+
|
|
99
|
+
if (!id) throw Error('User ID Not Found')
|
|
100
|
+
if (user.revoked) throw Error('User Revoked')
|
|
101
|
+
|
|
102
|
+
const groups = user.groups
|
|
103
|
+
|
|
104
|
+
const keys = AUTH_USER_FIELDS_JWT_PAYLOAD.split(',')
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
if (key && user[key] !== undefined) user_meta[key] = user[key]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
options.allowInsecureKeySizes = !!JWT_ALLOW_INSECURE_KEY_SIZES
|
|
110
|
+
options.algorithm = JWT_ALG
|
|
111
|
+
options.expiresIn = JWT_EXPIRY
|
|
112
|
+
const access_token = jwt.sign({ id, groups }, getSecret('sign', 'access'), options)
|
|
113
|
+
|
|
114
|
+
options.expiresIn = JWT_REFRESH_EXPIRY
|
|
115
|
+
const refresh_token = jwt.sign({ id }, getSecret('sign', 'refresh'), options) // store only ID in refresh token?
|
|
116
|
+
await setRefreshToken(id, refresh_token) // store in DB or Cache
|
|
117
|
+
return {
|
|
118
|
+
access_token,
|
|
119
|
+
refresh_token,
|
|
120
|
+
user_meta
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const setTokensToHeader = (res, {access_token, refresh_token}) => {
|
|
125
|
+
const _access_token = `Bearer ${access_token}`
|
|
126
|
+
if (COOKIE_HTTPONLY) {
|
|
127
|
+
res.setHeader('Set-Cookie', [
|
|
128
|
+
`Authorization=${_access_token};Path=/;`+ httpOnlyCookie(),
|
|
129
|
+
`refresh_token=${refresh_token};Path=${AUTH_REFRESH_URL};`+ httpOnlyCookie() // send only if path contains refresh
|
|
130
|
+
])
|
|
131
|
+
} else {
|
|
132
|
+
res.setHeader('Authorization', `${_access_token}`)
|
|
133
|
+
res.setHeader('refresh_token', `${refresh_token}`)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const authUser = async (req, res, next) => {
|
|
138
|
+
// console.log('auth express', req.baseUrl, req.path, req.cookies, req.signedCookies)
|
|
139
|
+
let access_token = null
|
|
140
|
+
try {
|
|
141
|
+
let tmp = req.cookies?.Authorization || req.header('Authorization') || req.query?.Authorization
|
|
142
|
+
access_token = tmp.split(' ')[1]
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return res.status(401).json({ message: 'Token Format Error' })
|
|
145
|
+
}
|
|
146
|
+
if (access_token) {
|
|
147
|
+
try {
|
|
148
|
+
let access_result = jwt.verify(access_token, getSecret('verify', 'access'), { algorithm: [JWT_ALG] }) // and options
|
|
149
|
+
if (access_result) {
|
|
150
|
+
req.decoded = access_result
|
|
151
|
+
return next()
|
|
152
|
+
} else {
|
|
153
|
+
return res.status(401).json({ message: 'Access Error' })
|
|
154
|
+
}
|
|
155
|
+
} catch (e) {
|
|
156
|
+
if (e.name === 'TokenExpiredError') {
|
|
157
|
+
return res.status(401).json({ message: 'Token Expired Error' })
|
|
158
|
+
} else {
|
|
159
|
+
console.log('auth err', e.name)
|
|
160
|
+
return res.status(401).json({ message: 'Token Error' })
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
return res.status(401).json({ message: 'Token Missing' })
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const authRefresh = async (req, res) => { // get refresh token
|
|
169
|
+
try {
|
|
170
|
+
const refresh_token = req.cookies?.refresh_token || req.header('refresh_token') || req.query?.refresh_token // check refresh token & user - always stateful
|
|
171
|
+
const refresh_result = jwt.verify(refresh_token, getSecret('verify', 'refresh'), { algorithm: [JWT_ALG] }) // throw if expired or invalid
|
|
172
|
+
const { id } = refresh_result
|
|
173
|
+
let refreshToken = await getRefreshToken(id)
|
|
174
|
+
if (String(refreshToken) === String(refresh_token)) { // ok... generate new access token & refresh token?
|
|
175
|
+
const user = await findUser({ id })
|
|
176
|
+
const tokens = await createToken(user) // 5 minute expire for login
|
|
177
|
+
setTokensToHeader(res, tokens)
|
|
178
|
+
return res.status(200).json(tokens)
|
|
179
|
+
} else {
|
|
180
|
+
return res.status(401).json({ message: 'Refresh Token Error: Uncaught' })
|
|
181
|
+
}
|
|
182
|
+
} catch (err) { // use err instead of e (fix no-catch-shadow issue)
|
|
183
|
+
console.log('authRefresh', err)
|
|
184
|
+
return res.status(401).json({ message: 'Refresh Token Error' })
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export {
|
|
189
|
+
setup,
|
|
190
|
+
authFns,
|
|
191
|
+
// findUser, updateUser,
|
|
192
|
+
getSecret,
|
|
193
|
+
createToken,
|
|
194
|
+
setTokensToHeader,
|
|
195
|
+
authUser,
|
|
196
|
+
authRefresh,
|
|
197
|
+
bcrypt
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// do refresh token check from backend ?
|
|
201
|
+
/*
|
|
202
|
+
Signout across tabs
|
|
203
|
+
window.addEventListener('storage', this.syncLogout)
|
|
204
|
+
//....
|
|
205
|
+
syncLogout (event) {
|
|
206
|
+
if (event.key === 'logout') {
|
|
207
|
+
console.log('logged out from storage!')
|
|
208
|
+
Router.push('/login')
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function logout () {
|
|
212
|
+
inMemoryToken = null;
|
|
213
|
+
const url = 'http://localhost:3010/auth/logout'
|
|
214
|
+
const response = await fetch(url, { method: 'POST', credentials: 'include', })
|
|
215
|
+
// to support logging out from all windows
|
|
216
|
+
window.localStorage.setItem('logout', Date.now())
|
|
217
|
+
}
|
|
218
|
+
*/
|
|
219
|
+
|
|
220
|
+
// The user logs in with a login API call.
|
|
221
|
+
// Server generates JWT Token and refresh_token
|
|
222
|
+
// Server sets a HttpOnly cookie with refresh_token. jwt_token and jwt_token_expiry are returned back to the client as a JSON payload.
|
|
223
|
+
// The jwt_token is stored in memory.
|
|
224
|
+
// A countdown to a future silent refresh is started based on jwt_token_expiry
|
|
225
|
+
|
|
226
|
+
// https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/
|
package/auth/keyv.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
let keyv
|
|
2
|
+
const setTokenService = (service) => keyv = service
|
|
3
|
+
const setRefreshToken = async (id, refresh_token) => keyv.set(id, refresh_token)
|
|
4
|
+
const getRefreshToken = async (id) => keyv.get(id)
|
|
5
|
+
const revokeRefreshToken = async(id) => keyv.delete(id)
|
|
6
|
+
|
|
7
|
+
const setUserService = () => {}
|
|
8
|
+
const setRefreshTokenStoreName = () => {}
|
|
9
|
+
const setAuthUserStoreName = () => {}
|
|
10
|
+
const findUser = () => {}
|
|
11
|
+
const updateUser = () => {}
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
setTokenService,
|
|
15
|
+
setUserService,
|
|
16
|
+
setRefreshTokenStoreName,
|
|
17
|
+
setAuthUserStoreName,
|
|
18
|
+
setRefreshToken,
|
|
19
|
+
getRefreshToken,
|
|
20
|
+
revokeRefreshToken,
|
|
21
|
+
findUser,
|
|
22
|
+
updateUser
|
|
23
|
+
}
|
package/auth/knex.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
let knex
|
|
2
|
+
let JWT_REFRESH_STORE_NAME
|
|
3
|
+
let AUTH_USER_STORE_NAME
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const setTokenService = (service) => knex = service
|
|
7
|
+
const setUserService = (service) => knex = service
|
|
8
|
+
const setRefreshTokenStoreName = (name) => JWT_REFRESH_STORE_NAME = name
|
|
9
|
+
const setAuthUserStoreName = (name) => AUTH_USER_STORE_NAME = name
|
|
10
|
+
|
|
11
|
+
// id field must be unique, upsert for PostgreSQL, MySQL, and SQLite only
|
|
12
|
+
const setRefreshToken = async (id, refresh_token) => knex(JWT_REFRESH_STORE_NAME).insert({ id, refresh_token }).onConflict('id').merge()
|
|
13
|
+
const getRefreshToken = async (id) => ( await knex(JWT_REFRESH_STORE_NAME).where({ id: id }).first() ).refresh_token
|
|
14
|
+
const revokeRefreshToken = async(id) => knex(JWT_REFRESH_STORE_NAME).where({ id: id }).delete()
|
|
15
|
+
|
|
16
|
+
const findUser = async (where) => knex(AUTH_USER_STORE_NAME).where(where).first()
|
|
17
|
+
const updateUser = async (where, payload) => knex(AUTH_USER_STORE_NAME).where(where).first().update(payload)
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
setTokenService,
|
|
21
|
+
setUserService,
|
|
22
|
+
setRefreshTokenStoreName,
|
|
23
|
+
setAuthUserStoreName,
|
|
24
|
+
setRefreshToken,
|
|
25
|
+
getRefreshToken,
|
|
26
|
+
revokeRefreshToken,
|
|
27
|
+
findUser,
|
|
28
|
+
updateUser
|
|
29
|
+
}
|
package/auth/redis.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
let redis
|
|
2
|
+
const setTokenService = (service) => redis = service
|
|
3
|
+
const setRefreshToken = async (id, refresh_token) => redis.set(id, refresh_token)
|
|
4
|
+
const getRefreshToken = async (id) => redis.get(id)
|
|
5
|
+
const revokeRefreshToken = async(id) => redis.del(id)
|
|
6
|
+
|
|
7
|
+
const setUserService = () => {}
|
|
8
|
+
const setRefreshTokenStoreName = () => {}
|
|
9
|
+
const setAuthUserStoreName = () => {}
|
|
10
|
+
const findUser = () => {}
|
|
11
|
+
const updateUser = () => {}
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
setTokenService,
|
|
15
|
+
setUserService,
|
|
16
|
+
setRefreshTokenStoreName,
|
|
17
|
+
setAuthUserStoreName,
|
|
18
|
+
setRefreshToken,
|
|
19
|
+
getRefreshToken,
|
|
20
|
+
revokeRefreshToken,
|
|
21
|
+
findUser,
|
|
22
|
+
updateUser
|
|
23
|
+
}
|
package/comms/email.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
|
|
3
|
+
const { SENDGRID_KEY, SENDGRID_SENDER_NAME, SENDGRID_SENDER_EMAIL, SENDGRID_TEMPLATE_ID, SENDGRID_URL = 'https://api.sendgrid.com/v3/mail/send' } = process.env
|
|
4
|
+
|
|
5
|
+
// generate random hash to prevent duplicate emails
|
|
6
|
+
const generateRandomHash = () => {
|
|
7
|
+
return crypto.createHash('sha256').update(new Date().toString()).digest('hex').slice(0, 15)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hit SendGrid API to send email
|
|
12
|
+
*
|
|
13
|
+
* Full documentation: https://docs.sendgrid.com/api-reference/mail-send/mail-send#body
|
|
14
|
+
*
|
|
15
|
+
* @param {Object} body needed parameters to send email
|
|
16
|
+
*/
|
|
17
|
+
const sendMail = async (body) => {
|
|
18
|
+
try {
|
|
19
|
+
const headers = {
|
|
20
|
+
Authorization: `Bearer ${SENDGRID_KEY}`,
|
|
21
|
+
'Content-Type': 'application/json'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const response = await fetch(SENDGRID_URL, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers,
|
|
27
|
+
body: JSON.stringify(body)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
return response
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw error
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Send html email using SendGrid
|
|
38
|
+
*
|
|
39
|
+
* @param {String} to a recepient email address
|
|
40
|
+
* @param {String} subject the subject of the email
|
|
41
|
+
* @param {String} content concatenated of the email content in html format
|
|
42
|
+
*
|
|
43
|
+
* @example sendDynamicEmail('user@mail.com', 'Test Email', '<p>Hello world!</p>')
|
|
44
|
+
*/
|
|
45
|
+
export const sendEmail = async (to, subject, content) => {
|
|
46
|
+
try {
|
|
47
|
+
if (!SENDGRID_KEY) throw new Error('SENDGRID_KEY is not defined')
|
|
48
|
+
if (!SENDGRID_SENDER_NAME) throw new Error('SENDGRID_SENDER_NAME is not defined')
|
|
49
|
+
if (!SENDGRID_SENDER_EMAIL) throw new Error('SENDGRID_SENDER_EMAIL is not defined')
|
|
50
|
+
|
|
51
|
+
// generate random hash to prevent duplicate emails
|
|
52
|
+
const hash = generateRandomHash()
|
|
53
|
+
|
|
54
|
+
// request body
|
|
55
|
+
const body = {
|
|
56
|
+
personalizations: [{ to: [{ email: to }] }],
|
|
57
|
+
from: {
|
|
58
|
+
name: SENDGRID_SENDER_NAME,
|
|
59
|
+
email: SENDGRID_SENDER_EMAIL
|
|
60
|
+
},
|
|
61
|
+
subject,
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: 'text/html',
|
|
65
|
+
value: content
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
headers: {
|
|
69
|
+
'X-Entity-Ref-ID': hash
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const response = await sendMail(body)
|
|
74
|
+
return response
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw error
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {String} to a recepient email address
|
|
82
|
+
* @param {String} subject the subject of the email
|
|
83
|
+
* @param {String} content concatenated of the email content in html format
|
|
84
|
+
*
|
|
85
|
+
* @example sendDynamicEmail('user@mail.com', 'Test Email', '<p>Hello world!</p>')
|
|
86
|
+
*/
|
|
87
|
+
export const sendDynamicEmail = async (to, subject, content) => {
|
|
88
|
+
try {
|
|
89
|
+
if (!SENDGRID_KEY) throw new Error('SENDGRID_KEY is not defined')
|
|
90
|
+
if (!SENDGRID_SENDER_NAME) throw new Error('SENDGRID_SENDER_NAME is not defined')
|
|
91
|
+
if (!SENDGRID_SENDER_EMAIL) throw new Error('SENDGRID_SENDER_EMAIL is not defined')
|
|
92
|
+
if (!SENDGRID_TEMPLATE_ID) throw new Error('SENDGRID_TEMPLATE_ID is not defined')
|
|
93
|
+
|
|
94
|
+
// generate random hash to prevent duplicate emails
|
|
95
|
+
const hash = generateRandomHash()
|
|
96
|
+
|
|
97
|
+
// request body
|
|
98
|
+
const body = {
|
|
99
|
+
personalizations: [
|
|
100
|
+
{
|
|
101
|
+
to: [{ email: to }],
|
|
102
|
+
dynamic_template_data: {
|
|
103
|
+
subject: subject,
|
|
104
|
+
content: content
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
from: {
|
|
109
|
+
name: SENDGRID_SENDER_NAME,
|
|
110
|
+
email: SENDGRID_SENDER_EMAIL
|
|
111
|
+
},
|
|
112
|
+
template_id: SENDGRID_TEMPLATE_ID,
|
|
113
|
+
headers: {
|
|
114
|
+
'X-Entity-Ref-ID': hash
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const response = await sendMail(body)
|
|
119
|
+
return response
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw error
|
|
122
|
+
}
|
|
123
|
+
}
|
package/comms/nexmo.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
|
|
3
|
+
//NOSONAR
|
|
4
|
+
// nexmo.isms('6596935500', 'Blah ' + new Date())
|
|
5
|
+
// nexmo.ismsSend('6596935500', 'Blah ' + new Date())
|
|
6
|
+
const { NEXMO_KEY, NEXMO_SECRET, NEXMO_SENDER = 'SMSnotice' } = process.env
|
|
7
|
+
|
|
8
|
+
// sms = 6511112222
|
|
9
|
+
// one at a time...
|
|
10
|
+
export const send = async (sms, message, from) => {
|
|
11
|
+
try {
|
|
12
|
+
if (!from) from = NEXMO_SENDER
|
|
13
|
+
if (sms && message) {
|
|
14
|
+
return await fetch(`https://rest.nexmo.com/sms/json?api_key=${NEXMO_KEY}&api_secret=${NEXMO_SECRET}&to=${sms}&from=${from}&text=${message}`)
|
|
15
|
+
}
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.log('send', e.toString())
|
|
18
|
+
}
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// sms = 6511112222
|
|
23
|
+
// one at a time...
|
|
24
|
+
export const ismsSend = async (sms, message, from) => {
|
|
25
|
+
const url = 'https://sms.era.sg/isms_mt.php?'
|
|
26
|
+
try {
|
|
27
|
+
if (sms && message) {
|
|
28
|
+
const options = {
|
|
29
|
+
params: {
|
|
30
|
+
uid: NEXMO_KEY,
|
|
31
|
+
pwd: crypto.createHash('md5').update( NEXMO_SECRET ).digest('hex'),
|
|
32
|
+
dnr: sms,
|
|
33
|
+
snr: from || NEXMO_SENDER,
|
|
34
|
+
msg: message,
|
|
35
|
+
split: 5
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return await fetch(url, options)
|
|
39
|
+
}
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.log('ismsSend', e.toString())
|
|
42
|
+
}
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Setting up webhook: https://api.telegram.org/bot{my_bot_token}/setWebhook?url={url_to_send_updates_to}
|
|
2
|
+
// Querying webhook: https://api.telegram.org/bot{my_bot_token}/getWebhookInfo
|
|
3
|
+
/*
|
|
4
|
+
{
|
|
5
|
+
update_id: 165679876,
|
|
6
|
+
message: {
|
|
7
|
+
message_id: 3,
|
|
8
|
+
from: {
|
|
9
|
+
id: 123456789,
|
|
10
|
+
is_bot: false,
|
|
11
|
+
first_name: 'A',
|
|
12
|
+
last_name: 'G',
|
|
13
|
+
username: 'aaronjxz',
|
|
14
|
+
language_code: 'en'
|
|
15
|
+
},
|
|
16
|
+
chat: {
|
|
17
|
+
id: 123456789,
|
|
18
|
+
first_name: 'A',
|
|
19
|
+
last_name: 'G',
|
|
20
|
+
username: 'aaronjxz',
|
|
21
|
+
type: 'private'
|
|
22
|
+
},
|
|
23
|
+
date: 1694045266,
|
|
24
|
+
text: 'test'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
const { TELEGRAM_API_KEY, TELEGRAM_CHANNEL_ID } = process.env
|
|
31
|
+
|
|
32
|
+
export const sendMsg = async (text, chatId = '') => {
|
|
33
|
+
try {
|
|
34
|
+
// console.log('text, chatId', text, chatId)
|
|
35
|
+
//NOSONAR { id, date, pts, seq }
|
|
36
|
+
if (!chatId) chatId = TELEGRAM_CHANNEL_ID // channel message
|
|
37
|
+
return await fetch(`https://api.telegram.org/bot${TELEGRAM_API_KEY}/sendMessage?chat_id=${chatId}&text=${text}`)
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return { err: e.toString() }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const sendChannelMsg = async (text) => await sendMsg(text) // TODEPRECATE
|