@waline/vercel 1.36.4 → 1.37.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.
- package/__tests__/xss.spec.js +8 -19
- package/development.js +1 -0
- package/index.js +4 -3
- package/package.json +17 -18
- package/src/config/adapter.js +9 -11
- package/src/config/config.js +4 -12
- package/src/config/extend.js +0 -6
- package/src/config/middleware.js +2 -5
- package/src/controller/article.js +5 -12
- package/src/controller/comment.js +33 -53
- package/src/controller/db.js +3 -9
- package/src/controller/oauth.js +8 -8
- package/src/controller/rest.js +1 -1
- package/src/controller/user/password.js +3 -12
- package/src/controller/user.js +18 -45
- package/src/controller/verification.js +3 -2
- package/src/extend/think.js +33 -23
- package/src/logic/base.js +31 -47
- package/src/logic/comment.js +7 -4
- package/src/logic/db.js +1 -1
- package/src/logic/token.js +2 -2
- package/src/middleware/dashboard.js +1 -0
- package/src/middleware/plugin.js +1 -1
- package/src/service/akismet.js +10 -3
- package/src/service/avatar.js +2 -4
- package/src/service/markdown/highlight.js +1 -1
- package/src/service/markdown/mathCommon.js +2 -8
- package/src/service/markdown/mathjax.js +1 -2
- package/src/service/markdown/utils.js +5 -5
- package/src/service/markdown/xss.js +5 -10
- package/src/service/notify.js +111 -135
- package/src/service/storage/base.js +2 -7
- package/src/service/storage/cloudbase.js +9 -7
- package/src/service/storage/github.js +39 -42
- package/src/service/storage/leancloud.js +41 -54
- package/src/service/storage/mongodb.js +11 -8
- package/src/service/storage/mysql.js +7 -13
- package/src/service/storage/postgresql.js +7 -11
- package/src/service/storage/deta.js +0 -310
package/src/controller/rest.js
CHANGED
|
@@ -21,7 +21,7 @@ module.exports = class extends think.Controller {
|
|
|
21
21
|
const filename = this.__filename || __filename;
|
|
22
22
|
const last = filename.lastIndexOf(path.sep);
|
|
23
23
|
|
|
24
|
-
return filename.
|
|
24
|
+
return filename.slice(last + 1, filename.length - last - 4);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
getId() {
|
|
@@ -4,14 +4,8 @@ const BaseRest = require('../rest.js');
|
|
|
4
4
|
|
|
5
5
|
module.exports = class extends BaseRest {
|
|
6
6
|
async putAction() {
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
SMTP_SERVICE,
|
|
10
|
-
SENDER_EMAIL,
|
|
11
|
-
SENDER_NAME,
|
|
12
|
-
SMTP_USER,
|
|
13
|
-
SITE_NAME,
|
|
14
|
-
} = process.env;
|
|
7
|
+
const { SMTP_HOST, SMTP_SERVICE, SENDER_EMAIL, SENDER_NAME, SMTP_USER, SITE_NAME } =
|
|
8
|
+
process.env;
|
|
15
9
|
const hasMailService = SMTP_HOST || SMTP_SERVICE;
|
|
16
10
|
|
|
17
11
|
if (!hasMailService) {
|
|
@@ -31,10 +25,7 @@ module.exports = class extends BaseRest {
|
|
|
31
25
|
const profileUrl = `${this.ctx.serverURL}/ui/profile?token=${token}`;
|
|
32
26
|
|
|
33
27
|
await notify.transporter.sendMail({
|
|
34
|
-
from:
|
|
35
|
-
SENDER_EMAIL && SENDER_NAME
|
|
36
|
-
? `"${SENDER_NAME}" <${SENDER_EMAIL}>`
|
|
37
|
-
: SMTP_USER,
|
|
28
|
+
from: SENDER_EMAIL && SENDER_NAME ? `"${SENDER_NAME}" <${SENDER_EMAIL}>` : SMTP_USER,
|
|
38
29
|
to: user[0].email,
|
|
39
30
|
subject: this.locale('[{{name | safe}}] Reset Password', {
|
|
40
31
|
name: SITE_NAME || 'Waline',
|
package/src/controller/user.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const BaseRest = require('./rest.js');
|
|
2
2
|
|
|
3
|
-
module.exports = class extends BaseRest {
|
|
3
|
+
module.exports = class UserController extends BaseRest {
|
|
4
4
|
constructor(...args) {
|
|
5
5
|
super(...args);
|
|
6
6
|
this.modelInstance = this.getModel('Users');
|
|
@@ -50,28 +50,17 @@ module.exports = class extends BaseRest {
|
|
|
50
50
|
email: data.email,
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
if (
|
|
54
|
-
!think.isEmpty(resp) &&
|
|
55
|
-
['administrator', 'guest'].includes(resp[0].type)
|
|
56
|
-
) {
|
|
53
|
+
if (!think.isEmpty(resp) && ['administrator', 'guest'].includes(resp[0].type)) {
|
|
57
54
|
return this.fail(this.locale('USER_EXIST'));
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
const count = await this.modelInstance.count();
|
|
61
58
|
|
|
62
|
-
const {
|
|
63
|
-
|
|
64
|
-
SMTP_SERVICE,
|
|
65
|
-
SENDER_EMAIL,
|
|
66
|
-
SENDER_NAME,
|
|
67
|
-
SMTP_USER,
|
|
68
|
-
SITE_NAME,
|
|
69
|
-
} = process.env;
|
|
59
|
+
const { SMTP_HOST, SMTP_SERVICE, SENDER_EMAIL, SENDER_NAME, SMTP_USER, SITE_NAME } =
|
|
60
|
+
process.env;
|
|
70
61
|
const hasMailService = SMTP_HOST || SMTP_SERVICE;
|
|
71
62
|
|
|
72
|
-
const token = Array.from({ length: 4 }, () =>
|
|
73
|
-
Math.round(Math.random() * 9),
|
|
74
|
-
).join('');
|
|
63
|
+
const token = Array.from({ length: 4 }, () => Math.round(Math.random() * 9)).join('');
|
|
75
64
|
const normalType = hasMailService
|
|
76
65
|
? `verify:${token}:${Date.now() + 1 * 60 * 60 * 1000}`
|
|
77
66
|
: 'guest';
|
|
@@ -91,16 +80,13 @@ module.exports = class extends BaseRest {
|
|
|
91
80
|
|
|
92
81
|
try {
|
|
93
82
|
const notify = this.service('notify', this);
|
|
94
|
-
const apiUrl = think.buildUrl(this.ctx.serverURL
|
|
83
|
+
const apiUrl = think.buildUrl(`${this.ctx.serverURL}/verification`, {
|
|
95
84
|
token,
|
|
96
85
|
email: data.email,
|
|
97
86
|
});
|
|
98
87
|
|
|
99
88
|
await notify.transporter.sendMail({
|
|
100
|
-
from:
|
|
101
|
-
SENDER_EMAIL && SENDER_NAME
|
|
102
|
-
? `"${SENDER_NAME}" <${SENDER_EMAIL}>`
|
|
103
|
-
: SMTP_USER,
|
|
89
|
+
from: SENDER_EMAIL && SENDER_NAME ? `"${SENDER_NAME}" <${SENDER_EMAIL}>` : SMTP_USER,
|
|
104
90
|
to: data.email,
|
|
105
91
|
subject: this.locale('[{{name | safe}}] Registration Confirm Mail', {
|
|
106
92
|
name: SITE_NAME || 'Waline',
|
|
@@ -125,8 +111,7 @@ module.exports = class extends BaseRest {
|
|
|
125
111
|
}
|
|
126
112
|
|
|
127
113
|
async putAction() {
|
|
128
|
-
const { display_name, url, avatar, password, type, label, email } =
|
|
129
|
-
this.post();
|
|
114
|
+
const { display_name, url, avatar, password, type, label, email } = this.post();
|
|
130
115
|
const { objectId } = this.ctx.state.userInfo;
|
|
131
116
|
const twoFactorAuth = this.post('2fa');
|
|
132
117
|
|
|
@@ -194,6 +179,7 @@ module.exports = class extends BaseRest {
|
|
|
194
179
|
return this.success();
|
|
195
180
|
}
|
|
196
181
|
|
|
182
|
+
// oxlint-disable-next-line max-statements
|
|
197
183
|
async getUsersListByCount() {
|
|
198
184
|
const { pageSize } = this.get();
|
|
199
185
|
const commentModel = this.getModel('Comment');
|
|
@@ -209,13 +195,11 @@ module.exports = class extends BaseRest {
|
|
|
209
195
|
counts.sort((a, b) => b.count - a.count);
|
|
210
196
|
counts.length = Math.min(pageSize, counts.length);
|
|
211
197
|
|
|
212
|
-
const userIds = counts
|
|
213
|
-
.filter(({ user_id }) => user_id)
|
|
214
|
-
.map(({ user_id }) => user_id);
|
|
198
|
+
const userIds = counts.filter(({ user_id }) => user_id).map(({ user_id }) => user_id);
|
|
215
199
|
|
|
216
|
-
|
|
200
|
+
const usersMap = {};
|
|
217
201
|
|
|
218
|
-
if (userIds.length) {
|
|
202
|
+
if (userIds.length > 0) {
|
|
219
203
|
const users = await this.modelInstance.select({
|
|
220
204
|
objectId: ['IN', userIds],
|
|
221
205
|
});
|
|
@@ -237,10 +221,7 @@ module.exports = class extends BaseRest {
|
|
|
237
221
|
let level = 0;
|
|
238
222
|
|
|
239
223
|
if (user.count) {
|
|
240
|
-
const _level = think.findLastIndex(
|
|
241
|
-
this.config('levels'),
|
|
242
|
-
(l) => l <= user.count,
|
|
243
|
-
);
|
|
224
|
+
const _level = think.findLastIndex(this.config('levels'), (level) => level <= user.count);
|
|
244
225
|
|
|
245
226
|
if (_level !== -1) {
|
|
246
227
|
level = _level;
|
|
@@ -250,15 +231,10 @@ module.exports = class extends BaseRest {
|
|
|
250
231
|
}
|
|
251
232
|
|
|
252
233
|
if (count.user_id && users[count.user_id]) {
|
|
253
|
-
const {
|
|
254
|
-
display_name: nick,
|
|
255
|
-
url: link,
|
|
256
|
-
avatar: avatarUrl,
|
|
257
|
-
label,
|
|
258
|
-
} = users[count.user_id];
|
|
234
|
+
const { display_name: nick, url: link, avatar: avatarUrl, label } = users[count.user_id];
|
|
259
235
|
const avatar =
|
|
260
236
|
avatarProxy && !avatarUrl.includes(avatarProxy)
|
|
261
|
-
? avatarProxy
|
|
237
|
+
? `${avatarProxy}?url=${encodeURIComponent(avatarUrl)}`
|
|
262
238
|
: avatarUrl;
|
|
263
239
|
|
|
264
240
|
Object.assign(user, { nick, link, avatar, label });
|
|
@@ -266,15 +242,12 @@ module.exports = class extends BaseRest {
|
|
|
266
242
|
continue;
|
|
267
243
|
}
|
|
268
244
|
|
|
269
|
-
const comments = await commentModel.select(
|
|
270
|
-
{ mail: count.mail },
|
|
271
|
-
{ limit: 1 },
|
|
272
|
-
);
|
|
245
|
+
const comments = await commentModel.select({ mail: count.mail }, { limit: 1 });
|
|
273
246
|
|
|
274
247
|
if (think.isEmpty(comments)) {
|
|
275
248
|
continue;
|
|
276
249
|
}
|
|
277
|
-
const comment = comments
|
|
250
|
+
const [comment] = comments;
|
|
278
251
|
|
|
279
252
|
if (think.isEmpty(comment)) {
|
|
280
253
|
continue;
|
|
@@ -283,7 +256,7 @@ module.exports = class extends BaseRest {
|
|
|
283
256
|
const avatarUrl = await think.service('avatar').stringify(comment);
|
|
284
257
|
const avatar =
|
|
285
258
|
avatarProxy && !avatarUrl.includes(avatarProxy)
|
|
286
|
-
? avatarProxy
|
|
259
|
+
? `${avatarProxy}?url=${encodeURIComponent(avatarUrl)}`
|
|
287
260
|
: avatarUrl;
|
|
288
261
|
|
|
289
262
|
Object.assign(user, { nick, link, avatar });
|
|
@@ -21,10 +21,11 @@ module.exports = class extends BaseRest {
|
|
|
21
21
|
return this.fail(this.locale('USER_REGISTERED'));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (token === match[1] && Date.now() < parseInt(match[2])) {
|
|
24
|
+
if (token === match[1] && Date.now() < Number.parseInt(match[2])) {
|
|
25
25
|
await this.modelInstance.update({ type: 'guest' }, { email });
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
this.redirect('/ui/login');
|
|
28
|
+
return;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
return this.fail(this.locale('TOKEN_EXPIRED'));
|
package/src/extend/think.js
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
const
|
|
2
|
-
const helper = require('think-helper');
|
|
1
|
+
const IP2Region = require('ip2region').default;
|
|
3
2
|
const parser = require('ua-parser-js');
|
|
4
3
|
|
|
5
4
|
const preventMessage = 'PREVENT_NEXT_PROCESS';
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
// Cached IP2Region instance using IIFE closure pattern
|
|
7
|
+
// Instance is created on first access and reused for all subsequent calls
|
|
8
|
+
const getIP2RegionInstance = (() => {
|
|
9
|
+
let instance = null;
|
|
10
|
+
|
|
11
|
+
return () => {
|
|
12
|
+
if (!instance) {
|
|
13
|
+
instance = new IP2Region({
|
|
14
|
+
ipv4db: process.env.IP2REGION_DB_V4 || process.env.IP2REGION_DB,
|
|
15
|
+
ipv6db: process.env.IP2REGION_DB_V6,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return instance;
|
|
20
|
+
};
|
|
21
|
+
})();
|
|
8
22
|
|
|
9
23
|
const OS_VERSION_MAP = {
|
|
10
24
|
Windows: {
|
|
@@ -34,15 +48,17 @@ module.exports = {
|
|
|
34
48
|
},
|
|
35
49
|
promiseAllQueue(promises, taskNum) {
|
|
36
50
|
return new Promise((resolve, reject) => {
|
|
37
|
-
if (
|
|
38
|
-
|
|
51
|
+
if (promises.length === 0) {
|
|
52
|
+
resolve();
|
|
53
|
+
|
|
54
|
+
return;
|
|
39
55
|
}
|
|
40
56
|
|
|
41
57
|
const ret = [];
|
|
42
58
|
let index = 0;
|
|
43
59
|
let count = 0;
|
|
44
60
|
|
|
45
|
-
|
|
61
|
+
const runTask = () => {
|
|
46
62
|
const idx = index;
|
|
47
63
|
|
|
48
64
|
index += 1;
|
|
@@ -59,7 +75,7 @@ module.exports = {
|
|
|
59
75
|
|
|
60
76
|
return runTask();
|
|
61
77
|
}, reject);
|
|
62
|
-
}
|
|
78
|
+
};
|
|
63
79
|
|
|
64
80
|
for (let i = 0; i < taskNum; i++) {
|
|
65
81
|
runTask();
|
|
@@ -67,21 +83,17 @@ module.exports = {
|
|
|
67
83
|
});
|
|
68
84
|
},
|
|
69
85
|
async ip2region(ip, { depth = 1 }) {
|
|
70
|
-
if (!ip
|
|
86
|
+
if (!ip) return '';
|
|
71
87
|
|
|
72
88
|
try {
|
|
73
|
-
const
|
|
74
|
-
const result = await search(ip);
|
|
89
|
+
const res = getIP2RegionInstance().search(ip);
|
|
75
90
|
|
|
76
|
-
if (!
|
|
91
|
+
if (!res) {
|
|
77
92
|
return '';
|
|
78
93
|
}
|
|
79
|
-
const { region } = result;
|
|
80
|
-
const [, , province, city, isp] = region.split('|');
|
|
81
|
-
const address = Array.from(
|
|
82
|
-
new Set([province, city, isp].filter((v) => v)),
|
|
83
|
-
);
|
|
84
94
|
|
|
95
|
+
const { province, city, isp } = res;
|
|
96
|
+
const address = [...new Set([province, city, isp].filter(Boolean))];
|
|
85
97
|
return address.slice(0, depth).join(' ');
|
|
86
98
|
} catch (err) {
|
|
87
99
|
console.log(err);
|
|
@@ -106,7 +118,7 @@ module.exports = {
|
|
|
106
118
|
return defaultLevel;
|
|
107
119
|
}
|
|
108
120
|
|
|
109
|
-
const level = think.findLastIndex(levels, (
|
|
121
|
+
const level = think.findLastIndex(levels, (level) => level <= val);
|
|
110
122
|
|
|
111
123
|
return level === -1 ? defaultLevel : level;
|
|
112
124
|
},
|
|
@@ -141,7 +153,7 @@ module.exports = {
|
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
if (think.isArray(middleware)) {
|
|
144
|
-
return middleware.filter((
|
|
156
|
+
return middleware.filter((middleware) => think.isFunction(middleware));
|
|
145
157
|
}
|
|
146
158
|
});
|
|
147
159
|
|
|
@@ -149,10 +161,8 @@ module.exports = {
|
|
|
149
161
|
},
|
|
150
162
|
getPluginHook(hookName) {
|
|
151
163
|
return think
|
|
152
|
-
.pluginMap('hooks', (hook) =>
|
|
153
|
-
|
|
154
|
-
)
|
|
155
|
-
.filter((v) => v);
|
|
164
|
+
.pluginMap('hooks', (hook) => (think.isFunction(hook[hookName]) ? hook[hookName] : null))
|
|
165
|
+
.filter(Boolean);
|
|
156
166
|
},
|
|
157
167
|
buildUrl(path, query = {}) {
|
|
158
168
|
const notEmptyQuery = {};
|
|
@@ -169,7 +179,7 @@ module.exports = {
|
|
|
169
179
|
let destUrl = path;
|
|
170
180
|
|
|
171
181
|
if (destUrl && notEmptyQueryStr) {
|
|
172
|
-
destUrl += destUrl.
|
|
182
|
+
destUrl += destUrl.includes('?') ? '&' : '?';
|
|
173
183
|
}
|
|
174
184
|
if (notEmptyQueryStr) {
|
|
175
185
|
destUrl += notEmptyQueryStr;
|
package/src/logic/base.js
CHANGED
|
@@ -3,7 +3,7 @@ const qs = require('node:querystring');
|
|
|
3
3
|
|
|
4
4
|
const jwt = require('jsonwebtoken');
|
|
5
5
|
|
|
6
|
-
module.exports = class extends think.Logic {
|
|
6
|
+
module.exports = class BaseLogic extends think.Logic {
|
|
7
7
|
constructor(...args) {
|
|
8
8
|
super(...args);
|
|
9
9
|
this.modelInstance = this.getModel('Users');
|
|
@@ -11,26 +11,25 @@ module.exports = class extends think.Logic {
|
|
|
11
11
|
this.id = this.getId();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// oxlint-disable-next-line max-statements
|
|
14
15
|
async __before() {
|
|
15
16
|
const referrer = this.ctx.referrer(true);
|
|
16
|
-
let origin = this.ctx
|
|
17
|
+
let { origin } = this.ctx;
|
|
17
18
|
|
|
18
19
|
if (origin) {
|
|
19
20
|
try {
|
|
20
21
|
const parsedOrigin = new URL(origin);
|
|
21
22
|
|
|
22
23
|
origin = parsedOrigin.hostname;
|
|
23
|
-
} catch (
|
|
24
|
-
console.error('Invalid origin format:', origin,
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error('Invalid origin format:', origin, err);
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
let { secureDomains } = this.config();
|
|
29
30
|
|
|
30
31
|
if (secureDomains) {
|
|
31
|
-
secureDomains = think.isArray(secureDomains)
|
|
32
|
-
? secureDomains
|
|
33
|
-
: [secureDomains];
|
|
32
|
+
secureDomains = think.isArray(secureDomains) ? secureDomains : [secureDomains];
|
|
34
33
|
|
|
35
34
|
secureDomains.push(
|
|
36
35
|
'localhost',
|
|
@@ -41,27 +40,20 @@ module.exports = class extends think.Logic {
|
|
|
41
40
|
// 'api.weibo.com',
|
|
42
41
|
// 'graph.qq.com',
|
|
43
42
|
);
|
|
44
|
-
secureDomains =
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
secureDomains = [
|
|
44
|
+
...secureDomains,
|
|
45
|
+
...this.ctx.state.oauthServices.map(({ origin }) => origin),
|
|
46
|
+
];
|
|
47
47
|
|
|
48
48
|
// 转换可能的正则表达式字符串为正则表达式对象
|
|
49
49
|
secureDomains = secureDomains
|
|
50
50
|
.map((domain) => {
|
|
51
51
|
// 如果是正则表达式字符串,创建一个 RegExp 对象
|
|
52
|
-
if (
|
|
53
|
-
typeof domain === 'string' &&
|
|
54
|
-
domain.startsWith('/') &&
|
|
55
|
-
domain.endsWith('/')
|
|
56
|
-
) {
|
|
52
|
+
if (typeof domain === 'string' && domain.startsWith('/') && domain.endsWith('/')) {
|
|
57
53
|
try {
|
|
58
54
|
return new RegExp(domain.slice(1, -1)); // 去掉斜杠并创建 RegExp 对象
|
|
59
|
-
} catch (
|
|
60
|
-
console.error(
|
|
61
|
-
'Invalid regex pattern in secureDomains:',
|
|
62
|
-
domain,
|
|
63
|
-
e,
|
|
64
|
-
);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error('Invalid regex pattern in secureDomains:', domain, err);
|
|
65
57
|
|
|
66
58
|
return null;
|
|
67
59
|
}
|
|
@@ -72,11 +64,9 @@ module.exports = class extends think.Logic {
|
|
|
72
64
|
.filter(Boolean); // 过滤掉无效的正则表达式
|
|
73
65
|
|
|
74
66
|
// 有 referrer 检查 referrer,没有则检查 origin
|
|
75
|
-
const checking = referrer
|
|
67
|
+
const checking = referrer || origin;
|
|
76
68
|
const isSafe = secureDomains.some((domain) =>
|
|
77
|
-
think.isFunction(domain.test)
|
|
78
|
-
? domain.test(checking)
|
|
79
|
-
: domain === checking,
|
|
69
|
+
think.isFunction(domain.test) ? domain.test(checking) : domain === checking,
|
|
80
70
|
);
|
|
81
71
|
|
|
82
72
|
if (!isSafe) {
|
|
@@ -96,8 +86,8 @@ module.exports = class extends think.Logic {
|
|
|
96
86
|
|
|
97
87
|
try {
|
|
98
88
|
userId = jwt.verify(token, think.config('jwtKey'));
|
|
99
|
-
} catch (
|
|
100
|
-
think.logger.debug(
|
|
89
|
+
} catch (err) {
|
|
90
|
+
think.logger.debug(err);
|
|
101
91
|
}
|
|
102
92
|
|
|
103
93
|
if (think.isEmpty(userId) || !think.isString(userId)) {
|
|
@@ -125,19 +115,19 @@ module.exports = class extends think.Logic {
|
|
|
125
115
|
return;
|
|
126
116
|
}
|
|
127
117
|
|
|
128
|
-
const userInfo = user
|
|
118
|
+
const [userInfo] = user;
|
|
129
119
|
|
|
130
|
-
let avatarUrl =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
120
|
+
let avatarUrl =
|
|
121
|
+
userInfo.avatar ||
|
|
122
|
+
(await think.service('avatar').stringify({
|
|
123
|
+
mail: userInfo.email,
|
|
124
|
+
nick: userInfo.display_name,
|
|
125
|
+
link: userInfo.url,
|
|
126
|
+
}));
|
|
137
127
|
const { avatarProxy } = think.config();
|
|
138
128
|
|
|
139
129
|
if (avatarProxy) {
|
|
140
|
-
avatarUrl = avatarProxy
|
|
130
|
+
avatarUrl = `${avatarProxy}?url=${encodeURIComponent(avatarUrl)}`;
|
|
141
131
|
}
|
|
142
132
|
userInfo.avatar = avatarUrl;
|
|
143
133
|
this.ctx.state.userInfo = userInfo;
|
|
@@ -148,7 +138,7 @@ module.exports = class extends think.Logic {
|
|
|
148
138
|
const filename = this.__filename || __filename;
|
|
149
139
|
const last = filename.lastIndexOf(path.sep);
|
|
150
140
|
|
|
151
|
-
return filename.
|
|
141
|
+
return filename.slice(last + 1, filename.length - last - 4);
|
|
152
142
|
}
|
|
153
143
|
|
|
154
144
|
getId() {
|
|
@@ -205,28 +195,22 @@ module.exports = class extends think.Logic {
|
|
|
205
195
|
remoteip: this.ctx.ip,
|
|
206
196
|
});
|
|
207
197
|
|
|
208
|
-
const requestUrl = method === 'GET' ? api
|
|
198
|
+
const requestUrl = method === 'GET' ? `${api}?${query}` : api;
|
|
209
199
|
const options =
|
|
210
200
|
method === 'GET'
|
|
211
201
|
? {}
|
|
212
202
|
: {
|
|
213
203
|
method,
|
|
214
204
|
headers: {
|
|
215
|
-
'content-type':
|
|
216
|
-
'application/x-www-form-urlencoded; charset=UTF-8',
|
|
205
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
217
206
|
},
|
|
218
207
|
body: query,
|
|
219
208
|
};
|
|
220
209
|
|
|
221
|
-
const response = await fetch(requestUrl, options).then((resp) =>
|
|
222
|
-
resp.json(),
|
|
223
|
-
);
|
|
210
|
+
const response = await fetch(requestUrl, options).then((resp) => resp.json());
|
|
224
211
|
|
|
225
212
|
if (!response.success) {
|
|
226
|
-
think.logger.debug(
|
|
227
|
-
'RecaptchaV3 or Turnstile Result:',
|
|
228
|
-
JSON.stringify(response, null, '\t'),
|
|
229
|
-
);
|
|
213
|
+
think.logger.debug('RecaptchaV3 or Turnstile Result:', JSON.stringify(response, null, '\t'));
|
|
230
214
|
|
|
231
215
|
return this.ctx.throw(403);
|
|
232
216
|
}
|
package/src/logic/comment.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const Base = require('./base.js');
|
|
2
2
|
|
|
3
|
-
module.exports = class extends Base {
|
|
3
|
+
module.exports = class CommentLogic extends Base {
|
|
4
4
|
checkAdmin() {
|
|
5
5
|
const { userInfo } = this.ctx.state;
|
|
6
6
|
|
|
@@ -117,7 +117,7 @@ module.exports = class extends Base {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
switch (type) {
|
|
120
|
-
case 'recent':
|
|
120
|
+
case 'recent': {
|
|
121
121
|
this.rules = {
|
|
122
122
|
count: {
|
|
123
123
|
int: { max: 50 },
|
|
@@ -125,14 +125,16 @@ module.exports = class extends Base {
|
|
|
125
125
|
},
|
|
126
126
|
};
|
|
127
127
|
break;
|
|
128
|
+
}
|
|
128
129
|
|
|
129
|
-
case 'count':
|
|
130
|
+
case 'count': {
|
|
130
131
|
this.rules = {
|
|
131
132
|
url: {
|
|
132
133
|
array: true,
|
|
133
134
|
},
|
|
134
135
|
};
|
|
135
136
|
break;
|
|
137
|
+
}
|
|
136
138
|
|
|
137
139
|
case 'list': {
|
|
138
140
|
const { userInfo } = this.ctx.state;
|
|
@@ -153,7 +155,7 @@ module.exports = class extends Base {
|
|
|
153
155
|
break;
|
|
154
156
|
}
|
|
155
157
|
|
|
156
|
-
default:
|
|
158
|
+
default: {
|
|
157
159
|
this.rules = {
|
|
158
160
|
path: {
|
|
159
161
|
string: true,
|
|
@@ -173,6 +175,7 @@ module.exports = class extends Base {
|
|
|
173
175
|
},
|
|
174
176
|
};
|
|
175
177
|
break;
|
|
178
|
+
}
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
181
|
|
package/src/logic/db.js
CHANGED
package/src/logic/token.js
CHANGED
|
@@ -34,8 +34,8 @@ module.exports = class extends Base {
|
|
|
34
34
|
* @apiSuccess (200) {Number} errno 0
|
|
35
35
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
36
36
|
*/
|
|
37
|
-
postAction() {
|
|
38
|
-
|
|
37
|
+
async postAction() {
|
|
38
|
+
await this.useCaptchaCheck();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
package/src/middleware/plugin.js
CHANGED
|
@@ -3,7 +3,7 @@ const compose = require('koa-compose');
|
|
|
3
3
|
module.exports = () => async (ctx, next) => {
|
|
4
4
|
const middlewares = think.getPluginMiddlewares();
|
|
5
5
|
|
|
6
|
-
if (!think.isArray(middlewares) ||
|
|
6
|
+
if (!think.isArray(middlewares) || middlewares.length === 0) {
|
|
7
7
|
return next();
|
|
8
8
|
}
|
|
9
9
|
|
package/src/service/akismet.js
CHANGED
|
@@ -2,6 +2,7 @@ const Akismet = require('akismet');
|
|
|
2
2
|
|
|
3
3
|
const DEFAULT_KEY = '70542d86693e';
|
|
4
4
|
|
|
5
|
+
// oxlint-disable-next-line func-names
|
|
5
6
|
module.exports = function (comment, blog) {
|
|
6
7
|
let { AKISMET_KEY, SITE_URL } = process.env;
|
|
7
8
|
|
|
@@ -13,14 +14,18 @@ module.exports = function (comment, blog) {
|
|
|
13
14
|
return Promise.resolve(false);
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
// oxlint-disable-next-line func-names
|
|
16
18
|
return new Promise(function (resolve, reject) {
|
|
17
19
|
const akismet = Akismet.client({ blog, apiKey: AKISMET_KEY });
|
|
18
20
|
|
|
21
|
+
// oxlint-disable-next-line func-names
|
|
19
22
|
akismet.verifyKey(function (err, verifyKey) {
|
|
20
23
|
if (err) {
|
|
21
|
-
|
|
24
|
+
reject(err);
|
|
25
|
+
return;
|
|
22
26
|
} else if (!verifyKey) {
|
|
23
|
-
|
|
27
|
+
reject(new Error('Akismet API_KEY verify failed!'));
|
|
28
|
+
return;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
akismet.checkComment(
|
|
@@ -30,9 +35,11 @@ module.exports = function (comment, blog) {
|
|
|
30
35
|
comment_author: comment.nick,
|
|
31
36
|
comment_content: comment.comment,
|
|
32
37
|
},
|
|
38
|
+
// oxlint-disable-next-line func-names
|
|
33
39
|
function (err, spam) {
|
|
34
40
|
if (err) {
|
|
35
|
-
|
|
41
|
+
reject(err);
|
|
42
|
+
return;
|
|
36
43
|
}
|
|
37
44
|
resolve(spam);
|
|
38
45
|
},
|
package/src/service/avatar.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
1
|
+
const crypto = require('node:crypto');
|
|
2
2
|
|
|
3
3
|
const nunjucks = require('nunjucks');
|
|
4
4
|
const helper = require('think-helper');
|
|
@@ -8,9 +8,7 @@ const { GRAVATAR_STR } = process.env;
|
|
|
8
8
|
const env = new nunjucks.Environment();
|
|
9
9
|
|
|
10
10
|
env.addFilter('md5', (str) => helper.md5(str));
|
|
11
|
-
env.addFilter('sha256', (str) =>
|
|
12
|
-
crypto.createHash('sha256').update(str).digest('hex'),
|
|
13
|
-
);
|
|
11
|
+
env.addFilter('sha256', (str) => crypto.createHash('sha256').update(str).digest('hex'));
|
|
14
12
|
|
|
15
13
|
const DEFAULT_GRAVATAR_STR = `{%- set numExp = r/^[0-9]+$/g -%}
|
|
16
14
|
{%- set qqMailExp = r/^[0-9]+@qq.com$/ig -%}
|
|
@@ -12,11 +12,7 @@ const isValidDelim = (state, pos) => {
|
|
|
12
12
|
* Check non-whitespace conditions for opening and closing, and
|
|
13
13
|
* check that closing delimiter isn’t followed by a number
|
|
14
14
|
*/
|
|
15
|
-
canClose: !(
|
|
16
|
-
prevChar === ' ' ||
|
|
17
|
-
prevChar === '\t' ||
|
|
18
|
-
/[0-9]/u.exec(nextChar)
|
|
19
|
-
),
|
|
15
|
+
canClose: !(prevChar === ' ' || prevChar === '\t' || /[0-9]/u.exec(nextChar)),
|
|
20
16
|
};
|
|
21
17
|
};
|
|
22
18
|
|
|
@@ -141,9 +137,7 @@ const blockTeX = (state, start, end, silent) => {
|
|
|
141
137
|
? `${firstLine}\n`
|
|
142
138
|
: '') +
|
|
143
139
|
state.getLines(start + 1, next, state.tShift[start], true) +
|
|
144
|
-
((lastLine === null || lastLine === void 0 ? void 0 : lastLine.trim())
|
|
145
|
-
? lastLine
|
|
146
|
-
: '');
|
|
140
|
+
((lastLine === null || lastLine === void 0 ? void 0 : lastLine.trim()) ? lastLine : '');
|
|
147
141
|
token.map = [start, state.line];
|
|
148
142
|
token.markup = '$$';
|
|
149
143
|
|