Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add password recovery by email #2

Merged
merged 1 commit into from
May 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified data/lexonomy.sqlite.template
Binary file not shown.
16 changes: 16 additions & 0 deletions data/updates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const path=require("path");
const fs=require("fs-extra");
const sqlite3 = require('sqlite3').verbose(); //https://www.npmjs.com/package/sqlite3

fs.readFile(path.join(__dirname, "../website/siteconfig.json"), "utf8", function(err, content){
var siteconfig=JSON.parse(content);
var db=new sqlite3.Database(path.join(siteconfig.dataDir, "lexonomy.sqlite"), sqlite3.OPEN_READWRITE);
db.run("CREATE TABLE IF NOT EXISTS recovery_tokens (email text, requestAddress text, token text, expiration datetime, usedDate datetime, usedAddress text)", {}, function(err) {
if (err) {
return console.error(err.message);
}
console.log("Table recovery_tokens created.");
});
db.close();
});

24 changes: 23 additions & 1 deletion website/lexonomy.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const url=require("url");
const querystring=require("querystring");
const libxslt=require("libxslt"); //https://www.npmjs.com/package/libxslt
const sqlite3 = require('sqlite3').verbose(); //https://www.npmjs.com/package/sqlite3
const nodemailer = require('nodemailer');
ops.mailtransporter = nodemailer.createTransport(siteconfig.mailconfig);

const PORT=process.env.PORT||siteconfig.port||80;
app.use(function (req, res, next) {
Expand Down Expand Up @@ -87,7 +89,15 @@ app.get(siteconfig.rootPath+"signup/", function(req, res){
});
app.get(siteconfig.rootPath+"forgotpwd/", function(req, res){
ops.verifyLogin(req.cookies.email, req.cookies.sessionkey, function(user){
res.render("forgotpwd.ejs", {user: user, email: siteconfig.admins[0], siteconfig: siteconfig});
res.render("forgotpwd.ejs", {user: user, redirectUrl: siteconfig.baseUrl, siteconfig: siteconfig});
});
});
app.get(siteconfig.rootPath+"recoverpwd/:token/", function(req, res){
ops.verifyLogin(req.cookies.email, req.cookies.sessionkey, function(user){
ops.verifyToken(req.params.token, function(valid){
var tokenValid = valid;
res.render("recoverpwd.ejs", {user: user, redirectUrl: siteconfig.baseUrl, siteconfig: siteconfig, token: req.params.token, tokenValid: tokenValid});
});
});
});
app.get(siteconfig.rootPath+"changepwd/", function(req, res){
Expand Down Expand Up @@ -130,6 +140,18 @@ app.post(siteconfig.rootPath+"changepwd.json", function(req, res){
}
});
});
app.post(siteconfig.rootPath+"forgotpwd.json", function(req, res){
var remoteip = req.connection.remoteAddress.replace('::ffff:','');
ops.sendToken(req.body.email, remoteip, req.body.mailSubject, req.body.mailText, function(success){
res.json({success: success});
});
});
app.post(siteconfig.rootPath+"recoverpwd.json", function(req, res){
var remoteip = req.connection.remoteAddress.replace('::ffff:','');
ops.resetPwd(req.body.token, req.body.password, remoteip, function(success){
res.json({success: success});
});
});

//DOCS:
app.get(siteconfig.rootPath+"docs/:docID/", function(req, res){
Expand Down
29 changes: 26 additions & 3 deletions website/libs/screenful/screenful-forgotpwd.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
Screenful.ForgotPwd={
start: function(){
Screenful.createEnvelope(true);
$("#envelope").html("<form id='middlebox'></form>");
$("#middlebox").append("<div class='message'>"+Screenful.Loc.forgotPwdEmail+"</div>");
$("#middlebox").append("<div class='url'><a href='mailto:"+Screenful.ForgotPwd.email+"'>"+Screenful.ForgotPwd.email+"</a></div>");
$("#envelope").html("<form id='middlebox' onsubmit='return false;'><div class='one'></div><div class='two' style='display: none'></div></form>");
$("#middlebox .one").append("<div class='message'>"+Screenful.Loc.forgotPwdEmail+"</div>");
$("#middlebox .one").append("<div class='field email'><div class='label'>"+Screenful.Loc.username+"</div><input class='textbox' type='email'/></div>");
$("#middlebox .one").append("<div class='field submit'><input class='button' type='submit' value='"+Screenful.Loc.recoverPwd+"'/></div>");
$("#middlebox .two").append("<div class='message'>"+Screenful.Loc.tokenSent+"</div>");
$("#middlebox .two").append("<div class='field submit'><button class='return'>"+Screenful.Loc.ok+"</button></div>");

$("#middlebox div.field.email input").focus();
$("#middlebox").on("submit", function(e){
var email=$("#middlebox div.field.email input").val();
if (email!="") Screenful.ForgotPwd.sendToken(email);
return false;
});

$("#middlebox button.return").on("click", function(e){
window.location=Screenful.ForgotPwd.returnUrl;
});
},

sendToken: function(email){
$.ajax({url: Screenful.ForgotPwd.actionUrl, dataType: "json", method: "POST", data: {email: email, mailSubject: Screenful.Loc.recoverEmailSubject, mailText: Screenful.Loc.recoverEmailText}}).done(function(data){
if(data.success) {
$("#middlebox .one").hide();
$("#middlebox .two").show()
}
});
},
};
$(window).ready(Screenful.ForgotPwd.start);
8 changes: 7 additions & 1 deletion website/libs/screenful/screenful-loc-en.js
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ Screenful.Loc={
changePwd: "Change your password",
signup: "Get an account",
forgotPwd: "Forgot your password?",
recoverPwd: "Get new password",
recoverEmailSubject: "Lexonomy, password recovery",
recoverEmailText: "Dear Lexonomy user,\nsomebody (hopefully you, from the address <%=remoteip%>) requested a new password for Lexonomy account <%=email%>. No changes have been made to your account yet.\n\nYou can reset your password by clicking the link below:\n\n <%=tokenurl%>\n\nFor security reason, this link is only valid until <%=expiredate%>.\n\nIf you did not request this password reset, please feel free to ignore this message.\n\nYours,\nLexonomy team",
recoverPwdMsg: "Please, enter your new password.",
invalidToken: "This recovery token is invalid, either it expired, or was already used to reset password. If you need to change your password, please request new recover token <a href='../forgotpwd'>here</a>.",
signupEmail: "To get an account send an e-mail to",
forgotPwdEmail: "If you have forgotten your password send an e-mail to",
forgotPwdEmail: "If you have forgotten your password, enter your e-mail and we will send you information how to create a new one.",
tokenSent: "We have sent e-mail with further instructions to specified address.",
newPassword: "New password",
change: "Change",
passwordChanged: "Your password has been changed.",
Expand Down
18 changes: 18 additions & 0 deletions website/libs/screenful/screenful-recoverpwd.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#middlebox {max-width: 600px; padding: 40px 30px; min-height: 100px; margin: 75px auto 50px auto; border: 1px solid rgb(38, 122, 181); background-color: #ffffff; border-radius: 4px; box-shadow: 0px 0px 4px #666666; }

#middlebox div.field {margin-top: 30px;}
#middlebox div.field:first-child {margin-top: 0px;}
#middlebox div.field div.label {font-weight: bold; margin: 0px 0px 5px 0px; color: #333333;}
#middlebox div.field input.textbox {box-sizing: border-box; width: 100%; margin: 0px 0px 0px 0px; font: inherit; border-width: 0px; border-radius: 4px; background-color: #ffffff; box-shadow: inset 0px 0px 2px #666666; padding: 9px 8px; min-height: 1.3em; display: inline-block; vertical-align: middle;}
#middlebox div.field input.button {box-sizing: border-box; margin: 0px 0px 0px 0px; font: inherit; border-width: 0px; border-radius: 4px; background-color: #ffffff; box-shadow: 0px 0px 2px #666666; padding: 9px 30px; min-height: 1.3em; display: inline-block; vertical-align: middle; color: #267ab5; cursor: pointer;}
#middlebox div.field input.button:hover {color: #4698d1;}
#middlebox div.field button {box-sizing: border-box; margin: 0px 0px 0px 0px; font: inherit; border-width: 0px; border-radius: 4px; background-color: #ffffff; box-shadow: 0px 0px 2px #666666; padding: 9px 30px; min-height: 1.3em; display: inline-block; vertical-align: middle; color: #267ab5; cursor: pointer;}
#middlebox div.field button:hover {color: #4698d1;}

#middlebox div.field.submit {text-align: center;}
#middlebox div.field.submit input.button {font-weight: bold;}
#middlebox div.field.submit button {font-weight: bold;}

#middlebox div.error {background-color: #ffcdcc; color: #99004d; font-weight: bold; text-align: center; padding: 40px; margin: 30px -30px -40px -30px; text-shadow: 1px 1px 0px #eeeeee;}

#middlebox div.two div.message {text-align: center; margin: 20px 0px 30px 0px;}
43 changes: 43 additions & 0 deletions website/libs/screenful/screenful-recoverpwd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Screenful.RecoverPwd={
start: function(){
Screenful.createEnvelope(true);
$("#envelope").html("<form id='middlebox'><div class='one'></div><div class='two' style='display: none'></div></form>");
if (Screenful.RecoverPwd.tokenValid) {
$("#middlebox .one").append("<div class='message'>"+Screenful.Loc.recoverPwdMsg+"</div>");
$("#middlebox .one").append("<div class='field password'><div class='label'>"+Screenful.Loc.newPassword+"</div><input class='textbox' type='password'/></div>");
$("#middlebox .one").append("<div class='field submit'><input class='button' type='submit' value='"+Screenful.Loc.change+"'/></div>");
$("#middlebox .one").append("<div class='error' style='display: none'></div>");
$("#middlebox .two").append("<div class='message'>"+Screenful.Loc.passwordChanged+"</div>");
$("#middlebox .two").append("<div class='field submit'><button class='return'>"+Screenful.Loc.ok+"</button></div>");
} else {
$("#middlebox .one").append("<div class='error'>"+Screenful.Loc.invalidToken+"</div>");
}

$("#middlebox div.field.password input").focus();

$("#middlebox").on("submit", function(e){
var password=$("#middlebox div.field.password input").val();
if(password=="") { $("#middlebox .error").html(Screenful.Loc.passwordEmpty).show(); return false; }
if(password.length<6) { $("#middlebox .error").html(Screenful.Loc.passwordShort).show(); return false; }
if($.trim(password)!=password) { $("#middlebox .error").html(Screenful.Loc.passwordWhitespace).show(); return false; }
Screenful.RecoverPwd.go(password);
return false;
});

$("#middlebox button.return").on("click", function(e){
window.location=Screenful.RecoverPwd.returnUrl;
});
},

go: function(password){
$.ajax({url: Screenful.RecoverPwd.actionUrl, dataType: "json", method: "POST", data: {password: password, token: Screenful.RecoverPwd.token}}).done(function(data){
if(data.success) {
$("#middlebox .one").hide();
$("#middlebox .two").show()
}
});
},


};
$(window).ready(Screenful.RecoverPwd.start);
48 changes: 47 additions & 1 deletion website/ops.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ const xmldom=require("xmldom"); //https://www.npmjs.com/package/xmldom
const sqlite3 = require('sqlite3').verbose(); //https://www.npmjs.com/package/sqlite3
const sha1 = require('sha1'); //https://www.npmjs.com/package/sha1
const markdown = require("markdown").markdown; //https://www.npmjs.com/package/markdown
const nodemailer = require('nodemailer');

module.exports={
siteconfig: {}, //populated by lexonomy.js on startup
mailtransporter: null,
getDB: function(dictID, readonly){
var mode=(readonly ? sqlite3.OPEN_READONLY : sqlite3.OPEN_READWRITE)
var db=new sqlite3.Database(
Expand Down Expand Up @@ -699,6 +701,50 @@ module.exports={
callnext(true);
});
},
sendToken: function(email, remoteip, mailSubject, mailText, callnext){
var db=new sqlite3.Database(path.join(module.exports.siteconfig.dataDir, "lexonomy.sqlite"), sqlite3.OPEN_READWRITE);
db.get("select email from users where email=$email", {$email: email}, function(err, row){
if (row) {
var expireDate = (new Date()); expireDate.setHours(expireDate.getHours()+48);
expireDate = expireDate.toISOString();
var token = sha1(sha1(Math.random()));
var tokenurl = module.exports.siteconfig.baseUrl + 'recoverpwd/' + token;
mailText = mailText
.replace('<%=remoteip%>',remoteip)
.replace('<%=email%>',email)
.replace('<%=expiredate%>',expireDate)
.replace('<%=tokenurl%>', tokenurl);
db.run("insert into recovery_tokens (email, requestAddress, token, expiration) values ($email, $remoteip, $token, $expire)", {$email: email, $expire: expireDate, $remoteip: remoteip, $token: token}, function(err, row){
module.exports.mailtransporter.sendMail({from: 'xrambous@fi.muni.cz', to: email, subject: mailSubject, text: mailText}, (err, info) => {});
db.close();
});
}
});
callnext(true);
},
verifyToken: function(token, callnext){
var db=new sqlite3.Database(path.join(module.exports.siteconfig.dataDir, "lexonomy.sqlite"), sqlite3.OPEN_READWRITE);
db.get("select * from recovery_tokens where token=$token and expiration>=datetime('now') and usedDate is null", {$token: token}, function(err, row){
db.close();
if (!row) callnext(false);
else callnext(true);
});
},
resetPwd: function(token, password, remoteip, callnext){
var db=new sqlite3.Database(path.join(module.exports.siteconfig.dataDir, "lexonomy.sqlite"), sqlite3.OPEN_READWRITE);
db.get("select * from recovery_tokens where token=$token and expiration>=datetime('now') and usedDate is null", {$token: token}, function(err, row){
if (row) {
var email = row.email;
var hash = sha1(password);
db.run("update users set passwordHash=$hash where email=$email", {$hash: hash, $email: email}, function(err, row){
db.run("update recovery_tokens set usedDate=datetime('now'), usedAddress=$remoteip where token=$token", {$remoteip: remoteip, $token: token}, function(err, row){
db.close();
callnext(true);
});
});
}
});
},
verifyLogin: function(email, sessionkey, callnext){
var yesterday=(new Date()); yesterday.setHours(yesterday.getHours()-24); yesterday=yesterday.toISOString();
var db=new sqlite3.Database(path.join(module.exports.siteconfig.dataDir, "lexonomy.sqlite"), sqlite3.OPEN_READWRITE);
Expand Down Expand Up @@ -975,4 +1021,4 @@ function generateDictID(){
return "z"+id;
}

const prohibitedDictIDs=["login", "logout", "make", "signup", "forgotpwd", "changepwd", "users", "dicts", "oneclick"];
const prohibitedDictIDs=["login", "logout", "make", "signup", "forgotpwd", "changepwd", "users", "dicts", "oneclick", "recoverpwd"];
1 change: 1 addition & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"libxslt": "^0.7.0",
"markdown": "^0.5.0",
"multer": "^1.3.0",
"nodemailer": "^4.6.4",
"sha1": "^1.1.1",
"sqlite3": "^3.1.13",
"xmldom": "^0.1.27"
Expand Down
1 change: 1 addition & 0 deletions website/siteconfig.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"admins": ["root@localhost"],
"trackingCode": "",
"welcome": "Welcome to your <a href='http://www.lexonomy.eu/'>Lexonomy</a> installation.",
"mailconfig": {"host": "smtp.server.example", "port": 465,"secure": true},
"licences": {
"cc-by-4.0": {
"title": "Creative Commons Attribution 4.0 International",
Expand Down
3 changes: 2 additions & 1 deletion website/views/forgotpwd.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
<script type="text/javascript" src="../libs/screenful/screenful-forgotpwd.js"></script>
<script type="text/javascript" src="../libs/screenful/screenful-loc-en.js"></script>
<script type="text/javascript">
Screenful.ForgotPwd.email="<%=email%>";
Screenful.ForgotPwd.actionUrl="../forgotpwd.json";
Screenful.ForgotPwd.returnUrl="<%=redirectUrl%>";
</script>
</head>
<body>
Expand Down
35 changes: 35 additions & 0 deletions website/views/recoverpwd.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8"/>
<link rel="icon" type="image/x-icon" href="../furniture/favicon.ico"/>
<title>Reset your password</title>
<script type="text/javascript" src="../libs/jquery.js"></script>
<script type="text/javascript" src="../libs/screenful/screenful.js"></script>
<script type="text/javascript" src="../libs/screenful/screenful-user.js"></script>
<link type="text/css" rel="stylesheet" href="../libs/screenful/screenful-user.css" />
<link type="text/css" rel="stylesheet" href="../libs/screenful/screenful.css" />
<link type="text/css" rel="stylesheet" href="../libs/screenful/screenful-recoverpwd.css" />
<link type="text/css" rel="stylesheet" href="../furniture/public.css" />
<script type="text/javascript">
Screenful.User.loggedin=<%=(user.loggedin ? "true" : "false")%>;
Screenful.User.username="<%=user.email%>";
</script>
<script type="text/javascript">var rootPath="../";</script>
<script type="text/javascript" src="../furniture/screenful-user-config.js"></script>
<script type="text/javascript" src="../libs/screenful/screenful-recoverpwd.js"></script>
<script type="text/javascript" src="../libs/screenful/screenful-loc-en.js"></script>
<script type="text/javascript">
Screenful.RecoverPwd.actionUrl="../recoverpwd.json";
Screenful.RecoverPwd.returnUrl="<%=redirectUrl%>";
Screenful.RecoverPwd.token="<%=token%>";
Screenful.RecoverPwd.tokenValid=<%=tokenValid%>;
</script>
</head>
<body>
<div id="header">
<a class="sitehome" href="../"></a>
<div class="email ScreenfulUser"></div>
</div>
</body>
</html>