@waline/vercel 1.18.7 → 1.19.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 (40) hide show
  1. package/__tests__/katex.spec.js +2 -0
  2. package/index.js +2 -0
  3. package/package.json +10 -10
  4. package/src/config/adapter.js +16 -0
  5. package/src/config/config.js +2 -1
  6. package/src/config/extend.js +6 -4
  7. package/src/controller/article.js +1 -0
  8. package/src/controller/comment.js +29 -0
  9. package/src/controller/db.js +54 -2
  10. package/src/controller/oauth.js +14 -8
  11. package/src/controller/rest.js +1 -0
  12. package/src/controller/token/2fa.js +4 -0
  13. package/src/controller/token.js +4 -0
  14. package/src/controller/user/password.js +3 -1
  15. package/src/controller/user.js +4 -1
  16. package/src/controller/verification.js +3 -0
  17. package/src/extend/controller.js +4 -0
  18. package/src/extend/think.js +8 -1
  19. package/src/locales/en.json +2 -2
  20. package/src/locales/zh-CN.json +2 -2
  21. package/src/locales/zh-TW.json +2 -2
  22. package/src/logic/base.js +15 -0
  23. package/src/logic/comment.js +5 -0
  24. package/src/logic/db.js +1 -0
  25. package/src/logic/token/2fa.js +1 -0
  26. package/src/logic/user.js +2 -0
  27. package/src/service/akismet.js +1 -0
  28. package/src/service/avatar.js +5 -0
  29. package/src/service/markdown/katex.js +2 -0
  30. package/src/service/markdown/mathCommon.js +5 -0
  31. package/src/service/markdown/mathjax.js +3 -0
  32. package/src/service/notify.js +109 -80
  33. package/src/service/storage/cloudbase.js +21 -0
  34. package/src/service/storage/deta.js +28 -0
  35. package/src/service/storage/github.js +77 -49
  36. package/src/service/storage/leancloud.js +42 -1
  37. package/src/service/storage/mongodb.js +17 -0
  38. package/src/service/storage/mysql.js +11 -0
  39. package/src/service/storage/postgresql.js +23 -2
  40. package/vanilla.js +1 -0
@@ -48,6 +48,7 @@ describe('inline katex', () => {
48
48
  it('Should render error msg when content is wrong', () => {
49
49
  // eslint-disable-next-line @typescript-eslint/unbound-method
50
50
  const originalWarn = global.console.warn;
51
+
51
52
  global.console.warn = vi.fn();
52
53
 
53
54
  expect(markdownItWithError.render('$\\fra{a}{b}$')).toEqual(
@@ -119,6 +120,7 @@ $$
119
120
  it('Should render error msg when content is wrong', () => {
120
121
  // eslint-disable-next-line @typescript-eslint/unbound-method
121
122
  const originalWarn = global.console.warn;
123
+
122
124
  global.console.warn = vi.fn();
123
125
  expect(markdownItWithError.render('$$\\fra{a}{b}$$')).toMatch(
124
126
  /<p class='katex-block katex-error' title='[\s\S]*?'>[\s\S]*?<\/p>/
package/index.js CHANGED
@@ -16,6 +16,7 @@ module.exports = function (configParams = {}) {
16
16
  });
17
17
 
18
18
  const loader = new Loader(app.options);
19
+
19
20
  loader.loadAll('worker');
20
21
 
21
22
  return function (req, res) {
@@ -30,6 +31,7 @@ module.exports = function (configParams = {}) {
30
31
  })
31
32
  .then(() => {
32
33
  const callback = think.app.callback();
34
+
33
35
  return callback(req, res);
34
36
  })
35
37
  .then(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waline/vercel",
3
- "version": "1.18.7",
3
+ "version": "1.19.0",
4
4
  "description": "vercel server for waline comment system",
5
5
  "keywords": [
6
6
  "waline",
@@ -19,35 +19,35 @@
19
19
  "@koa/cors": "3.3.0",
20
20
  "akismet": "2.0.7",
21
21
  "deta": "1.1.0",
22
- "dompurify": "2.3.8",
22
+ "dompurify": "2.3.10",
23
23
  "dy-node-ip2region": "1.0.1",
24
24
  "fast-csv": "4.3.6",
25
- "jsdom": "19.0.0",
25
+ "form-data": "4.0.0",
26
+ "jsdom": "20.0.0",
26
27
  "jsonwebtoken": "8.5.1",
27
28
  "katex": "0.16.0",
28
- "leancloud-storage": "4.12.2",
29
+ "leancloud-storage": "4.13.1",
29
30
  "markdown-it": "13.0.1",
30
31
  "markdown-it-emoji": "2.0.2",
31
32
  "markdown-it-sub": "1.0.0",
32
33
  "markdown-it-sup": "1.0.0",
33
34
  "mathjax-full": "3.2.2",
34
- "nodemailer": "6.7.5",
35
+ "nodemailer": "6.7.7",
35
36
  "nunjucks": "3.2.3",
36
37
  "phpass": "0.1.1",
37
38
  "prismjs": "1.28.0",
38
- "request": "2.88.2",
39
- "request-promise-native": "1.0.9",
40
39
  "speakeasy": "2.0.0",
41
- "think-helper": "^1.1.3",
40
+ "think-helper": "1.1.3",
42
41
  "think-logger3": "1.3.1",
43
42
  "think-model": "1.5.4",
44
43
  "think-model-mysql": "1.1.7",
45
- "think-model-postgresql": "1.1.6",
44
+ "think-model-postgresql": "1.1.7",
46
45
  "think-model-sqlite": "1.2.3",
47
46
  "think-mongo": "2.2.1",
48
47
  "think-router-rest": "1.0.5",
49
48
  "thinkjs": "3.2.14",
50
- "ua-parser-js": "1.0.2"
49
+ "ua-parser-js": "1.0.2",
50
+ "undici": "^5.8.0"
51
51
  },
52
52
  "engines": {
53
53
  "node": ">=14"
@@ -18,6 +18,7 @@ const {
18
18
  MYSQL_PASSWORD,
19
19
  MYSQL_PREFIX,
20
20
  MYSQL_CHARSET,
21
+ MYSQL_SSL,
21
22
  SQLITE_PATH,
22
23
  SQLITE_DB,
23
24
  SQLITE_PREFIX,
@@ -27,6 +28,7 @@ const {
27
28
  PG_PORT,
28
29
  PG_PREFIX,
29
30
  PG_USER,
31
+ PG_SSL,
30
32
  MONGO_AUTHSOURCE,
31
33
  MONGO_DB,
32
34
  MONGO_HOST,
@@ -38,6 +40,7 @@ const {
38
40
 
39
41
  let type = 'common';
40
42
  const mongoOpt = {};
43
+
41
44
  if (MONGO_REPLICASET) mongoOpt.replicaSet = MONGO_REPLICASET;
42
45
  if (MONGO_AUTHSOURCE) mongoOpt.authSource = MONGO_AUTHSOURCE;
43
46
 
@@ -49,6 +52,7 @@ if (MONGO_DB) {
49
52
  .slice(10)
50
53
  .toLocaleLowerCase()
51
54
  .replace(/_([a-z])/g, (_, b) => b.toUpperCase());
55
+
52
56
  mongoOpt[key] = process.env[envKeys];
53
57
  }
54
58
  }
@@ -93,6 +97,12 @@ exports.model = {
93
97
  port: PG_PORT || '3211',
94
98
  connectionLimit: 1,
95
99
  prefix: PG_PREFIX || 'wl_',
100
+ ssl:
101
+ PG_SSL == 'true'
102
+ ? {
103
+ rejectUnauthorized: false,
104
+ }
105
+ : null,
96
106
  },
97
107
 
98
108
  sqlite: {
@@ -113,6 +123,12 @@ exports.model = {
113
123
  password: MYSQL_PASSWORD,
114
124
  prefix: MYSQL_PREFIX || 'wl_',
115
125
  charset: MYSQL_CHARSET || 'utf8mb4',
126
+ ssl:
127
+ MYSQL_SSL === 'true'
128
+ ? {
129
+ rejectUnauthorized: false,
130
+ }
131
+ : null,
116
132
  },
117
133
  };
118
134
 
@@ -93,11 +93,12 @@ const markdown = {
93
93
  if (isFalse(MARKDOWN_HIGHLIGHT)) markdown.config.highlight = false;
94
94
 
95
95
  let avatarProxy = '';
96
+
96
97
  if (AVATAR_PROXY) {
97
98
  avatarProxy = !isFalse(AVATAR_PROXY) ? AVATAR_PROXY : '';
98
99
  }
99
100
 
100
- const oauthUrl = OAUTH_URL || 'https://user.75.team';
101
+ const oauthUrl = OAUTH_URL || 'https://oauth.lithub.cc';
101
102
 
102
103
  module.exports = {
103
104
  workers: 1,
@@ -1,6 +1,6 @@
1
+ const { fetch } = require('undici');
1
2
  const Model = require('think-model');
2
3
  const Mongo = require('think-mongo');
3
- const request = require('request-promise-native');
4
4
 
5
5
  module.exports = [
6
6
  Model(think.app),
@@ -9,27 +9,29 @@ module.exports = [
9
9
  context: {
10
10
  get serverURL() {
11
11
  const { SERVER_URL } = process.env;
12
+
12
13
  if (SERVER_URL) {
13
14
  return SERVER_URL;
14
15
  }
15
16
 
16
17
  const { protocol, host } = this;
18
+
17
19
  return `${protocol}://${host}`;
18
20
  },
19
21
  async webhook(type, data) {
20
22
  const { WEBHOOK } = process.env;
23
+
21
24
  if (!WEBHOOK) {
22
25
  return;
23
26
  }
24
27
 
25
- return request({
26
- uri: WEBHOOK,
28
+ return fetch(WEBHOOK, {
27
29
  method: 'POST',
28
30
  headers: {
29
31
  'content-type': 'application/json',
30
32
  },
31
33
  body: JSON.stringify({ type, data }),
32
- });
34
+ }).then((resp) => resp.json());
33
35
  },
34
36
  },
35
37
  },
@@ -30,6 +30,7 @@ module.exports = class extends BaseRest {
30
30
 
31
31
  const respObj = resp.reduce((o, n) => {
32
32
  o[n.url] = n.time;
33
+
33
34
  return o;
34
35
  }, {});
35
36
 
@@ -4,6 +4,7 @@ const akismet = require('../service/akismet');
4
4
  const { getMarkdownParser } = require('../service/markdown');
5
5
 
6
6
  const markdownParser = getMarkdownParser();
7
+
7
8
  async function formatCmt(
8
9
  { ua, user_id, ip, ...comment },
9
10
  users = [],
@@ -20,6 +21,7 @@ async function formatCmt(
20
21
  }
21
22
 
22
23
  const user = users.find(({ objectId }) => user_id === objectId);
24
+
23
25
  if (!think.isEmpty(user)) {
24
26
  comment.nick = user.display_name;
25
27
  comment.mail = user.email;
@@ -32,12 +34,14 @@ async function formatCmt(
32
34
  user && user.avatar
33
35
  ? user.avatar
34
36
  : await think.service('avatar').stringify(comment);
37
+
35
38
  comment.avatar =
36
39
  avatarProxy && !avatarUrl.includes(avatarProxy)
37
40
  ? avatarProxy + '?url=' + encodeURIComponent(avatarUrl)
38
41
  : avatarUrl;
39
42
 
40
43
  const isAdmin = loginUser && loginUser.type === 'administrator';
44
+
41
45
  if (!isAdmin) {
42
46
  delete comment.mail;
43
47
  } else {
@@ -51,6 +55,7 @@ async function formatCmt(
51
55
  }
52
56
  comment.comment = markdownParser(comment.comment);
53
57
  comment.like = Number(comment.like) || 0;
58
+
54
59
  return comment;
55
60
  }
56
61
 
@@ -71,6 +76,7 @@ module.exports = class extends BaseRest {
71
76
  case 'recent': {
72
77
  const { count } = this.get();
73
78
  const where = {};
79
+
74
80
  if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
75
81
  where.status = ['NOT IN', ['waiting', 'spam']];
76
82
  } else {
@@ -111,6 +117,7 @@ module.exports = class extends BaseRest {
111
117
  );
112
118
 
113
119
  let users = [];
120
+
114
121
  if (user_ids.length) {
115
122
  users = await userModel.select(
116
123
  { objectId: ['IN', user_ids] },
@@ -139,6 +146,7 @@ module.exports = class extends BaseRest {
139
146
  case 'count': {
140
147
  const { url } = this.get();
141
148
  const where = { url: ['IN', url] };
149
+
142
150
  if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
143
151
  where.status = ['NOT IN', ['waiting', 'spam']];
144
152
  } else {
@@ -162,6 +170,7 @@ module.exports = class extends BaseRest {
162
170
 
163
171
  if (owner === 'mine') {
164
172
  const { userInfo } = this.ctx.state;
173
+
165
174
  where.mail = userInfo.email;
166
175
  }
167
176
 
@@ -198,6 +207,7 @@ module.exports = class extends BaseRest {
198
207
  );
199
208
 
200
209
  let users = [];
210
+
201
211
  if (user_ids.length) {
202
212
  users = await userModel.select(
203
213
  { objectId: ['IN', user_ids] },
@@ -231,6 +241,7 @@ module.exports = class extends BaseRest {
231
241
  default: {
232
242
  const { path: url, page, pageSize } = this.get();
233
243
  const where = { url };
244
+
234
245
  if (think.isEmpty(userInfo) || this.config('storage') === 'deta') {
235
246
  where.status = ['NOT IN', ['waiting', 'spam']];
236
247
  } else if (userInfo.type !== 'administrator') {
@@ -290,6 +301,7 @@ module.exports = class extends BaseRest {
290
301
  ...comments.filter(({ rid, sticky }) => !rid && !sticky),
291
302
  ].slice(pageOffset, pageOffset + pageSize);
292
303
  const rootIds = {};
304
+
293
305
  rootComments.forEach(({ objectId }) => {
294
306
  rootIds[objectId] = true;
295
307
  });
@@ -312,6 +324,7 @@ module.exports = class extends BaseRest {
312
324
  },
313
325
  selectOptions
314
326
  );
327
+
315
328
  comments = [...rootComments, ...children];
316
329
  rootCount = await this.modelInstance.count({
317
330
  ...where,
@@ -349,12 +362,14 @@ module.exports = class extends BaseRest {
349
362
  status: ['NOT IN', ['waiting', 'spam']],
350
363
  _complex: {},
351
364
  };
365
+
352
366
  if (user_ids.length) {
353
367
  countWhere._complex.user_id = ['IN', user_ids];
354
368
  }
355
369
  const mails = Array.from(
356
370
  new Set(comments.map(({ mail }) => mail).filter((v) => v))
357
371
  );
372
+
358
373
  if (mails.length) {
359
374
  countWhere._complex.mail = ['IN', mails];
360
375
  }
@@ -366,20 +381,24 @@ module.exports = class extends BaseRest {
366
381
  const counts = await this.modelInstance.count(countWhere, {
367
382
  group: ['user_id', 'mail'],
368
383
  });
384
+
369
385
  comments.forEach((cmt) => {
370
386
  const countItem = (counts || []).find(({ mail, user_id }) => {
371
387
  if (cmt.user_id) {
372
388
  return user_id === cmt.user_id;
373
389
  }
390
+
374
391
  return mail === cmt.mail;
375
392
  });
376
393
 
377
394
  let level = 0;
395
+
378
396
  if (countItem) {
379
397
  const _level = think.findLastIndex(
380
398
  this.config('levels'),
381
399
  (l) => l <= countItem.count
382
400
  );
401
+
383
402
  if (_level !== -1) {
384
403
  level = _level;
385
404
  }
@@ -401,12 +420,14 @@ module.exports = class extends BaseRest {
401
420
  this.config(),
402
421
  userInfo
403
422
  );
423
+
404
424
  cmt.children = await Promise.all(
405
425
  comments
406
426
  .filter(({ rid }) => rid === cmt.objectId)
407
427
  .map((cmt) => formatCmt(cmt, users, this.config(), userInfo))
408
428
  .reverse()
409
429
  );
430
+
410
431
  return cmt;
411
432
  })
412
433
  ),
@@ -486,6 +507,7 @@ module.exports = class extends BaseRest {
486
507
 
487
508
  if (!think.isEmpty(recent)) {
488
509
  think.logger.debug(`The author has posted in ${IPQPS} seconeds.`);
510
+
489
511
  return this.fail(this.locale('Comment too fast!'));
490
512
  }
491
513
 
@@ -520,6 +542,7 @@ module.exports = class extends BaseRest {
520
542
 
521
543
  if (!think.isEmpty(forbiddenWords)) {
522
544
  const regexp = new RegExp('(' + forbiddenWords.join('|') + ')', 'ig');
545
+
523
546
  if (regexp.test(comment)) {
524
547
  data.status = 'spam';
525
548
  }
@@ -544,6 +567,7 @@ module.exports = class extends BaseRest {
544
567
  think.logger.debug(`Comment have been added to storage.`);
545
568
 
546
569
  let parentComment;
570
+
547
571
  if (pid) {
548
572
  parentComment = await this.modelInstance.select({ objectId: pid });
549
573
  parentComment = parentComment[0];
@@ -556,6 +580,7 @@ module.exports = class extends BaseRest {
556
580
 
557
581
  if (comment.status !== 'spam') {
558
582
  const notify = this.service('notify');
583
+
559
584
  await notify.run(
560
585
  { ...resp, comment: markdownParser(resp.comment), rawComment: comment },
561
586
  parentComment
@@ -579,6 +604,7 @@ module.exports = class extends BaseRest {
579
604
  const { userInfo } = this.ctx.state;
580
605
  let data = this.post();
581
606
  let oldData = await this.modelInstance.select({ objectId: this.id });
607
+
582
608
  if (think.isEmpty(oldData)) {
583
609
  return this.success();
584
610
  }
@@ -590,6 +616,7 @@ module.exports = class extends BaseRest {
590
616
  }
591
617
 
592
618
  const likeIncMax = this.config('LIKE_INC_MAX') || 1;
619
+
593
620
  data = {
594
621
  like:
595
622
  (Number(oldData.like) || 0) +
@@ -618,9 +645,11 @@ module.exports = class extends BaseRest {
618
645
  let pComment = await this.modelInstance.select({
619
646
  objectId: oldData.pid,
620
647
  });
648
+
621
649
  pComment = pComment[0];
622
650
 
623
651
  const notify = this.service('notify');
652
+
624
653
  await notify.run(
625
654
  { ...newData, comment: markdownParser(newData.comment) },
626
655
  { ...pComment, comment: markdownParser(pComment.comment) },
@@ -21,6 +21,7 @@ function formatID(data, idGenerator) {
21
21
 
22
22
  return data;
23
23
  }
24
+
24
25
  module.exports = class extends BaseRest {
25
26
  async getAction() {
26
27
  const exportData = {
@@ -49,36 +50,87 @@ module.exports = class extends BaseRest {
49
50
 
50
51
  async postAction() {
51
52
  const file = this.file('file');
53
+
52
54
  try {
53
55
  const jsonText = await readFileAsync(file.path, 'utf-8');
54
56
  const importData = JSON.parse(jsonText);
57
+
55
58
  if (!importData || importData.type !== 'waline') {
56
59
  return this.fail(this.locale('import data format not support!'));
57
60
  }
58
61
 
62
+ const idMaps = {};
63
+ const storage = this.config('storage');
64
+
59
65
  for (let i = 0; i < importData.tables.length; i++) {
60
66
  const tableName = importData.tables[i];
61
- const storage = this.config('storage');
62
67
  const model = this.service(`storage/${storage}`, tableName);
63
68
 
69
+ idMaps[tableName] = new Map();
64
70
  let data = importData.data[tableName];
65
71
  if (['postgresql', 'mysql', 'sqlite'].includes(storage)) {
66
72
  let i = 0;
67
73
  data = formatID(data, () => (i = i + 1));
74
+ } else if (storage === 'leancloud') {
75
+ data
76
+ .filter(({ insertedAt }) => insertedAt)
77
+ .forEach((item) => {
78
+ item.insertedAt = new Date(item.insertedAt);
79
+ });
68
80
  }
69
81
 
70
82
  // delete all data at first
71
83
  await model.delete({});
72
84
  // then add data one by one
73
85
  for (let j = 0; j < data.length; j++) {
74
- await model.add(data[j]);
86
+ const ret = await model.add(data[j]);
87
+
88
+ idMaps[tableName].set(data[j].objectId, ret.objectId);
75
89
  }
76
90
  }
91
+
92
+ const cmtModel = this.service(`storage/${storage}`, 'Comment');
93
+ const commentData = importData.data.Comment;
94
+ const willUpdateData = [];
95
+
96
+ for (let i = 0; i < commentData.length; i++) {
97
+ const cmt = commentData[i];
98
+ const willUpdateItem = {};
99
+
100
+ [
101
+ { tableName: 'Comment', field: 'pid' },
102
+ { tableName: 'Comment', field: 'rid' },
103
+ { tableName: 'Users', field: 'user_id' },
104
+ ].forEach(({ tableName, field }) => {
105
+ if (!cmt[field]) {
106
+ return;
107
+ }
108
+ const oldId = cmt[field];
109
+ const newId = idMaps[tableName].get(cmt[field]);
110
+
111
+ if (oldId !== newId) {
112
+ willUpdateItem[field] = newId;
113
+ }
114
+ });
115
+ if (!think.isEmpty(willUpdateItem)) {
116
+ willUpdateData.push([
117
+ willUpdateItem,
118
+ { objectId: idMaps.Comment.get(cmt.objectId) },
119
+ ]);
120
+ }
121
+ }
122
+ for (let i = 0; i < willUpdateData.length; i++) {
123
+ const [data, where] = willUpdateData[i];
124
+
125
+ await cmtModel.update(data, where);
126
+ }
127
+
77
128
  return this.success();
78
129
  } catch (e) {
79
130
  if (think.isPrevent(e)) {
80
131
  return this.success();
81
132
  }
133
+ console.log(e);
82
134
  return this.fail(e.message);
83
135
  }
84
136
  }
@@ -1,7 +1,8 @@
1
- const qs = require('querystring');
2
1
  const jwt = require('jsonwebtoken');
2
+ const { fetch } = require('undici');
3
3
  const { PasswordHash } = require('phpass');
4
- const request = require('request-promise-native');
4
+ const qs = require('querystring');
5
+
5
6
  module.exports = class extends think.Controller {
6
7
  constructor(ctx) {
7
8
  super(ctx);
@@ -17,12 +18,14 @@ module.exports = class extends think.Controller {
17
18
 
18
19
  const hasCode =
19
20
  type === 'twitter' ? oauth_token && oauth_verifier : Boolean(code);
21
+
20
22
  if (!hasCode) {
21
23
  const { serverURL } = this.ctx;
22
24
  const redirectUrl = `${serverURL}/oauth?${qs.stringify({
23
25
  redirect,
24
26
  type,
25
27
  })}`;
28
+
26
29
  return this.redirect(
27
30
  `${oauthUrl}/${type}?${qs.stringify({
28
31
  redirect: redirectUrl,
@@ -35,28 +38,29 @@ module.exports = class extends think.Controller {
35
38
  * user = { id, name, email, avatar,url };
36
39
  */
37
40
  const params = { code, oauth_verifier, oauth_token };
41
+
38
42
  if (type === 'facebook') {
39
43
  const { serverURL } = this.ctx;
40
44
  const redirectUrl = `${serverURL}/oauth?${qs.stringify({
41
45
  redirect,
42
46
  type,
43
47
  })}`;
48
+
44
49
  params.state = qs.stringify({
45
50
  redirect: redirectUrl,
46
51
  state: this.ctx.state.token || '',
47
52
  });
48
53
  }
49
54
 
50
- const user = await request({
51
- url: `${oauthUrl}/${type}?${qs.stringify(params)}`,
55
+ const user = await fetch(`${oauthUrl}/${type}?${qs.stringify(params)}`, {
52
56
  method: 'GET',
53
- json: true,
54
57
  headers: {
55
- 'User-Agent': '@waline',
58
+ 'user-agent': '@waline',
56
59
  },
57
- });
60
+ }).then((resp) => resp.json());
61
+
58
62
  if (!user || !user.id) {
59
- return this.fail();
63
+ return this.fail(user);
60
64
  }
61
65
 
62
66
  const userBySocial = await this.modelInstance.select({ [type]: user.id });
@@ -78,6 +82,7 @@ module.exports = class extends think.Controller {
78
82
  }
79
83
 
80
84
  const current = this.ctx.state.userInfo;
85
+
81
86
  if (!think.isEmpty(current)) {
82
87
  const updateData = { [type]: user.id };
83
88
 
@@ -93,6 +98,7 @@ module.exports = class extends think.Controller {
93
98
  }
94
99
 
95
100
  const userByEmail = await this.modelInstance.select({ email: user.email });
101
+
96
102
  if (think.isEmpty(userByEmail)) {
97
103
  const count = await this.modelInstance.count();
98
104
  const data = {
@@ -42,6 +42,7 @@ module.exports = class extends think.Controller {
42
42
 
43
43
  isLogin() {
44
44
  const { userInfo } = this.ctx.state;
45
+
45
46
  return think.isEmpty(userInfo);
46
47
  }
47
48
 
@@ -19,10 +19,12 @@ module.exports = class extends BaseRest {
19
19
  }
20
20
  );
21
21
  const is2FAEnabled = !think.isEmpty(user) && Boolean(user[0]['2fa']);
22
+
22
23
  return this.success({ enable: is2FAEnabled });
23
24
  }
24
25
 
25
26
  const name = `waline_${userInfo.objectId}`;
27
+
26
28
  if (userInfo['2fa'] && userInfo['2fa'].length == 32) {
27
29
  return this.success({
28
30
  otpauth_url: `otpauth://totp/${name}?secret=${userInfo['2fa']}`,
@@ -34,6 +36,7 @@ module.exports = class extends BaseRest {
34
36
  length: 20,
35
37
  name,
36
38
  });
39
+
37
40
  return this.success({ otpauth_url, secret });
38
41
  }
39
42
 
@@ -55,6 +58,7 @@ module.exports = class extends BaseRest {
55
58
  'Users'
56
59
  );
57
60
  const { objectId } = this.ctx.state.userInfo;
61
+
58
62
  await userModel.update({ ['2fa']: data.secret }, { objectId });
59
63
 
60
64
  return this.success();
@@ -35,6 +35,7 @@ module.exports = class extends BaseRest {
35
35
  }
36
36
 
37
37
  const twoFactorAuthSecret = user[0]['2fa'];
38
+
38
39
  if (twoFactorAuthSecret) {
39
40
  const verified = speakeasy.totp.verify({
40
41
  secret: twoFactorAuthSecret,
@@ -42,6 +43,7 @@ module.exports = class extends BaseRest {
42
43
  token: code,
43
44
  window: 2,
44
45
  });
46
+
45
47
  if (!verified) {
46
48
  return this.fail();
47
49
  }
@@ -55,10 +57,12 @@ module.exports = class extends BaseRest {
55
57
  link: user[0].url,
56
58
  });
57
59
  const { avatarProxy } = think.config();
60
+
58
61
  if (avatarProxy) {
59
62
  avatarUrl = avatarProxy + '?url=' + encodeURIComponent(avatarUrl);
60
63
  }
61
64
  user[0].avatar = avatarUrl;
65
+
62
66
  return this.success({
63
67
  ...user[0],
64
68
  password: null,
@@ -12,6 +12,7 @@ module.exports = class extends BaseRest {
12
12
  SITE_NAME,
13
13
  } = process.env;
14
14
  const hasMailServie = SMTP_HOST || SMTP_SERVICE;
15
+
15
16
  if (!hasMailServie) {
16
17
  return this.fail();
17
18
  }
@@ -22,6 +23,7 @@ module.exports = class extends BaseRest {
22
23
  'Users'
23
24
  );
24
25
  const user = await userModel.select({ email });
26
+
25
27
  if (think.isEmpty(user)) {
26
28
  return this.fail();
27
29
  }
@@ -36,7 +38,7 @@ module.exports = class extends BaseRest {
36
38
  ? `"${SENDER_NAME}" <${SENDER_EMAIL}>`
37
39
  : SMTP_USER,
38
40
  to: user[0].email,
39
- subject: this.locale('[{{name}}] Reset Password', {
41
+ subject: this.locale('[{{name | safe}}] Reset Password', {
40
42
  name: SITE_NAME || 'Waline',
41
43
  }),
42
44
  html: this.locale(