w-web-perm 1.1.8 → 1.1.9
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/README.md +14 -4
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.ecb7ce08.js +1 -0
- package/dist/js/app.ecb7ce08.js +1 -0
- package/dist/js/{chunk-vendors-legacy.00f159e8.js → chunk-vendors-legacy.da785a99.js} +3 -3
- package/dist/js/{chunk-vendors.00f159e8.js → chunk-vendors.da785a99.js} +3 -3
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +78 -128
- package/docs/global.html +1280 -39
- package/docs/index.html +2 -2
- package/docs/server_WWebPerm.mjs.html +1247 -1196
- package/docs/server_dataChannel.mjs.html +131 -0
- package/docs/server_lockSave.mjs.html +2 -2
- package/docs/server_maskLog.mjs.html +2 -2
- package/docs/server_procCore.mjs.html +53 -11
- package/docs/server_staLogs_resolveShellWorker.mjs.html +180 -0
- package/docs/server_validateRows.mjs.html +184 -0
- package/docs/server_wrapInjected.mjs.html +2 -2
- package/docs/src_fetchJson.mjs.html +2 -2
- package/docs/src_js_legendFit.mjs.html +2 -2
- package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
- package/docs/src_plugins_submitGuard.mjs.html +2 -2
- package/g_backupDb.mjs +4 -0
- package/g_initialData.mjs +1 -0
- package/g_initialTestData.mjs +1 -0
- package/g_mOrm.mjs +2 -7
- package/g_provideGrups.mjs +1 -0
- package/g_providePemis.mjs +1 -0
- package/g_provideTargets.mjs +1 -0
- package/g_provideUsers.mjs +1 -0
- package/package.json +10 -10
- package/server/WWebPerm.mjs +1245 -1194
- package/server/dataChannel.mjs +59 -0
- package/server/procCore.mjs +51 -9
- package/server/procLang.mjs +13 -3
- package/server/staLogs/resolveShellWorker.mjs +108 -0
- package/server/staLogs/staLogsCore.callWorker.mjs +29 -9
- package/server/validateRows.mjs +112 -0
- package/src/components/LayoutContentGrups.vue +8 -8
- package/src/components/LayoutContentPemis.vue +8 -8
- package/src/components/LayoutContentTargets.vue +2 -2
- package/src/components/LayoutContentUsers.vue +4 -4
- package/src/components/VeCgrups.vue +3 -3
- package/src/components/VeCpemis.vue +3 -3
- package/src/components/VeCrules.vue +3 -3
- package/src/components/VeGrupBlngUsers.vue +6 -6
- package/src/components/VePemiBlngGrups.vue +6 -6
- package/srv.mjs +0 -4
- package/dist/js/app-legacy.6f82d8bd.js +0 -1
- package/dist/js/app.6f82d8bd.js +0 -1
package/server/WWebPerm.mjs
CHANGED
|
@@ -1,1194 +1,1245 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import get from 'lodash-es/get.js'
|
|
4
|
-
import map from 'lodash-es/map.js'
|
|
5
|
-
import keys from 'lodash-es/keys.js'
|
|
6
|
-
import join from 'lodash-es/join.js'
|
|
7
|
-
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
8
|
-
import isestr from 'wsemi/src/isestr.mjs'
|
|
9
|
-
import ispint from 'wsemi/src/ispint.mjs'
|
|
10
|
-
import isearr from 'wsemi/src/isearr.mjs'
|
|
11
|
-
import isErr from 'wsemi/src/isErr.mjs'
|
|
12
|
-
import isfun from 'wsemi/src/isfun.mjs'
|
|
13
|
-
import ispm from 'wsemi/src/ispm.mjs'
|
|
14
|
-
import cint from 'wsemi/src/cint.mjs'
|
|
15
|
-
import j2o from 'wsemi/src/j2o.mjs'
|
|
16
|
-
import strleft from 'wsemi/src/strleft.mjs'
|
|
17
|
-
import strright from 'wsemi/src/strright.mjs'
|
|
18
|
-
import strdelright from 'wsemi/src/strdelright.mjs'
|
|
19
|
-
import pm2resolve from 'wsemi/src/pm2resolve.mjs'
|
|
20
|
-
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
|
|
21
|
-
import fsIsFile from 'wsemi/src/fsIsFile.mjs'
|
|
22
|
-
import replace from 'wsemi/src/replace.mjs'
|
|
23
|
-
import strdelleft from 'wsemi/src/strdelleft.mjs'
|
|
24
|
-
import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
|
|
25
|
-
import pmKeyMutex from 'wsemi/src/pmKeyMutex.mjs'
|
|
26
|
-
import cacheSt from 'wsemi/src/cacheSt.mjs'
|
|
27
|
-
import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
|
|
28
|
-
import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
|
|
29
|
-
import
|
|
30
|
-
import
|
|
31
|
-
import
|
|
32
|
-
import
|
|
33
|
-
import
|
|
34
|
-
import
|
|
35
|
-
import
|
|
36
|
-
import
|
|
37
|
-
import
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* @
|
|
47
|
-
* @param {Function}
|
|
48
|
-
* @param {
|
|
49
|
-
* @param {
|
|
50
|
-
* @param {
|
|
51
|
-
* @param {
|
|
52
|
-
* @param {
|
|
53
|
-
* @param {
|
|
54
|
-
* @param {
|
|
55
|
-
* @param {Object} [opt.webName={}] 輸入站台名稱物件,至少包含語系eng與cht鍵的名稱,預設{}
|
|
56
|
-
* @param {Object} [opt.webDescription={}] 輸入站台描述物件,至少包含語系eng與cht鍵的名稱,預設{}
|
|
57
|
-
* @param {String} [opt.webLogo=''] 輸入站台logo字串,採base64格式,預設''
|
|
58
|
-
* @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
|
|
59
|
-
* @param {String} [opt.urlRedirect=''] 輸入錯誤時自動轉址字串,提供站台例如無法登入或驗證失敗時須自動轉址,預設''
|
|
60
|
-
* @param {String} [opt.mappingBy='
|
|
61
|
-
* @param {String} [opt.logFd='./logs'] 輸入log紀錄儲存位置字串,預設'./logs'
|
|
62
|
-
* @param {String} [opt.logInterval='hr'] 輸入log紀錄檔案拆檔時長字串,可選'hr'或'day',預設'hr'
|
|
63
|
-
* @param {Integer} [opt.logNumKeep=null] 輸入log檔保留數上限正整數,超出者自最舊刪除,未給採w-syslog預設(hr模式365*24、day模式365),預設null
|
|
64
|
-
* @returns {Object}
|
|
65
|
-
* @example
|
|
66
|
-
*
|
|
67
|
-
* import WOrm from 'w-orm-lmdb/src/WOrmLmdb.mjs'
|
|
68
|
-
* import WWebPerm from './server/WWebPerm.mjs'
|
|
69
|
-
* import getSettings from './g_getSettings.mjs'
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* //st
|
|
73
|
-
* let st = getSettings()
|
|
74
|
-
*
|
|
75
|
-
* let url = st.dbUrl
|
|
76
|
-
* let db = st.dbName
|
|
77
|
-
* let opt = {
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* 'eng': '
|
|
89
|
-
* 'cht': '
|
|
90
|
-
* },
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* }
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
let
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
//
|
|
203
|
-
let
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
let
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
//
|
|
219
|
-
let
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
let
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
let
|
|
241
|
-
let
|
|
242
|
-
let
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
let
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
let
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
let
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
//
|
|
262
|
-
let
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
let
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
console.log('user', user)
|
|
394
|
-
srLog.error({ event: 'checkUser-error', msg: '
|
|
395
|
-
return Promise.reject(`
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
// console.log('
|
|
441
|
-
|
|
442
|
-
//check
|
|
443
|
-
if (!
|
|
444
|
-
console.log('userSelf', userSelf)
|
|
445
|
-
console.log('mappingBy', mappingBy)
|
|
446
|
-
srLog.error({ event: 'getTokenUser-error', msg: '
|
|
447
|
-
return Promise.reject(`
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
userFind =
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
//check
|
|
511
|
-
if (!
|
|
512
|
-
console.log(
|
|
513
|
-
srLog.error({ event: 'getAndVerifyAppUser-error', msg: '
|
|
514
|
-
return Promise.reject(`
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
//
|
|
608
|
-
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
//
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
let
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
//
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
//
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
//
|
|
829
|
-
let
|
|
830
|
-
// console.log('
|
|
831
|
-
|
|
832
|
-
//check
|
|
833
|
-
if (!
|
|
834
|
-
console.log('
|
|
835
|
-
console.log('
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
console.log('token', maskTok(token))
|
|
873
|
-
console.log('
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
//
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
//
|
|
906
|
-
|
|
907
|
-
//
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
console.log('
|
|
912
|
-
console.log('[API]
|
|
913
|
-
srLog.error({ event: 'api/
|
|
914
|
-
return Promise.reject(`tokenNoPermission`)
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
//
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
console.log('
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
//check
|
|
985
|
-
if (!
|
|
986
|
-
console.log('
|
|
987
|
-
console.log('
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
//
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
//
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
//w-serv-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import get from 'lodash-es/get.js'
|
|
4
|
+
import map from 'lodash-es/map.js'
|
|
5
|
+
import keys from 'lodash-es/keys.js'
|
|
6
|
+
import join from 'lodash-es/join.js'
|
|
7
|
+
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
8
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
9
|
+
import ispint from 'wsemi/src/ispint.mjs'
|
|
10
|
+
import isearr from 'wsemi/src/isearr.mjs'
|
|
11
|
+
import isErr from 'wsemi/src/isErr.mjs'
|
|
12
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
13
|
+
import ispm from 'wsemi/src/ispm.mjs'
|
|
14
|
+
import cint from 'wsemi/src/cint.mjs'
|
|
15
|
+
import j2o from 'wsemi/src/j2o.mjs'
|
|
16
|
+
import strleft from 'wsemi/src/strleft.mjs'
|
|
17
|
+
import strright from 'wsemi/src/strright.mjs'
|
|
18
|
+
import strdelright from 'wsemi/src/strdelright.mjs'
|
|
19
|
+
import pm2resolve from 'wsemi/src/pm2resolve.mjs'
|
|
20
|
+
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
|
|
21
|
+
import fsIsFile from 'wsemi/src/fsIsFile.mjs'
|
|
22
|
+
import replace from 'wsemi/src/replace.mjs'
|
|
23
|
+
import strdelleft from 'wsemi/src/strdelleft.mjs'
|
|
24
|
+
import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
|
|
25
|
+
import pmKeyMutex from 'wsemi/src/pmKeyMutex.mjs'
|
|
26
|
+
import cacheSt from 'wsemi/src/cacheSt.mjs'
|
|
27
|
+
import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
|
|
28
|
+
import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
|
|
29
|
+
import checkOptRemoved from 'w-serv-orm/src/checkOptRemoved.mjs'
|
|
30
|
+
import ds from '../src/schema/index.mjs'
|
|
31
|
+
import { getUserRules } from '../src/plugins/mShare.mjs'
|
|
32
|
+
import procCore, { toOutKey } from './procCore.mjs'
|
|
33
|
+
import createLockSave from './lockSave.mjs'
|
|
34
|
+
import procLang from './procLang.mjs'
|
|
35
|
+
import srLogInit from './srLog.mjs'
|
|
36
|
+
import procStaInfor from './procStaInfor.mjs'
|
|
37
|
+
import { maskTok, maskQuery, maskUrl } from './maskLog.mjs'
|
|
38
|
+
import wrapInjected from './wrapInjected.mjs'
|
|
39
|
+
import validateRowsBeforeWrite from './validateRows.mjs'
|
|
40
|
+
import { createAuthorizeExec, wrapOperOrm } from './dataChannel.mjs'
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 權限伺服器
|
|
45
|
+
*
|
|
46
|
+
* @class
|
|
47
|
+
* @param {Function} WOrm 輸入資料庫ORM函數
|
|
48
|
+
* @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
|
|
49
|
+
* @param {String} db 輸入資料庫名稱字串
|
|
50
|
+
* @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n'。注意: token 回傳之'isAdmin'僅供識別,權限系統一律以自身users表該使用者之'isAdmin'欄位值為權威,不會被 token 宣稱之值複寫。函數拋錯或reject時視同查無使用者(回自家錯誤key,錯誤原文不回前端、不入log,只於srLog記warn)
|
|
51
|
+
* @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件(perm users 表之列)與來源字串from,通過此函數識別後回傳布林值(可為Promise),允許使用者回傳true,反之回傳false。from 值域: 'getUserByToken'(登入)、'getPerm'(api/getPerm)、以及資料通道之 apiType 'main'(RPC 與 ORM 執行)、'upload-controller'、'upload-slice'、'download-get-filename'、'download-get-file'、'download';資料通道與登入採同一授權強度(反查 perm users 且 isActive='y' 後再經本函數);函數拋錯或reject時視同回傳false
|
|
52
|
+
* @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false;函數拋錯或reject時視同回傳false
|
|
53
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
54
|
+
* @param {Integer} [opt.serverPort=11006] 輸入伺服器通訊port,預設11006。註:原opt.useCheckUser、opt.getUserById、opt.useExcludeWhenNotAdmin已隨w-serv-orm 1.1.9移除,給予undefined、null、false以外之值時建構即拋錯(錯誤訊息出自w-serv-orm之checkOptRemoved,並記srLog);資料通道之使用者授權由verifyConn(verifyClientUser)負責、表操作只開放select,刪除該三鍵即可,無須替代設定
|
|
55
|
+
* @param {Object} [opt.webName={}] 輸入站台名稱物件,至少包含語系eng與cht鍵的名稱,預設{}
|
|
56
|
+
* @param {Object} [opt.webDescription={}] 輸入站台描述物件,至少包含語系eng與cht鍵的名稱,預設{}
|
|
57
|
+
* @param {String} [opt.webLogo=''] 輸入站台logo字串,採base64格式,預設''
|
|
58
|
+
* @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
|
|
59
|
+
* @param {String} [opt.urlRedirect=''] 輸入錯誤時自動轉址字串,提供站台例如無法登入或驗證失敗時須自動轉址,預設''
|
|
60
|
+
* @param {String} [opt.mappingBy='id'] 輸入外部系統識別使用者token後所提供之資料物件,與權限系統之使用者資料物件,兩者間查找之對應欄位,可選'id'、'email'、'name',未給或非此三者時為'id',預設'id'
|
|
61
|
+
* @param {String} [opt.logFd='./logs'] 輸入log紀錄儲存位置字串,預設'./logs'
|
|
62
|
+
* @param {String} [opt.logInterval='hr'] 輸入log紀錄檔案拆檔時長字串,可選'hr'或'day',預設'hr'
|
|
63
|
+
* @param {Integer} [opt.logNumKeep=null] 輸入log檔保留數上限正整數,超出者自最舊刪除,未給採w-syslog預設(hr模式365*24、day模式365),預設null
|
|
64
|
+
* @returns {Object} 回傳w-serv-hapi之WServHapiServer事件物件,可監聽error事件;另提供ready函數(回傳promise,啟動成功resolve為hapi伺服器實體,失敗如埠被占用時reject)、stop函數(釋放資料同步等下游資源後停止伺服器)、getServer函數與getInstWConverServer函數。資料通道之表操作只開放select(新增、修改、刪除一律經updateTargets等函數);設定錯誤(含已移除之opt鍵)於建構時同步拋出並記srLog
|
|
65
|
+
* @example
|
|
66
|
+
*
|
|
67
|
+
* import WOrm from 'w-orm-lmdb/src/WOrmLmdb.mjs'
|
|
68
|
+
* import WWebPerm from './server/WWebPerm.mjs'
|
|
69
|
+
* import getSettings from './g_getSettings.mjs'
|
|
70
|
+
*
|
|
71
|
+
*
|
|
72
|
+
* //st
|
|
73
|
+
* let st = getSettings()
|
|
74
|
+
*
|
|
75
|
+
* let url = st.dbUrl
|
|
76
|
+
* let db = st.dbName
|
|
77
|
+
* let opt = {
|
|
78
|
+
*
|
|
79
|
+
* serverPort: 11006,
|
|
80
|
+
* subfolder: '', //mperm
|
|
81
|
+
* urlRedirect: 'https://www.google.com/', //本機測試時得先編譯, 再瀏覽: http://localhost:11006/
|
|
82
|
+
*
|
|
83
|
+
* webName: {
|
|
84
|
+
* 'eng': 'Permission Service',
|
|
85
|
+
* 'cht': '權限管理系統',
|
|
86
|
+
* },
|
|
87
|
+
* webDescription: {
|
|
88
|
+
* 'eng': 'A web service package for user permissions and management targets.',
|
|
89
|
+
* 'cht': 'A web service package for user permissions and management targets.',
|
|
90
|
+
* },
|
|
91
|
+
* webLogo: 'data:image/svg+xml;base64,...',
|
|
92
|
+
*
|
|
93
|
+
* }
|
|
94
|
+
*
|
|
95
|
+
* let getUserByToken = async (token) => {
|
|
96
|
+
* // return {} //測試無法登入
|
|
97
|
+
* if (token === '{token-for-application}') { //提供外部應用系統作為存取使用者
|
|
98
|
+
* return {
|
|
99
|
+
* id: 'id-for-application',
|
|
100
|
+
* name: 'application',
|
|
101
|
+
* email: 'application@example.com',
|
|
102
|
+
* isAdmin: 'y',
|
|
103
|
+
* }
|
|
104
|
+
* }
|
|
105
|
+
* if (token === 'sys') { //開發階段w-ui-loginout自動給予browser使用者(且位於localhost)的token為sys
|
|
106
|
+
* return {
|
|
107
|
+
* id: 'id-for-admin',
|
|
108
|
+
* name: 'tester',
|
|
109
|
+
* email: 'admin@example.com', //mappingBy為email, 開發階段時會使用email找到所建置之使用者資料
|
|
110
|
+
* isAdmin: 'y',
|
|
111
|
+
* }
|
|
112
|
+
* }
|
|
113
|
+
* console.log('invalid token') //勿印權杖值
|
|
114
|
+
* console.log('於生產環境時得加入SSO等驗證token機制')
|
|
115
|
+
* return {}
|
|
116
|
+
* }
|
|
117
|
+
*
|
|
118
|
+
* let verifyClientUser = (user, from) => {
|
|
119
|
+
* console.log('verifyClientUser/user', user)
|
|
120
|
+
* // return false //測試無法登入
|
|
121
|
+
* console.log('於生產環境時得加入限制瀏覽器使用者身份機制')
|
|
122
|
+
* return user.isAdmin === 'y' //測試僅系統管理者使用
|
|
123
|
+
* }
|
|
124
|
+
*
|
|
125
|
+
* let verifyAppUser = (user, from) => {
|
|
126
|
+
* console.log('verifyAppUser/user', user)
|
|
127
|
+
* // return false //測試無法登入
|
|
128
|
+
* console.log('於生產環境時得加入限制應用程式使用者身份機制')
|
|
129
|
+
* return user.isAdmin === 'y' //測試僅系統管理者使用
|
|
130
|
+
* }
|
|
131
|
+
*
|
|
132
|
+
* //WWebPerm
|
|
133
|
+
* let instWWebPerm = WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, opt)
|
|
134
|
+
*
|
|
135
|
+
* instWWebPerm.on('error', (err) => {
|
|
136
|
+
* console.log(err)
|
|
137
|
+
* })
|
|
138
|
+
*
|
|
139
|
+
*/
|
|
140
|
+
function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, opt = {}) {
|
|
141
|
+
let instWServHapiServer = null
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
//check WOrm
|
|
145
|
+
if (!isfun(WOrm)) {
|
|
146
|
+
console.log('invalid WOrm', WOrm)
|
|
147
|
+
throw new Error('invalid WOrm')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
//check url
|
|
152
|
+
if (!isestr(url)) {
|
|
153
|
+
console.log('invalid url', url)
|
|
154
|
+
throw new Error('invalid url')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
//check db
|
|
159
|
+
if (!isestr(db)) {
|
|
160
|
+
console.log('invalid db', db)
|
|
161
|
+
throw new Error('invalid db')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
//check getUserByToken
|
|
166
|
+
if (!isfun(getUserByToken)) {
|
|
167
|
+
console.log('invalid getUserByToken', getUserByToken)
|
|
168
|
+
throw new Error('invalid getUserByToken')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
//check verifyClientUser
|
|
173
|
+
if (!isfun(verifyClientUser)) {
|
|
174
|
+
console.log('invalid verifyClientUser', verifyClientUser)
|
|
175
|
+
throw new Error('invalid verifyClientUser')
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
//check verifyAppUser
|
|
180
|
+
if (!isfun(verifyAppUser)) {
|
|
181
|
+
console.log('invalid verifyAppUser', verifyAppUser)
|
|
182
|
+
throw new Error('invalid verifyAppUser')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
//serverPort
|
|
187
|
+
let serverPort = get(opt, 'serverPort')
|
|
188
|
+
if (!ispint(serverPort)) {
|
|
189
|
+
serverPort = 11006
|
|
190
|
+
}
|
|
191
|
+
serverPort = cint(serverPort)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
//webName
|
|
195
|
+
let webName = get(opt, 'webName', {})
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
//webDescription
|
|
199
|
+
let webDescription = get(opt, 'webDescription', {})
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
//webLogo
|
|
203
|
+
let webLogo = get(opt, 'webLogo', '')
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
//subfolder
|
|
207
|
+
let subfolder = get(opt, 'subfolder', '')
|
|
208
|
+
if (isestr(subfolder)) {
|
|
209
|
+
if (strright(subfolder, 1) === '/') { //右邊不需要給「/」
|
|
210
|
+
subfolder = strdelright(subfolder, 1)
|
|
211
|
+
}
|
|
212
|
+
if (strleft(subfolder, 1) !== '/') { //左邊需要給「/」
|
|
213
|
+
subfolder = `/${subfolder}`
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
//urlRedirect
|
|
219
|
+
let urlRedirect = get(opt, 'urlRedirect', '')
|
|
220
|
+
// console.log('urlRedirect', urlRedirect)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
//params
|
|
224
|
+
let showLanguage = get(opt, 'showLanguage', 'n')
|
|
225
|
+
let language = get(opt, 'language', 'eng')
|
|
226
|
+
let showModeEditTargets = get(opt, 'showModeEditTargets', 'n')
|
|
227
|
+
let showModeEditPemis = get(opt, 'showModeEditPemis', 'n')
|
|
228
|
+
let showModeEditGrups = get(opt, 'showModeEditGrups', 'n')
|
|
229
|
+
let showModeEditUsers = get(opt, 'showModeEditUsers', 'n')
|
|
230
|
+
let modeEditTargets = get(opt, 'modeEditTargets', 'n')
|
|
231
|
+
let modeEditPemis = get(opt, 'modeEditPemis', 'n')
|
|
232
|
+
let modeEditGrups = get(opt, 'modeEditGrups', 'n')
|
|
233
|
+
let modeEditUsers = get(opt, 'modeEditUsers', 'n')
|
|
234
|
+
let widthTargetId = get(opt, 'widthTargetId', '')
|
|
235
|
+
let widthTargetDescription = get(opt, 'widthTargetDescription', '')
|
|
236
|
+
let widthPemisName = get(opt, 'widthPemisName', '')
|
|
237
|
+
let widthPemisDescription = get(opt, 'widthPemisDescription', '')
|
|
238
|
+
let widthGrupsName = get(opt, 'widthGrupsName', '')
|
|
239
|
+
let widthGrupsDescription = get(opt, 'widthGrupsDescription', '')
|
|
240
|
+
let widthUsersName = get(opt, 'widthUsersName', '')
|
|
241
|
+
let widthUsersEmail = get(opt, 'widthUsersEmail', '')
|
|
242
|
+
let widthUsersDescription = get(opt, 'widthUsersDescription', '')
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
//mappingBy
|
|
246
|
+
let mappingBy = get(opt, 'mappingBy', '')
|
|
247
|
+
if (mappingBy !== 'id' && mappingBy !== 'email' && mappingBy !== 'name') {
|
|
248
|
+
mappingBy = 'id'
|
|
249
|
+
}
|
|
250
|
+
// console.log('mappingBy', mappingBy)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
//kpLangExt
|
|
254
|
+
let kpLangExt = get(opt, 'kpLangExt', null)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
//kpLang
|
|
258
|
+
let kpLang = procLang({ kpLangExt, webName, webDescription })
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
//srLog
|
|
262
|
+
let srLog = srLogInit(opt)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
//w-serv-orm 1.1.9 已移除之設定鍵(useCheckUser / getUserById / useExcludeWhenNotAdmin): 給予 undefined、null、false 以外之值即拋錯(fail-fast, ADR-026; 規則帳本 R-ORM-03),
|
|
266
|
+
//判準與訊息單一來源在 w-serv-orm 之 checkOptRemoved; 於 srLogInit 之後檢查以便記 key, 並先於 WServOrm 建構(不開啟資料庫).
|
|
267
|
+
//why fail-fast(而非 w-web-sso ADR-083 之忽略+警告): 本套件過去把三鍵透傳給 w-serv-orm 且實際生效(含依使用者過濾 select 結果),
|
|
268
|
+
//靜默忽略會無聲失去部署方以為仍在之可見範圍保證; sso 過去即已忽略三鍵, 其「忽略並警告」係延續自身歷史, 兩者不必一致.
|
|
269
|
+
try {
|
|
270
|
+
checkOptRemoved(opt)
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
srLog.error({ event: 'WWebPerm-init-error', msg: err.message })
|
|
274
|
+
throw err
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
//注入函數一律經包裝(W 契約, spec/設計要點與取捨.md ADR-023), 以包裝版取代三個參數
|
|
279
|
+
//why: 注入函數拋錯或 reject 時原會被 await 原樣上拋, 其原文經 pm2resolve 回前端、經 verifyConn 之 msg 入 srLog、經 error 事件印出;
|
|
280
|
+
//部署方常以 w-web-sso 對外 helper(≤1.1.4)包裝 getUserByToken, 其 reject 字串含已代入系統介接權杖與使用者權杖之完整網址.
|
|
281
|
+
//包裝後失敗視同否定結果(getUserByToken → null 等同查無, verify* → false 等同無權限), 走各呼叫點既有分支回自家 key;
|
|
282
|
+
//上游原文不回前端、不入 log, 只於此記 warn(白名單 key, 或型別與 Error.name). 同一次失敗 log 2~3 筆: 此處 warn(因) + 呼叫點 error(果).
|
|
283
|
+
//以下取代後, 本函數內除此三行外不得再直接使用原始參數(盤點見 CLAUDE_rulebook.md「權杖不外洩」, 測試 unit-wrapInjected WRAP-SITE-*)
|
|
284
|
+
let logInjectFail = (name) => {
|
|
285
|
+
return (d) => {
|
|
286
|
+
srLog.warn({ event: `inject-${name}-fail`, ...d })
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
getUserByToken = wrapInjected(getUserByToken, { failValue: null, onFail: logInjectFail('getUserByToken') })
|
|
290
|
+
verifyClientUser = wrapInjected(verifyClientUser, { failValue: false, onFail: logInjectFail('verifyClientUser') })
|
|
291
|
+
verifyAppUser = wrapInjected(verifyAppUser, { failValue: false, onFail: logInjectFail('verifyAppUser') })
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
//WServOrm, w-serv-orm 1.1.10 起無設定鍵(不帶 opt)
|
|
295
|
+
let wp = {}
|
|
296
|
+
try {
|
|
297
|
+
wp = WServOrm(ds, WOrm, url, db)
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
console.log(err)
|
|
301
|
+
srLog.error({ event: 'WServOrm-init-error', msg: (err && err.message) || 'WServOrmInitFailed' })
|
|
302
|
+
throw err //fail-fast: ORM 初始化失敗直接終止啟動, 不以 wp={} 繼續組裝出「port 起得來但 woItems/procOrm 為 undefined、CRUD 全 crash」的半死伺服器
|
|
303
|
+
}
|
|
304
|
+
let { woItems, procOrm } = wp
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
//pmKeyMutex: per-key in-memory mutex, 同 key 序列化、不同 key 並行.
|
|
308
|
+
//perm 為「整表批次 diff」寫入, key 粒度為 {op}:{keyTable}, 防同表並行 race (lost update).
|
|
309
|
+
let kmx = pmKeyMutex()
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
//lockSave: 後台整表寫入(updateTabItems)之雙擊防護(後端, 見 lockSave.mjs, ADR-025): 以「updateTabItems:{表名}:{操作者 id}」於 cacheSt 原子占位,
|
|
313
|
+
//同一操作者之同表寫入處理中再送出即 reject 'saveInProgress'(不排隊); 包在 kmx 之外層, 不同操作者之並行仍由 kmx 序列化(ADR-012)
|
|
314
|
+
let cst = cacheSt()
|
|
315
|
+
let lockSave = createLockSave(cst)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
//procCore: 整表批次 diff 寫入邏輯 (updateTabItems + updateTargets/Pemis/Grups/Users)
|
|
319
|
+
let pc = procCore(woItems, procOrm, { srLog, kmx, lockSave })
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
//procStaInfor: 統計資訊, fdLog 對齊 srLog 使用同一目錄(讀 settings.logFd, fallback 邏輯同 srLog.mjs); mock 由 settings.staEventMock 注入(e2e 統計圖穩定用)
|
|
323
|
+
let logFd = get(opt, 'logFd', '')
|
|
324
|
+
if (!isestr(logFd)) {
|
|
325
|
+
logFd = './logs'
|
|
326
|
+
}
|
|
327
|
+
let psi = procStaInfor({ fdLog: logFd, mock: get(opt, 'staEventMock', false), srLog })
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
//getWebInfor
|
|
331
|
+
let getWebInfor = (userId) => {
|
|
332
|
+
|
|
333
|
+
//info
|
|
334
|
+
srLog.info({ event: 'getWebInfor-success', userId })
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
|
|
338
|
+
// webName, //已併入kpLang
|
|
339
|
+
// webDescription, //已併入kpLang
|
|
340
|
+
webLogo,
|
|
341
|
+
// urlRedirect, //登入失敗就需要轉址, 故須通過html模板取代提供, 無法用api提供
|
|
342
|
+
|
|
343
|
+
showLanguage,
|
|
344
|
+
language,
|
|
345
|
+
kpLang,
|
|
346
|
+
|
|
347
|
+
showModeEditTargets,
|
|
348
|
+
showModeEditPemis,
|
|
349
|
+
showModeEditGrups,
|
|
350
|
+
showModeEditUsers,
|
|
351
|
+
modeEditTargets,
|
|
352
|
+
modeEditPemis,
|
|
353
|
+
modeEditGrups,
|
|
354
|
+
modeEditUsers,
|
|
355
|
+
|
|
356
|
+
widthTargetId,
|
|
357
|
+
widthTargetDescription,
|
|
358
|
+
widthPemisName,
|
|
359
|
+
widthPemisDescription,
|
|
360
|
+
widthGrupsName,
|
|
361
|
+
widthGrupsDescription,
|
|
362
|
+
widthUsersName,
|
|
363
|
+
widthUsersEmail,
|
|
364
|
+
widthUsersDescription,
|
|
365
|
+
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
//checkUser
|
|
371
|
+
let checkUser = async(user) => {
|
|
372
|
+
let id = get(user, 'id', '')
|
|
373
|
+
if (!isestr(id)) {
|
|
374
|
+
console.log('user', user)
|
|
375
|
+
srLog.error({ event: 'checkUser-error', msg: 'cannotGetUserId' })
|
|
376
|
+
return Promise.reject(`cannotGetUserId`)
|
|
377
|
+
}
|
|
378
|
+
let email = get(user, 'email', '')
|
|
379
|
+
if (!isestr(email)) {
|
|
380
|
+
console.log('user', user)
|
|
381
|
+
srLog.error({ event: 'checkUser-error', msg: 'cannotGetUserEmail' })
|
|
382
|
+
return Promise.reject(`cannotGetUserEmail`)
|
|
383
|
+
}
|
|
384
|
+
let name = get(user, 'name', '')
|
|
385
|
+
if (!isestr(name)) {
|
|
386
|
+
console.log('user', user)
|
|
387
|
+
srLog.error({ event: 'checkUser-error', msg: 'cannotGetUserName' })
|
|
388
|
+
return Promise.reject(`cannotGetUserName`)
|
|
389
|
+
}
|
|
390
|
+
let isAdmin = get(user, 'isAdmin', '')
|
|
391
|
+
if (isAdmin !== 'y' && isAdmin !== 'n') {
|
|
392
|
+
console.log('user', user)
|
|
393
|
+
console.log('user.isAdmin is not y or n', user.isAdmin)
|
|
394
|
+
srLog.error({ event: 'checkUser-error', msg: 'cannotGetUserRole' })
|
|
395
|
+
return Promise.reject(`cannotGetUserRole`)
|
|
396
|
+
}
|
|
397
|
+
return true
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
//getTokenUser
|
|
402
|
+
let getTokenUser = async(token) => {
|
|
403
|
+
//基於token查找使用者, 會基於外部getUserByToken以及perm內部users進行查找並比對
|
|
404
|
+
|
|
405
|
+
//userSelf
|
|
406
|
+
let userSelf = null
|
|
407
|
+
if (isestr(token)) {
|
|
408
|
+
userSelf = getUserByToken(token)
|
|
409
|
+
if (ispm(userSelf)) {
|
|
410
|
+
userSelf = await userSelf
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// console.log('userSelf', userSelf)
|
|
414
|
+
|
|
415
|
+
//check
|
|
416
|
+
if (!iseobj(userSelf)) {
|
|
417
|
+
console.log(`token`, maskTok(token))
|
|
418
|
+
srLog.error({ event: 'getTokenUser-error', msg: 'cannotFindUserFromToken' })
|
|
419
|
+
return Promise.reject(`cannotFindUserFromToken`)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
//check userSelf
|
|
423
|
+
await checkUser(userSelf)
|
|
424
|
+
|
|
425
|
+
//須反查perm內users, 提供正規化屬性
|
|
426
|
+
let vSelf = get(userSelf, mappingBy, '')
|
|
427
|
+
// console.log('vSelf', vSelf)
|
|
428
|
+
|
|
429
|
+
//check
|
|
430
|
+
if (!isestr(vSelf)) {
|
|
431
|
+
console.log('userSelf', userSelf)
|
|
432
|
+
console.log('mappingBy', mappingBy)
|
|
433
|
+
srLog.error({ event: 'getTokenUser-error', msg: 'cannotGetUserProp', mappingBy })
|
|
434
|
+
return Promise.reject(`cannotGetUserProp`)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
//userFind
|
|
438
|
+
let userFind = await woItems.users.select({ [mappingBy]: vSelf, isActive: 'y' })
|
|
439
|
+
userFind = get(userFind, 0, null)
|
|
440
|
+
// console.log('userFind', userFind)
|
|
441
|
+
|
|
442
|
+
//check
|
|
443
|
+
if (!iseobj(userFind)) {
|
|
444
|
+
console.log('userSelf', userSelf)
|
|
445
|
+
console.log('mappingBy', mappingBy)
|
|
446
|
+
srLog.error({ event: 'getTokenUser-error', msg: 'cannotGetUserFromPerm', mappingBy })
|
|
447
|
+
return Promise.reject(`cannotGetUserFromPerm`)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
//isAdmin 權威為 perm users 表之欄位值, 不以 token(getUserByToken)宣稱之 isAdmin 複寫。
|
|
451
|
+
//(2026-07-11 稽核 A-C1 裁決(b): 原此處有一段恆為 no-op 之「複寫isAdmin」死碼
|
|
452
|
+
// (userFind.isAdmin = isAdminSelf, 把自己指派給自己), 與 JSDoc 舊文「輸入'y'時會複寫」互斥;
|
|
453
|
+
// 依業主裁決採保守解: 刪除死碼、文件對齊實際行為, 不啟用 token→admin 提權路徑。)
|
|
454
|
+
|
|
455
|
+
return userFind
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
//getAndVerifyClientUser
|
|
460
|
+
let getAndVerifyClientUser = async (token, from = '') => {
|
|
461
|
+
//基於token查找client使用者, 會基於外部getUserByToken以及perm內部users進行查找並比對
|
|
462
|
+
|
|
463
|
+
//getTokenUser
|
|
464
|
+
let user = await getTokenUser(token)
|
|
465
|
+
|
|
466
|
+
//verifyClientUser
|
|
467
|
+
let b = verifyClientUser(user, from)
|
|
468
|
+
if (ispm(b)) {
|
|
469
|
+
b = await b
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
//check
|
|
473
|
+
if (!b) {
|
|
474
|
+
console.log('user', user)
|
|
475
|
+
srLog.error({ event: 'getAndVerifyClientUser-error', msg: 'userNoPermission', from })
|
|
476
|
+
return Promise.reject(`userNoPermission`)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return user
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
//getAndVerifyAppUser
|
|
484
|
+
let getAndVerifyAppUser = async (token, from = '') => {
|
|
485
|
+
//基於token查找app使用者, 會基於外部getUserByToken進行查找, 故只要getUserByToken提供使用者即可, 可支援getUserByToken給予針對應用程式所產生之虛擬使用者
|
|
486
|
+
|
|
487
|
+
//user
|
|
488
|
+
let user = null
|
|
489
|
+
if (isestr(token)) {
|
|
490
|
+
user = getUserByToken(token)
|
|
491
|
+
if (ispm(user)) {
|
|
492
|
+
user = await user
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// console.log('user', user)
|
|
496
|
+
|
|
497
|
+
//check
|
|
498
|
+
if (!iseobj(user)) {
|
|
499
|
+
console.log(`token`, maskTok(token))
|
|
500
|
+
srLog.error({ event: 'getAndVerifyAppUser-error', msg: 'cannotFindUserFromToken' })
|
|
501
|
+
return Promise.reject(`cannotFindUserFromToken`)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
//verifyAppUser
|
|
505
|
+
let b = verifyAppUser(user, from)
|
|
506
|
+
if (ispm(b)) {
|
|
507
|
+
b = await b
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
//check
|
|
511
|
+
if (!b) {
|
|
512
|
+
console.log('user', user)
|
|
513
|
+
srLog.error({ event: 'getAndVerifyAppUser-error', msg: 'userNoPermission', from })
|
|
514
|
+
return Promise.reject(`userNoPermission`)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return user
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
//p
|
|
522
|
+
let p = {
|
|
523
|
+
//checkToken, 資料通道(/api/main 之 RPC 與 ORM 執行、上傳、下載)之連線驗證, 與 api/getUserByToken(登入)採同一授權強度:
|
|
524
|
+
//外部 getUserByToken → 欄位齊備 → 反查 perm users(mappingBy)且 isActive='y' → verifyClientUser(user, from)
|
|
525
|
+
//why: 2026-09-07 前僅驗外部 token 欄位齊備(checkUser), 不查 perm users 與 isActive, 亦不經 verifyClientUser,
|
|
526
|
+
//致不在 perm users 表之外部使用者、被停用(isActive='n')之使用者仍可經資料通道讀寫全部權限資料(實測 A3/A4, 見 tmp/probe-report.json);
|
|
527
|
+
//改為與登入入口對稱, 使 UI 之「是否有效」與部署層 verifyClientUser 政策對資料通道亦生效.
|
|
528
|
+
//from 為 w-converhp 之 apiType('main' / 'upload-controller' / 'upload-slice' / 'download-get-filename' / 'download-get-file' / 'download'),
|
|
529
|
+
//供部署層 verifyClientUser(user, from) 區分入口.
|
|
530
|
+
checkToken: async(token, from = '') => {
|
|
531
|
+
|
|
532
|
+
//getAndVerifyClientUser, reject 時 key: cannotFindUserFromToken / cannotGetUserFromPerm / userNoPermission 等
|
|
533
|
+
await getAndVerifyClientUser(token, from)
|
|
534
|
+
|
|
535
|
+
return true
|
|
536
|
+
},
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
//parsePayload
|
|
541
|
+
let parsePayload = async (req) => {
|
|
542
|
+
|
|
543
|
+
//payload
|
|
544
|
+
let payload = get(req, 'payload')
|
|
545
|
+
|
|
546
|
+
//to obj
|
|
547
|
+
if (isestr(payload)) {
|
|
548
|
+
payload = j2o(payload)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
//check
|
|
552
|
+
if (!iseobj(payload)) {
|
|
553
|
+
console.log('payload', payload)
|
|
554
|
+
srLog.error({ event: 'parsePayload-error', msg: 'invalidPayload' })
|
|
555
|
+
return Promise.reject(`invalidPayload`)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
//from
|
|
559
|
+
let from = get(payload, 'from', '')
|
|
560
|
+
|
|
561
|
+
//check
|
|
562
|
+
if (!isestr(from)) {
|
|
563
|
+
console.log('payload', payload)
|
|
564
|
+
srLog.error({ event: 'parsePayload-error', msg: 'invalidFromInPayload' })
|
|
565
|
+
return Promise.reject(`invalidFromInPayload`)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
//rows
|
|
569
|
+
let rows = get(payload, 'rows', [])
|
|
570
|
+
|
|
571
|
+
//check
|
|
572
|
+
if (!isearr(rows)) {
|
|
573
|
+
console.log('payload', payload)
|
|
574
|
+
srLog.error({ event: 'parsePayload-error', msg: 'invalidRowsInPayload' })
|
|
575
|
+
return Promise.reject(`invalidRowsInPayload`)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
//inp
|
|
579
|
+
let inp = {
|
|
580
|
+
from,
|
|
581
|
+
rows,
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return inp
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
//syncAndReplaceTabs
|
|
589
|
+
let syncAndReplaceTabs = async (userId, inp, keyTable) => {
|
|
590
|
+
|
|
591
|
+
//序列化同表之並行整表 delAll+insert: 防 lost update (同 keyTable 序列化, 不同表並行)
|
|
592
|
+
return await kmx('syncAndReplaceTabs:' + keyTable, async () => {
|
|
593
|
+
|
|
594
|
+
//from
|
|
595
|
+
let from = get(inp, 'from', '')
|
|
596
|
+
// console.log('from', from)
|
|
597
|
+
|
|
598
|
+
//check
|
|
599
|
+
if (!isestr(from)) {
|
|
600
|
+
console.log('inp', inp)
|
|
601
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidFrom', keyTable })
|
|
602
|
+
return Promise.reject(`invalidFrom`)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
//rows
|
|
606
|
+
let rows = get(inp, 'rows', [])
|
|
607
|
+
// console.log(keyTable, rows)
|
|
608
|
+
|
|
609
|
+
//check
|
|
610
|
+
if (!isearr(rows)) {
|
|
611
|
+
console.log('inp', inp)
|
|
612
|
+
console.log('keyTable', keyTable)
|
|
613
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', keyTable })
|
|
614
|
+
return Promise.reject(`invalidRows`)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
//save from
|
|
618
|
+
rows = map(rows, (v, k) => {
|
|
619
|
+
// v.order = k + 1 //order大部分由外部給予, 不能複寫
|
|
620
|
+
v.from = from
|
|
621
|
+
return v
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
//ltdtmapping
|
|
625
|
+
rows = ltdtmapping(rows, ds[keyTable].keys)
|
|
626
|
+
// console.log('ltdtmapping rows', rows)
|
|
627
|
+
|
|
628
|
+
//寫入前驗證(規則帳本 R-ORM-01, ADR-026): 於 delAll 之前擋下可預判之寫入失敗, 該 from 之既有列不變(ADR-008 防誤清源).
|
|
629
|
+
//why: 先 delAll 再 insert, 而 w-serv-orm 1.1.8 起對「會被 clearXSS 改寫之主鍵」整批拒絕、LMDB 對 > 1978 B 之主鍵整批拒絕,
|
|
630
|
+
//修正前遇之即該 from 之既有列全數清空(2026-10-14 V4/V7 實測). 主鍵問題 → invalidRows; 任一字串欄位含標記 → htmlMarkupNotAllowed
|
|
631
|
+
let ve = validateRowsBeforeWrite(rows)
|
|
632
|
+
if (ve !== null) {
|
|
633
|
+
let key = (ve.reason === 'htmlMarkup') ? 'htmlMarkupNotAllowed' : 'invalidRows'
|
|
634
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: key, reason: ve.reason, keyTable, from, row: ve.k, field: ve.field })
|
|
635
|
+
return Promise.reject(key)
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
//跨 from 同鍵(規則帳本 R-SYNC-01): 本批有給之主鍵若已屬其他 from, 整批拒絕且不進 delAll(本 from 與他 from 之既有列皆不變).
|
|
639
|
+
//why: insert 對已存在之主鍵略過而整批仍回 success, 該列不會寫入本 from(靜默遺失); w-serv-orm 1.1.11 起數字主鍵存為其字串形式(原被換成隨機主鍵),
|
|
640
|
+
//以流水號為主鍵之各應用即互撞(2026-10-14 w-serv-orm 實測). 此處直呼 woItems(不經 procOrm 之轉型), 查詢條件一律用 String(id)(R-ORM-04);
|
|
641
|
+
//同 from 之既有列不算衝突(隨後由 delAll 重寫); 本段與 delAll/insert 同在 kmx 內(同表之並行同步已序列化)
|
|
642
|
+
let keyOfId = (v) => {
|
|
643
|
+
let id = get(v, 'id')
|
|
644
|
+
return (isestr(id) || (typeof id === 'number' && Number.isFinite(id))) ? String(id) : null
|
|
645
|
+
}
|
|
646
|
+
let sids = rows.map(keyOfId).filter((s) => s !== null)
|
|
647
|
+
if (sids.length > 0) {
|
|
648
|
+
let rsExist = null
|
|
649
|
+
try {
|
|
650
|
+
rsExist = await woItems[keyTable].select({ id: { $in: sids } })
|
|
651
|
+
}
|
|
652
|
+
catch (err) {
|
|
653
|
+
let raw = (typeof err === 'string') ? err : ((err && err.message) || String(err))
|
|
654
|
+
let key = toOutKey(err, 'anUnexpectedErrorOccurred') //尚未寫入任何資料, 不回 saveWriteFail
|
|
655
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: key, raw, keyTable, from, step: 'select' })
|
|
656
|
+
return Promise.reject(key)
|
|
657
|
+
}
|
|
658
|
+
let fromById = new Map()
|
|
659
|
+
for (let v of rsExist) {
|
|
660
|
+
fromById.set(String(v.id), v.from)
|
|
661
|
+
}
|
|
662
|
+
let k = rows.findIndex((v) => {
|
|
663
|
+
let s = keyOfId(v)
|
|
664
|
+
return s !== null && fromById.has(s) && fromById.get(s) !== from
|
|
665
|
+
})
|
|
666
|
+
if (k >= 0) {
|
|
667
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', reason: 'idOtherFrom', keyTable, from, row: k, field: 'id' })
|
|
668
|
+
return Promise.reject('invalidRows')
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
//delAll from, insert; ORM/I/O 失敗之原文只入 srLog, 外部應用收 saveWriteFail(規則帳本 R-ORM-02)
|
|
673
|
+
let r = null
|
|
674
|
+
try {
|
|
675
|
+
await woItems[keyTable].delAll({ from })
|
|
676
|
+
r = await procOrm(userId, keyTable, 'insert', rows) //須使用procOrm才有辦法自動給予相關欄位
|
|
677
|
+
}
|
|
678
|
+
catch (err) {
|
|
679
|
+
let raw = (typeof err === 'string') ? err : ((err && err.message) || String(err))
|
|
680
|
+
let key = toOutKey(err)
|
|
681
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: key, raw, keyTable, from })
|
|
682
|
+
return Promise.reject(key)
|
|
683
|
+
}
|
|
684
|
+
// console.log('r', r)
|
|
685
|
+
|
|
686
|
+
return r
|
|
687
|
+
|
|
688
|
+
})
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
//getGenUserByKV
|
|
693
|
+
let getGenUserByKV = async(keyUser, valueUser) => {
|
|
694
|
+
|
|
695
|
+
//userFind
|
|
696
|
+
let userFind = await woItems.users.select({ [keyUser]: valueUser, isActive: 'y' })
|
|
697
|
+
userFind = get(userFind, 0, null)
|
|
698
|
+
|
|
699
|
+
//check
|
|
700
|
+
if (!iseobj(userFind)) {
|
|
701
|
+
return null
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return userFind
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
//getGenUserByUserId
|
|
709
|
+
let getGenUserByUserId = async(userId) => {
|
|
710
|
+
|
|
711
|
+
//userFind
|
|
712
|
+
let userFind = await getGenUserByKV('id', userId)
|
|
713
|
+
|
|
714
|
+
//check
|
|
715
|
+
if (!iseobj(userFind)) {
|
|
716
|
+
return null
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return userFind
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
//getGenUserAndRulesByUserId
|
|
724
|
+
let getGenUserAndRulesByUserId = async (userId) => {
|
|
725
|
+
|
|
726
|
+
//userFind
|
|
727
|
+
let userFind = await getGenUserByUserId(userId)
|
|
728
|
+
// console.log('userFind', userFind)
|
|
729
|
+
|
|
730
|
+
//grups
|
|
731
|
+
let grups = await woItems.grups.select()
|
|
732
|
+
// console.log('grups', grups)
|
|
733
|
+
|
|
734
|
+
//pemis
|
|
735
|
+
let pemis = await woItems.pemis.select()
|
|
736
|
+
// console.log('pemis', pemis)
|
|
737
|
+
|
|
738
|
+
//targets
|
|
739
|
+
let targets = await woItems.targets.select()
|
|
740
|
+
// console.log('targets', targets)
|
|
741
|
+
|
|
742
|
+
//getUserRules
|
|
743
|
+
let kur = getUserRules(userFind, grups, pemis, targets)
|
|
744
|
+
// console.log('getUserRules', kur)
|
|
745
|
+
|
|
746
|
+
//check
|
|
747
|
+
if (!iseobj(kur)) {
|
|
748
|
+
console.log('userId', userId)
|
|
749
|
+
srLog.error({ event: 'getGenUserAndRulesByUserId-error', msg: 'cannotGetUserRules', userId })
|
|
750
|
+
return Promise.reject(`cannotGetUserRules`)
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
//grupsNames
|
|
754
|
+
let grupsNames = map(get(kur, 'grups', []), 'name') //kur.grups僅會提供isActive='y', 故grupsNames為有效權限群組名稱
|
|
755
|
+
grupsNames = join(grupsNames, ';')
|
|
756
|
+
|
|
757
|
+
return {
|
|
758
|
+
user: {
|
|
759
|
+
...userFind,
|
|
760
|
+
grupsNames,
|
|
761
|
+
},
|
|
762
|
+
// grups: get(kur, 'grups', []),
|
|
763
|
+
rules: get(kur, 'rules', []),
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
//pathStaticFiles
|
|
769
|
+
let pathStaticFiles = 'dist'
|
|
770
|
+
let npmPathStaticFiles = './node_modules/w-web-perm/dist'
|
|
771
|
+
if (fsIsFolder(npmPathStaticFiles)) {
|
|
772
|
+
pathStaticFiles = npmPathStaticFiles
|
|
773
|
+
}
|
|
774
|
+
// console.log('pathStaticFiles', pathStaticFiles)
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
//subfolder
|
|
778
|
+
let fnEntryIn = 'index.tmp'
|
|
779
|
+
let fnEntryOut = 'index.html'
|
|
780
|
+
try {
|
|
781
|
+
let fpEntryIn = path.resolve(pathStaticFiles, fnEntryIn)
|
|
782
|
+
if (!fsIsFile(fpEntryIn)) {
|
|
783
|
+
fpEntryIn = path.resolve(pathStaticFiles, fnEntryOut) //本機開發另使用html替代tmp
|
|
784
|
+
}
|
|
785
|
+
if (!fsIsFile(fpEntryIn)) {
|
|
786
|
+
console.log('fpEntryIn', fpEntryIn)
|
|
787
|
+
throw new Error(`invalid fpEntryIn`)
|
|
788
|
+
}
|
|
789
|
+
let fpEntryOut = path.resolve(pathStaticFiles, fnEntryOut)
|
|
790
|
+
let c = fs.readFileSync(fpEntryIn, 'utf8')
|
|
791
|
+
c = replace(c, '/mperm/', '{sfd}/') //方法同genEntry
|
|
792
|
+
c = replace(c, '{sfd}', subfolder)
|
|
793
|
+
c = replace(c, '{urlRedirect}', urlRedirect)
|
|
794
|
+
c = replace(c, '{language}', language)
|
|
795
|
+
fs.writeFileSync(fpEntryOut, c, 'utf8')
|
|
796
|
+
}
|
|
797
|
+
catch (err) {
|
|
798
|
+
console.log(err)
|
|
799
|
+
console.log(`can not generate ${fnEntryOut}`)
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
//apis
|
|
804
|
+
let apis = [
|
|
805
|
+
|
|
806
|
+
{
|
|
807
|
+
method: 'GET',
|
|
808
|
+
path: '/api/getUserByToken', //未登入主頁時需先檢測token, getUserByToken為w-ui-loginout預設值, 若要更改兩邊須同時修改
|
|
809
|
+
handler: async function (req, res) {
|
|
810
|
+
// console.log('getUserByToken', req)
|
|
811
|
+
|
|
812
|
+
async function core() {
|
|
813
|
+
//提供對象為browser使用者, 須先檢測token是否有效, 確認後回傳該使用者之資訊物件
|
|
814
|
+
|
|
815
|
+
//token
|
|
816
|
+
let token = get(req, 'query.token', '')
|
|
817
|
+
// console.log('token', token)
|
|
818
|
+
|
|
819
|
+
//check
|
|
820
|
+
if (!isestr(token)) {
|
|
821
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
822
|
+
console.log('token', maskTok(token))
|
|
823
|
+
console.log('[API]getUserByToken/check token: invalid token')
|
|
824
|
+
srLog.error({ event: 'api/getUserByToken-error', msg: 'tokenNoPermission' })
|
|
825
|
+
return Promise.reject(`tokenNoPermission`)
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
//getAndVerifyClientUser
|
|
829
|
+
let user = await getAndVerifyClientUser(token, 'getUserByToken')
|
|
830
|
+
// console.log('user', user)
|
|
831
|
+
|
|
832
|
+
//check
|
|
833
|
+
if (!iseobj(user)) {
|
|
834
|
+
console.log('token', maskTok(token))
|
|
835
|
+
console.log('[API]getUserByToken/check user: invalid user')
|
|
836
|
+
srLog.error({ event: 'api/getUserByToken-error', msg: 'tokenNoPermission' })
|
|
837
|
+
return Promise.reject(`tokenNoPermission`)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
//info
|
|
841
|
+
srLog.info({ event: 'api/getUserByToken-success', userId: get(user, 'id', '') })
|
|
842
|
+
|
|
843
|
+
return user
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
//pm2resolve core
|
|
847
|
+
let r = await pm2resolve(core)() //w-ui-loginout接收已預設格式用pm2resolve轉過, 須另提取state進行判斷
|
|
848
|
+
if (isErr(r.msg)) {
|
|
849
|
+
r.msg = r.msg.message
|
|
850
|
+
}
|
|
851
|
+
// console.log('getUserByToken', r)
|
|
852
|
+
|
|
853
|
+
return r
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
method: 'GET',
|
|
858
|
+
path: '/api/getPerm',
|
|
859
|
+
handler: async function (req, res) {
|
|
860
|
+
// console.log('getPerm', req)
|
|
861
|
+
|
|
862
|
+
async function core() {
|
|
863
|
+
//提供對象為node與browser端, 須為client使用者, 須先檢測token是否有效, 再依照token取得使用者資訊物件, 再由其userId查詢回傳該使用者之權限資訊物件
|
|
864
|
+
|
|
865
|
+
//token
|
|
866
|
+
let token = get(req, 'query.token', '')
|
|
867
|
+
// console.log('token', token)
|
|
868
|
+
|
|
869
|
+
//check
|
|
870
|
+
if (!isestr(token)) {
|
|
871
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
872
|
+
console.log('token', maskTok(token))
|
|
873
|
+
console.log('[API]getPerm/check token: invalid token')
|
|
874
|
+
srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
|
|
875
|
+
return Promise.reject(`tokenNoPermission`)
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
//getAndVerifyClientUser, 發起token的所屬使用者, 驗證僅對client使用者, 不得使用app使用者
|
|
879
|
+
let userSelf = await getAndVerifyClientUser(token, 'getPerm')
|
|
880
|
+
// console.log('userSelf', userSelf)
|
|
881
|
+
|
|
882
|
+
//check
|
|
883
|
+
if (!iseobj(userSelf)) {
|
|
884
|
+
console.log('token', maskTok(token))
|
|
885
|
+
console.log('[API]getPerm/check userSelf: invalid userSelf')
|
|
886
|
+
srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
|
|
887
|
+
return Promise.reject(`tokenNoPermission`)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
//userIdSelf, client使用者的userId
|
|
891
|
+
let userIdSelf = get(userSelf, 'id', '')
|
|
892
|
+
// console.log('userIdSelf', userIdSelf)
|
|
893
|
+
|
|
894
|
+
//check
|
|
895
|
+
if (!isestr(userIdSelf)) {
|
|
896
|
+
console.log('token', maskTok(token))
|
|
897
|
+
console.log('userSelf', userSelf)
|
|
898
|
+
console.log('[API]getPerm/check userIdSelf: invalid userIdSelf')
|
|
899
|
+
srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
|
|
900
|
+
return Promise.reject(`tokenNoPermission`)
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
//getGenUserAndRulesByUserId, 查找到的client使用者與權限
|
|
904
|
+
let userWithRulesSelf = await getGenUserAndRulesByUserId(userIdSelf)
|
|
905
|
+
// console.log('getGenUserAndRulesByUserId', r)
|
|
906
|
+
|
|
907
|
+
//check
|
|
908
|
+
if (!iseobj(userWithRulesSelf)) {
|
|
909
|
+
console.log('token', maskTok(token))
|
|
910
|
+
console.log('userSelf', userSelf)
|
|
911
|
+
console.log('userIdSelf', userIdSelf)
|
|
912
|
+
console.log('[API]getPerm/check userWithRulesSelf: invalid userWithRulesSelf')
|
|
913
|
+
srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
|
|
914
|
+
return Promise.reject(`tokenNoPermission`)
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
//info
|
|
918
|
+
srLog.info({ event: 'api/getPerm-success', userId: userIdSelf })
|
|
919
|
+
|
|
920
|
+
return userWithRulesSelf
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
//pm2resolve core
|
|
924
|
+
let r = await pm2resolve(core)()
|
|
925
|
+
if (isErr(r.msg)) {
|
|
926
|
+
r.msg = r.msg.message
|
|
927
|
+
}
|
|
928
|
+
// console.log('getPerm r', r)
|
|
929
|
+
|
|
930
|
+
return r
|
|
931
|
+
},
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
method: 'GET',
|
|
935
|
+
path: '/api/getPermUserInfor',
|
|
936
|
+
handler: async function (req, res) {
|
|
937
|
+
// console.log('getPermUserInfor', req)
|
|
938
|
+
|
|
939
|
+
async function core() {
|
|
940
|
+
//提供對象為node端, 須為app使用者, 須先檢測token是否有效, 再由其userId查詢回傳該使用者權限資訊物件
|
|
941
|
+
|
|
942
|
+
//tokenSelf
|
|
943
|
+
let tokenSelf = get(req, 'query.token', '')
|
|
944
|
+
// console.log('token', token)
|
|
945
|
+
|
|
946
|
+
//check
|
|
947
|
+
if (!isestr(tokenSelf)) {
|
|
948
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
949
|
+
console.log('[API]getPermUserInfor/check tokenSelf: invalid tokenSelf')
|
|
950
|
+
srLog.error({ event: 'api/getPermUserInfor-error', msg: 'tokenNoPermission' })
|
|
951
|
+
return Promise.reject(`tokenNoPermission`)
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
//getAndVerifyAppUser, 發起token的所屬使用者, 驗證須為app使用者
|
|
955
|
+
let userSelf = await getAndVerifyAppUser(tokenSelf, 'getPermUserInfor')
|
|
956
|
+
// console.log('userSelf', userSelf)
|
|
957
|
+
|
|
958
|
+
//check
|
|
959
|
+
if (!iseobj(userSelf)) {
|
|
960
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
961
|
+
console.log('tokenSelf', maskTok(tokenSelf))
|
|
962
|
+
console.log('[API]getPermUserInfor/check userSelf: invalid userSelf')
|
|
963
|
+
srLog.error({ event: 'api/getPermUserInfor-error', msg: 'tokenNoPermission' })
|
|
964
|
+
return Promise.reject(`tokenNoPermission`)
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
//userIdFind, 欲查找使用者的userId
|
|
968
|
+
let userIdFind = get(req, 'query.userId', '')
|
|
969
|
+
// console.log('userIdFind', userIdFind)
|
|
970
|
+
|
|
971
|
+
//check
|
|
972
|
+
if (!isestr(userIdFind)) {
|
|
973
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
974
|
+
console.log('tokenSelf', maskTok(tokenSelf))
|
|
975
|
+
console.log('[API]getPermUserInfor/check userIdFind: invalid userIdFind')
|
|
976
|
+
srLog.error({ event: 'api/getPermUserInfor-error', msg: 'tokenNoPermission' })
|
|
977
|
+
return Promise.reject(`tokenNoPermission`)
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
//getGenUserAndRulesByUserId, 查找到的使用者與權限
|
|
981
|
+
let userWithRulesFind = await getGenUserAndRulesByUserId(userIdFind)
|
|
982
|
+
// console.log('getGenUserAndRulesByUserId', userWithRulesFind)
|
|
983
|
+
|
|
984
|
+
//check
|
|
985
|
+
if (!iseobj(userWithRulesFind)) {
|
|
986
|
+
console.log('userIdFind', userIdFind)
|
|
987
|
+
console.log('[API]getPermUserInfor/check userWithRulesFind: invalid userWithRulesFind')
|
|
988
|
+
srLog.error({ event: 'api/getPermUserInfor-error', msg: 'userIdNoPermrules' })
|
|
989
|
+
return Promise.reject(`userIdNoPermrules`)
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
//info
|
|
993
|
+
srLog.info({ event: 'api/getPermUserInfor-success', userId: userIdFind })
|
|
994
|
+
|
|
995
|
+
return userWithRulesFind
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
//pm2resolve core
|
|
999
|
+
let r = await pm2resolve(core)() //w-ui-loginout接收已預設格式用pm2resolve轉過, 須另提取state進行判斷
|
|
1000
|
+
if (isErr(r.msg)) {
|
|
1001
|
+
r.msg = r.msg.message
|
|
1002
|
+
}
|
|
1003
|
+
// console.log('getPermUserInfor', r)
|
|
1004
|
+
|
|
1005
|
+
return r
|
|
1006
|
+
},
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
method: 'POST',
|
|
1010
|
+
path: '/syncAndReplaceTabs',
|
|
1011
|
+
handler: async function (req, res) {
|
|
1012
|
+
// console.log('syncAndReplaceTabs', req)
|
|
1013
|
+
// console.log('syncAndReplaceTabs payload', req.payload)
|
|
1014
|
+
|
|
1015
|
+
async function core() {
|
|
1016
|
+
//提供對象為node使用者, 須為系統管理者, 須先檢測token是否有效, 再進行數據變更
|
|
1017
|
+
|
|
1018
|
+
//token
|
|
1019
|
+
let token = get(req, 'query.token', '')
|
|
1020
|
+
|
|
1021
|
+
//check
|
|
1022
|
+
if (!isestr(token)) {
|
|
1023
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
1024
|
+
console.log('token', maskTok(token))
|
|
1025
|
+
console.log('[API]syncAndReplaceTabs/check token: invalid token')
|
|
1026
|
+
srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
|
|
1027
|
+
return Promise.reject(`tokenNoPermission`)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
//keyTable
|
|
1031
|
+
let keyTable = get(req, 'query.keyTable', '')
|
|
1032
|
+
|
|
1033
|
+
//check
|
|
1034
|
+
if (!isestr(keyTable)) {
|
|
1035
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
1036
|
+
console.log('keyTable', keyTable)
|
|
1037
|
+
console.log('[API]syncAndReplaceTabs/check keyTable: invalid keyTable')
|
|
1038
|
+
srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
|
|
1039
|
+
return Promise.reject(`tokenNoPermission`)
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
//check keyTable
|
|
1043
|
+
let allowedTables = ['targets', 'pemis', 'grups', 'users']
|
|
1044
|
+
if (!allowedTables.includes(keyTable)) {
|
|
1045
|
+
console.log('[API]syncAndReplaceTabs/check keyTable: unexpected keyTable', keyTable)
|
|
1046
|
+
srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'invalidKeyTable', keyTable })
|
|
1047
|
+
return Promise.reject(`invalidKeyTable`)
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
//getAndVerifyAppUser
|
|
1051
|
+
let user = await getAndVerifyAppUser(token, 'syncAndReplaceTabs')
|
|
1052
|
+
|
|
1053
|
+
//check user
|
|
1054
|
+
if (!iseobj(user)) {
|
|
1055
|
+
console.log('token', maskTok(token))
|
|
1056
|
+
console.log('[API]syncAndReplaceTabs/check user: invalid user')
|
|
1057
|
+
srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
|
|
1058
|
+
return Promise.reject(`tokenNoPermission`)
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
//userId
|
|
1062
|
+
let userId = get(user, 'id', '')
|
|
1063
|
+
|
|
1064
|
+
//check userId
|
|
1065
|
+
if (!isestr(userId)) {
|
|
1066
|
+
console.log('token', maskTok(token))
|
|
1067
|
+
console.log('user', user)
|
|
1068
|
+
console.log('[API]syncAndReplaceTabs/check userId: invalid userId')
|
|
1069
|
+
srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
|
|
1070
|
+
return Promise.reject(`tokenNoPermission`)
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
//parsePayload
|
|
1074
|
+
let inp = await parsePayload(req)
|
|
1075
|
+
|
|
1076
|
+
//syncAndReplaceTabs
|
|
1077
|
+
let r = await syncAndReplaceTabs(userId, inp, keyTable)
|
|
1078
|
+
|
|
1079
|
+
//info
|
|
1080
|
+
srLog.info({ event: 'api/syncAndReplaceTabs-success', userId, keyTable })
|
|
1081
|
+
|
|
1082
|
+
return r
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
//pm2resolve core
|
|
1086
|
+
let r = await pm2resolve(core)()
|
|
1087
|
+
if (isErr(r.msg)) {
|
|
1088
|
+
r.msg = r.msg.message
|
|
1089
|
+
}
|
|
1090
|
+
// console.log('syncAndReplaceTabs', r)
|
|
1091
|
+
|
|
1092
|
+
return r
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
]
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
//tableNamesExec, tableNamesSync
|
|
1099
|
+
let tableNamesExec = keys(ds)
|
|
1100
|
+
// let tableNamesSync = filter(tableNamesExec, (v) => {
|
|
1101
|
+
// return strright(v, 5) !== 'Items'//不同步數據
|
|
1102
|
+
// })
|
|
1103
|
+
let tableNamesSync = tableNamesExec
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
//WServHapiServer, w-serv-hapi 1.1.6 起設定錯誤於建構時同步拋出: 記 srLog(key)後轉拋(規則帳本 R-INIT-01, ADR-026)
|
|
1107
|
+
let optWServHapiServer = {
|
|
1108
|
+
port: serverPort,
|
|
1109
|
+
pathStaticFiles,
|
|
1110
|
+
apiName: 'api',
|
|
1111
|
+
apis,
|
|
1112
|
+
tokenType: 'Bearer',
|
|
1113
|
+
verifyConn: async ({ apiType, authorization, query, headers, req }) => {
|
|
1114
|
+
// console.log('verifyConn', `apiType[${apiType}]`, `authorization[${authorization}]`)
|
|
1115
|
+
|
|
1116
|
+
//token
|
|
1117
|
+
let token = strdelleft(authorization, 7) //刪除Bearer
|
|
1118
|
+
// console.log('token', token)
|
|
1119
|
+
|
|
1120
|
+
//check
|
|
1121
|
+
if (!isestr(token)) {
|
|
1122
|
+
console.log('apiType', apiType)
|
|
1123
|
+
console.log('authorization', maskTok(authorization))
|
|
1124
|
+
console.log('invalid token')
|
|
1125
|
+
srLog.error({ event: 'verifyConn-error', ok: false, apiType, msg: 'tokenNoPermission' }) //認證失敗須入 log 供稽核/統計(CLAUDE.md「log 記錄要點」; 同 w-web-api、w-web-task); 只記 key, 不記 authorization 原值
|
|
1126
|
+
return false
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
//checkToken, 失敗一律回 false(傳輸層回 permission denied); reject 之 err key 記於 verifyConn log 之 msg
|
|
1130
|
+
let b = false
|
|
1131
|
+
let msg = ''
|
|
1132
|
+
await p.checkToken(token, apiType)
|
|
1133
|
+
.then(() => {
|
|
1134
|
+
b = true
|
|
1135
|
+
})
|
|
1136
|
+
.catch((err) => {
|
|
1137
|
+
msg = (typeof err === 'string') ? err : ((err && err.message) || 'checkTokenFail')
|
|
1138
|
+
})
|
|
1139
|
+
// console.log('b', b)
|
|
1140
|
+
|
|
1141
|
+
//ip, 對齊sso慣例x-forwarded-for取第1個, fallback到req remoteAddress, 取不到則略過
|
|
1142
|
+
let ip = get(headers, 'x-forwarded-for', '').split(',')[0].trim()
|
|
1143
|
+
if (!isestr(ip)) {
|
|
1144
|
+
ip = get(req, 'info.remoteAddress', '') || get(req, 'socket.remoteAddress', '') || get(req, 'connection.remoteAddress', '')
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
//origin
|
|
1148
|
+
let origin = get(headers, 'origin', '')
|
|
1149
|
+
|
|
1150
|
+
//referer, 只記 origin + pathname(ADR-023): 前端以 ?token= 開站(w-ui-loginout 讀完不清網址), 同源請求之 Referer 預設帶完整網址, 原樣記錄即把使用者權杖寫入 srLog
|
|
1151
|
+
let referer = maskUrl(get(headers, 'referer', ''))
|
|
1152
|
+
|
|
1153
|
+
//info, 失敗時 msg 為 err key(cannotFindUserFromToken / cannotGetUserFromPerm / userNoPermission 等), 供資安稽核與統計
|
|
1154
|
+
if (b) {
|
|
1155
|
+
srLog.info({ event: 'verifyConn', ok: b, apiType, ip, origin, referer })
|
|
1156
|
+
}
|
|
1157
|
+
else {
|
|
1158
|
+
srLog.error({ event: 'verifyConn-error', ok: b, apiType, ip, origin, referer, msg })
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
return b
|
|
1162
|
+
},
|
|
1163
|
+
getUserIdByToken: async (token) => { //可使用async或sync函數
|
|
1164
|
+
// console.log('getUserIdByToken', token)
|
|
1165
|
+
let user = await getUserByToken(token)
|
|
1166
|
+
let userId = get(user, 'id', '')
|
|
1167
|
+
if (!isestr(userId)) {
|
|
1168
|
+
console.log('token', maskTok(token))
|
|
1169
|
+
console.log('userId', userId)
|
|
1170
|
+
srLog.error({ event: 'getUserIdByToken-error', msg: 'cannotFindUserId' })
|
|
1171
|
+
return Promise.reject(`cannotFindUserId`)
|
|
1172
|
+
}
|
|
1173
|
+
return userId
|
|
1174
|
+
},
|
|
1175
|
+
corsOrigins: ['*'],
|
|
1176
|
+
useDbOrm: true,
|
|
1177
|
+
kpOrm: woItems,
|
|
1178
|
+
//operOrm: procOrm 之包裝(輸入為 userId, tableName, methodName, input), ORM 拒絕之原文只入 srLog、對前端回 key(規則帳本 R-ORM-02)
|
|
1179
|
+
operOrm: wrapOperOrm(procOrm, { srLog }),
|
|
1180
|
+
tableNamesExec,
|
|
1181
|
+
//資料通道只開 select(ADR-026、ADR-015 Update; 規則帳本 R-WSD-01): 前端同步只用 select, 寫入一律經 kpFunExt 之 updateXxx(lockSave、kmx、寫入前驗證、整表比對);
|
|
1182
|
+
//裸 insert/save/del/delAll 繞過上述不變式, 2026-10-14 起不開放(直呼回 invalid func[表名.方法]); 對自寫客戶端直呼裸表操作之部署方為行為變更(README Upgrade Notes)
|
|
1183
|
+
methodsExec: ['select'],
|
|
1184
|
+
//authorizeExec: w-serv-webdata 1.1.7 起 useDbOrm:true 必填; 只對已取得 userId 之 select 放行(methodsExec 之外之第二道), 拒絕記 srLog event 'authorizeExec'.
|
|
1185
|
+
//連線者身分之授權在 verifyConn(getAndVerifyClientUser), 此處不重做
|
|
1186
|
+
authorizeExec: createAuthorizeExec({ srLog }),
|
|
1187
|
+
tableNamesSync,
|
|
1188
|
+
kpFunExt: { //接收參數第1個為userId, 之後才是前端給予參數
|
|
1189
|
+
|
|
1190
|
+
getWebInfor,
|
|
1191
|
+
|
|
1192
|
+
updateTargets: pc.updateTargets,
|
|
1193
|
+
updatePemis: pc.updatePemis,
|
|
1194
|
+
updateGrups: pc.updateGrups,
|
|
1195
|
+
//updateUsers, modeEditUsers='for:grups' 時 API 與 UI 一致只採納既有使用者之 cgrups 變更, 身分欄/新增/刪除/順序以資料庫為準
|
|
1196
|
+
updateUsers: (userId, rows) => {
|
|
1197
|
+
let opt = (modeEditUsers === 'for:grups') ? { pickKeysOnly: ['cgrups'] } : {}
|
|
1198
|
+
return pc.updateUsers(userId, rows, opt)
|
|
1199
|
+
},
|
|
1200
|
+
|
|
1201
|
+
getStaEvent: async (userId, timeLength, timeInterval) => {
|
|
1202
|
+
try {
|
|
1203
|
+
let rs = await psi.getStaEvent(userId, timeLength, timeInterval)
|
|
1204
|
+
srLog.info({ event: 'getStaEvent-success', userId })
|
|
1205
|
+
return rs
|
|
1206
|
+
}
|
|
1207
|
+
catch (err) {
|
|
1208
|
+
srLog.error({ event: 'getStaEvent-error', userId, msg: (typeof err === 'string') ? err : ((err && err.message) || 'cannotGetStaEvent') })
|
|
1209
|
+
throw err
|
|
1210
|
+
}
|
|
1211
|
+
},
|
|
1212
|
+
|
|
1213
|
+
getStaEventTable: async (userId) => {
|
|
1214
|
+
try {
|
|
1215
|
+
let rs = await psi.getStaEventTable(userId)
|
|
1216
|
+
srLog.info({ event: 'getStaEventTable-success', userId })
|
|
1217
|
+
return rs
|
|
1218
|
+
}
|
|
1219
|
+
catch (err) {
|
|
1220
|
+
srLog.error({ event: 'getStaEventTable-error', userId, msg: (typeof err === 'string') ? err : ((err && err.message) || 'cannotGetStaEventTable') })
|
|
1221
|
+
throw err
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
|
|
1225
|
+
},
|
|
1226
|
+
//fpTableTags: 各資料表時間戳檔案(供前端判定是否需重新同步)。
|
|
1227
|
+
//w-serv-hapi 1.0.87 起之正式名稱; 舊名 fnTableTags 於該版之前傳下去等同未給(w-serv-webdata 只讀 fpTableTags),
|
|
1228
|
+
//故 1.0.86 及之前實際寫的是預設之 ./tableTags.json, 升版後改用本檔名。
|
|
1229
|
+
fpTableTags: 'tableTags-web-perm.json',
|
|
1230
|
+
}
|
|
1231
|
+
try {
|
|
1232
|
+
instWServHapiServer = new WServHapiServer(optWServHapiServer)
|
|
1233
|
+
}
|
|
1234
|
+
catch (err) {
|
|
1235
|
+
console.log(err)
|
|
1236
|
+
srLog.error({ event: 'WServHapiServer-init-error', msg: (err && err.message) || 'WServHapiServerInitFailed' })
|
|
1237
|
+
throw err
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
return instWServHapiServer
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
export default WWebPerm
|