topbit 1.0.0 → 2.0.0

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.
Files changed (71) hide show
  1. package/LICENSE +128 -0
  2. package/README.cn.md +1562 -0
  3. package/README.md +1272 -0
  4. package/bin/app.js +17 -0
  5. package/bin/loadinfo.sh +18 -0
  6. package/bin/new-ctl.js +230 -0
  7. package/bin/newapp.js +22 -0
  8. package/cache/allow.js +130 -0
  9. package/cache/errserv.js +45 -0
  10. package/cache/minserv.js +167 -0
  11. package/cache/router.js +84 -0
  12. package/cache/rsa/localhost-cert.pem +19 -0
  13. package/cache/rsa/localhost-privkey.pem +28 -0
  14. package/cache/servsock.js +286 -0
  15. package/cache/sni.js +66 -0
  16. package/demo/allow.js +98 -0
  17. package/demo/group-api.js +161 -0
  18. package/demo/group-api2.js +109 -0
  19. package/demo/log.js +118 -0
  20. package/demo/memlimit.js +31 -0
  21. package/demo/min.js +7 -0
  22. package/demo/serv.js +15 -0
  23. package/images/middleware.jpg +0 -0
  24. package/images/titbit-middleware.png +0 -0
  25. package/images/titbit.png +0 -0
  26. package/package.json +38 -8
  27. package/src/bodyparser.js +420 -0
  28. package/src/connfilter.js +125 -0
  29. package/src/context1.js +166 -0
  30. package/src/context2.js +179 -0
  31. package/src/ctxpool.js +38 -0
  32. package/src/ext.js +318 -0
  33. package/src/fastParseUrl.js +426 -0
  34. package/src/headerLimit.js +18 -0
  35. package/src/http1.js +337 -0
  36. package/src/http2.js +337 -0
  37. package/src/httpc.js +251 -0
  38. package/src/loader/loader.js +999 -0
  39. package/src/logger.js +32 -0
  40. package/src/loggermsg.js +349 -0
  41. package/src/makeId.js +200 -0
  42. package/src/midcore.js +213 -0
  43. package/src/middleware1.js +104 -0
  44. package/src/middleware2.js +121 -0
  45. package/src/monitor.js +380 -0
  46. package/src/movefile.js +30 -0
  47. package/src/optionsCheck.js +54 -0
  48. package/src/randstring.js +23 -0
  49. package/src/router.js +682 -0
  50. package/src/sendmsg.js +27 -0
  51. package/src/strong.js +72 -0
  52. package/src/token/token.js +462 -0
  53. package/src/topbit.js +1291 -0
  54. package/src/versionCheck.js +31 -0
  55. package/test/test-bigctx.js +29 -0
  56. package/test/test-daemon-args.js +7 -0
  57. package/test/test-find.js +69 -0
  58. package/test/test-helper.js +81 -0
  59. package/test/test-route-sort.js +71 -0
  60. package/test/test-route.js +49 -0
  61. package/test/test-route2.js +51 -0
  62. package/test/test-run-args.js +7 -0
  63. package/test/test-url.js +52 -0
  64. package/tmp/buff-code +134 -0
  65. package/tmp/devplan +9 -0
  66. package/tmp/evt-test.js +34 -0
  67. package/tmp/fastParseUrl.js +302 -0
  68. package/tmp/router-rule.js +559 -0
  69. package/tmp/test-cdps.js +122 -0
  70. package/tmp/titbit.js +1286 -0
  71. package/main.js +0 -0
package/src/strong.js ADDED
@@ -0,0 +1,72 @@
1
+ 'use strict'
2
+
3
+ const process = require('node:process')
4
+
5
+ class Strong {
6
+
7
+ constructor(options = {}) {
8
+
9
+ this.catchErrors = [
10
+ 'TypeError', 'ReferenceError', 'RangeError', 'AssertionError', 'URIError', 'Error'
11
+ ]
12
+
13
+ this.quiet = false
14
+
15
+ this.errorHandle = (err, str) => {
16
+ if (this.catchErrors.indexOf(err.constructor.name) >= 0) {
17
+ if (!this.quiet) {
18
+ console.error(str, err)
19
+ }
20
+ return true
21
+ }
22
+
23
+ console.error(err)
24
+ process.exit(1)
25
+ }
26
+
27
+ if (typeof options !== 'object') {
28
+ options = {}
29
+ }
30
+
31
+ for (let k in options) {
32
+ switch (k) {
33
+
34
+ case 'catchErrors':
35
+ if (typeof options[k] === 'string') {
36
+ options[k] = [ options[k] ]
37
+ }
38
+
39
+ if (Array.isArray(options[k])) {
40
+ this.catchErrors = options[k]
41
+ }
42
+ break
43
+
44
+ case 'errorHandle':
45
+ if (typeof options[k] === 'function') {
46
+ this.errorHandle = options[k]
47
+ }
48
+ break
49
+
50
+ case 'quiet':
51
+ this.quiet = !!options[k]
52
+ break
53
+ }
54
+ }
55
+
56
+ }
57
+
58
+ init() {
59
+
60
+ process.on('unhandledRejection', (err, pr) => {
61
+ this.errorHandle(err, '--CATCH-REJECTION--')
62
+ })
63
+
64
+ process.on('uncaughtException', (err, origin) => {
65
+ this.errorHandle(err, '--CATCH-EXCEPTION--')
66
+ })
67
+
68
+ }
69
+
70
+ }
71
+
72
+ module.exports = Strong
@@ -0,0 +1,462 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('node:crypto')
4
+ const {Buffer} = require('node:buffer')
5
+
6
+ let _randstrList = [
7
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g',
8
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n',
9
+ 'o', 'p', 'q', 'r', 's', 't', 'u',
10
+ 'v', 'w', 'x', 'y', 'z',
11
+
12
+ '1', '2', '3', '4', '5', '6', '7', '8', '9'
13
+ ]
14
+
15
+ function randstring(length = 8) {
16
+ let rstr = ''
17
+ let ind = 0
18
+
19
+ for (let i = 0; i < length; i++) {
20
+ ind = parseInt(Math.random() * _randstrList.length)
21
+ rstr += _randstrList[ind]
22
+ }
23
+ return rstr
24
+ }
25
+
26
+ class TopbitToken {
27
+
28
+ constructor(options = {}) {
29
+
30
+ //sm4-cbc or aes-256-gcm
31
+ //openssl list -cipher-algorithms
32
+ this.algorithm = 'aes-256-gcm'
33
+
34
+ this.iv = randstring(12)
35
+ this.isGCM = true
36
+ this.ivLength = 12
37
+
38
+ this.key = randstring(32)
39
+
40
+ this.tokenEncoding = 'base64url'
41
+
42
+ //默认3小时有效
43
+ this.expires = 10800000
44
+
45
+ this.refresh = 0
46
+
47
+ this.failedCode = 401
48
+
49
+ /**
50
+ * tokenId用于识别token是否有效,如果发现token存在泄漏的可能,则在服务运行时,即可更改此值。
51
+ * 此时,再次验证token则会失效。
52
+ * */
53
+
54
+ this.tokenMap = new Map()
55
+
56
+ this.idKeyIV = new Map()
57
+
58
+ this.tokenIds = []
59
+
60
+ this.idIndex = -1
61
+
62
+ this.tokenTag = 'titbit-token'
63
+ this.tokenStartIndex = this.tokenTag.length + 1
64
+
65
+ if (typeof options !== 'object') {
66
+ options = {}
67
+ }
68
+
69
+ for (let k in options) {
70
+ switch (k) {
71
+ case 'iv':
72
+ this.iv = options[k]
73
+ break
74
+
75
+ case 'key':
76
+ this.key = options[k]
77
+ break
78
+
79
+ case 'expires':
80
+ if (typeof options[k] === 'number' && options[k] > 0) {
81
+ this.expires = parseInt(options[k] * 1000)
82
+ }
83
+ break
84
+
85
+ case 'encoding':
86
+ this.tokenEncoding = options[k]
87
+ break
88
+
89
+ case 'alg':
90
+ case 'algorithm':
91
+ if ([
92
+ 'aes-256-gcm', 'aes-192-gcm', 'aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc', 'sm4-cbc', 'sm4'
93
+ ].indexOf(options[k].toLowerCase()) >= 0) {
94
+ this.algorithm = options[k].toLowerCase()
95
+ if (this.algorithm.indexOf('-gcm') < 0) {
96
+ this.iv = randstring(16)
97
+ this.isGCM = false
98
+ this.ivLength = 16
99
+ }
100
+ }
101
+ if (this.algorithm === 'sm4') {
102
+ this.algorithm = 'sm4-cbc'
103
+ }
104
+
105
+ case 'failedCode':
106
+ if (typeof options[k] === 'number' && options[k] >= 400 && options[k] <= 499) {
107
+ this.failedCode = options[k]
108
+ }
109
+ break
110
+
111
+ }
112
+ }
113
+
114
+ this.key = this.fixKey(this.key)
115
+ this.iv = this.fixIv(this.iv)
116
+
117
+ }
118
+
119
+ _aesEncrypt(data, key, iv, options = {}) {
120
+ let h = crypto.createCipheriv(this.algorithm, key, iv, options)
121
+ let hd = h.update(data, 'utf8')
122
+ let final_data = h.final()
123
+
124
+ if (!this.isGCM) return Buffer.concat([hd, final_data])
125
+
126
+ let authtag = h.getAuthTag()
127
+ return Buffer.concat([hd, final_data, authtag])
128
+ }
129
+
130
+ _aesDecrypt(data, key, iv, options = {}) {
131
+ let h = crypto.createDecipheriv(this.algorithm, key, iv, options)
132
+ if (this.isGCM) {
133
+ let bdata = Buffer.from(data, this.tokenEncoding)
134
+ let tag = bdata.slice(-16)
135
+ let cdata = bdata.slice(0, -16)
136
+ h.setAuthTag(tag)
137
+ return Buffer.concat([h.update(cdata), h.final()])
138
+ }
139
+
140
+ return Buffer.concat([h.update(data, this.tokenEncoding), h.final()])
141
+ }
142
+
143
+ addTokenId(tid) {
144
+ if (typeof tid === 'string') {
145
+ tid = [ tid ]
146
+ }
147
+
148
+ if (tid.toString() === '[object Object]') {
149
+
150
+ for (let k in tid) {
151
+ if (this.tokenIds.indexOf(k) < 0) {
152
+ this.tokenIds.push(k)
153
+ this.setMapId(k, tid[k])
154
+ }
155
+ }
156
+
157
+ } else if (tid instanceof Array) {
158
+
159
+ for (let i=0; i < tid.length; i++) {
160
+ if (this.tokenIds.indexOf(tid[i]) < 0) {
161
+ this.tokenIds.push(tid[i])
162
+ this.tokenMap.set(tid[i], this.tokenIds.length)
163
+ }
164
+ }
165
+
166
+ }
167
+
168
+ }
169
+
170
+ setMapId(tid, iv = null) {
171
+ this.tokenMap.set(tid, iv || tid)
172
+ }
173
+
174
+ fixKey(key) {
175
+ let leng = key.length
176
+ if (leng < 16) {
177
+ throw new Error(`key.length must >= 16`)
178
+ }
179
+
180
+ let real_leng = 32
181
+ if (this.algorithm.indexOf('sm4') === 0 || this.algorithm.indexOf('aes-128') === 0) {
182
+ real_leng = 16
183
+ } else if (this.algorithm.indexOf('aes-192') === 0) {
184
+ real_leng = 24
185
+ } else if (this.algorithm.indexOf('aes-256') === 0) {
186
+ real_leng = 32
187
+ }
188
+
189
+ if (leng === real_leng) return key;
190
+ if (leng > real_leng) return key.substring(0, real_leng);
191
+
192
+ let new_key = key + key;
193
+ if (new_key.length === real_leng) return new_key;
194
+
195
+ return new_key.substring(0, real_leng);
196
+ }
197
+
198
+ fixIv(iv) {
199
+ let leng = iv.length
200
+
201
+ if (leng === this.ivLength) return iv;
202
+ if (leng > this.ivLength) return iv.substring(0, this.ivLength);
203
+
204
+ let arr = [];
205
+ for (let i = this.ivLength - leng; i > 0; i--) {
206
+ arr.push(`${i}`);
207
+ }
208
+
209
+ return iv + arr.join('');
210
+ }
211
+
212
+ /**
213
+ *
214
+ * @param {string} id
215
+ * @param {string} key
216
+ * @param {string} id
217
+ */
218
+ setIdKeyIv(id, key, iv) {
219
+ key = this.fixKey(key);
220
+ iv = this.fixIv(iv);
221
+
222
+ this.addTokenId(id)
223
+
224
+ this.idKeyIV.set(id, {
225
+ key : key,
226
+ iv : iv,
227
+ id : id,
228
+ })
229
+ }
230
+
231
+ hasId(tid) {
232
+ return this.tokenMap.has(tid)
233
+ }
234
+
235
+ /**
236
+ * 允许tokenMap和tokenIds不一致,这种情况是针对以下需求设计:
237
+ * 验证token是有效的,但是却只针对某些用户签发,对其他用户是不会签发的,
238
+ * 签发是通过make传递参数指定的。
239
+ *
240
+ * @param {string} tid
241
+ */
242
+ removeTokenId(tid) {
243
+ let ind = this.tokenIds.indexOf(tid)
244
+
245
+ if (ind >= 0) {
246
+ this.tokenIds.splice(ind, 1)
247
+ }
248
+
249
+ this.tokenMap.delete(tid)
250
+ this.idKeyIV.delete(tid)
251
+ }
252
+
253
+ randId() {
254
+ if (this.tokenIds.length <= 0) {
255
+ return ''
256
+ }
257
+
258
+ let ind = parseInt( Math.random() * this.tokenIds.length)
259
+
260
+ return this.tokenIds[ind]
261
+ }
262
+
263
+ stepId() {
264
+ if (this.tokenIds.length <= 0) {
265
+ return ''
266
+ }
267
+
268
+ if (this.idIndex >= this.tokenIds.length - 1) {
269
+ this.idIndex = -1
270
+ }
271
+
272
+ this.idIndex += 1
273
+
274
+ return this.tokenIds[this.idIndex]
275
+ }
276
+
277
+ setIv(iv) {
278
+ this.iv = this.fixIv(iv)
279
+ }
280
+
281
+ setKey(key) {
282
+ this.key = this.fixKey(key)
283
+ }
284
+
285
+ setEncoding(ecode) {
286
+ this.tokenEncoding = ecode
287
+ }
288
+
289
+ setExpires(expires) {
290
+ this.expires = expires
291
+ }
292
+
293
+ setRefresh(flag = true) {
294
+ if (flag) {
295
+ this.refresh = parseInt(this.expires / 5)
296
+ } else {
297
+ this.refresh = 0
298
+ }
299
+ }
300
+
301
+ refreshToken(t, ikv = null) {
302
+ if (t.data.expires + t.data.timestamp - t.now < this.refresh) {
303
+ if (ikv && typeof ikv === 'object') {
304
+ return this.makeikv(t.data, ikv)
305
+ }
306
+ return this.make(t.data, t.data.__tokenId__)
307
+ }
308
+ return null
309
+ }
310
+
311
+ /**
312
+ * @param {object} userinfo
313
+ */
314
+ make(userinfo, tokenId = null) {
315
+ if (!userinfo.expires || typeof userinfo.expires !== 'number') {
316
+ userinfo.expires = this.expires
317
+ }
318
+
319
+ userinfo.timestamp = Date.now()
320
+
321
+ userinfo.__tokenId__ = tokenId || this.stepId()
322
+
323
+ let tk
324
+
325
+ let ikv = tokenId ? this.idKeyIV.get(tokenId) : null
326
+
327
+ if (tokenId && ikv) {
328
+ tk = this._aesEncrypt(JSON.stringify(userinfo), ikv.key, ikv.iv)
329
+ } else {
330
+ tk = this._aesEncrypt(JSON.stringify(userinfo), this.key, this.iv)
331
+ }
332
+
333
+ return tk.toString(this.tokenEncoding)
334
+ }
335
+
336
+ randIvToken(info, id = null, key = null) {
337
+ let riv = randstring(16)
338
+ let tid = id || this.randId()
339
+ let opts = {
340
+ id : tid,
341
+ key : key || this.key,
342
+ iv : riv
343
+ }
344
+
345
+ opts.token = this.makeikv(info, opts)
346
+
347
+ return opts
348
+ }
349
+
350
+ makeAccessToken(info, id=null, key=null) {
351
+ let riv = randstring(this.ivLength)
352
+ let tid = id || this.randId()
353
+ let token = this.makeikv(info, {id: tid, iv: riv, key: key||this.key})
354
+ return `${riv}.${token}`
355
+ }
356
+
357
+ verifyAccessToken(edata) {
358
+ let tarr = edata.split('.')
359
+
360
+ if (tarr.length != 2 || !tarr[0] || !tarr[1]) {
361
+ return {
362
+ ok: false,
363
+ errcode: 'ILLEGAL'
364
+ }
365
+ }
366
+
367
+ return this.verify(tarr[1], {iv: tarr[0]})
368
+ }
369
+
370
+ makeikv(userinfo, ikv) {
371
+ if (!userinfo.expires || typeof userinfo.expires !== 'number') {
372
+ userinfo.expires = this.expires
373
+ }
374
+
375
+ userinfo.timestamp = Date.now()
376
+ userinfo.__tokenId__ = ikv.id
377
+
378
+ let tk = this._aesEncrypt(JSON.stringify(userinfo), ikv.key, ikv.iv)
379
+
380
+ return tk.toString(this.tokenEncoding)
381
+ }
382
+
383
+ verifyikv(edata, ikv) {
384
+ return this.verify(edata, ikv)
385
+ }
386
+
387
+ verifyid(edata, tid) {
388
+ let tk = this.idKeyIV.get(tid)
389
+ if (tk) {
390
+ return this.verify(edata, tk)
391
+ }
392
+ return this.verify(edata)
393
+ }
394
+
395
+ verify(edata, ikv={}) {
396
+ try {
397
+ let u = this._aesDecrypt(edata, ikv.key || this.key, ikv.iv || this.iv)
398
+ let uj = JSON.parse(u)
399
+ let tm = Date.now()
400
+
401
+ if (uj.timestamp + uj.expires < tm) {
402
+ return {
403
+ ok : false,
404
+ errcode : 'TIMEOUT'
405
+ }
406
+ }
407
+
408
+ if (uj.__tokenId__
409
+ && ( (ikv.id && uj.__tokenId__ !== ikv.id) || !this.tokenMap.has(uj.__tokenId__)) )
410
+ {
411
+ return {
412
+ ok : false,
413
+ errcode : 'ILLEGAL'
414
+ }
415
+ }
416
+
417
+ return {
418
+ ok : true,
419
+ data : uj,
420
+ now : tm
421
+ }
422
+
423
+ } catch (err) {
424
+ return {
425
+ ok : false,
426
+ errcode : 'FAILED'
427
+ }
428
+ }
429
+
430
+ }
431
+
432
+ mid() {
433
+ let self = this
434
+
435
+ return async (c, next) => {
436
+ let token = c.headers.authorization
437
+
438
+ if (!token) {
439
+ return c.status(self.failedCode).send('!token')
440
+ }
441
+
442
+ let uinfo = self.verify(token)
443
+
444
+ if (!uinfo.ok) {
445
+ return c.status(self.failedCode).send(uinfo.errcode)
446
+ }
447
+
448
+ c.box.user = uinfo
449
+
450
+ if (uinfo.data.expires + uinfo.data.timestamp - uinfo.now < self.refresh) {
451
+ let new_token = self.make(uinfo.data, uinfo.data.__tokenId__)
452
+ c.setHeader('x-refresh-token', new_token)
453
+ }
454
+
455
+ await next()
456
+ }
457
+ }
458
+
459
+ }
460
+
461
+ module.exports = TopbitToken
462
+