diff --git a/data/lexonomy.sqlite.template b/data/lexonomy.sqlite.template index 2afa38f4..5fa56138 100644 Binary files a/data/lexonomy.sqlite.template and b/data/lexonomy.sqlite.template differ diff --git a/website/adminscripts/updates.js b/website/adminscripts/updates.js index fa58046f..e7f7606c 100644 --- a/website/adminscripts/updates.js +++ b/website/adminscripts/updates.js @@ -6,12 +6,28 @@ const sqlite3 = require('sqlite3').verbose(); //https://www.npmjs.com/package/sq fs.readFile(path.join(__dirname, "../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); + 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.run("CREATE TABLE IF NOT EXISTS register_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 register_tokens created."); + }); + db.run("ALTER TABLE users ADD COLUMN ske_id INTEGER", {}, function(err) { + if (err) { + return console.error(err.message); + } + }); + db.run("ALTER TABLE users ADD COLUMN ske_username TEXT", {}, function(err) { + if (err) { + return console.error(err.message); + } + }); db.close(); }); diff --git a/website/furniture/public.css b/website/furniture/public.css index 5e0bbd1a..bfbc2d81 100644 --- a/website/furniture/public.css +++ b/website/furniture/public.css @@ -136,6 +136,11 @@ div.usertop a:hover {color: #2c7fba; } div.usertop > div.title {font-size: 1.5rem; padding-top: 10px;} div.usertop > div.subtitle {margin-top: 10px; color: #666666;} +div.skelogin a {text-decoration: none; color: #09587e;} +div.skelogin a:hover {color: #2c7fba; } +div.skelogin {font-weight: bold; text-align: center; padding: 10px; margin: 20px 0px 0px 0px; border-radius: 4px; border: 1px solid #279FD2;} +div.skelogin img {vertical-align: middle;} + div.field {margin-top: 10px;} div.field:first-child {margin-top: 0px;} div.field div.label {font-weight: bold; margin: 0px 0px 5px 0px; color: #333333;} diff --git a/website/lexonomy.js b/website/lexonomy.js index c226efa4..d7145cd4 100644 --- a/website/lexonomy.js +++ b/website/lexonomy.js @@ -22,6 +22,7 @@ const sqlite3 = require('sqlite3').verbose(); //https://www.npmjs.com/package/sq const nodemailer = require('nodemailer'); ops.mailtransporter = nodemailer.createTransport(siteconfig.mailconfig); const PORT=process.env.PORT||siteconfig.port||80; +const jwt = require("jsonwebtoken"); //Do this for each request: app.use(function (req, res, next) { @@ -60,7 +61,12 @@ app.get(siteconfig.rootPath+":dictID/en/", function(req, res){ res.redirect("/"+ app.get(siteconfig.rootPath, function(req, res){ ops.verifyLogin(req.cookies.email, req.cookies.sessionkey, function(user){ ops.getDictsByUser(user.email, function(dicts){ - res.render("home.ejs", {siteconfig: siteconfig, user: user, dicts: dicts}); + var error = null; + if (req.cookies.jwt_error) { + error = req.cookies.jwt_error; + res.clearCookie('jwt_error'); + } + res.render("home.ejs", {siteconfig: siteconfig, user: user, dicts: dicts, error: error}); }); }); }); @@ -89,7 +95,7 @@ app.get(siteconfig.rootPath+"make/", function(req, res){ }); app.get(siteconfig.rootPath+"signup/", function(req, res){ ops.verifyLogin(req.cookies.email, req.cookies.sessionkey, function(user){ - res.render("signup.ejs", {user: user, email: siteconfig.admins[0], siteconfig: siteconfig}); + res.render("signup.ejs", {user: user, redirectUrl: siteconfig.baseUrl, siteconfig: siteconfig}); }); }); app.get(siteconfig.rootPath+"forgotpwd/", function(req, res){ @@ -97,9 +103,17 @@ app.get(siteconfig.rootPath+"forgotpwd/", function(req, res){ res.render("forgotpwd.ejs", {user: user, redirectUrl: siteconfig.baseUrl, siteconfig: siteconfig}); }); }); +app.get(siteconfig.rootPath+"createaccount/:token/", function(req, res){ + ops.verifyLogin(req.cookies.email, req.cookies.sessionkey, function(user){ + ops.verifyToken(req.params.token, "register", function(valid){ + var tokenValid = valid; + res.render("createaccount.ejs", {user: user, redirectUrl: siteconfig.baseUrl, siteconfig: siteconfig, token: req.params.token, tokenValid: tokenValid}); + }); + }); +}); 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){ + ops.verifyToken(req.params.token, "recovery", function(valid){ var tokenValid = valid; res.render("recoverpwd.ejs", {user: user, redirectUrl: siteconfig.baseUrl, siteconfig: siteconfig, token: req.params.token, tokenValid: tokenValid}); }); @@ -145,12 +159,24 @@ app.post(siteconfig.rootPath+"changepwd.json", function(req, res){ } }); }); +app.post(siteconfig.rootPath+"signup.json", function(req, res){ + var remoteip = req.connection.remoteAddress.replace('::ffff:',''); + ops.sendSignupToken(req.body.email, remoteip, function(success){ + res.json({success: success}); + }); +}); app.post(siteconfig.rootPath+"forgotpwd.json", function(req, res){ var remoteip = req.connection.remoteAddress.replace('::ffff:',''); ops.sendToken(req.body.email, remoteip, function(success){ res.json({success: success}); }); }); +app.post(siteconfig.rootPath+"createaccount.json", function(req, res){ + var remoteip = req.connection.remoteAddress.replace('::ffff:',''); + ops.createAccount(req.body.token, req.body.password, remoteip, 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){ @@ -266,6 +292,35 @@ app.post(siteconfig.rootPath+"dicts/dictread.json", function(req, res){ }); }); +//SKETCHENGINE LOGIN JSON endpoint: +app.get(siteconfig.rootPath+"skelogin.json/:token", function(req, res){ + //var token = req.headers.authorization.replace('Bearer ', ''); + var token = req.params.token; + var secret = siteconfig.sketchengineKey; + jwt.verify(token, secret, {audience:'lexonomy.eu'}, function(err, decoded) { + if (err == null) { + console.log(decoded) + ops.verifyLogin(req.cookies.email, req.cookies.sessionkey, function(user){ + ops.processJWT(user, decoded, function(success, email, sessionkey){ + if (success) { + res.cookie("email", email, {}); + res.cookie("sessionkey", sessionkey, {}); + res.redirect(siteconfig.baseUrl) + } else { + res.cookie("jwt_error", email, {}); + res.redirect(siteconfig.baseUrl) + } + }); + }); + } else { + //JWT not verified, error + res.cookie("jwt_error",err.message,{}) + res.redirect(siteconfig.baseUrl) + } + }); +}); + + //ONE-CLICK UI and JSON endpoints: app.get(siteconfig.rootPath+"oneclick/", function(req, res){ ops.verifyLogin(req.cookies.email, req.cookies.sessionkey, function(user){ diff --git a/website/libs/screenful/screenful-createaccount.css b/website/libs/screenful/screenful-createaccount.css new file mode 100644 index 00000000..51a5427a --- /dev/null +++ b/website/libs/screenful/screenful-createaccount.css @@ -0,0 +1,19 @@ +#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.bigerror {background-color: #ffcdcc; color: #99004d; font-weight: bold; text-align: center; padding: 40px; margin: -40px -30px 0px -30px; text-shadow: 1px 1px 0px #eeeeee;} +#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;} diff --git a/website/libs/screenful/screenful-createaccount.js b/website/libs/screenful/screenful-createaccount.js new file mode 100644 index 00000000..c9991fca --- /dev/null +++ b/website/libs/screenful/screenful-createaccount.js @@ -0,0 +1,47 @@ +Screenful.CreateAccount={ + start: function(){ + Screenful.createEnvelope(true); + $("#envelope").html("
"); + if (Screenful.CreateAccount.tokenValid) { + $("#middlebox .one").append("
"+Screenful.Loc.setPwdMsg+"
"); + $("#middlebox .one").append("
"+Screenful.Loc.newPassword+"
"); + $("#middlebox .one").append("
"); + $("#middlebox .one").append(""); + $("#middlebox .two").append("
"+Screenful.Loc.accountCreated+"
"); + $("#middlebox .two").append("
"); + } else { + $("#middlebox .one").append("
"+Screenful.Loc.invalidSignupToken+"
"); + $("#middlebox .one").append("
"); + } + + $("#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.CreateAccount.go(password); + return false; + }); + + $("#middlebox button.return").on("click", function(e){ + window.location=Screenful.CreateAccount.returnUrl; + return false; + }); + }, + + go: function(password){ + $.ajax({url: Screenful.CreateAccount.actionUrl, dataType: "json", method: "POST", data: {password: password, token: Screenful.CreateAccount.token}}).done(function(data){ + if(data.success) { + $("#middlebox .one").hide(); + $("#middlebox .two").show() + } else { + $("#middlebox .error").html(Screenful.Loc.signupAccountExists).show(); + } + }); + }, + + +}; +$(window).ready(Screenful.CreateAccount.start); diff --git a/website/libs/screenful/screenful-forgotpwd.js b/website/libs/screenful/screenful-forgotpwd.js index 5d91625c..f9fbc374 100644 --- a/website/libs/screenful/screenful-forgotpwd.js +++ b/website/libs/screenful/screenful-forgotpwd.js @@ -7,7 +7,7 @@ Screenful.ForgotPwd={ $("#middlebox .one").append("
"); $("#middlebox .two").append("
"+Screenful.Loc.tokenSent+"
"); $("#middlebox .two").append("
"); - $("#middlebox").append(""); + $("#middlebox").append(""); $("#middlebox div.field.email input").focus(); $("#middlebox").on("submit", function(e){ diff --git a/website/libs/screenful/screenful-loc-cs.js b/website/libs/screenful/screenful-loc-cs.js index 4c0004e7..00547791 100755 --- a/website/libs/screenful/screenful-loc-cs.js +++ b/website/libs/screenful/screenful-loc-cs.js @@ -59,13 +59,20 @@ Screenful.Loc={ invalidSourceCode: "Neplatný zdrojový kód", unsavedConfirm: "Más neuložené změny. Opravdu o ně chceš přijít?", doItLater: "Udělej to jindy", + badEmailError: "Nesprávná e-mailová adresa.", forgotPwdEmail: "Zapomněli-li jste svoje heslo, zadejte tady svoji e-mailovou adresu a my vám pošleme instrukce, jak si získat nové heslo.", recoverPwd: "Chci nové heslo", - forgotPwdError: "Nesprávná e-mailová adresa.", tokenSent: "Poslali jsme vám instrukce, jak si získat nové heslo.", recoverPwdMsg: "Nyní si můžete změnit heslo.", changePwdMsg: "Tady si můžete změnit heslo.", invalidToken: "Tento odkaz je neplatný. Možná už vypršel, nebo byl už použit.", + signupInfoEmail: "To get a new account, enter your e-mail address and we will send you instructions.", + signupButton: "Create new account", + signupTokenSent: "We have sent you an e-mail with instructions on how to reset your password.", + invalidSignupToken: "This signup link is invalid. It may have expired or has been used before.", + setPwdMsg: "Please, set your password now.", + accountCreated: "We have created your account. You may now log-in with your e-mail and password.", + signupAccountExists: "You are trying to create account for e-mail that is already existing. Did you forget your password?", }; diff --git a/website/libs/screenful/screenful-loc-en.js b/website/libs/screenful/screenful-loc-en.js index fd0fa57e..70f0353b 100644 --- a/website/libs/screenful/screenful-loc-en.js +++ b/website/libs/screenful/screenful-loc-en.js @@ -59,13 +59,20 @@ Screenful.Loc={ invalidSourceCode: "Invalid source code", unsavedConfirm: "You have unsaved changes. Are you sure you want to lose them?", doItLater: "Do it later", + badEmailError: "Incorrect e-mail address.", forgotPwdEmail: "If you have forgotten your password, enter your e-mail address and we will send you instructions on how to create a new one.", recoverPwd: "Get a new password", - forgotPwdError: "Incorrect e-mail address.", tokenSent: "We have sent you an e-mail with instructions on how to reset your password.", recoverPwdMsg: "You can change your password now.", changePwdMsg: "You can change your password here.", invalidToken: "This password reset link is invalid. It may have expired or has been used before.", + signupInfoEmail: "To get a new account, enter your e-mail address and we will send you instructions.", + signupButton: "Create new account", + signupTokenSent: "We have sent you an e-mail with instructions on how to reset your password.", + invalidSignupToken: "This signup link is invalid. It may have expired or has been used before.", + setPwdMsg: "Please, set your password now.", + accountCreated: "We have created your account. You may now log-in with your e-mail and password.", + signupAccountExists: "You are trying to create account for e-mail that is already existing. Did you forget your password?", }; diff --git a/website/libs/screenful/screenful-loc-ga.js b/website/libs/screenful/screenful-loc-ga.js index dfa1b1a6..5a066330 100755 --- a/website/libs/screenful/screenful-loc-ga.js +++ b/website/libs/screenful/screenful-loc-ga.js @@ -59,13 +59,20 @@ Screenful.Loc={ invalidSourceCode: "Cód foinseach neamhbhailí", unsavedConfirm: "Tá athruithe déanta agat nach bhfuil sábháilte go fóill. An cuma leat go gcaillfear iad?", doItLater: "Déan níos déanaí é", + badEmailError: "Seoladh ríomhphoist mícheart.", forgotPwdEmail: "Má tá do phasfhocal dearmadta agat, cuir do sheoladh ríomhphoist isteach anseo. Cuirfidh muidne treoracha chugat maidir leis an dóigh chun do phasfhocal a athrú.", recoverPwd: "Faigh pasfhocal nua", - forgotPwdError: "Seoladh ríomhphoist mícheart.", tokenSent: "Tá treoracha seolta againn chugat maidir leis an dóigh chun do phasfhocal a athrú.", recoverPwdMsg: "Is féidir leat do phasfhocal a athrú anois.", changePwdMsg: "Is féidir leat do phasfhocal a athrú anseo.", invalidToken: "Níl an nasc seo bailí. Seans go bhfuil sé imithe in éag, nó b'fhéidir gur baineadh úsáid as cheana.", + signupInfoEmail: "To get a new account, enter your e-mail address and we will send you instructions.", + signupButton: "Create new account", + signupTokenSent: "We have sent you an e-mail with instructions on how to reset your password.", + invalidSignupToken: "This signup link is invalid. It may have expired or has been used before.", + setPwdMsg: "Please, set your password now.", + accountCreated: "We have created your account. You may now log-in with your e-mail and password.", + signupAccountExists: "You are trying to create account for e-mail that is already existing. Did you forget your password?", }; diff --git a/website/libs/screenful/screenful-signup.css b/website/libs/screenful/screenful-signup.css index 15d29622..7604433b 100644 --- a/website/libs/screenful/screenful-signup.css +++ b/website/libs/screenful/screenful-signup.css @@ -1,6 +1,16 @@ #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.message {text-align: center; margin: 50px 0px 0px 0px;} -#middlebox div.url {text-align: center; margin: 10px 0px 50px 0px;} -#middlebox div.url a {text-decoration: none; color: #004f9b;} -#middlebox div.url a:hover {color: #006ed7;} +#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;} diff --git a/website/libs/screenful/screenful-signup.js b/website/libs/screenful/screenful-signup.js index 4266e713..83c28543 100644 --- a/website/libs/screenful/screenful-signup.js +++ b/website/libs/screenful/screenful-signup.js @@ -1,9 +1,37 @@ Screenful.Signup={ start: function(){ Screenful.createEnvelope(true); - $("#envelope").html("
"); - $("#middlebox").append("
"+Screenful.Loc.signupEmail+"
"); - $("#middlebox").append("
"+Screenful.Signup.email+"
"); + $("#envelope").html("
"); + $("#middlebox .one").append("
"+Screenful.Loc.signupInfoEmail+"
"); + $("#middlebox .one").append("
"+Screenful.Loc.username+"
"); + $("#middlebox .one").append("
"); + $("#middlebox .two").append("
"+Screenful.Loc.signupTokenSent+"
"); + $("#middlebox .two").append("
"); + $("#middlebox").append(""); + + $("#middlebox div.field.email input").focus(); + $("#middlebox").on("submit", function(e){ + var email=$("#middlebox div.field.email input").val(); + if (email!="") Screenful.Signup.sendToken(email); + return false; + }); + + $("#middlebox button.return").on("click", function(e){ + window.location=Screenful.Signup.returnUrl; + return false; + }); + }, + + sendToken: function(email){ + $("#middlebox div.error").hide(); + $.ajax({url: Screenful.Signup.actionUrl, dataType: "json", method: "POST", data: {email: email}}).done(function(data){ + if(data.success) { + $("#middlebox .one").hide(); + $("#middlebox .two").show(); + } else { + $("#middlebox div.error").fadeIn(); + } + }); }, }; $(window).ready(Screenful.Signup.start); diff --git a/website/ops.js b/website/ops.js index b6b73514..05c66769 100644 --- a/website/ops.js +++ b/website/ops.js @@ -902,6 +902,31 @@ module.exports={ callnext(true); }); }, + sendSignupToken: function(email, remoteip, 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==undefined) { + var expireDate = (new Date()); expireDate.setHours(expireDate.getHours()+48); + expireDate = expireDate.toISOString(); + var token = sha1(sha1(Math.random())); + var tokenurl = module.exports.siteconfig.baseUrl + 'createaccount/' + token; + var mailSubject="Lexonomy signup"; + var mailText = `Dear Lexonomy user,\n\n`; + mailText+=`Somebody (hopefully you, from the address ${remoteip}) requested to create a new account for Lexonomy. Please follow the link below to create your account:\n\n` + mailText+=`${tokenurl}\n\n`; + mailText+=`For security reasons this link is only valid for two days (until ${expireDate}). If you did not request an account, you can safely ignore this message. \n\n`; + mailText+=`Yours,\nThe Lexonomy team`; + db.run("insert into register_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: module.exports.siteconfig.admins[0], to: email, subject: mailSubject, text: mailText}, (err, info) => {}); + db.close(); + callnext(true); + }); + } else { + db.close(); + callnext(false); + } + }); + }, sendToken: function(email, remoteip, 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){ @@ -927,13 +952,34 @@ module.exports={ } }); }, - verifyToken: function(token, callnext){ + verifyToken: function(token, type, 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.get("select * from "+type+"_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); }); }, + createAccount: 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 register_tokens where token=$token and expiration>=datetime('now') and usedDate is null", {$token: token}, function(err, row){ + if (row) { + var email = row.email; + db.get("select * from users where email=$email", {$email: email}, function(err, row){ + if (row==undefined) { + var hash = sha1(password); + db.run("insert into users (email,passwordHash) values ($email,$hash)", {$hash: hash, $email: email}, function(err, row){ + db.run("update register_tokens set usedDate=datetime('now'), usedAddress=$remoteip where token=$token", {$remoteip: remoteip, $token: token}, function(err, row){ + db.close(); + callnext(true); + }); + }); + } else { + callnext(false); + } + }); + } + }); + }, 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){ @@ -949,20 +995,63 @@ module.exports={ } }); }, + processJWT: function(user, jwtData, callnext){ + var db = new sqlite3.Database(path.join(module.exports.siteconfig.dataDir, "lexonomy.sqlite"), sqlite3.OPEN_READWRITE); + if (user.loggedin) { + //user logged in = save SkE ID in database + var key = generateKey(); + var now = (new Date()).toISOString(); + db.run("update users set ske_id=$ske_id, ske_username=$ske_username, sessionKey=$key, sessionLast=$now where email=$email", {$ske_id: jwtData.user.id, $ske_username: jwtData.user.username, $email:user.email, $key:key, $now:now}, function(err, row){ + db.close(); + callnext(true, user.email, key); + }); + } else { + //user not logged in = + // if SkE ID in database = log in user + // if SkE ID not in database = register and log in user + db.get("select email from users where ske_id=$ske_id", {$ske_id: jwtData.user.id}, function(err, row){ + if (!row) { + var email = jwtData.user.username + '@sketchengine.co.uk'; + db.get("select * from users where email=$email", {$email: email}, function(err, row){ + if (row == undefined) { + var key = generateKey(); + var now = (new Date()).toISOString(); + db.run("insert into users (email, passwordHash, ske_id, ske_username, sessionKey, sessionLast) values ($email, null, $ske_id, $ske_username, $key, $now)", {$ske_id: jwtData.user.id, $ske_username: jwtData.user.username, $email: email, $key:key, $now:now}, function(err, row){ + db.close(); + callnext(true, email, key); + }); + } else { + db.close(); + callnext(false, "user already exists "+email, ""); + } + }); + } else { + var email = row.email; + var key = generateKey(); + var now = (new Date()).toISOString(); + db.run("update users set sessionKey=$key, sessionLast=$now where ske_id=$ske_id", {$key: key, $now: now, $ske_id: jwtData.user.id}, function(err, row){ + db.close(); + callnext(true, email, key); + }); + } + }); + } + }, 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); - db.get("select email from users where email=$email and sessionKey=$key and sessionLast>=$yesterday", {$email: email, $key: sessionkey, $yesterday: yesterday}, function(err, row){ + db.get("select email, ske_username from users where email=$email and sessionKey=$key and sessionLast>=$yesterday", {$email: email, $key: sessionkey, $yesterday: yesterday}, function(err, row){ if(!row || module.exports.siteconfig.readonly){ db.close(); callnext({loggedin: false, email: null}); } else { email=row.email; + var ske_username = row.ske_username; var now=(new Date()).toISOString(); db.run("update users set sessionLast=$now where email=$email", {$now: now, $email: email}, function(err, row){ db.close(); module.exports.readSiteConfig(function(siteconfig){ - callnext({loggedin: true, email: email, isAdmin: (siteconfig.admins.indexOf(email)>-1)}); + callnext({loggedin: true, email: email, ske_username: ske_username, isAdmin: (siteconfig.admins.indexOf(email)>-1)}); }); }); } @@ -1248,4 +1337,4 @@ function getDoctype(xml){ return ret; } -const prohibitedDictIDs=["login", "logout", "make", "signup", "forgotpwd", "changepwd", "users", "dicts", "oneclick", "recoverpwd"]; +const prohibitedDictIDs=["login", "logout", "make", "signup", "forgotpwd", "changepwd", "users", "dicts", "oneclick", "recoverpwd","createaccount"]; diff --git a/website/package-lock.json b/website/package-lock.json index c74d4842..65b2a559 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -48,6 +48,11 @@ "type-is": "1.6.16" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", @@ -172,6 +177,14 @@ "streamsearch": "0.1.2" } }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "requires": { + "safe-buffer": "5.1.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -324,6 +337,49 @@ "graceful-fs": "4.1.11" } }, + "jsonwebtoken": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.2.tgz", + "integrity": "sha512-rFFq7ow/JpPzwgaz4IyRL9cp7f4ptjW92eZgsQyqkysLBmDjSSBhnKfQESoq0GU+qJXK/CQ0o4shgwbUPiFCdw==", + "requires": { + "jws": "3.1.5", + "lodash.includes": "4.3.0", + "lodash.isboolean": "3.0.3", + "lodash.isinteger": "4.0.4", + "lodash.isnumber": "3.0.3", + "lodash.isplainobject": "4.0.6", + "lodash.isstring": "4.0.1", + "lodash.once": "4.1.1", + "ms": "2.1.1", + "xtend": "4.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "5.1.1" + } + }, + "jws": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "requires": { + "jwa": "1.1.6", + "safe-buffer": "5.1.1" + } + }, "libxmljs-mt": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/libxmljs-mt/-/libxmljs-mt-0.18.0.tgz", @@ -343,6 +399,41 @@ "nan": "2.9.2" } }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "markdown": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/markdown/-/markdown-0.5.0.tgz", @@ -428,9 +519,9 @@ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, "nodemailer": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.6.4.tgz", - "integrity": "sha512-SD4uuX7NMzZ5f5m1XHDd13J4UC3SmdJk8DsmU1g6Nrs5h3x9LcXr6EBPZIqXRJ3LrF7RdklzGhZRF/TuylTcLg==" + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.6.5.tgz", + "integrity": "sha512-+bt+BgmnOXDz1uIaWXfXuTESth8UHkhtu7+X8+X2W+CHAn0AuuCyCk854qnathYQLWEC2jkpx7/pkVHcfmLKDw==" }, "nopt": { "version": "2.1.2", diff --git a/website/package.json b/website/package.json index 1073b53e..1cd8005a 100644 --- a/website/package.json +++ b/website/package.json @@ -4,10 +4,11 @@ "ejs": "^2.5.7", "express": "^4.16.3", "fs-extra": "^5.0.0", + "jsonwebtoken": "^8.2.2", "libxslt": "^0.7.0", "markdown": "^0.5.0", "multer": "^1.3.0", - "nodemailer": "^4.6.4", + "nodemailer": "^4.6.5", "sha1": "^1.1.1", "sqlite3": "^3.1.13", "xmldom": "^0.1.27" diff --git a/website/siteconfig.json.template b/website/siteconfig.json.template index 763be84d..b0f3a749 100644 --- a/website/siteconfig.json.template +++ b/website/siteconfig.json.template @@ -9,6 +9,8 @@ "trackingCode": "", "welcome": "Welcome to your Lexonomy installation.", "mailconfig": {"host": "smtp.server.example", "port": 465,"secure": true, "from": "noreply@example.com"}, + "sketchengineKey": null, + "sketchengineLoginPage": null, "licences": { "cc-by-4.0": { "title": "Creative Commons Attribution 4.0 International", diff --git a/website/views/createaccount.ejs b/website/views/createaccount.ejs new file mode 100644 index 00000000..56f304b0 --- /dev/null +++ b/website/views/createaccount.ejs @@ -0,0 +1,35 @@ + + + + + + Create your account + + + + + + + + + + + + + + + + + + diff --git a/website/views/home.ejs b/website/views/home.ejs index 896dfc4a..9966d4ac 100644 --- a/website/views/home.ejs +++ b/website/views/home.ejs @@ -73,6 +73,12 @@ }); }); + <%if(error){%>
Sketch Engine login error: <%=error%>
<%}%> + <%if(siteconfig.sketchengineLoginPage){%> +
+ Log in / register with SketchEngine +
+ <%}%> <%}else{%>
<%=user.email%>
@@ -92,6 +98,16 @@
Create a dictionary
One-Click Dictionary settings
+ <%if(siteconfig.sketchengineLoginPage){%> +
SketchEngine login
+ + <%}%> <%if(user.isAdmin){%>
Administration
diff --git a/website/views/signup.ejs b/website/views/signup.ejs index 81e4dd15..fca2f693 100644 --- a/website/views/signup.ejs +++ b/website/views/signup.ejs @@ -20,7 +20,8 @@