w-web-api 1.0.75 → 1.0.76

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.
@@ -45,936 +45,936 @@
45
45
 
46
46
  <section>
47
47
  <article>
48
- <pre class="prettyprint source linenums"><code>import path from 'path'
49
- import fs from 'fs'
50
- import get from 'lodash-es/get.js'
51
- import each from 'lodash-es/each.js'
52
- import map from 'lodash-es/map.js'
53
- import keys from 'lodash-es/keys.js'
54
- import j2o from 'wsemi/src/j2o.mjs'
55
- import iseobj from 'wsemi/src/iseobj.mjs'
56
- import isestr from 'wsemi/src/isestr.mjs'
57
- import isearr from 'wsemi/src/isearr.mjs'
58
- import ispint from 'wsemi/src/ispint.mjs'
59
- import isErr from 'wsemi/src/isErr.mjs'
60
- import isfun from 'wsemi/src/isfun.mjs'
61
- import ispm from 'wsemi/src/ispm.mjs'
62
- import cint from 'wsemi/src/cint.mjs'
63
- import strleft from 'wsemi/src/strleft.mjs'
64
- import strdelleft from 'wsemi/src/strdelleft.mjs'
65
- import strright from 'wsemi/src/strright.mjs'
66
- import strdelright from 'wsemi/src/strdelright.mjs'
67
- import pm2resolve from 'wsemi/src/pm2resolve.mjs'
68
- import b642str from 'wsemi/src/b642str.mjs'
69
- import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
70
- import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
71
- import fsIsFile from 'wsemi/src/fsIsFile.mjs'
72
- import replace from 'wsemi/src/replace.mjs'
73
- import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
74
- import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
75
- import procLang from './procLang.mjs'
76
- import procProxy from './procProxy.mjs'
77
- import procApis from './procApis.mjs'
78
- import staEvent from './staLogs/staEvent.mjs'
79
- import srLogInit from './srLog.mjs'
80
- import ds from '../src/schema/index.mjs'
81
-
82
-
83
- /**
84
- * API伺服器
85
- *
86
- * @class
87
- * @param {Function} WOrm 輸入資料庫ORM函數
88
- * @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
89
- * @param {String} db 輸入資料庫名稱字串
90
- * @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n',且輸入'y'時會複寫權限系統該使用者之'isAdmin'欄位值
91
- * @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
92
- * @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
93
- * @param {Object} [opt={}] 輸入設定物件,預設{}
94
- * @param {Integer} [opt.serverPort=11005] 輸入伺服器通訊port,預設11005
95
- * @param {Boolean} [opt.useCheckUser=false] 輸入是否檢查使用者資訊布林值,預設false
96
- * @param {Function} [opt.getUserById=null] 輸入當useCheckUser=true時依照使用者ID取得使用者資訊物件函數,預設null
97
- * @param {Boolean} [opt.useExcludeWhenNotAdmin=false] 輸入使用ORM的select方法時是否自動刪除數據內isActive欄位之布林值,預設false
98
- * @param {Object} [opt.webName={}] 輸入站台名稱物件,至少包含語系eng與cht鍵的名稱,預設{}
99
- * @param {Object} [opt.webDescription={}] 輸入站台描述物件,至少包含語系eng與cht鍵的名稱,預設{}
100
- * @param {String} [opt.webLogo=''] 輸入站台logo字串,採base64格式,預設''
101
- * @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
102
- * @param {String} [opt.urlRedirect=''] 輸入錯誤時自動轉址字串,提供站台例如無法登入或驗證失敗時須自動轉址,預設''
103
- * @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
104
- * @example
105
- *
106
- * import WOrm from 'w-orm-lmdb/src/WOrmLmdb.mjs'
107
- * import WWebApi from './server/WWebApi.mjs'
108
- * import getSettings from './g.getSettings.mjs'
109
- *
110
- *
111
- * //st
112
- * let st = getSettings()
113
- *
114
- * let url = st.dbUrl
115
- * let db = st.dbName
116
- * let opt = {
117
- *
118
- * useCheckUser: false,
119
- * getUserById: null,
120
- * useExcludeWhenNotAdmin: false,
121
- *
122
- * serverPort: 11005,
123
- * subfolder: '', //mapi
124
- * urlRedirect: 'https://www.google.com/', //本機測試時得先編譯, 再瀏覽: http://localhost:11005/
125
- *
126
- * webName: {
127
- * 'eng': 'API Service',
128
- * 'cht': 'API管理系統',
129
- * },
130
- * webDescription: {
131
- * 'eng': 'A web service package as methods to send requests to and receive responses from an API.',
132
- * 'cht': 'A web service package as methods to send requests to and receive responses from an API.',
133
- * },
134
- * webLogo: 'data:image/svg+xml;base64,...',
135
- *
136
- * }
137
- *
138
- * let getUserByToken = async (token) => {
139
- * // return {} //測試無法登入
140
- * if (token === '{token-for-application}') { //提供外部應用系統作為存取使用者
141
- * return {
142
- * id: 'id-for-application',
143
- * name: 'application',
144
- * email: 'application@example.com',
145
- * isAdmin: 'y',
146
- * }
147
- * }
148
- * if (token === 'sys') { //開發階段w-ui-loginout自動給予browser使用者(且位於localhost)的token為sys
149
- * return {
150
- * id: 'id-for-admin',
151
- * name: '測試者',
152
- * email: 'admin@example.com',
153
- * isAdmin: 'y',
154
- * }
155
- * }
156
- * console.log('invalid token', token)
157
- * console.log('於生產環境時得加入SSO等驗證token機制')
158
- * return {}
159
- * }
160
- *
161
- * let verifyClientUser = (user, from) => {
162
- * console.log('verifyClientUser/user', user)
163
- * // return false //測試無法登入
164
- * console.log('於生產環境時得加入限制瀏覽器使用者身份機制')
165
- * return user.isAdmin === 'y' //測試僅系統管理者使用
166
- * }
167
- *
168
- * let verifyAppUser = (user, from) => {
169
- * console.log('verifyAppUser/user', user)
170
- * // return false //測試無法登入
171
- * console.log('於生產環境時得加入限制應用程式使用者身份機制')
172
- * return user.isAdmin === 'y' //測試僅系統管理者使用
173
- * }
174
- *
175
- * //WWebApi
176
- * let instWWebApi = WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, opt)
177
- *
178
- * instWWebApi.on('error', (err) => {
179
- * console.log(err)
180
- * })
181
- *
182
- */
183
- function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, opt = {}) {
184
- let instWServHapiServer = null
185
-
186
-
187
- //check WOrm
188
- if (!isfun(WOrm)) {
189
- console.log('invalid WOrm', WOrm)
190
- throw new Error('invalid WOrm')
191
- }
192
-
193
-
194
- //check url
195
- if (!isestr(url)) {
196
- console.log('invalid url', url)
197
- throw new Error('invalid url')
198
- }
199
-
200
-
201
- //check db
202
- if (!isestr(db)) {
203
- console.log('invalid db', db)
204
- throw new Error('invalid db')
205
- }
206
-
207
-
208
- //check getUserByToken
209
- if (!isfun(getUserByToken)) {
210
- console.log('invalid getUserByToken', getUserByToken)
211
- throw new Error('invalid getUserByToken')
212
- }
213
-
214
-
215
- //check verifyClientUser
216
- if (!isfun(verifyClientUser)) {
217
- console.log('invalid verifyClientUser', verifyClientUser)
218
- throw new Error('invalid verifyClientUser')
219
- }
220
-
221
-
222
- //check verifyAppUser
223
- if (!isfun(verifyAppUser)) {
224
- console.log('invalid verifyAppUser', verifyAppUser)
225
- throw new Error('invalid verifyAppUser')
226
- }
227
-
228
-
229
- //serverPort
230
- let serverPort = get(opt, 'serverPort')
231
- if (!ispint(serverPort)) {
232
- serverPort = 11005
233
- }
234
- serverPort = cint(serverPort)
235
-
236
-
237
- //useCheckUser
238
- let useCheckUser = get(opt, 'useCheckUser', false)
239
-
240
-
241
- //getUserById
242
- let getUserById = get(opt, 'getUserById', null)
243
-
244
-
245
- //useExcludeWhenNotAdmin
246
- let useExcludeWhenNotAdmin = get(opt, 'useExcludeWhenNotAdmin', false)
247
-
248
-
249
- //webName
250
- let webName = get(opt, 'webName', {})
251
-
252
-
253
- //webDescription
254
- let webDescription = get(opt, 'webDescription', {})
255
-
256
-
257
- //webLogo
258
- let webLogo = get(opt, 'webLogo', '')
259
-
260
-
261
- //subfolder
262
- let subfolder = get(opt, 'subfolder', '')
263
- if (isestr(subfolder)) {
264
- if (strright(subfolder, 1) === '/') { //右邊不需要給「/」
265
- subfolder = strdelright(subfolder, 1)
266
- }
267
- if (strleft(subfolder, 1) !== '/') { //左邊需要給「/」
268
- subfolder = `/${subfolder}`
269
- }
270
- }
271
-
272
-
273
- //urlRedirect
274
- let urlRedirect = get(opt, 'urlRedirect', '')
275
-
276
-
277
- //params
278
- let showLanguage = get(opt, 'showLanguage', 'y')
279
- let language = get(opt, 'language', 'eng')
280
- let logFd = get(opt, 'logFd', './logs') //srLog 寫入 + staEvent 統計共用之 log 資料夾
281
-
282
-
283
- //kpLangExt
284
- let kpLangExt = get(opt, 'kpLangExt', {})
285
-
286
-
287
- //kpLang
288
- let kpLang = procLang({ kpLangExt, webName, webDescription })
289
-
290
-
291
- //WServOrm
292
- let optWServOrm = {
293
- useCheckUser,
294
- getUserById,
295
- useExcludeWhenNotAdmin,
296
- }
297
- let wp = {}
298
- try {
299
- wp = WServOrm(ds, WOrm, url, db, optWServOrm)
300
- }
301
- catch (err) {
302
- //ORM 為必要依賴:初始化失敗即 fail-fast,不以 wp={} 帶 undefined woItems/procOrm 殘缺啟動(與建構子其餘必要依賴一致)
303
- console.log('WServOrm init failed', err)
304
- throw err
305
- }
306
- let { woItems, procOrm } = wp
307
-
308
-
309
- //srLog(後端 log;錯誤一律 log err-key 供識別,對齊 w-web-sso)
310
- let srLog = srLogInit(opt)
311
-
312
- //maskTok:debug console.log 不印完整 token/authorization(憑證),僅前 4 碼 + ***,避免憑證落入 stdout
313
- let maskTok = (t) => isestr(t) ? (strleft(t, 4) + '***') : t
314
-
315
- //kpFunExt 邊界統一處理:成功記 info(供 staEvent 統計呼叫頻率);catch 後端錯誤 → srLog 該 err-key →
316
- //原樣 re-reject(前端依 key 查自己 lang 顯示)。event 統一加 'kpfun-' 前綴 → 專用事件、與 api/verifyConn 區分。
317
- let wrapErrLog = (event, fn) => {
318
- let ev = 'kpfun-' + event
319
- return async (...args) => {
320
- try {
321
- let result = await fn(...args)
322
- srLog.info({ event: ev }) //成功也記 info
323
- return result
324
- }
325
- catch (err) {
326
- srLog.error({ event: ev, err })
327
- return Promise.reject(err)
328
- }
329
- }
330
- }
331
-
332
-
333
- //procProxy
334
- let { proxyRequest } = procProxy({ srLog })
335
-
336
-
337
- //procApis
338
- let { getApisList, saveApi, deleteApi } = procApis({ woItems, procOrm, ds })
339
-
340
-
341
- //getWebInfor
342
- let getWebInfor = (userId) => {
343
- return {
344
-
345
- // webName, //已併入kpLang
346
- // webDescription, //已併入kpLang
347
- webLogo,
348
-
349
- showLanguage,
350
- language,
351
- kpLang,
352
-
353
- }
354
- }
355
-
356
-
357
- //checkUser
358
- let checkUser = async(user) => {
359
- let id = get(user, 'id', '')
360
- if (!isestr(id)) {
361
- console.log('user', user)
362
- console.log('can not get the userId')
363
- return Promise.reject('errUserIdMissing')
364
- }
365
- let email = get(user, 'email', '')
366
- if (!isestr(email)) {
367
- console.log('user', user)
368
- console.log('can not get the email of user')
369
- return Promise.reject('errUserEmailMissing')
370
- }
371
- let name = get(user, 'name', '')
372
- if (!isestr(name)) {
373
- console.log('user', user)
374
- console.log('can not get userName')
375
- return Promise.reject('errUserNameMissing')
376
- }
377
- let isAdmin = get(user, 'isAdmin', '')
378
- if (isAdmin !== 'y' &amp;&amp; isAdmin !== 'n') {
379
- console.log('user', user)
380
- console.log('user.isAdmin is not y or n', user.isAdmin)
381
- console.log('can not get the role of user')
382
- return Promise.reject('errUserRoleMissing')
383
- }
384
- return true
385
- }
386
-
387
-
388
- //getTokenUser
389
- let getTokenUser = async (token) => {
390
-
391
- //getUserByToken
392
- let userSelf = getUserByToken(token)
393
- if (ispm(userSelf)) {
394
- userSelf = await userSelf
395
- }
396
-
397
- //check
398
- await checkUser(userSelf)
399
-
400
- return userSelf
401
- }
402
-
403
-
404
- //p
405
- let p = {
406
- checkToken: async(token) => {
407
-
408
- //getUserByToken
409
- let u = await getUserByToken(token)
410
- // console.log('u', u)
411
-
412
- //checkUser
413
- await checkUser(u)
414
-
415
- return true
416
- },
417
- }
418
-
419
-
420
- //getAndVerifyClientUser
421
- let getAndVerifyClientUser = async (token, from = '') => {
422
- //基於token查找client使用者, 會基於外部getUserByToken以及perm內部users進行查找並比對
423
-
424
- //getTokenUser
425
- let user = await getTokenUser(token)
426
-
427
- //verifyClientUser
428
- let b = verifyClientUser(user, from)
429
- if (ispm(b)) {
430
- b = await b
431
- }
432
-
433
- //check
434
- if (!b) {
435
- console.log('user', user)
436
- console.log(`user does not have permission`)
437
- return Promise.reject('errUserNoPermission')
438
- }
439
-
440
- srLog.info({ event: 'verifyClientUser', from, userId: get(user, 'id', '') }) //瀏覽使用者驗證通過
441
- return user
442
- }
443
-
444
-
445
- //getAndVerifyAppUser
446
- let getAndVerifyAppUser = async (token, from = '') => {
447
- //基於token查找app使用者, 會基於外部getUserByToken進行查找, 故只要getUserByToken提供使用者即可, 可支援getUserByToken給予針對應用程式所產生之虛擬使用者
448
-
449
- //user
450
- let user = null
451
- if (isestr(token)) {
452
- user = getUserByToken(token)
453
- if (ispm(user)) {
454
- user = await user
455
- }
456
- }
457
- // console.log('user', user)
458
-
459
- //check
460
- if (!iseobj(user)) {
461
- console.log(`token`, token)
462
- console.log(`can not find the user from token`)
463
- return Promise.reject('errUserNotFound')
464
- }
465
-
466
- //verifyAppUser
467
- let b = verifyAppUser(user, from)
468
- if (ispm(b)) {
469
- b = await b
470
- }
471
-
472
- //check
473
- if (!b) {
474
- console.log('user', user)
475
- console.log(`user does not have permission`)
476
- return Promise.reject('errUserNoPermission')
477
- }
478
-
479
- srLog.info({ event: 'verifyAppUser', from, userId: get(user, 'id', '') }) //應用程式使用者驗證通過
480
- return user
481
- }
482
-
483
-
484
- // //getAPIsList
485
- // let getAPIsList = async (query = {}) => {
486
- // let rs = await woItems.apis.select(query)
487
- // return rs
488
- // }
489
-
490
-
491
- //parsePayload
492
- let parsePayload = async (req) => {
493
-
494
- //payload
495
- let payload = get(req, 'payload')
496
-
497
- //to obj
498
- if (isestr(payload)) {
499
- payload = j2o(payload)
500
- }
501
-
502
- //check
503
- if (!iseobj(payload)) {
504
- console.log('payload', payload)
505
- console.log(`invalid payload in req`)
506
- return Promise.reject('errPayloadInvalid')
507
- }
508
-
509
- //group
510
- let group = get(payload, 'group', '')
511
-
512
- //check
513
- if (!isestr(group)) {
514
- console.log('payload', payload)
515
- console.log(`invalid group in payload`)
516
- return Promise.reject('errGroupInvalid')
517
- }
518
-
519
- //rows
520
- let rows = get(payload, 'rows', [])
521
-
522
- //check
523
- if (!isearr(rows)) {
524
- console.log('payload', payload)
525
- console.log(`invalid rows in payload`)
526
- return Promise.reject('errRowsInvalid')
527
- }
528
-
529
- //r
530
- let r = {
531
- group,
532
- rows,
533
- }
534
-
535
- return r
536
- }
537
-
538
-
539
- // //updateAPIs
540
- // let updateAPIs = async (inp = {}) => {
541
-
542
- // //spread inp
543
- // let { apis: rsApis } = inp
544
- // // console.log('group', group)
545
- // // console.log('rsApis', rsApis)
546
-
547
- // //save
548
- // let r = await woItems.apis.save(rsApis)
549
-
550
- // return r
551
- // }
552
-
553
-
554
- //syncAndReplaceTabs
555
- let syncAndReplaceTabs = async (userId, inp, keyTable) => {
556
-
557
- //group
558
- let group = get(inp, 'group', '')
559
- // console.log('group', group)
560
-
561
- //check
562
- if (!isestr(group)) {
563
- console.log('inp', inp)
564
- console.log('group', group)
565
- console.log(`invalid group`)
566
- return Promise.reject('errGroupInvalid')
567
- }
568
-
569
- //rows
570
- let rows = get(inp, 'rows', [])
571
- // console.log('rows', rows)
572
-
573
- //check
574
- if (!isearr(rows)) {
575
- console.log('inp', inp)
576
- console.log('keyTable', keyTable)
577
- return Promise.reject('errRowsInvalid')
578
- }
579
-
580
- //save from
581
- rows = map(rows, (v, k) => {
582
- // v.order = k + 1 //order大部分由外部給予, 不能複寫
583
- v.group = group
584
- return v
585
- })
586
-
587
- //偵測b64自動恢復
588
- if (keyTable === 'apis') {
589
- each(rows, (row, krow) => {
590
- // console.log(kapi, row.name)
591
- each(row, (v, k) => {
592
- // console.log(k, v)
593
- if (isestr(v)) {
594
- if (strleft(v, 7) === 'base64:') {
595
- // console.log(k, 'get base64')
596
- v = strdelleft(v, 7)
597
- v = b642str(v)
598
- // console.log(v)
599
- rows[krow][k] = v
600
- }
601
- }
602
- })
603
- })
604
- }
605
-
606
- //ltdtmapping
607
- rows = ltdtmapping(rows, ds[keyTable].keys)
608
- // console.log('ltdtmapping rows', rows)
609
-
610
- //delAll group
611
- await woItems[keyTable].delAll({ group })
612
-
613
- //insert
614
- // let r = await woItems[keyTable].insert(rows)
615
- let r = await procOrm(userId, keyTable, 'insert', rows) //須使用procOrm才有辦法自動給予相關欄位
616
- // console.log('r', r)
617
-
618
- return r
619
- }
620
-
621
-
622
- //pathStaticFiles
623
- let pathStaticFiles = 'dist'
624
- let npmPathStaticFiles = './node_modules/w-web-api/dist'
625
- if (fsIsFolder(npmPathStaticFiles)) {
626
- pathStaticFiles = npmPathStaticFiles
627
- }
628
- // console.log('pathStaticFiles', pathStaticFiles)
629
-
630
-
631
- //subfolder
632
- let fnEntryIn = 'index.tmp'
633
- let fnEntryOut = 'index.html'
634
- try {
635
- let fpEntryIn = path.resolve(pathStaticFiles, fnEntryIn)
636
- if (!fsIsFile(fpEntryIn)) {
637
- fpEntryIn = path.resolve(pathStaticFiles, fnEntryOut) //本機開發另使用html替代tmp
638
- }
639
- if (!fsIsFile(fpEntryIn)) {
640
- console.log('fpEntryIn', fpEntryIn)
641
- throw new Error(`invalid fpEntryIn`)
642
- }
643
- let fpEntryOut = path.resolve(pathStaticFiles, fnEntryOut)
644
- let c = fs.readFileSync(fpEntryIn, 'utf8')
645
- c = replace(c, '/mapi/', '{sfd}/') //方法同genEntry
646
- c = replace(c, '{sfd}', subfolder)
647
- c = replace(c, '{urlRedirect}', urlRedirect)
648
- c = replace(c, '{language}', language) //注入初始語系 → window.___pmwperm___.language(前端 getLang 之來源之一)
649
- fs.writeFileSync(fpEntryOut, c, 'utf8')
650
- }
651
- catch (err) {
652
- console.log(err)
653
- console.log(`can not generate ${fnEntryOut}`)
654
- }
655
-
656
-
657
- //apis
658
- let apis = [
659
-
660
- {
661
- method: 'GET',
662
- path: '/api/getUserByToken', //未登入主頁時需先檢測token, getUserByToken為w-ui-loginout預設值, 若要更改兩邊須同時修改
663
- handler: async function (req, res) {
664
- // console.log('getUserByToken', req)
665
-
666
- async function core() {
667
-
668
- //token
669
- let token = get(req, 'query.token', '')
670
-
671
- //check
672
- if (!isestr(token)) {
673
- console.log('req.query', get(req, 'query'))
674
- console.log('token', maskTok(token))
675
- console.log('[API]getUserByToken/check token: invalid token')
676
- return Promise.reject('errTokenNoPermission')
677
- }
678
-
679
- //getAndVerifyClientUser
680
- let user = await getAndVerifyClientUser(token, 'getUserByToken')
681
-
682
- //check
683
- if (!iseobj(user)) {
684
- console.log('token', maskTok(token))
685
- console.log('[API]getUserByToken/check user: invalid user')
686
- return Promise.reject('errTokenNoPermission')
687
- }
688
-
689
- srLog.info({ event: 'api/getUserByToken', userId: get(user, 'id', '') })
690
- return user
691
- }
692
-
693
- //pm2resolve core
694
- let r = await pm2resolve(core)() //w-ui-loginout接收已預設格式用pm2resolve轉過, 會提取state進行判斷
695
- if (isErr(r.msg)) {
696
- r.msg = r.msg.message
697
- }
698
- // console.log('getUserByToken', r)
699
-
700
- return r
701
- },
702
- },
703
-
704
- // {
705
- // method: 'GET',
706
- // path: '/getAPIsList',
707
- // handler: async function (req, res) {
708
- // // console.log('getAPIsList', req)
709
-
710
- // async function core() {
711
-
712
- // //token
713
- // let token = get(req, 'query.token', '')
714
-
715
- // //check
716
- // if (!isestr(token)) {
717
- // console.log('req.query', get(req, 'query'))
718
- // console.log('token', token)
719
- // console.log('[API]getAPIsList/check token: invalid token')
720
- // return Promise.reject('errTokenNoPermission')
721
- // }
722
-
723
- // //getAndVerifyClientUser
724
- // let user = await getAndVerifyClientUser(token, 'getAPIsList')
725
-
726
- // //check
727
- // if (!iseobj(user)) {
728
- // console.log('token', token)
729
- // console.log('[API]getAPIsList/check user: invalid user')
730
- // return Promise.reject('errTokenNoPermission')
731
- // }
732
-
733
- // //getAPIsList
734
- // let r = await getAPIsList({ isActive: 'y' })
735
-
736
- // return r
737
- // }
738
-
739
- // //pm2resolve core
740
- // let r = await pm2resolve(core)()
741
- // if (isErr(r.msg)) {
742
- // r.msg = r.msg.message
743
- // }
744
- // // console.log('getAPIsList', r)
745
-
746
- // return r
747
- // },
748
- // },
749
-
750
- // {
751
- // method: 'POST',
752
- // path: '/updateAPIs',
753
- // handler: async function (req, res) {
754
- // // console.log('updateAPIs', req)
755
- // // console.log('payload', req.payload)
756
-
757
- // async function core() {
758
-
759
- // //token
760
- // let token = get(req, 'query.token', '')
761
-
762
- // //check
763
- // if (!isestr(token)) {
764
- // console.log('req.query', get(req, 'query'))
765
- // console.log('token', token)
766
- // console.log('[API]updateAPIs/check token: invalid token')
767
- // return Promise.reject('errTokenNoPermission')
768
- // }
769
-
770
- // //getAndVerifyClientUser
771
- // let user = await getAndVerifyClientUser(token, 'updateAPIs')
772
-
773
- // //check
774
- // if (!iseobj(user)) {
775
- // console.log('token', token)
776
- // console.log('[API]updateAPIs/check user: invalid user')
777
- // return Promise.reject('errTokenNoPermission')
778
- // }
779
-
780
- // //parsePayload
781
- // let inp = await parsePayload(req)
782
-
783
- // //updateAPIs
784
- // let r = await updateAPIs(inp)
785
-
786
- // return r
787
- // }
788
-
789
- // //pm2resolve core
790
- // let r = await pm2resolve(core)()
791
- // if (isErr(r.msg)) {
792
- // r.msg = r.msg.message
793
- // }
794
- // // console.log('updateAPIs', r)
795
-
796
- // return r
797
- // },
798
- // },
799
-
800
- {
801
- method: 'POST',
802
- path: '/syncAndReplaceTabs',
803
- handler: async function (req, res) {
804
- // console.log('syncAndReplaceTabs', req)
805
- // console.log('payload', req.payload)
806
-
807
- async function core() {
808
-
809
- //token
810
- let token = get(req, 'query.token', '')
811
-
812
- //check
813
- if (!isestr(token)) {
814
- console.log('req.query', get(req, 'query'))
815
- console.log('token', maskTok(token))
816
- console.log('[API]syncAndReplaceTabs/check token: invalid token')
817
- return Promise.reject('errTokenNoPermission')
818
- }
819
-
820
- //keyTable
821
- let keyTable = get(req, 'query.keyTable', '')
822
-
823
- //check
824
- if (!isestr(keyTable)) {
825
- console.log('req.query', get(req, 'query'))
826
- console.log('keyTable', keyTable)
827
- console.log('[API]syncAndReplaceTabs/check keyTable: invalid keyTable')
828
- return Promise.reject('errKeyTableInvalid')
829
- }
830
-
831
- //check keyTable
832
- let allowedTables = ['apis']
833
- if (!allowedTables.includes(keyTable)) {
834
- console.log('[API]syncAndReplaceTabs/check keyTable: unexpected keyTable', keyTable)
835
- return Promise.reject('errKeyTableInvalid')
836
- }
837
-
838
- //getAndVerifyAppUser
839
- let user = await getAndVerifyAppUser(token, 'syncAndReplaceTabs')
840
-
841
- //check
842
- if (!iseobj(user)) {
843
- console.log('token', maskTok(token))
844
- console.log('[API]syncAndReplaceTabs/check user: invalid user')
845
- return Promise.reject('errTokenNoPermission')
846
- }
847
-
848
- //userId
849
- let userId = get(user, 'id', '')
850
-
851
- //check userId
852
- if (!isestr(userId)) {
853
- console.log('token', maskTok(token))
854
- console.log('user', user)
855
- console.log('[API]syncAndReplaceTabs/check userId: invalid userId')
856
- return Promise.reject('errTokenNoPermission')
857
- }
858
-
859
- //parsePayload
860
- let inp = await parsePayload(req)
861
-
862
- //syncAndReplaceTabs
863
- let r = await syncAndReplaceTabs(userId, inp, keyTable)
864
-
865
- srLog.info({ event: 'api/syncAndReplaceTabs', userId, keyTable })
866
- return r
867
- }
868
-
869
- //pm2resolve core
870
- let r = await pm2resolve(core)()
871
- if (isErr(r.msg)) {
872
- r.msg = r.msg.message
873
- }
874
- // console.log('syncAndReplaceTabs', r)
875
-
876
- return r
877
- },
878
- },
879
-
880
- ]
881
-
882
-
883
- //tableNamesExec, tableNamesSync
884
- let tableNamesExec = keys(ds)
885
- // let tableNamesSync = filter(tableNamesExec, (v) => {
886
- // return strright(v, 5) !== 'Items'//不同步數據
887
- // })
888
- let tableNamesSync = tableNamesExec
889
-
890
-
891
- //WServHapiServer
892
- instWServHapiServer = new WServHapiServer({
893
- port: serverPort,
894
- pathStaticFiles,
895
- apiName: 'api',
896
- apis,
897
- tokenType: 'Bearer',
898
- verifyConn: async ({ apiType, authorization, query, headers, req }) => {
899
- // console.log('verifyConn', `apiType[${apiType}]`, `authorization[${authorization}]`)
900
-
901
- //token
902
- let token = strdelleft(authorization, 7) //刪除Bearer
903
- // console.log('token', token)
904
-
905
- //check
906
- if (!isestr(token)) {
907
- console.log('apiType', apiType)
908
- console.log('authorization', maskTok(authorization))
909
- console.log('invalid token')
910
- return false
911
- }
912
-
913
- //checkToken:套件層連線閘僅做「token 認證」(解析 user + 驗欄位齊備)。**授權**(如限管理員)屬
914
- //套件使用者之責——由其提供之 getUserByToken(可對非授權者回 {})/ verifyClientUser / verifyAppUser,
915
- //或 WServOrm 之 useCheckUser/useExcludeWhenNotAdmin 等鉤子決定;套件不對連線層強加特定授權政策(避免越界、
916
- //保留使用者彈性)。詳見 spec/設計要點與取捨.md「套件授權責任」。
917
- let b = await p.checkToken(token)
918
-
919
- srLog.info({ event: 'verifyConn', apiType }) //連線之 token 認證通過(checkToken 失敗會 throw 不到此)
920
- return b
921
- },
922
- getUserIdByToken: async (token) => { //可使用async或sync函數
923
- // console.log('getUserIdByToken', token)
924
- if (!isestr(token)) {
925
- return ''
926
- }
927
- let user = await getUserByToken(token)
928
- let userId = get(user, 'id', '')
929
- return userId
930
- },
931
- useDbOrm: true,
932
- kpOrm: woItems,
933
- operOrm: procOrm, //procOrm的輸入為: userId, tableName, methodName, input
934
- tableNamesExec,
935
- methodsExec: ['select', 'insert', 'save', 'del'], //mix需於procOrm內註冊以提供
936
- tableNamesSync,
937
- kpFunExt: { //接收參數第1個為userId, 之後才是前端給予參數(皆經 wrapErrLog:錯誤 srLog err-key 後 re-reject)
938
-
939
- getWebInfor: wrapErrLog('getWebInfor', getWebInfor),
940
-
941
- proxyRequest: wrapErrLog('proxyRequest', (userId, reqSpec) => {
942
- return proxyRequest(reqSpec)
943
- }),
944
-
945
- getApisList: wrapErrLog('getApisList', (userId) => {
946
- return getApisList(userId)
947
- }),
948
- saveApi: wrapErrLog('saveApi', (userId, row) => {
949
- return saveApi(userId, row)
950
- }),
951
- deleteApi: wrapErrLog('deleteApi', (userId, id) => {
952
- return deleteApi(userId, id)
953
- }),
954
-
955
- //統計資訊頁:回傳各 event 之時段發生頻率(讀 logFd 之 log)。連線之 token 認證由 verifyConn 把關;
956
- //授權(限管理員)屬套件使用者責任(見 verifyConn 註解與設計要點與取捨「套件授權責任」)。timeLength 天數 / timeInterval 'hr'|'day'。
957
- getStaEvent: wrapErrLog('getStaEvent', (userId, timeLength = 7, timeInterval = 'hr') => {
958
- return staEvent(timeLength, timeInterval, { fdLog: logFd })
959
- }),
960
-
961
- // getAPIsList: (userId, query = {}) => { //待提供api管理系統界面使用
962
- // return getAPIsList(query)
963
- // },
964
- // updateApis: (userId, rows) => { //待提供api管理系統界面使用
965
- // return updateApis(rows)
966
- // },
967
-
968
- },
969
- fnTableTags: 'tableTags-web-api.json',
970
- })
971
-
972
-
973
- return instWServHapiServer
974
- }
975
-
976
-
977
- export default WWebApi
48
+ <pre class="prettyprint source linenums"><code>import path from 'path'
49
+ import fs from 'fs'
50
+ import get from 'lodash-es/get.js'
51
+ import each from 'lodash-es/each.js'
52
+ import map from 'lodash-es/map.js'
53
+ import keys from 'lodash-es/keys.js'
54
+ import j2o from 'wsemi/src/j2o.mjs'
55
+ import iseobj from 'wsemi/src/iseobj.mjs'
56
+ import isestr from 'wsemi/src/isestr.mjs'
57
+ import isearr from 'wsemi/src/isearr.mjs'
58
+ import ispint from 'wsemi/src/ispint.mjs'
59
+ import isErr from 'wsemi/src/isErr.mjs'
60
+ import isfun from 'wsemi/src/isfun.mjs'
61
+ import ispm from 'wsemi/src/ispm.mjs'
62
+ import cint from 'wsemi/src/cint.mjs'
63
+ import strleft from 'wsemi/src/strleft.mjs'
64
+ import strdelleft from 'wsemi/src/strdelleft.mjs'
65
+ import strright from 'wsemi/src/strright.mjs'
66
+ import strdelright from 'wsemi/src/strdelright.mjs'
67
+ import pm2resolve from 'wsemi/src/pm2resolve.mjs'
68
+ import b642str from 'wsemi/src/b642str.mjs'
69
+ import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
70
+ import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
71
+ import fsIsFile from 'wsemi/src/fsIsFile.mjs'
72
+ import replace from 'wsemi/src/replace.mjs'
73
+ import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
74
+ import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
75
+ import procLang from './procLang.mjs'
76
+ import procProxy from './procProxy.mjs'
77
+ import procApis from './procApis.mjs'
78
+ import staEvent from './staLogs/staEvent.mjs'
79
+ import srLogInit from './srLog.mjs'
80
+ import ds from '../src/schema/index.mjs'
81
+
82
+
83
+ /**
84
+ * API伺服器
85
+ *
86
+ * @class
87
+ * @param {Function} WOrm 輸入資料庫ORM函數
88
+ * @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
89
+ * @param {String} db 輸入資料庫名稱字串
90
+ * @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n',且輸入'y'時會複寫權限系統該使用者之'isAdmin'欄位值
91
+ * @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
92
+ * @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
93
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
94
+ * @param {Integer} [opt.serverPort=11005] 輸入伺服器通訊port,預設11005
95
+ * @param {Boolean} [opt.useCheckUser=false] 輸入是否檢查使用者資訊布林值,預設false
96
+ * @param {Function} [opt.getUserById=null] 輸入當useCheckUser=true時依照使用者ID取得使用者資訊物件函數,預設null
97
+ * @param {Boolean} [opt.useExcludeWhenNotAdmin=false] 輸入使用ORM的select方法時是否自動刪除數據內isActive欄位之布林值,預設false
98
+ * @param {Object} [opt.webName={}] 輸入站台名稱物件,至少包含語系eng與cht鍵的名稱,預設{}
99
+ * @param {Object} [opt.webDescription={}] 輸入站台描述物件,至少包含語系eng與cht鍵的名稱,預設{}
100
+ * @param {String} [opt.webLogo=''] 輸入站台logo字串,採base64格式,預設''
101
+ * @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
102
+ * @param {String} [opt.urlRedirect=''] 輸入錯誤時自動轉址字串,提供站台例如無法登入或驗證失敗時須自動轉址,預設''
103
+ * @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
104
+ * @example
105
+ *
106
+ * import WOrm from 'w-orm-lmdb/src/WOrmLmdb.mjs'
107
+ * import WWebApi from './server/WWebApi.mjs'
108
+ * import getSettings from './g_getSettings.mjs'
109
+ *
110
+ *
111
+ * //st
112
+ * let st = getSettings()
113
+ *
114
+ * let url = st.dbUrl
115
+ * let db = st.dbName
116
+ * let opt = {
117
+ *
118
+ * useCheckUser: false,
119
+ * getUserById: null,
120
+ * useExcludeWhenNotAdmin: false,
121
+ *
122
+ * serverPort: 11005,
123
+ * subfolder: '', //mapi
124
+ * urlRedirect: 'https://www.google.com/', //本機測試時得先編譯, 再瀏覽: http://localhost:11005/
125
+ *
126
+ * webName: {
127
+ * 'eng': 'API Service',
128
+ * 'cht': 'API管理系統',
129
+ * },
130
+ * webDescription: {
131
+ * 'eng': 'A web service package as methods to send requests to and receive responses from an API.',
132
+ * 'cht': 'A web service package as methods to send requests to and receive responses from an API.',
133
+ * },
134
+ * webLogo: 'data:image/svg+xml;base64,...',
135
+ *
136
+ * }
137
+ *
138
+ * let getUserByToken = async (token) => {
139
+ * // return {} //測試無法登入
140
+ * if (token === '{token-for-application}') { //提供外部應用系統作為存取使用者
141
+ * return {
142
+ * id: 'id-for-application',
143
+ * name: 'application',
144
+ * email: 'application@example.com',
145
+ * isAdmin: 'y',
146
+ * }
147
+ * }
148
+ * if (token === 'sys') { //開發階段w-ui-loginout自動給予browser使用者(且位於localhost)的token為sys
149
+ * return {
150
+ * id: 'id-for-admin',
151
+ * name: '測試者',
152
+ * email: 'admin@example.com',
153
+ * isAdmin: 'y',
154
+ * }
155
+ * }
156
+ * console.log('invalid token', token)
157
+ * console.log('於生產環境時得加入SSO等驗證token機制')
158
+ * return {}
159
+ * }
160
+ *
161
+ * let verifyClientUser = (user, from) => {
162
+ * console.log('verifyClientUser/user', user)
163
+ * // return false //測試無法登入
164
+ * console.log('於生產環境時得加入限制瀏覽器使用者身份機制')
165
+ * return user.isAdmin === 'y' //測試僅系統管理者使用
166
+ * }
167
+ *
168
+ * let verifyAppUser = (user, from) => {
169
+ * console.log('verifyAppUser/user', user)
170
+ * // return false //測試無法登入
171
+ * console.log('於生產環境時得加入限制應用程式使用者身份機制')
172
+ * return user.isAdmin === 'y' //測試僅系統管理者使用
173
+ * }
174
+ *
175
+ * //WWebApi
176
+ * let instWWebApi = WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, opt)
177
+ *
178
+ * instWWebApi.on('error', (err) => {
179
+ * console.log(err)
180
+ * })
181
+ *
182
+ */
183
+ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, opt = {}) {
184
+ let instWServHapiServer = null
185
+
186
+
187
+ //check WOrm
188
+ if (!isfun(WOrm)) {
189
+ console.log('invalid WOrm', WOrm)
190
+ throw new Error('invalid WOrm')
191
+ }
192
+
193
+
194
+ //check url
195
+ if (!isestr(url)) {
196
+ console.log('invalid url', url)
197
+ throw new Error('invalid url')
198
+ }
199
+
200
+
201
+ //check db
202
+ if (!isestr(db)) {
203
+ console.log('invalid db', db)
204
+ throw new Error('invalid db')
205
+ }
206
+
207
+
208
+ //check getUserByToken
209
+ if (!isfun(getUserByToken)) {
210
+ console.log('invalid getUserByToken', getUserByToken)
211
+ throw new Error('invalid getUserByToken')
212
+ }
213
+
214
+
215
+ //check verifyClientUser
216
+ if (!isfun(verifyClientUser)) {
217
+ console.log('invalid verifyClientUser', verifyClientUser)
218
+ throw new Error('invalid verifyClientUser')
219
+ }
220
+
221
+
222
+ //check verifyAppUser
223
+ if (!isfun(verifyAppUser)) {
224
+ console.log('invalid verifyAppUser', verifyAppUser)
225
+ throw new Error('invalid verifyAppUser')
226
+ }
227
+
228
+
229
+ //serverPort
230
+ let serverPort = get(opt, 'serverPort')
231
+ if (!ispint(serverPort)) {
232
+ serverPort = 11005
233
+ }
234
+ serverPort = cint(serverPort)
235
+
236
+
237
+ //useCheckUser
238
+ let useCheckUser = get(opt, 'useCheckUser', false)
239
+
240
+
241
+ //getUserById
242
+ let getUserById = get(opt, 'getUserById', null)
243
+
244
+
245
+ //useExcludeWhenNotAdmin
246
+ let useExcludeWhenNotAdmin = get(opt, 'useExcludeWhenNotAdmin', false)
247
+
248
+
249
+ //webName
250
+ let webName = get(opt, 'webName', {})
251
+
252
+
253
+ //webDescription
254
+ let webDescription = get(opt, 'webDescription', {})
255
+
256
+
257
+ //webLogo
258
+ let webLogo = get(opt, 'webLogo', '')
259
+
260
+
261
+ //subfolder
262
+ let subfolder = get(opt, 'subfolder', '')
263
+ if (isestr(subfolder)) {
264
+ if (strright(subfolder, 1) === '/') { //右邊不需要給「/」
265
+ subfolder = strdelright(subfolder, 1)
266
+ }
267
+ if (strleft(subfolder, 1) !== '/') { //左邊需要給「/」
268
+ subfolder = `/${subfolder}`
269
+ }
270
+ }
271
+
272
+
273
+ //urlRedirect
274
+ let urlRedirect = get(opt, 'urlRedirect', '')
275
+
276
+
277
+ //params
278
+ let showLanguage = get(opt, 'showLanguage', 'y')
279
+ let language = get(opt, 'language', 'eng')
280
+ let logFd = get(opt, 'logFd', './logs') //srLog 寫入 + staEvent 統計共用之 log 資料夾
281
+
282
+
283
+ //kpLangExt
284
+ let kpLangExt = get(opt, 'kpLangExt', {})
285
+
286
+
287
+ //kpLang
288
+ let kpLang = procLang({ kpLangExt, webName, webDescription })
289
+
290
+
291
+ //WServOrm
292
+ let optWServOrm = {
293
+ useCheckUser,
294
+ getUserById,
295
+ useExcludeWhenNotAdmin,
296
+ }
297
+ let wp = {}
298
+ try {
299
+ wp = WServOrm(ds, WOrm, url, db, optWServOrm)
300
+ }
301
+ catch (err) {
302
+ //ORM 為必要依賴:初始化失敗即 fail-fast,不以 wp={} 帶 undefined woItems/procOrm 殘缺啟動(與建構子其餘必要依賴一致)
303
+ console.log('WServOrm init failed', err)
304
+ throw err
305
+ }
306
+ let { woItems, procOrm } = wp
307
+
308
+
309
+ //srLog(後端 log;錯誤一律 log err-key 供識別,對齊 w-web-sso)
310
+ let srLog = srLogInit(opt)
311
+
312
+ //maskTok:debug console.log 不印完整 token/authorization(憑證),僅前 4 碼 + ***,避免憑證落入 stdout
313
+ let maskTok = (t) => isestr(t) ? (strleft(t, 4) + '***') : t
314
+
315
+ //kpFunExt 邊界統一處理:成功記 info(供 staEvent 統計呼叫頻率);catch 後端錯誤 → srLog 該 err-key →
316
+ //原樣 re-reject(前端依 key 查自己 lang 顯示)。event 統一加 'kpfun-' 前綴 → 專用事件、與 api/verifyConn 區分。
317
+ let wrapErrLog = (event, fn) => {
318
+ let ev = 'kpfun-' + event
319
+ return async (...args) => {
320
+ try {
321
+ let result = await fn(...args)
322
+ srLog.info({ event: ev }) //成功也記 info
323
+ return result
324
+ }
325
+ catch (err) {
326
+ srLog.error({ event: ev, err })
327
+ return Promise.reject(err)
328
+ }
329
+ }
330
+ }
331
+
332
+
333
+ //procProxy
334
+ let { proxyRequest } = procProxy({ srLog })
335
+
336
+
337
+ //procApis
338
+ let { getApisList, saveApi, deleteApi } = procApis({ woItems, procOrm, ds })
339
+
340
+
341
+ //getWebInfor
342
+ let getWebInfor = (userId) => {
343
+ return {
344
+
345
+ // webName, //已併入kpLang
346
+ // webDescription, //已併入kpLang
347
+ webLogo,
348
+
349
+ showLanguage,
350
+ language,
351
+ kpLang,
352
+
353
+ }
354
+ }
355
+
356
+
357
+ //checkUser
358
+ let checkUser = async(user) => {
359
+ let id = get(user, 'id', '')
360
+ if (!isestr(id)) {
361
+ console.log('user', user)
362
+ console.log('can not get the userId')
363
+ return Promise.reject('errUserIdMissing')
364
+ }
365
+ let email = get(user, 'email', '')
366
+ if (!isestr(email)) {
367
+ console.log('user', user)
368
+ console.log('can not get the email of user')
369
+ return Promise.reject('errUserEmailMissing')
370
+ }
371
+ let name = get(user, 'name', '')
372
+ if (!isestr(name)) {
373
+ console.log('user', user)
374
+ console.log('can not get userName')
375
+ return Promise.reject('errUserNameMissing')
376
+ }
377
+ let isAdmin = get(user, 'isAdmin', '')
378
+ if (isAdmin !== 'y' &amp;&amp; isAdmin !== 'n') {
379
+ console.log('user', user)
380
+ console.log('user.isAdmin is not y or n', user.isAdmin)
381
+ console.log('can not get the role of user')
382
+ return Promise.reject('errUserRoleMissing')
383
+ }
384
+ return true
385
+ }
386
+
387
+
388
+ //getTokenUser
389
+ let getTokenUser = async (token) => {
390
+
391
+ //getUserByToken
392
+ let userSelf = getUserByToken(token)
393
+ if (ispm(userSelf)) {
394
+ userSelf = await userSelf
395
+ }
396
+
397
+ //check
398
+ await checkUser(userSelf)
399
+
400
+ return userSelf
401
+ }
402
+
403
+
404
+ //p
405
+ let p = {
406
+ checkToken: async(token) => {
407
+
408
+ //getUserByToken
409
+ let u = await getUserByToken(token)
410
+ // console.log('u', u)
411
+
412
+ //checkUser
413
+ await checkUser(u)
414
+
415
+ return true
416
+ },
417
+ }
418
+
419
+
420
+ //getAndVerifyClientUser
421
+ let getAndVerifyClientUser = async (token, from = '') => {
422
+ //基於token查找client使用者, 會基於外部getUserByToken以及perm內部users進行查找並比對
423
+
424
+ //getTokenUser
425
+ let user = await getTokenUser(token)
426
+
427
+ //verifyClientUser
428
+ let b = verifyClientUser(user, from)
429
+ if (ispm(b)) {
430
+ b = await b
431
+ }
432
+
433
+ //check
434
+ if (!b) {
435
+ console.log('user', user)
436
+ console.log(`user does not have permission`)
437
+ return Promise.reject('errUserNoPermission')
438
+ }
439
+
440
+ srLog.info({ event: 'verifyClientUser', from, userId: get(user, 'id', '') }) //瀏覽使用者驗證通過
441
+ return user
442
+ }
443
+
444
+
445
+ //getAndVerifyAppUser
446
+ let getAndVerifyAppUser = async (token, from = '') => {
447
+ //基於token查找app使用者, 會基於外部getUserByToken進行查找, 故只要getUserByToken提供使用者即可, 可支援getUserByToken給予針對應用程式所產生之虛擬使用者
448
+
449
+ //user
450
+ let user = null
451
+ if (isestr(token)) {
452
+ user = getUserByToken(token)
453
+ if (ispm(user)) {
454
+ user = await user
455
+ }
456
+ }
457
+ // console.log('user', user)
458
+
459
+ //check
460
+ if (!iseobj(user)) {
461
+ console.log(`token`, token)
462
+ console.log(`can not find the user from token`)
463
+ return Promise.reject('errUserNotFound')
464
+ }
465
+
466
+ //verifyAppUser
467
+ let b = verifyAppUser(user, from)
468
+ if (ispm(b)) {
469
+ b = await b
470
+ }
471
+
472
+ //check
473
+ if (!b) {
474
+ console.log('user', user)
475
+ console.log(`user does not have permission`)
476
+ return Promise.reject('errUserNoPermission')
477
+ }
478
+
479
+ srLog.info({ event: 'verifyAppUser', from, userId: get(user, 'id', '') }) //應用程式使用者驗證通過
480
+ return user
481
+ }
482
+
483
+
484
+ // //getAPIsList
485
+ // let getAPIsList = async (query = {}) => {
486
+ // let rs = await woItems.apis.select(query)
487
+ // return rs
488
+ // }
489
+
490
+
491
+ //parsePayload
492
+ let parsePayload = async (req) => {
493
+
494
+ //payload
495
+ let payload = get(req, 'payload')
496
+
497
+ //to obj
498
+ if (isestr(payload)) {
499
+ payload = j2o(payload)
500
+ }
501
+
502
+ //check
503
+ if (!iseobj(payload)) {
504
+ console.log('payload', payload)
505
+ console.log(`invalid payload in req`)
506
+ return Promise.reject('errPayloadInvalid')
507
+ }
508
+
509
+ //group
510
+ let group = get(payload, 'group', '')
511
+
512
+ //check
513
+ if (!isestr(group)) {
514
+ console.log('payload', payload)
515
+ console.log(`invalid group in payload`)
516
+ return Promise.reject('errGroupInvalid')
517
+ }
518
+
519
+ //rows
520
+ let rows = get(payload, 'rows', [])
521
+
522
+ //check
523
+ if (!isearr(rows)) {
524
+ console.log('payload', payload)
525
+ console.log(`invalid rows in payload`)
526
+ return Promise.reject('errRowsInvalid')
527
+ }
528
+
529
+ //r
530
+ let r = {
531
+ group,
532
+ rows,
533
+ }
534
+
535
+ return r
536
+ }
537
+
538
+
539
+ // //updateAPIs
540
+ // let updateAPIs = async (inp = {}) => {
541
+
542
+ // //spread inp
543
+ // let { apis: rsApis } = inp
544
+ // // console.log('group', group)
545
+ // // console.log('rsApis', rsApis)
546
+
547
+ // //save
548
+ // let r = await woItems.apis.save(rsApis)
549
+
550
+ // return r
551
+ // }
552
+
553
+
554
+ //syncAndReplaceTabs
555
+ let syncAndReplaceTabs = async (userId, inp, keyTable) => {
556
+
557
+ //group
558
+ let group = get(inp, 'group', '')
559
+ // console.log('group', group)
560
+
561
+ //check
562
+ if (!isestr(group)) {
563
+ console.log('inp', inp)
564
+ console.log('group', group)
565
+ console.log(`invalid group`)
566
+ return Promise.reject('errGroupInvalid')
567
+ }
568
+
569
+ //rows
570
+ let rows = get(inp, 'rows', [])
571
+ // console.log('rows', rows)
572
+
573
+ //check
574
+ if (!isearr(rows)) {
575
+ console.log('inp', inp)
576
+ console.log('keyTable', keyTable)
577
+ return Promise.reject('errRowsInvalid')
578
+ }
579
+
580
+ //save from
581
+ rows = map(rows, (v, k) => {
582
+ // v.order = k + 1 //order大部分由外部給予, 不能複寫
583
+ v.group = group
584
+ return v
585
+ })
586
+
587
+ //偵測b64自動恢復
588
+ if (keyTable === 'apis') {
589
+ each(rows, (row, krow) => {
590
+ // console.log(kapi, row.name)
591
+ each(row, (v, k) => {
592
+ // console.log(k, v)
593
+ if (isestr(v)) {
594
+ if (strleft(v, 7) === 'base64:') {
595
+ // console.log(k, 'get base64')
596
+ v = strdelleft(v, 7)
597
+ v = b642str(v)
598
+ // console.log(v)
599
+ rows[krow][k] = v
600
+ }
601
+ }
602
+ })
603
+ })
604
+ }
605
+
606
+ //ltdtmapping
607
+ rows = ltdtmapping(rows, ds[keyTable].keys)
608
+ // console.log('ltdtmapping rows', rows)
609
+
610
+ //delAll group
611
+ await woItems[keyTable].delAll({ group })
612
+
613
+ //insert
614
+ // let r = await woItems[keyTable].insert(rows)
615
+ let r = await procOrm(userId, keyTable, 'insert', rows) //須使用procOrm才有辦法自動給予相關欄位
616
+ // console.log('r', r)
617
+
618
+ return r
619
+ }
620
+
621
+
622
+ //pathStaticFiles
623
+ let pathStaticFiles = 'dist'
624
+ let npmPathStaticFiles = './node_modules/w-web-api/dist'
625
+ if (fsIsFolder(npmPathStaticFiles)) {
626
+ pathStaticFiles = npmPathStaticFiles
627
+ }
628
+ // console.log('pathStaticFiles', pathStaticFiles)
629
+
630
+
631
+ //subfolder
632
+ let fnEntryIn = 'index.tmp'
633
+ let fnEntryOut = 'index.html'
634
+ try {
635
+ let fpEntryIn = path.resolve(pathStaticFiles, fnEntryIn)
636
+ if (!fsIsFile(fpEntryIn)) {
637
+ fpEntryIn = path.resolve(pathStaticFiles, fnEntryOut) //本機開發另使用html替代tmp
638
+ }
639
+ if (!fsIsFile(fpEntryIn)) {
640
+ console.log('fpEntryIn', fpEntryIn)
641
+ throw new Error(`invalid fpEntryIn`)
642
+ }
643
+ let fpEntryOut = path.resolve(pathStaticFiles, fnEntryOut)
644
+ let c = fs.readFileSync(fpEntryIn, 'utf8')
645
+ c = replace(c, '/mapi/', '{sfd}/') //方法同genEntry
646
+ c = replace(c, '{sfd}', subfolder)
647
+ c = replace(c, '{urlRedirect}', urlRedirect)
648
+ c = replace(c, '{language}', language) //注入初始語系 → window.___pmwperm___.language(前端 getLang 之來源之一)
649
+ fs.writeFileSync(fpEntryOut, c, 'utf8')
650
+ }
651
+ catch (err) {
652
+ console.log(err)
653
+ console.log(`can not generate ${fnEntryOut}`)
654
+ }
655
+
656
+
657
+ //apis
658
+ let apis = [
659
+
660
+ {
661
+ method: 'GET',
662
+ path: '/api/getUserByToken', //未登入主頁時需先檢測token, getUserByToken為w-ui-loginout預設值, 若要更改兩邊須同時修改
663
+ handler: async function (req, res) {
664
+ // console.log('getUserByToken', req)
665
+
666
+ async function core() {
667
+
668
+ //token
669
+ let token = get(req, 'query.token', '')
670
+
671
+ //check
672
+ if (!isestr(token)) {
673
+ console.log('req.query', get(req, 'query'))
674
+ console.log('token', maskTok(token))
675
+ console.log('[API]getUserByToken/check token: invalid token')
676
+ return Promise.reject('errTokenNoPermission')
677
+ }
678
+
679
+ //getAndVerifyClientUser
680
+ let user = await getAndVerifyClientUser(token, 'getUserByToken')
681
+
682
+ //check
683
+ if (!iseobj(user)) {
684
+ console.log('token', maskTok(token))
685
+ console.log('[API]getUserByToken/check user: invalid user')
686
+ return Promise.reject('errTokenNoPermission')
687
+ }
688
+
689
+ srLog.info({ event: 'api/getUserByToken', userId: get(user, 'id', '') })
690
+ return user
691
+ }
692
+
693
+ //pm2resolve core
694
+ let r = await pm2resolve(core)() //w-ui-loginout接收已預設格式用pm2resolve轉過, 會提取state進行判斷
695
+ if (isErr(r.msg)) {
696
+ r.msg = r.msg.message
697
+ }
698
+ // console.log('getUserByToken', r)
699
+
700
+ return r
701
+ },
702
+ },
703
+
704
+ // {
705
+ // method: 'GET',
706
+ // path: '/getAPIsList',
707
+ // handler: async function (req, res) {
708
+ // // console.log('getAPIsList', req)
709
+
710
+ // async function core() {
711
+
712
+ // //token
713
+ // let token = get(req, 'query.token', '')
714
+
715
+ // //check
716
+ // if (!isestr(token)) {
717
+ // console.log('req.query', get(req, 'query'))
718
+ // console.log('token', token)
719
+ // console.log('[API]getAPIsList/check token: invalid token')
720
+ // return Promise.reject('errTokenNoPermission')
721
+ // }
722
+
723
+ // //getAndVerifyClientUser
724
+ // let user = await getAndVerifyClientUser(token, 'getAPIsList')
725
+
726
+ // //check
727
+ // if (!iseobj(user)) {
728
+ // console.log('token', token)
729
+ // console.log('[API]getAPIsList/check user: invalid user')
730
+ // return Promise.reject('errTokenNoPermission')
731
+ // }
732
+
733
+ // //getAPIsList
734
+ // let r = await getAPIsList({ isActive: 'y' })
735
+
736
+ // return r
737
+ // }
738
+
739
+ // //pm2resolve core
740
+ // let r = await pm2resolve(core)()
741
+ // if (isErr(r.msg)) {
742
+ // r.msg = r.msg.message
743
+ // }
744
+ // // console.log('getAPIsList', r)
745
+
746
+ // return r
747
+ // },
748
+ // },
749
+
750
+ // {
751
+ // method: 'POST',
752
+ // path: '/updateAPIs',
753
+ // handler: async function (req, res) {
754
+ // // console.log('updateAPIs', req)
755
+ // // console.log('payload', req.payload)
756
+
757
+ // async function core() {
758
+
759
+ // //token
760
+ // let token = get(req, 'query.token', '')
761
+
762
+ // //check
763
+ // if (!isestr(token)) {
764
+ // console.log('req.query', get(req, 'query'))
765
+ // console.log('token', token)
766
+ // console.log('[API]updateAPIs/check token: invalid token')
767
+ // return Promise.reject('errTokenNoPermission')
768
+ // }
769
+
770
+ // //getAndVerifyClientUser
771
+ // let user = await getAndVerifyClientUser(token, 'updateAPIs')
772
+
773
+ // //check
774
+ // if (!iseobj(user)) {
775
+ // console.log('token', token)
776
+ // console.log('[API]updateAPIs/check user: invalid user')
777
+ // return Promise.reject('errTokenNoPermission')
778
+ // }
779
+
780
+ // //parsePayload
781
+ // let inp = await parsePayload(req)
782
+
783
+ // //updateAPIs
784
+ // let r = await updateAPIs(inp)
785
+
786
+ // return r
787
+ // }
788
+
789
+ // //pm2resolve core
790
+ // let r = await pm2resolve(core)()
791
+ // if (isErr(r.msg)) {
792
+ // r.msg = r.msg.message
793
+ // }
794
+ // // console.log('updateAPIs', r)
795
+
796
+ // return r
797
+ // },
798
+ // },
799
+
800
+ {
801
+ method: 'POST',
802
+ path: '/syncAndReplaceTabs',
803
+ handler: async function (req, res) {
804
+ // console.log('syncAndReplaceTabs', req)
805
+ // console.log('payload', req.payload)
806
+
807
+ async function core() {
808
+
809
+ //token
810
+ let token = get(req, 'query.token', '')
811
+
812
+ //check
813
+ if (!isestr(token)) {
814
+ console.log('req.query', get(req, 'query'))
815
+ console.log('token', maskTok(token))
816
+ console.log('[API]syncAndReplaceTabs/check token: invalid token')
817
+ return Promise.reject('errTokenNoPermission')
818
+ }
819
+
820
+ //keyTable
821
+ let keyTable = get(req, 'query.keyTable', '')
822
+
823
+ //check
824
+ if (!isestr(keyTable)) {
825
+ console.log('req.query', get(req, 'query'))
826
+ console.log('keyTable', keyTable)
827
+ console.log('[API]syncAndReplaceTabs/check keyTable: invalid keyTable')
828
+ return Promise.reject('errKeyTableInvalid')
829
+ }
830
+
831
+ //check keyTable
832
+ let allowedTables = ['apis']
833
+ if (!allowedTables.includes(keyTable)) {
834
+ console.log('[API]syncAndReplaceTabs/check keyTable: unexpected keyTable', keyTable)
835
+ return Promise.reject('errKeyTableInvalid')
836
+ }
837
+
838
+ //getAndVerifyAppUser
839
+ let user = await getAndVerifyAppUser(token, 'syncAndReplaceTabs')
840
+
841
+ //check
842
+ if (!iseobj(user)) {
843
+ console.log('token', maskTok(token))
844
+ console.log('[API]syncAndReplaceTabs/check user: invalid user')
845
+ return Promise.reject('errTokenNoPermission')
846
+ }
847
+
848
+ //userId
849
+ let userId = get(user, 'id', '')
850
+
851
+ //check userId
852
+ if (!isestr(userId)) {
853
+ console.log('token', maskTok(token))
854
+ console.log('user', user)
855
+ console.log('[API]syncAndReplaceTabs/check userId: invalid userId')
856
+ return Promise.reject('errTokenNoPermission')
857
+ }
858
+
859
+ //parsePayload
860
+ let inp = await parsePayload(req)
861
+
862
+ //syncAndReplaceTabs
863
+ let r = await syncAndReplaceTabs(userId, inp, keyTable)
864
+
865
+ srLog.info({ event: 'api/syncAndReplaceTabs', userId, keyTable })
866
+ return r
867
+ }
868
+
869
+ //pm2resolve core
870
+ let r = await pm2resolve(core)()
871
+ if (isErr(r.msg)) {
872
+ r.msg = r.msg.message
873
+ }
874
+ // console.log('syncAndReplaceTabs', r)
875
+
876
+ return r
877
+ },
878
+ },
879
+
880
+ ]
881
+
882
+
883
+ //tableNamesExec, tableNamesSync
884
+ let tableNamesExec = keys(ds)
885
+ // let tableNamesSync = filter(tableNamesExec, (v) => {
886
+ // return strright(v, 5) !== 'Items'//不同步數據
887
+ // })
888
+ let tableNamesSync = tableNamesExec
889
+
890
+
891
+ //WServHapiServer
892
+ instWServHapiServer = new WServHapiServer({
893
+ port: serverPort,
894
+ pathStaticFiles,
895
+ apiName: 'api',
896
+ apis,
897
+ tokenType: 'Bearer',
898
+ verifyConn: async ({ apiType, authorization, query, headers, req }) => {
899
+ // console.log('verifyConn', `apiType[${apiType}]`, `authorization[${authorization}]`)
900
+
901
+ //token
902
+ let token = strdelleft(authorization, 7) //刪除Bearer
903
+ // console.log('token', token)
904
+
905
+ //check
906
+ if (!isestr(token)) {
907
+ console.log('apiType', apiType)
908
+ console.log('authorization', maskTok(authorization))
909
+ console.log('invalid token')
910
+ return false
911
+ }
912
+
913
+ //checkToken:套件層連線閘僅做「token 認證」(解析 user + 驗欄位齊備)。**授權**(如限管理員)屬
914
+ //套件使用者之責——由其提供之 getUserByToken(可對非授權者回 {})/ verifyClientUser / verifyAppUser,
915
+ //或 WServOrm 之 useCheckUser/useExcludeWhenNotAdmin 等鉤子決定;套件不對連線層強加特定授權政策(避免越界、
916
+ //保留使用者彈性)。詳見 spec/設計要點與取捨.md「套件授權責任」。
917
+ let b = await p.checkToken(token)
918
+
919
+ srLog.info({ event: 'verifyConn', apiType }) //連線之 token 認證通過(checkToken 失敗會 throw 不到此)
920
+ return b
921
+ },
922
+ getUserIdByToken: async (token) => { //可使用async或sync函數
923
+ // console.log('getUserIdByToken', token)
924
+ if (!isestr(token)) {
925
+ return ''
926
+ }
927
+ let user = await getUserByToken(token)
928
+ let userId = get(user, 'id', '')
929
+ return userId
930
+ },
931
+ useDbOrm: true,
932
+ kpOrm: woItems,
933
+ operOrm: procOrm, //procOrm的輸入為: userId, tableName, methodName, input
934
+ tableNamesExec,
935
+ methodsExec: ['select', 'insert', 'save', 'del'], //mix需於procOrm內註冊以提供
936
+ tableNamesSync,
937
+ kpFunExt: { //接收參數第1個為userId, 之後才是前端給予參數(皆經 wrapErrLog:錯誤 srLog err-key 後 re-reject)
938
+
939
+ getWebInfor: wrapErrLog('getWebInfor', getWebInfor),
940
+
941
+ proxyRequest: wrapErrLog('proxyRequest', (userId, reqSpec) => {
942
+ return proxyRequest(reqSpec)
943
+ }),
944
+
945
+ getApisList: wrapErrLog('getApisList', (userId) => {
946
+ return getApisList(userId)
947
+ }),
948
+ saveApi: wrapErrLog('saveApi', (userId, row) => {
949
+ return saveApi(userId, row)
950
+ }),
951
+ deleteApi: wrapErrLog('deleteApi', (userId, id) => {
952
+ return deleteApi(userId, id)
953
+ }),
954
+
955
+ //統計資訊頁:回傳各 event 之時段發生頻率(讀 logFd 之 log)。連線之 token 認證由 verifyConn 把關;
956
+ //授權(限管理員)屬套件使用者責任(見 verifyConn 註解與設計要點與取捨「套件授權責任」)。timeLength 天數 / timeInterval 'hr'|'day'。
957
+ getStaEvent: wrapErrLog('getStaEvent', (userId, timeLength = 7, timeInterval = 'hr') => {
958
+ return staEvent(timeLength, timeInterval, { fdLog: logFd })
959
+ }),
960
+
961
+ // getAPIsList: (userId, query = {}) => { //待提供api管理系統界面使用
962
+ // return getAPIsList(query)
963
+ // },
964
+ // updateApis: (userId, rows) => { //待提供api管理系統界面使用
965
+ // return updateApis(rows)
966
+ // },
967
+
968
+ },
969
+ fnTableTags: 'tableTags-web-api.json',
970
+ })
971
+
972
+
973
+ return instWServHapiServer
974
+ }
975
+
976
+
977
+ export default WWebApi
978
978
  </code></pre>
979
979
  </article>
980
980
  </section>
@@ -989,7 +989,7 @@ export default WWebApi
989
989
  <br class="clear">
990
990
 
991
991
  <footer>
992
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 25 2026 16:48:23 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
992
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Aug 26 2026 18:01:59 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
993
993
  </footer>
994
994
 
995
995
  <script>prettyPrint();</script>