Browse Source

Add optional email address verification

pull/6/head
Menci 7 years ago
parent
commit
424c98e02d
  1. 5
      config-example.json
  2. 80
      modules/api.js
  3. 1
      package.json
  4. 11
      utility.js
  5. 27
      views/sign_up.ejs

5
config-example.json

@ -10,6 +10,11 @@
"dialect": "sqlite",
"storage": "syzoj.db"
},
"register_mail": {
"enabled": true,
"address": "test@test.domain",
"key": "test"
},
"upload_dir": "uploads",
"default": {
"problem": {

80
modules/api.js

@ -54,6 +54,9 @@ app.post('/api/sign_up', async (req, res) => {
res.setHeader('Content-Type', 'application/json');
let user = await User.fromName(req.body.username);
if (user) throw 2008;
user = await User.findOne({ where: { email: req.body.email } });
if (user) throw 2009;
// Because the salt is "syzoj2_xxx" and the "syzoj2_xxx" 's md5 is"59cb..."
// the empty password 's md5 will equal "59cb.."
@ -62,20 +65,87 @@ app.post('/api/sign_up', async (req, res) => {
if (!(req.body.email = req.body.email.trim())) throw 2006;
if (!syzoj.utils.isValidUsername(req.body.username)) throw 2002;
if (syzoj.config.register_mail.enabled) {
let sendmail = Promise.promisify(require('sendmail')());
let sendObj = {
username: req.body.username,
password: req.body.password,
email: req.body.email,
prevUrl: req.body.prevUrl,
r: Math.random()
};
let encrypted = encodeURIComponent(syzoj.utils.encrypt(JSON.stringify(sendObj), syzoj.config.register_mail.key).toString('base64'));
let url = req.protocol + '://' + req.get('host') + syzoj.utils.makeUrl(['api', 'sign_up', encrypted]);
try {
await sendmail({
from: syzoj.config.register_mail.address,
to: req.body.email,
type: 'text/html',
subject: `${req.body.username}${syzoj.config.title} 注册验证邮件`,
html: `<p>请点击该链接完成您在 ${syzoj.config.title} 的注册:<a href="${url}">${url}</a>。</p><p>如果您不是 ${req.body.username},请忽略此邮件。</p>`
});
} catch (e) {
throw 2010
}
res.send(JSON.stringify({ error_code: 2 }));
} else {
user = await User.create({
username: req.body.username,
password: req.body.password,
email: req.body.email
});
await user.save();
req.session.user_id = user.id;
setLoginCookie(user.username, user.password, res);
res.send(JSON.stringify({ error_code: 1 }));
}
} catch (e) {
syzoj.log(e);
res.send(JSON.stringify({ error_code: e }));
}
});
app.get('/api/sign_up/:token', async (req, res) => {
try {
let obj;
try {
let decrypted = syzoj.utils.decrypt(Buffer.from(req.params.token, 'base64'), syzoj.config.register_mail.key).toString();
obj = JSON.parse(decrypted);
} catch (e) {
throw new ErrorMessage('无效的注册验证链接。');
}
let user = await User.fromName(obj.username);
if (user) throw new ErrorMessage('用户名已被占用。');
user = await User.findOne({ where: { email: obj.email } });
if (user) throw new ErrorMessage('邮件地址已被占用。');
// Because the salt is "syzoj2_xxx" and the "syzoj2_xxx" 's md5 is"59cb..."
// the empty password 's md5 will equal "59cb.."
let syzoj2_xxx_md5 = '59cb65ba6f9ad18de0dcd12d5ae11bd2';
if (obj.password === syzoj2_xxx_md5) throw new ErrorMessage('密码不能为空。');
if (!(obj.email = obj.email.trim())) throw new ErrorMessage('邮件地址不能为空。');
if (!syzoj.utils.isValidUsername(obj.username)) throw new ErrorMessage('用户名不合法。');
user = await User.create({
username: req.body.username,
password: req.body.password,
email: req.body.email
username: obj.username,
password: obj.password,
email: obj.email
});
await user.save();
req.session.user_id = user.id;
setLoginCookie(user.username, user.password, res);
res.send(JSON.stringify({ error_code: 1 }));
res.redirect(obj.prevUrl || '/');
} catch (e) {
syzoj.log(e);
res.send(JSON.stringify({ error_code: e }));
res.render('error', {
err: e
});
}
});

1
package.json

@ -44,6 +44,7 @@
"pygmentize-bundled-cached": "^1.1.0",
"request": "^2.74.0",
"request-promise": "^4.1.1",
"sendmail": "^1.1.1",
"sequelize": "^3.24.3",
"session-file-store": "^1.0.0",
"sqlite3": "^3.1.4",

11
utility.js

@ -329,5 +329,16 @@ module.exports = {
let s = JSON.stringify(key);
if (!this.locks[s]) this.locks[s] = new AsyncLock();
return this.locks[s].acquire(s, cb);
},
encrypt(buffer, password) {
if (typeof buffer === 'string') buffer = Buffer.from(buffer);
let crypto = require('crypto');
let cipher = crypto.createCipher('aes-256-ctr', password);
return Buffer.concat([cipher.update(buffer), cipher.final()]);
},
decrypt(buffer, password) {
let crypto = require('crypto');
let decipher = crypto.createDecipher('aes-256-ctr', password);
return Buffer.concat([decipher.update(buffer), decipher.final()]);
}
};

27
views/sign_up.ejs

@ -14,10 +14,6 @@
<label for="email">邮箱</label>
<input type="email" placeholder="" id="email">
</div>
<!--div class="field">
<label for="email">邀请码</label>
<input type="text" placeholder="Invitation code" id="invitation_code">
</div-->
<div class="two fields">
<div class="field">
<label class="ui header">密码</label>
@ -37,10 +33,20 @@ function show_error(error) {
$("#error_info").text(error);
$("#error").show();
}
function success() {
alert("注册成功!");
alert("注册成功");
window.location.href = <%- JSON.stringify(req.query.url || '/') %>;
}
function mail_required() {
alert("注册确认邮件已经发送到您的邮箱的垃圾箱,点击邮件内的链接即可完成注册。");
var s = $("#email").val();
var mailWebsite = 'https://mail.' + s.substring(s.indexOf('@') + 1, s.length);
if (mailWebsite === 'https://mail.gmail.com') mailWebsite = 'https://mail.google.com';
window.location.href = mailWebsite;
}
function submit() {
if ($("#password1").val() != $("#password2").val()) {
show_error("两次输入的密码不一致");
@ -55,7 +61,8 @@ function submit() {
data: {
username: $("#username").val(),
password: password,
email: $("#email").val()
email: $("#email").val(),
prevUrl: <%- JSON.stringify(req.query.url || '/') %>
},
success: function(data) {
error_code = data.error_code;
@ -79,11 +86,17 @@ function submit() {
show_error("已经有人用过这个用户名了");
break;
case 2009:
show_error("邀请码错误,请联系管理员索要");
show_error("邮箱地址已被占用");
break;
case 2010:
show_error("验证邮件发送失败");
break;
case 1:
success();
break;
case 2:
mail_required();
break;
default:
show_error("未知错误");
break;

Loading…
Cancel
Save