Browse Source

Finish email.

pull/6/head
t123yh 7 years ago
parent
commit
cd3817a3a5
  1. 15
      config-example.json
  2. 47
      libs/email.js
  3. 41
      modules/api.js
  4. 9
      package-lock.json
  5. 3
      package.json
  6. 15
      views/forget.ejs
  7. 38
      views/forget_confirm.ejs
  8. 4
      views/header.ejs
  9. 1
      views/login.ejs
  10. 1
      views/sign_up.ejs

15
config-example.json

@ -10,10 +10,15 @@
"dialect": "sqlite", "dialect": "sqlite",
"storage": "syzoj.db" "storage": "syzoj.db"
}, },
"register_mail": { "register_mail": true,
"enabled": true, "email": {
"address": "test@test.domain", "method": "aliyundm",
"key": "test" "key": "test",
"options": {
"AccessKeyId": "xxxx",
"AccessKeySecret": "xxxx",
"AccountName": "xxxx"
}
}, },
"upload_dir": "uploads", "upload_dir": "uploads",
"default": { "default": {
@ -164,4 +169,4 @@
"session_secret": "233", "session_secret": "233",
"judge_server_addr": "http://127.0.0.1:5284", "judge_server_addr": "http://127.0.0.1:5284",
"judge_token": "233" "judge_token": "233"
} }

47
libs/email.js

@ -1,16 +1,41 @@
const Promise = require('bluebird'); const Promise = require('bluebird');
const sendmail = Promise.promisify(require('sendmail')()); const sendmail = Promise.promisify(require('sendmail')());
const { DM } = require('waliyun');
async function send_sendmail(to, subject, body) { let doSendEmail;
await sendmail({
from: `${syzoj.config.title} <${syzoj.config.register_mail.address}>`, if (syzoj.config.email.method === "sendmail") {
to: to, doSendEmail = async function send_sendmail(to, subject, body) {
type: 'text/html', await sendmail({
subject: subject, from: `${syzoj.config.title} <${syzoj.config.email.options.address}>`,
html: body to: to,
type: 'text/html',
subject: subject,
html: body
});
}
} else if (syzoj.config.email.method === "aliyundm") {
const dm = DM({
AccessKeyId: syzoj.config.email.options.AccessKeyId,
AccessKeySecret: syzoj.config.email.options.AccessKeySecret
}); });
doSendEmail = async function send_aliyundm(to, subject, body) {
const result = await dm.singleSendMail({
AccountName: syzoj.config.email.options.AccountName,
AddressType: 1,
ReplyToAddress: false,
ToAddress: to,
FromAlias: syzoj.config.title,
Subject: subject,
HtmlBody: body
});
if (result.Code != null) {
throw new Error("阿里云 API 错误:" + JSON.stringify(result));
}
}
} else {
doSendEmail = async () => {
throw new Error("邮件发送配置不正确。");
}
} }
module.exports.send = doSendEmail;
module.exports.send = async function sendEmail(to, subject, body) {
await send_sendmail(to, subject, body);
}

41
modules/api.js

@ -57,16 +57,16 @@ app.post('/api/forget', async (req, res) => {
userId: user.id, userId: user.id,
}; };
const token = jwt.sign(sendObj, syzoj.config.register_mail.key, { const token = jwt.sign(sendObj, syzoj.config.email.key, {
subject: 'forget', subject: 'forget',
expiresIn: '12h' expiresIn: '12h'
}); });
const vurl = req.protocol + '://' + req.get('host') + syzoj.utils.makeUrl(['api', 'forget_confirm'], { token: token }); const vurl = req.protocol + '://' + req.get('host') + syzoj.utils.makeUrl(['api', 'forget_confirm'], { token: token });
try { try {
await Email.send(req.body.email, await Email.send(user.email,
`${req.body.username}${syzoj.config.title} 密码重置邮件`, `${user.username}${syzoj.config.title} 密码重置邮件`,
`<p>请点击该链接来重置密码:</p><p><a href="${vurl}">${vurl}</a></p><p>链接有效期为 12h。如果您不是 ${req.body.username},请忽略此邮件。</p>` `<p>请点击该链接来重置密码:</p><p><a href="${vurl}">${vurl}</a></p><p>链接有效期为 12h。如果您不是 ${user.username},请忽略此邮件。</p>`
); );
} catch (e) { } catch (e) {
return res.send({ return res.send({
@ -75,7 +75,8 @@ app.post('/api/forget', async (req, res) => {
}); });
} }
if (!user) res.send({ error_code: 1 }); throw 123;
res.send({ error_code: 1 });
} catch (e) { } catch (e) {
syzoj.log(e); syzoj.log(e);
res.send(JSON.stringify({ error_code: e })); res.send(JSON.stringify({ error_code: e }));
@ -99,14 +100,14 @@ app.post('/api/sign_up', async (req, res) => {
if (!(req.body.email = req.body.email.trim())) throw 2006; if (!(req.body.email = req.body.email.trim())) throw 2006;
if (!syzoj.utils.isValidUsername(req.body.username)) throw 2002; if (!syzoj.utils.isValidUsername(req.body.username)) throw 2002;
if (syzoj.config.register_mail.enabled) { if (syzoj.config.register_mail) {
let sendObj = { let sendObj = {
username: req.body.username, username: req.body.username,
password: req.body.password, password: req.body.password,
email: req.body.email, email: req.body.email,
}; };
const token = jwt.sign(sendObj, syzoj.config.register_mail.key, { const token = jwt.sign(sendObj, syzoj.config.email.key, {
subject: 'register', subject: 'register',
expiresIn: '2d' expiresIn: '2d'
}); });
@ -147,6 +148,11 @@ app.post('/api/sign_up', async (req, res) => {
app.get('/api/forget_confirm', async (req, res) => { app.get('/api/forget_confirm', async (req, res) => {
try { try {
try {
jwt.verify(req.query.token, syzoj.config.email.key, { subject: 'forget' });
} catch (e) {
throw new ErrorMessage("Token 不正确。");
}
res.render('forget_confirm', { res.render('forget_confirm', {
token: req.query.token token: req.query.token
}); });
@ -160,23 +166,28 @@ app.get('/api/forget_confirm', async (req, res) => {
app.post('/api/reset_password', async (req, res) => { app.post('/api/reset_password', async (req, res) => {
try { try {
res.setHeader('Content-Type', 'application/json');
let obj; let obj;
try { try {
obj = jwt.verify(req.query.token, syzoj.config.register_mail.key, { subject: 'forget' }); obj = jwt.verify(req.body.token, syzoj.config.email.key, { subject: 'forget' });
} catch (e) { } catch (e) {
throw 3001; throw 3001;
} }
let syzoj2_xxx_md5 = '59cb65ba6f9ad18de0dcd12d5ae11bd2'; let syzoj2_xxx_md5 = '59cb65ba6f9ad18de0dcd12d5ae11bd2';
if (req.query.password === syzoj2_xxx_md5) throw new ErrorMessage('密码不能为空。'); if (req.body.password === syzoj2_xxx_md5) throw new ErrorMessage('密码不能为空。');
const user = await User.fromId(obj.id); const user = await User.fromID(obj.userId);
user.password = req.query.password; user.password = req.body.password;
await user.save(); await user.save();
res.send(JSON.stringify({ error_code: 1 })); res.send(JSON.stringify({ error_code: 1 }));
} catch (e) { } catch (e) {
syzoj.log(e); syzoj.log(e);
res.send(JSON.stringify({ error_code: e })); if (typeof e === 'number') {
res.send(JSON.stringify({ error_code: e }));
} else {
res.send(JSON.stringify({ error_code: 1000 }));
}
} }
}); });
@ -184,9 +195,9 @@ app.get('/api/sign_up_confirm', async (req, res) => {
try { try {
let obj; let obj;
try { try {
obj = jwt.verify(req.query.token, syzoj.config.register_mail.key, { subject: 'register' }); obj = jwt.verify(req.query.token, syzoj.config.email.key, { subject: 'register' });
} catch (e) { } catch (e) {
throw new ErrorMessage('无效的注册验证链接。'); throw new ErrorMessage('无效的注册验证链接: ' + e.toString());
} }
let user = await User.fromName(obj.username); let user = await User.fromName(obj.username);
@ -226,7 +237,7 @@ app.get('/api/sign_up/:token', async (req, res) => {
try { try {
let obj; let obj;
try { try {
let decrypted = syzoj.utils.decrypt(Buffer.from(req.params.token, 'base64'), syzoj.config.register_mail.key).toString(); let decrypted = syzoj.utils.decrypt(Buffer.from(req.params.token, 'base64'), syzoj.config.email.key).toString();
obj = JSON.parse(decrypted); obj = JSON.parse(decrypted);
} catch (e) { } catch (e) {
throw new ErrorMessage('无效的注册验证链接。'); throw new ErrorMessage('无效的注册验证链接。');

9
package-lock.json generated

@ -3925,6 +3925,15 @@
"extsprintf": "1.0.2" "extsprintf": "1.0.2"
} }
}, },
"waliyun": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/waliyun/-/waliyun-3.1.1.tgz",
"integrity": "sha1-CCl+SSNBytTbRB8ruhUBVtvob1Q=",
"requires": {
"debug": "2.6.7",
"request": "2.81.0"
}
},
"webidl-conversions": { "webidl-conversions": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.1.tgz",

3
package.json

@ -56,5 +56,8 @@
"syzoj-divine": "^1.0.2", "syzoj-divine": "^1.0.2",
"tmp-promise": "^1.0.3", "tmp-promise": "^1.0.3",
"xss": "^0.3.3" "xss": "^0.3.3"
},
"optionalDependencies": {
"waliyun": "^3.1.1"
} }
} }

15
views/forget.ejs

@ -1,4 +1,5 @@
<% this.title = '忘记密码' %> <% this.title = '忘记密码' %>
<% this.noPreserveUrl = true; %>
<% include header %> <% include header %>
<div class="ui message" id="msgBox" hidden> <div class="ui message" id="msgBox" hidden>
<p id="msgContent"></p> <p id="msgContent"></p>
@ -11,17 +12,16 @@
忘记密码 忘记密码
</div> </div>
</h2> </h2>
<form class="ui large form"> <form class="ui large form" id="forgetForm">
<div class="ui existing segment"> <div class="ui existing segment">
<div class="field"> <div class="field">
<div class="ui left icon input"> <div class="ui left icon input">
<i class="at icon"></i> <i class="at icon"></i>
<input name="email" placeholder="电子邮件地址" type="text" id="email" onkeydown="key_login(event)"> <input name="email" placeholder="电子邮件地址" type="text" id="email">
</div> </div>
</div> </div>
<div class="ui fluid large submit button" id="sendEmail">找回密码</div> <button type="submit" class="ui fluid large submit button" id="sendEmail">找回密码</div>
</div> </div>
<div class="ui error message"></div>
</form> </form>
</div> </div>
</div> </div>
@ -48,14 +48,14 @@ function submitForm() {
case 1: case 1:
showMessage("positive", "找回密码邮件已经发至你电子邮箱的垃圾邮件文件夹。"); showMessage("positive", "找回密码邮件已经发至你电子邮箱的垃圾邮件文件夹。");
return; return;
case 1002: case 1001:
showMessage("error", "用户不存在"); showMessage("error", "用户不存在");
break; break;
default: default:
showMessage("error", "未知错误" + error_code); showMessage("error", "未知错误" + error_code);
break; break;
} }
$("#resetPassword").removeClass("loading"); $("#sendEmail").removeClass("loading");
}, },
error: function(XMLHttpRequest, textStatus, errorThrown) { error: function(XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest.responseText); alert(XMLHttpRequest.responseText);
@ -63,8 +63,9 @@ function submitForm() {
}); });
} }
$(document).ready(function() { $(document).ready(function() {
$("#sendEmail").click(function() { $("#forgetForm").submit(function(event) {
submitForm(); submitForm();
event.preventDefault();
}); });
}); });
</script> </script>

38
views/forget_confirm.ejs

@ -1,6 +1,9 @@
<% this.title = '重设密码' %> <% this.title = '重设密码'; %>
<% this.noPreserveUrl = true; %>
<% include header %> <% include header %>
<div class="ui error message" id="error" hidden></div> <div class="ui message" id="msgBox" hidden>
<p id="msgContent"></p>
</div>
<div class="ui middle aligned center aligned grid"> <div class="ui middle aligned center aligned grid">
<div class="row"> <div class="row">
<div class="column" style="max-width: 450px"> <div class="column" style="max-width: 450px">
@ -9,19 +12,21 @@
重设密码 重设密码
</div> </div>
</h2> </h2>
<form class="ui large form"> <form class="ui large form" id="resetForm">
<div class="ui existing segment"> <div class="ui existing segment">
<div class="two fields"> <div class="field">
<div class="field"> <div class="ui left icon input">
<label class="ui header">密码</label> <i class="lock icon"></i>
<input type="password" placeholder="" id="password1"> <input type="password" placeholder="密码" id="password1">
</div> </div>
<div class="field"> </div>
<label class="ui header">确认密码</label> <div class="field">
<input type="password" placeholder="" id="password2"> <div class="ui left icon input">
<i class="undo icon"></i>
<input type="password" placeholder="确认密码" id="password2">
</div> </div>
</div> </div>
<div class="ui fluid large submit button" id="reset">重设</div> <button type="submit" class="ui fluid large submit button" id="resetPassword">重设</div>
</div> </div>
</form> </form>
</div> </div>
@ -46,17 +51,19 @@ function submitForm() {
type: 'POST', type: 'POST',
data: { data: {
"token": <%- JSON.stringify(token) %>, "token": <%- JSON.stringify(token) %>,
"password": password "password": password,
"_csrf": document.head.getAttribute('data-csrf-token')
}, },
async: true, async: true,
success: function(data) { success: function(data) {
error_code = data.error_code; error_code = data.error_code;
alert('fuck' + error_code);
switch (error_code) { switch (error_code) {
case 1: case 1:
showMessage("positive", "密码重置成功。"); showMessage("positive", "密码重置成功。");
return; break;
default: default:
showMessage("error", "未知错误" + error_code); showMessage("error", "未知错误 " + error_code);
break; break;
} }
$("#resetPassword").removeClass("loading"); $("#resetPassword").removeClass("loading");
@ -67,8 +74,9 @@ function submitForm() {
}); });
} }
$(document).ready(function() { $(document).ready(function() {
$("#reset").click(function() { $("#resetForm").submit(function(event) {
submitForm(); submitForm();
event.preventDefault();
}); });
}); });
</script> </script>

4
views/header.ejs

@ -43,10 +43,10 @@
</a> </a>
<% } else { %> <% } else { %>
<div class="item"> <div class="item">
<a class="ui button" style="margin-right: 0.5em; " href="<%= syzoj.utils.makeUrl(['login'], { url: req.originalUrl }) %>"> <a class="ui button" style="margin-right: 0.5em; " href="<%= syzoj.utils.makeUrl(['login'], { url: this.noPreserveUrl ? undefined : req.originalUrl }) %>">
登录 登录
</a> </a>
<a class="ui primary button" href="<%= syzoj.utils.makeUrl(['sign_up'], { url: req.originalUrl }) %>"> <a class="ui primary button" href="<%= syzoj.utils.makeUrl(['sign_up'], { url: this.noPreserveUrl ? undefined : req.originalUrl }) %>">
注册 注册
</a> </a>
</div> </div>

1
views/login.ejs

@ -1,4 +1,5 @@
<% this.title = '登录' %> <% this.title = '登录' %>
<% this.noPreserveUrl = true; %>
<% include header %> <% include header %>
<div class="ui error message" id="error" hidden></div> <div class="ui error message" id="error" hidden></div>
<div class="ui middle aligned center aligned grid"> <div class="ui middle aligned center aligned grid">

1
views/sign_up.ejs

@ -1,4 +1,5 @@
<% this.title = '注册' %> <% this.title = '注册' %>
<% this.noPreserveUrl = true; %>
<% include header %> <% include header %>
<div class="padding"> <div class="padding">
<h1>注册</h1> <h1>注册</h1>

Loading…
Cancel
Save