twikoo-vercel 1.7.14 → 1.7.16

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 (2) hide show
  1. package/api/index.js +82 -51
  2. package/package.json +2 -2
package/api/index.js CHANGED
@@ -42,6 +42,13 @@ const {
42
42
  checkCommentOwnership,
43
43
  isValidEmail
44
44
  } = require('twikoo-func/utils')
45
+ const {
46
+ createCap,
47
+ mongoStorage,
48
+ createChallenge,
49
+ redeemChallenge,
50
+ isBuiltinCap
51
+ } = require('twikoo-func/utils/cap')
45
52
  const {
46
53
  jsonParse,
47
54
  commentImportValine,
@@ -68,10 +75,10 @@ const { RES_CODE, MAX_REQUEST_TIMES } = require('twikoo-func/utils/constants')
68
75
  // 全局变量 / variables
69
76
  let db = null
70
77
  let config
71
- let accessToken
72
78
  const requestTimes = {}
73
79
 
74
80
  module.exports = async (request, response) => {
81
+ let accessToken
75
82
  const event = request.body || {}
76
83
  logger.log('请求 IP:', getIp(request))
77
84
  logger.log('请求函数:', event.event)
@@ -92,28 +99,28 @@ module.exports = async (request, response) => {
92
99
  res = getFuncVersion({ VERSION })
93
100
  break
94
101
  case 'COMMENT_GET':
95
- res = await commentGet(event)
102
+ res = await commentGet(event, accessToken)
96
103
  break
97
104
  case 'COMMENT_GET_FOR_ADMIN':
98
- res = await commentGetForAdmin(event)
105
+ res = await commentGetForAdmin(event, accessToken)
99
106
  break
100
107
  case 'COMMENT_SET_FOR_ADMIN':
101
- res = await commentSetForAdmin(event)
108
+ res = await commentSetForAdmin(event, accessToken)
102
109
  break
103
110
  case 'COMMENT_DELETE_FOR_ADMIN':
104
- res = await commentDeleteForAdmin(event)
111
+ res = await commentDeleteForAdmin(event, accessToken)
105
112
  break
106
113
  case 'COMMENT_DELETE_FOR_USER':
107
- res = await commentDeleteForUser(event)
114
+ res = await commentDeleteForUser(event, accessToken)
108
115
  break
109
116
  case 'COMMENT_IMPORT_FOR_ADMIN':
110
- res = await commentImportForAdmin(event)
117
+ res = await commentImportForAdmin(event, accessToken)
111
118
  break
112
119
  case 'COMMENT_LIKE':
113
- res = await commentLike(event)
120
+ res = await commentLike(event, accessToken)
114
121
  break
115
122
  case 'COMMENT_SUBMIT':
116
- res = await commentSubmit(event, request)
123
+ res = await commentSubmit(event, request, accessToken)
117
124
  break
118
125
  case 'POST_SUBMIT':
119
126
  res = await postSubmit(event.comment, request)
@@ -125,16 +132,16 @@ module.exports = async (request, response) => {
125
132
  res = await getPasswordStatus(config, VERSION)
126
133
  break
127
134
  case 'SET_PASSWORD':
128
- res = await setPassword(event)
135
+ res = await setPassword(event, accessToken)
129
136
  break
130
137
  case 'GET_CONFIG':
131
- res = await getConfig({ config, VERSION, isAdmin: isAdmin() })
138
+ res = await getConfig({ config, VERSION, isAdmin: isAdmin(accessToken) })
132
139
  break
133
140
  case 'GET_CONFIG_FOR_ADMIN':
134
- res = await getConfigForAdmin({ config, isAdmin: isAdmin() })
141
+ res = await getConfigForAdmin({ config, isAdmin: isAdmin(accessToken) })
135
142
  break
136
143
  case 'SET_CONFIG':
137
- res = await setConfig(event)
144
+ res = await setConfig(event, accessToken)
138
145
  break
139
146
  case 'LOGIN':
140
147
  res = await login(event.password)
@@ -146,7 +153,7 @@ module.exports = async (request, response) => {
146
153
  res = await getRecentComments(event)
147
154
  break
148
155
  case 'EMAIL_TEST': // >= 1.4.6
149
- res = await emailTest(event, config, isAdmin())
156
+ res = await emailTest(event, config, isAdmin(accessToken))
150
157
  break
151
158
  case 'UPLOAD_IMAGE': // >= 1.5.0
152
159
  res = await uploadImage(event, config)
@@ -155,7 +162,13 @@ module.exports = async (request, response) => {
155
162
  res = await qqNickGet(event)
156
163
  break
157
164
  case 'COMMENT_EXPORT_FOR_ADMIN': // >= 1.6.13
158
- res = await commentExportForAdmin(event)
165
+ res = await commentExportForAdmin(event, accessToken)
166
+ break
167
+ case 'CAP_CHALLENGE':
168
+ res = await capChallenge()
169
+ break
170
+ case 'CAP_REDEEM':
171
+ res = await capRedeem(event)
159
172
  break
160
173
  default:
161
174
  if (event.event) {
@@ -247,8 +260,8 @@ async function connectToDatabase (uri) {
247
260
  }
248
261
 
249
262
  // 写入管理密码
250
- async function setPassword (event) {
251
- const isAdminUser = isAdmin()
263
+ async function setPassword (event, accessToken) {
264
+ const isAdminUser = isAdmin(accessToken)
252
265
  // 如果数据库里没有密码,则写入密码
253
266
  // 如果数据库里有密码,则只有管理员可以写入密码
254
267
  if (config.ADMIN_PASS && !isAdminUser) {
@@ -278,12 +291,12 @@ async function login (password) {
278
291
  }
279
292
 
280
293
  // 读取评论
281
- async function commentGet (event) {
294
+ async function commentGet (event, accessToken) {
282
295
  const res = {}
283
296
  try {
284
297
  validate(event, ['url'])
285
- const uid = getUid()
286
- const isAdminUser = isAdmin()
298
+ const uid = accessToken
299
+ const isAdminUser = isAdmin(accessToken)
287
300
  const limit = parseInt(config.COMMENT_PAGE_SIZE) || 8
288
301
  const sort = event.sort || 'newest'
289
302
  let more = false
@@ -377,9 +390,9 @@ function getCommentQuery ({ condition, uid, isAdminUser }) {
377
390
  }
378
391
 
379
392
  // 管理员读取评论
380
- async function commentGetForAdmin (event) {
393
+ async function commentGetForAdmin (event, accessToken) {
381
394
  const res = {}
382
- const isAdminUser = isAdmin()
395
+ const isAdminUser = isAdmin(accessToken)
383
396
  if (isAdminUser) {
384
397
  validate(event, ['per', 'page'])
385
398
  const collection = db
@@ -435,9 +448,9 @@ function getCommentSearchCondition (event) {
435
448
  }
436
449
 
437
450
  // 管理员修改评论
438
- async function commentSetForAdmin (event) {
451
+ async function commentSetForAdmin (event, accessToken) {
439
452
  const res = {}
440
- const isAdminUser = isAdmin()
453
+ const isAdminUser = isAdmin(accessToken)
441
454
  if (isAdminUser) {
442
455
  validate(event, ['id', 'set'])
443
456
  const data = await db
@@ -458,9 +471,9 @@ async function commentSetForAdmin (event) {
458
471
  }
459
472
 
460
473
  // 管理员删除评论
461
- async function commentDeleteForAdmin (event) {
474
+ async function commentDeleteForAdmin (event, accessToken) {
462
475
  const res = {}
463
- const isAdminUser = isAdmin()
476
+ const isAdminUser = isAdmin(accessToken)
464
477
  if (isAdminUser) {
465
478
  validate(event, ['id'])
466
479
  const data = await db
@@ -476,10 +489,10 @@ async function commentDeleteForAdmin (event) {
476
489
  }
477
490
 
478
491
  // 用户删除自己的评论
479
- async function commentDeleteForUser (event) {
492
+ async function commentDeleteForUser (event, accessToken) {
480
493
  const res = {}
481
494
  try {
482
- const uid = getUid()
495
+ const uid = accessToken
483
496
  await checkCommentOwnership(event.id, uid, async (id) => {
484
497
  return db.collection('comment').findOne({ _id: id })
485
498
  })
@@ -494,13 +507,13 @@ async function commentDeleteForUser (event) {
494
507
  }
495
508
 
496
509
  // 管理员导入评论
497
- async function commentImportForAdmin (event) {
510
+ async function commentImportForAdmin (event, accessToken) {
498
511
  const res = {}
499
512
  let logText = ''
500
513
  const log = (message) => {
501
514
  logText += `${new Date().toLocaleString()} ${message}\n`
502
515
  }
503
- const isAdminUser = isAdmin()
516
+ const isAdminUser = isAdmin(accessToken)
504
517
  if (isAdminUser) {
505
518
  try {
506
519
  validate(event, ['source', 'file'])
@@ -550,9 +563,9 @@ async function commentImportForAdmin (event) {
550
563
  return res
551
564
  }
552
565
 
553
- async function commentExportForAdmin (event) {
566
+ async function commentExportForAdmin (event, accessToken) {
554
567
  const res = {}
555
- const isAdminUser = isAdmin()
568
+ const isAdminUser = isAdmin(accessToken)
556
569
  if (isAdminUser) {
557
570
  const collection = event.collection || 'comment'
558
571
  const data = await db
@@ -595,11 +608,11 @@ async function bulkSaveComments (comments) {
595
608
  }
596
609
 
597
610
  // 点赞 / 反对 / 取消
598
- async function commentLike (event) {
611
+ async function commentLike (event, accessToken) {
599
612
  const res = {}
600
613
  validate(event, ['id'])
601
614
  const type = event.type || 'up'
602
- res.updated = await like(event.id, getUid(), type)
615
+ res.updated = await like(event.id, accessToken, type)
603
616
  return res
604
617
  }
605
618
 
@@ -654,7 +667,7 @@ async function like (id, uid, type) {
654
667
  * @param {String} event.pid 回复的 ID
655
668
  * @param {String} event.rid 评论楼 ID
656
669
  */
657
- async function commentSubmit (event, request) {
670
+ async function commentSubmit (event, request, accessToken) {
658
671
  const res = {}
659
672
  // 参数校验
660
673
  validate(event, ['url', 'ua', 'comment'])
@@ -663,7 +676,7 @@ async function commentSubmit (event, request) {
663
676
  // 验证码
664
677
  await checkCaptcha(event, request)
665
678
  // 预检测、转换
666
- const data = await parse(event, request)
679
+ const data = await parse(event, request, accessToken)
667
680
  // 保存
668
681
  const comment = await save(data)
669
682
  res.id = comment.id
@@ -714,16 +727,16 @@ async function postSubmit (comment, request) {
714
727
  }
715
728
 
716
729
  // 将评论转为数据库存储格式
717
- async function parse (comment, request) {
730
+ async function parse (comment, request, accessToken) {
718
731
  const timestamp = Date.now()
719
- const isAdminUser = isAdmin()
732
+ const isAdminUser = isAdmin(accessToken)
720
733
  const isBloggerMail = equalsMail(comment.mail, config.BLOGGER_EMAIL)
721
734
  if (isBloggerMail && !isAdminUser) throw new Error('请先登录管理面板,再使用博主身份发送评论')
722
735
  if (comment.mail && !isValidEmail(comment.mail)) throw new Error('邮箱格式不合法')
723
736
  const hashMethod = config.GRAVATAR_CDN === 'cravatar.cn' ? md5 : sha256
724
737
  const commentDo = {
725
738
  _id: uuidv4().replace(/-/g, ''),
726
- uid: getUid(),
739
+ uid: accessToken,
727
740
  nick: comment.nick ? comment.nick : '匿名',
728
741
  mail: comment.mail ? comment.mail : '',
729
742
  mailMd5: comment.mail ? hashMethod(normalizeMail(comment.mail)) : '',
@@ -796,6 +809,14 @@ async function checkCaptcha (comment, request) {
796
809
  geeTestPassToken: comment.geeTestPassToken,
797
810
  geeTestGenTime: comment.geeTestGenTime
798
811
  })
812
+ } else if (provider === 'Cap' && isBuiltinCap(config)) {
813
+ if (!comment.capToken) {
814
+ throw new Error('验证码 token 缺失,请刷新页面重试')
815
+ }
816
+ await checkCapCaptcha({
817
+ capToken: comment.capToken,
818
+ cap: createCap(mongoStorage(db))
819
+ })
799
820
  } else if (provider === 'Cap' && config.CAP_API_ENDPOINT && config.CAP_SECRET_KEY) {
800
821
  if (!comment.capToken) {
801
822
  throw new Error('验证码 token 缺失,请刷新页面重试')
@@ -806,7 +827,7 @@ async function checkCaptcha (comment, request) {
806
827
  capApiEndpoint: config.CAP_API_ENDPOINT
807
828
  })
808
829
  } else if (provider === 'Cap') {
809
- throw new Error('Cap 验证码配置不完整,请联系管理员')
830
+ throw new Error('Cap 验证码配置不完整:内嵌模式无需额外配置,外部模式需填写 CAP_API_ENDPOINT 与 CAP_SECRET_KEY')
810
831
  } else if (provider) {
811
832
  throw new Error(`不支持的验证码类型: ${provider}`)
812
833
  }
@@ -975,8 +996,8 @@ async function qqNickGet (event) {
975
996
  }
976
997
 
977
998
  // 修改配置
978
- async function setConfig (event) {
979
- const isAdminUser = isAdmin()
999
+ async function setConfig (event, accessToken) {
1000
+ const isAdminUser = isAdmin(accessToken)
980
1001
  if (isAdminUser) {
981
1002
  writeConfig(event.config)
982
1003
  return {
@@ -1043,15 +1064,9 @@ async function writeConfig (newConfig) {
1043
1064
  }
1044
1065
  }
1045
1066
 
1046
- // 获取用户 ID
1047
- function getUid () {
1048
- return accessToken
1049
- }
1050
-
1051
1067
  // 判断用户是否管理员
1052
- function isAdmin () {
1053
- const uid = getUid()
1054
- return config.ADMIN_PASS === md5(uid)
1068
+ function isAdmin (accessToken) {
1069
+ return config.ADMIN_PASS === md5(accessToken)
1055
1070
  }
1056
1071
 
1057
1072
  // 判断是否为递归调用(即云函数调用自身)
@@ -1061,7 +1076,7 @@ function isRecursion (request) {
1061
1076
 
1062
1077
  // 建立数据库 collections
1063
1078
  async function createCollections () {
1064
- const collections = ['comment', 'config', 'counter']
1079
+ const collections = ['comment', 'config', 'counter', 'cap_challenges', 'cap_tokens']
1065
1080
  const res = {}
1066
1081
  for (const collection of collections) {
1067
1082
  try {
@@ -1073,6 +1088,22 @@ async function createCollections () {
1073
1088
  return res
1074
1089
  }
1075
1090
 
1091
+ async function capChallenge () {
1092
+ if (!isBuiltinCap(config)) {
1093
+ return { code: RES_CODE.FAIL, message: '内嵌 Cap 未启用' }
1094
+ }
1095
+ const data = await createChallenge(createCap(mongoStorage(db)))
1096
+ return { code: RES_CODE.SUCCESS, ...data }
1097
+ }
1098
+
1099
+ async function capRedeem (event) {
1100
+ if (!isBuiltinCap(config)) {
1101
+ return { code: RES_CODE.FAIL, message: '内嵌 Cap 未启用' }
1102
+ }
1103
+ const data = await redeemChallenge(createCap(mongoStorage(db)), event)
1104
+ return { code: RES_CODE.SUCCESS, ...data }
1105
+ }
1106
+
1076
1107
  function getIp (request) {
1077
1108
  try {
1078
1109
  const { TWIKOO_IP_HEADERS } = process.env
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twikoo-vercel",
3
- "version": "1.7.14",
3
+ "version": "1.7.16",
4
4
  "description": "A simple comment system.",
5
5
  "author": "imaegoo <hello@imaegoo.com> (https://github.com/imaegoo)",
6
6
  "license": "MIT",
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "get-user-ip": "^1.0.1",
15
15
  "mongodb": "^6.3.0",
16
- "twikoo-func": "1.7.14",
16
+ "twikoo-func": "1.7.16",
17
17
  "uuid": "^8.3.2"
18
18
  }
19
19
  }