@waline/vercel 1.26.3 → 1.26.4
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/dist/404.html +39 -0
- package/dist/500.html +275 -0
- package/dist/index.js +58501 -0
- package/dist/package.json +54 -0
- package/dist/src/config/adapter.js +170 -0
- package/dist/src/config/config.js +134 -0
- package/dist/src/config/extend.js +38 -0
- package/dist/src/config/middleware.js +66 -0
- package/dist/src/config/router.js +1 -0
- package/dist/src/controller/article.js +91 -0
- package/dist/src/controller/comment.js +758 -0
- package/dist/src/controller/db.js +71 -0
- package/dist/src/controller/index.js +36 -0
- package/dist/src/controller/oauth.js +136 -0
- package/dist/src/controller/rest.js +60 -0
- package/dist/src/controller/token/2fa.js +66 -0
- package/dist/src/controller/token.js +75 -0
- package/dist/src/controller/user/password.js +52 -0
- package/dist/src/controller/user.js +289 -0
- package/dist/src/controller/verification.js +35 -0
- package/dist/src/extend/controller.js +25 -0
- package/dist/src/extend/think.js +84 -0
- package/dist/src/locales/en.json +19 -0
- package/dist/src/locales/index.js +12 -0
- package/dist/src/locales/zh-CN.json +19 -0
- package/dist/src/locales/zh-TW.json +19 -0
- package/dist/src/logic/article.js +27 -0
- package/dist/src/logic/base.js +164 -0
- package/dist/src/logic/comment.js +317 -0
- package/dist/src/logic/db.js +81 -0
- package/dist/src/logic/oauth.js +10 -0
- package/dist/src/logic/token/2fa.js +28 -0
- package/dist/src/logic/token.js +53 -0
- package/dist/src/logic/user/password.js +11 -0
- package/dist/src/logic/user.js +117 -0
- package/dist/src/middleware/dashboard.js +23 -0
- package/dist/src/middleware/version.js +6 -0
- package/dist/src/service/akismet.js +41 -0
- package/dist/src/service/avatar.js +35 -0
- package/dist/src/service/markdown/highlight.js +32 -0
- package/dist/src/service/markdown/index.js +63 -0
- package/dist/src/service/markdown/katex.js +49 -0
- package/dist/src/service/markdown/mathCommon.js +156 -0
- package/dist/src/service/markdown/mathjax.js +78 -0
- package/dist/src/service/markdown/utils.js +11 -0
- package/dist/src/service/markdown/xss.js +44 -0
- package/dist/src/service/notify.js +537 -0
- package/dist/src/service/storage/base.js +31 -0
- package/dist/src/service/storage/cloudbase.js +221 -0
- package/dist/src/service/storage/deta.js +307 -0
- package/dist/src/service/storage/github.js +377 -0
- package/dist/src/service/storage/leancloud.js +430 -0
- package/dist/src/service/storage/mongodb.js +179 -0
- package/dist/src/service/storage/mysql.js +123 -0
- package/dist/src/service/storage/postgresql.js +84 -0
- package/dist/src/service/storage/sqlite.js +11 -0
- package/dist/src/service/storage/tidb.js +3 -0
- package/package.json +1 -1
- package/src/controller/comment.js +1 -2
- package/src/extend/think.js +19 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const BaseRest = require('./rest');
|
|
2
|
+
|
|
3
|
+
module.exports = class extends BaseRest {
|
|
4
|
+
async getAction() {
|
|
5
|
+
const exportData = {
|
|
6
|
+
type: 'waline',
|
|
7
|
+
version: 1,
|
|
8
|
+
time: Date.now(),
|
|
9
|
+
tables: ['Comment', 'Counter', 'Users'],
|
|
10
|
+
data: {
|
|
11
|
+
Comment: [],
|
|
12
|
+
Counter: [],
|
|
13
|
+
Users: [],
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < exportData.tables.length; i++) {
|
|
18
|
+
const tableName = exportData.tables[i];
|
|
19
|
+
const storage = this.config('storage');
|
|
20
|
+
const model = this.service(`storage/${storage}`, tableName);
|
|
21
|
+
|
|
22
|
+
const data = await model.select({});
|
|
23
|
+
|
|
24
|
+
exportData.data[tableName] = data;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return this.success(exportData);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async postAction() {
|
|
31
|
+
const { table } = this.get();
|
|
32
|
+
const item = this.post();
|
|
33
|
+
const storage = this.config('storage');
|
|
34
|
+
const model = this.service(`storage/${storage}`, table);
|
|
35
|
+
|
|
36
|
+
if (storage === 'leancloud' || storage === 'mysql') {
|
|
37
|
+
item.insertedAt && (item.insertedAt = new Date(item.insertedAt));
|
|
38
|
+
item.createdAt && (item.createdAt = new Date(item.createdAt));
|
|
39
|
+
item.updatedAt && (item.updatedAt = new Date(item.updatedAt));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
delete item.objectId;
|
|
43
|
+
const resp = await model.add(item);
|
|
44
|
+
|
|
45
|
+
return this.success(resp);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async putAction() {
|
|
49
|
+
const { table, objectId } = this.get();
|
|
50
|
+
const data = this.post();
|
|
51
|
+
const storage = this.config('storage');
|
|
52
|
+
const model = this.service(`storage/${storage}`, table);
|
|
53
|
+
|
|
54
|
+
delete data.objectId;
|
|
55
|
+
delete data.createdAt;
|
|
56
|
+
delete data.updatedAt;
|
|
57
|
+
await model.update(data, { objectId });
|
|
58
|
+
|
|
59
|
+
return this.success();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async deleteAction() {
|
|
63
|
+
const { table } = this.get();
|
|
64
|
+
const storage = this.config('storage');
|
|
65
|
+
const model = this.service(`storage/${storage}`, table);
|
|
66
|
+
|
|
67
|
+
await model.delete({});
|
|
68
|
+
|
|
69
|
+
return this.success();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const { version } = require('../../package.json');
|
|
2
|
+
|
|
3
|
+
module.exports = class extends think.Controller {
|
|
4
|
+
indexAction() {
|
|
5
|
+
this.type = 'html';
|
|
6
|
+
this.body = `
|
|
7
|
+
<!DOCTYPE html>
|
|
8
|
+
<html lang="en">
|
|
9
|
+
<head>
|
|
10
|
+
<meta charset="UTF-8">
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
12
|
+
<title>Waline Example</title>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<div id="waline" style="max-width: 800px;margin: 0 auto;"></div>
|
|
16
|
+
<script src="https://unpkg.com/@waline/client@v2/dist/waline.js"></script>
|
|
17
|
+
<link href='//unpkg.com/@waline/client@v2/dist/waline.css' rel='stylesheet' />
|
|
18
|
+
<script>
|
|
19
|
+
console.log(
|
|
20
|
+
'%c @waline/server %c v${version} ',
|
|
21
|
+
'color: white; background: #0078E7; padding:5px 0;',
|
|
22
|
+
'padding:4px;border:1px solid #0078E7;'
|
|
23
|
+
);
|
|
24
|
+
const params = new URLSearchParams(location.search.slice(1));
|
|
25
|
+
const waline = Waline.init({
|
|
26
|
+
el: '#waline',
|
|
27
|
+
path: params.get('path') || '/',
|
|
28
|
+
lang: params.get('lng'),
|
|
29
|
+
serverURL: location.protocol + '//' + location.host + location.pathname.replace(/\\/+$/, ''),
|
|
30
|
+
recaptchaV3Key: '${process.env.RECAPTCHA_V3_KEY || ''}',
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
</body>
|
|
34
|
+
</html>`;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken');
|
|
2
|
+
const fetch = require('node-fetch');
|
|
3
|
+
const { PasswordHash } = require('phpass');
|
|
4
|
+
|
|
5
|
+
module.exports = class extends think.Controller {
|
|
6
|
+
constructor(ctx) {
|
|
7
|
+
super(ctx);
|
|
8
|
+
this.modelInstance = this.service(
|
|
9
|
+
`storage/${this.config('storage')}`,
|
|
10
|
+
'Users'
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async indexAction() {
|
|
15
|
+
const { code, oauth_verifier, oauth_token, type, redirect } = this.get();
|
|
16
|
+
const { oauthUrl } = this.config();
|
|
17
|
+
|
|
18
|
+
const hasCode =
|
|
19
|
+
type === 'twitter' ? oauth_token && oauth_verifier : Boolean(code);
|
|
20
|
+
|
|
21
|
+
if (!hasCode) {
|
|
22
|
+
const { serverURL } = this.ctx;
|
|
23
|
+
const redirectUrl = `${serverURL}/oauth?${new URLSearchParams({
|
|
24
|
+
redirect,
|
|
25
|
+
type,
|
|
26
|
+
}).toString()}`;
|
|
27
|
+
|
|
28
|
+
return this.redirect(
|
|
29
|
+
`${oauthUrl}/${type}?${new URLSearchParams({
|
|
30
|
+
redirect: redirectUrl,
|
|
31
|
+
state: this.ctx.state.token || '',
|
|
32
|
+
}).toString()}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* user = { id, name, email, avatar,url };
|
|
38
|
+
*/
|
|
39
|
+
const params = { code, oauth_verifier, oauth_token };
|
|
40
|
+
|
|
41
|
+
if (type === 'facebook') {
|
|
42
|
+
const { serverURL } = this.ctx;
|
|
43
|
+
const redirectUrl = `${serverURL}/oauth?${new URLSearchParams({
|
|
44
|
+
redirect,
|
|
45
|
+
type,
|
|
46
|
+
}).toString()}`;
|
|
47
|
+
|
|
48
|
+
params.state = new URLSearchParams({
|
|
49
|
+
redirect: redirectUrl,
|
|
50
|
+
state: this.ctx.state.token || '',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const user = await fetch(
|
|
55
|
+
`${oauthUrl}/${type}?${new URLSearchParams(params).toString()}`,
|
|
56
|
+
{
|
|
57
|
+
method: 'GET',
|
|
58
|
+
headers: {
|
|
59
|
+
'user-agent': '@waline',
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
).then((resp) => resp.json());
|
|
63
|
+
|
|
64
|
+
if (!user || !user.id) {
|
|
65
|
+
return this.fail(user);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const userBySocial = await this.modelInstance.select({ [type]: user.id });
|
|
69
|
+
|
|
70
|
+
if (!think.isEmpty(userBySocial)) {
|
|
71
|
+
const token = jwt.sign(userBySocial[0].email, this.config('jwtKey'));
|
|
72
|
+
|
|
73
|
+
if (redirect) {
|
|
74
|
+
return this.redirect(
|
|
75
|
+
redirect + (redirect.includes('?') ? '&' : '?') + 'token=' + token
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return this.success();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!user.email) {
|
|
83
|
+
user.email = `${user.id}@mail.${type}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const current = this.ctx.state.userInfo;
|
|
87
|
+
|
|
88
|
+
if (!think.isEmpty(current)) {
|
|
89
|
+
const updateData = { [type]: user.id };
|
|
90
|
+
|
|
91
|
+
if (!current.avatar && user.avatar) {
|
|
92
|
+
updateData.avatar = user.avatar;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await this.modelInstance.update(updateData, {
|
|
96
|
+
objectId: current.objectId,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return this.redirect('/ui/profile');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const userByEmail = await this.modelInstance.select({ email: user.email });
|
|
103
|
+
|
|
104
|
+
if (think.isEmpty(userByEmail)) {
|
|
105
|
+
const count = await this.modelInstance.count();
|
|
106
|
+
const data = {
|
|
107
|
+
display_name: user.name,
|
|
108
|
+
email: user.email,
|
|
109
|
+
url: user.url,
|
|
110
|
+
avatar: user.avatar,
|
|
111
|
+
[type]: user.id,
|
|
112
|
+
password: new PasswordHash().hashPassword(Math.random()),
|
|
113
|
+
type: think.isEmpty(count) ? 'administrator' : 'guest',
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
await this.modelInstance.add(data);
|
|
117
|
+
} else {
|
|
118
|
+
const updateData = { [type]: user.id };
|
|
119
|
+
|
|
120
|
+
if (!userByEmail.avatar && user.avatar) {
|
|
121
|
+
updateData.avatar = user.avatar;
|
|
122
|
+
}
|
|
123
|
+
await this.modelInstance.update(updateData, { email: user.email });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const token = jwt.sign(user.email, this.config('jwtKey'));
|
|
127
|
+
|
|
128
|
+
if (redirect) {
|
|
129
|
+
return this.redirect(
|
|
130
|
+
redirect + (redirect.includes('?') ? '&' : '?') + 'token=' + token
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return this.success();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
module.exports = class extends think.Controller {
|
|
4
|
+
static get _REST() {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static get _method() {
|
|
9
|
+
return 'method';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
constructor(ctx) {
|
|
13
|
+
super(ctx);
|
|
14
|
+
this.resource = this.getResource();
|
|
15
|
+
this.id = this.getId();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
__before() {}
|
|
19
|
+
|
|
20
|
+
getResource() {
|
|
21
|
+
const filename = this.__filename || __filename;
|
|
22
|
+
const last = filename.lastIndexOf(path.sep);
|
|
23
|
+
|
|
24
|
+
return filename.substr(last + 1, filename.length - last - 4);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getId() {
|
|
28
|
+
const id = this.get('id');
|
|
29
|
+
|
|
30
|
+
if (id && (think.isString(id) || think.isNumber(id))) {
|
|
31
|
+
return id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const last = decodeURIComponent(this.ctx.path.split('/').pop());
|
|
35
|
+
|
|
36
|
+
if (last !== this.resource && /^([a-z0-9]+,?)*$/i.test(last)) {
|
|
37
|
+
return last;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
isLogin() {
|
|
44
|
+
const { userInfo } = this.ctx.state;
|
|
45
|
+
|
|
46
|
+
return think.isEmpty(userInfo);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async hook(name, ...args) {
|
|
50
|
+
const fn = this.config(name);
|
|
51
|
+
|
|
52
|
+
if (!think.isFunction(fn)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return fn.call(this, ...args);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
__call() {}
|
|
60
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const speakeasy = require('speakeasy');
|
|
2
|
+
const BaseRest = require('../rest');
|
|
3
|
+
|
|
4
|
+
module.exports = class extends BaseRest {
|
|
5
|
+
async getAction() {
|
|
6
|
+
const { userInfo } = this.ctx.state;
|
|
7
|
+
const { email } = this.get();
|
|
8
|
+
|
|
9
|
+
if (think.isEmpty(userInfo) && email) {
|
|
10
|
+
const userModel = this.service(
|
|
11
|
+
`storage/${this.config('storage')}`,
|
|
12
|
+
'Users'
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const user = await userModel.select(
|
|
16
|
+
{ email },
|
|
17
|
+
{
|
|
18
|
+
field: ['2fa'],
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
const is2FAEnabled = !think.isEmpty(user) && Boolean(user[0]['2fa']);
|
|
22
|
+
|
|
23
|
+
return this.success({ enable: is2FAEnabled });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const name = `waline_${userInfo.objectId}`;
|
|
27
|
+
|
|
28
|
+
if (userInfo['2fa'] && userInfo['2fa'].length == 32) {
|
|
29
|
+
return this.success({
|
|
30
|
+
otpauth_url: `otpauth://totp/${name}?secret=${userInfo['2fa']}`,
|
|
31
|
+
secret: userInfo['2fa'],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { otpauth_url, base32: secret } = speakeasy.generateSecret({
|
|
36
|
+
length: 20,
|
|
37
|
+
name,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return this.success({ otpauth_url, secret });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async postAction() {
|
|
44
|
+
const data = this.post();
|
|
45
|
+
const verified = speakeasy.totp.verify({
|
|
46
|
+
secret: data.secret,
|
|
47
|
+
encoding: 'base32',
|
|
48
|
+
token: data.code,
|
|
49
|
+
window: 2,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!verified) {
|
|
53
|
+
return this.fail(this.locale('TWO_FACTOR_AUTH_ERROR_DETAIL'));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const userModel = this.service(
|
|
57
|
+
`storage/${this.config('storage')}`,
|
|
58
|
+
'Users'
|
|
59
|
+
);
|
|
60
|
+
const { objectId } = this.ctx.state.userInfo;
|
|
61
|
+
|
|
62
|
+
await userModel.update({ ['2fa']: data.secret }, { objectId });
|
|
63
|
+
|
|
64
|
+
return this.success();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const speakeasy = require('speakeasy');
|
|
2
|
+
const jwt = require('jsonwebtoken');
|
|
3
|
+
const helper = require('think-helper');
|
|
4
|
+
const { PasswordHash } = require('phpass');
|
|
5
|
+
const BaseRest = require('./rest');
|
|
6
|
+
|
|
7
|
+
module.exports = class extends BaseRest {
|
|
8
|
+
constructor(...args) {
|
|
9
|
+
super(...args);
|
|
10
|
+
this.modelInstance = this.service(
|
|
11
|
+
`storage/${this.config('storage')}`,
|
|
12
|
+
'Users'
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getAction() {
|
|
17
|
+
return this.success(this.ctx.state.userInfo);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async postAction() {
|
|
21
|
+
const { email, password, code } = this.post();
|
|
22
|
+
const user = await this.modelInstance.select({ email });
|
|
23
|
+
|
|
24
|
+
if (think.isEmpty(user) || /^verify:/i.test(user[0].type)) {
|
|
25
|
+
return this.fail();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const checkPassword = new PasswordHash().checkPassword(
|
|
29
|
+
password,
|
|
30
|
+
user[0].password
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (!checkPassword) {
|
|
34
|
+
return this.fail();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const twoFactorAuthSecret = user[0]['2fa'];
|
|
38
|
+
|
|
39
|
+
if (twoFactorAuthSecret) {
|
|
40
|
+
const verified = speakeasy.totp.verify({
|
|
41
|
+
secret: twoFactorAuthSecret,
|
|
42
|
+
encoding: 'base32',
|
|
43
|
+
token: code,
|
|
44
|
+
window: 2,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!verified) {
|
|
48
|
+
return this.fail();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let avatarUrl = user[0].avatar
|
|
53
|
+
? user[0].avatar
|
|
54
|
+
: await think.service('avatar').stringify({
|
|
55
|
+
mail: user[0].email,
|
|
56
|
+
nick: user[0].display_name,
|
|
57
|
+
link: user[0].url,
|
|
58
|
+
});
|
|
59
|
+
const { avatarProxy } = think.config();
|
|
60
|
+
|
|
61
|
+
if (avatarProxy) {
|
|
62
|
+
avatarUrl = avatarProxy + '?url=' + encodeURIComponent(avatarUrl);
|
|
63
|
+
}
|
|
64
|
+
user[0].avatar = avatarUrl;
|
|
65
|
+
|
|
66
|
+
return this.success({
|
|
67
|
+
...user[0],
|
|
68
|
+
password: null,
|
|
69
|
+
mailMd5: helper.md5(user[0].email.toLowerCase()),
|
|
70
|
+
token: jwt.sign(user[0].email, this.config('jwtKey')),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
deleteAction() {}
|
|
75
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken');
|
|
2
|
+
const BaseRest = require('../rest');
|
|
3
|
+
|
|
4
|
+
module.exports = class extends BaseRest {
|
|
5
|
+
async putAction() {
|
|
6
|
+
const {
|
|
7
|
+
SMTP_HOST,
|
|
8
|
+
SMTP_SERVICE,
|
|
9
|
+
SENDER_EMAIL,
|
|
10
|
+
SENDER_NAME,
|
|
11
|
+
SMTP_USER,
|
|
12
|
+
SITE_NAME,
|
|
13
|
+
} = process.env;
|
|
14
|
+
const hasMailService = SMTP_HOST || SMTP_SERVICE;
|
|
15
|
+
|
|
16
|
+
if (!hasMailService) {
|
|
17
|
+
return this.fail();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { email } = this.post();
|
|
21
|
+
const userModel = this.service(
|
|
22
|
+
`storage/${this.config('storage')}`,
|
|
23
|
+
'Users'
|
|
24
|
+
);
|
|
25
|
+
const user = await userModel.select({ email });
|
|
26
|
+
|
|
27
|
+
if (think.isEmpty(user)) {
|
|
28
|
+
return this.fail();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const notify = this.service('notify', this);
|
|
32
|
+
const token = jwt.sign(user[0].email, this.config('jwtKey'));
|
|
33
|
+
const profileUrl = `${this.ctx.serverURL}/ui/profile?token=${token}`;
|
|
34
|
+
|
|
35
|
+
await notify.transporter.sendMail({
|
|
36
|
+
from:
|
|
37
|
+
SENDER_EMAIL && SENDER_NAME
|
|
38
|
+
? `"${SENDER_NAME}" <${SENDER_EMAIL}>`
|
|
39
|
+
: SMTP_USER,
|
|
40
|
+
to: user[0].email,
|
|
41
|
+
subject: this.locale('[{{name | safe}}] Reset Password', {
|
|
42
|
+
name: SITE_NAME || 'Waline',
|
|
43
|
+
}),
|
|
44
|
+
html: this.locale(
|
|
45
|
+
'Please click <a href="{{url}}">{{url}}</a> to login and change your password as soon as possible!',
|
|
46
|
+
{ url: profileUrl }
|
|
47
|
+
),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return this.success();
|
|
51
|
+
}
|
|
52
|
+
};
|