diff --git a/meshmail.js b/meshmail.js index 1c21568d..68e3d748 100644 --- a/meshmail.js +++ b/meshmail.js @@ -233,7 +233,7 @@ module.exports.CreateMeshMail = function (parent) { }; // Send account reset mail - obj.sendAccountResetMail = function (domain, username, email, language, loginkey) { + obj.sendAccountResetMail = function (domain, username, userid, email, language, loginkey) { obj.checkEmail(email, function (checked) { if (checked) { parent.debug('email', "Sending account password reset to " + email); @@ -252,7 +252,7 @@ module.exports.CreateMeshMail = function (parent) { // Set all the options. var options = { username: username, email: email, servername: domain.title ? domain.title : 'MeshCentral' }; if (loginkey != null) { options.urlargs1 = '?key=' + loginkey; options.urlargs2 = '&key=' + loginkey; } else { options.urlargs1 = ''; options.urlargs2 = ''; } - options.cookie = obj.parent.encodeCookie({ u: domain.id + '/' + username, e: email, a: 2 }, obj.mailCookieEncryptionKey); + options.cookie = obj.parent.encodeCookie({ u: userid, e: email, a: 2 }, obj.mailCookieEncryptionKey); // Send the email obj.pendingMails.push({ to: email, from: parent.config.smtp.from, subject: mailReplacements(template.htmlSubject, domain, options), text: mailReplacements(template.txt, domain, options), html: mailReplacements(template.html, domain, options) }); diff --git a/package.json b/package.json index 106e503b..5d2e7448 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,9 @@ "express": "^4.17.0", "express-handlebars": "^3.1.0", "express-ws": "^4.0.0", + "html-minifier": "^4.0.0", "ipcheck": "^0.1.0", + "minify-js": "0.0.4", "minimist": "^1.2.0", "multiparty": "^4.2.1", "nedb": "^1.8.0", diff --git a/public/scripts/amt-wsman-0.2.0-min.js b/public/scripts/amt-wsman-0.2.0-min.js index 86d1ae50..a8eb3bca 100644 --- a/public/scripts/amt-wsman-0.2.0-min.js +++ b/public/scripts/amt-wsman-0.2.0-min.js @@ -1 +1 @@ -var WsmanStackCreateService=function(e,s,r,a,o,t){var p={};function l(e){if(!e)return"";var s=" ";for(var r in e)e.hasOwnProperty(r)&&0===r.indexOf("@")&&(s+=r.substring(1)+'="'+e[r]+'" ');return s}function w(e){if(!e)return"";if("string"==typeof e)return e;if(e.InstanceID)return''+e.InstanceID+"";var s="";for(var r in e)if(e.hasOwnProperty(r)){if(s+='',e[r].ReferenceParameters){s+="",s+=""+e[r].Address+""+e[r].ReferenceParameters.ResourceURI+"";var a=e[r].ReferenceParameters.SelectorSet.Selector;if(Array.isArray(a))for(var o=0;o"+a[o].Value+"";else s+=""+a.Value+"";s+=""}else s+=e[r];s+=""}return s+=""}return p.NextMessageId=1,p.Address="/wsman",p.comm=CreateWsmanComm(e,s,r,a,o,t),p.PerformAjax=function(e,o,s,r,a){null==a&&(a=""),p.comm.PerformAjax('
"+e,function(e,s,r){if(200==s){var a=p.ParseWsman(e);a&&null!=a?o(p,a.Header.ResourceURI,a,200,r):o(p,null,{Header:{HttpError:s}},601,r)}else o(p,null,{Header:{HttpError:s}},s,r)},s,r)},p.CancelAllQueries=function(e){p.comm.CancelAllQueries(e)},p.GetNameFromUrl=function(e){var s=e.lastIndexOf("/");return-1==s?e:e.substring(s+1)},p.ExecSubscribe=function(e,s,r,a,o,t,n,l,c,d){var m="",i="";null!=c&&null!=d&&(m="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken"+c+''+d+"",i=''),l=null!=l&&null!=l?""+l+"":"";var u="http://schemas.xmlsoap.org/ws/2004/08/eventing/Subscribe"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"+w(n)+m+'
'+r+""+i+"PT0.000000S";p.PerformAjax(u+"
",a,o,t,'xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:se="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:m="http://x.com"')},p.ExecUnSubscribe=function(e,s,r,a,o){var t="http://schemas.xmlsoap.org/ws/2004/08/eventing/Unsubscribe"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"+w(o)+"";p.PerformAjax(t+"",s,r,a,'xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing"')},p.ExecPut=function(e,s,r,a,o,t){var n="http://schemas.xmlsoap.org/ws/2004/09/transfer/Put"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60.000S"+w(t)+""+function(e,s){if(!e||null==s)return"";var r=p.GetNameFromUrl(e),a="';for(var o in s)if(s.hasOwnProperty(o)&&0!==o.indexOf("__")&&0!==o.indexOf("@")&&void 0!==s[o]&&null!==s[o]&&"function"!=typeof s[o])if("object"==typeof s[o]&&s[o].ReferenceParameters){a+=""+s[o].Address+""+s[o].ReferenceParameters.ResourceURI+"";var t=s[o].ReferenceParameters.SelectorSet.Selector;if(Array.isArray(t))for(var n=0;n"+t[n].Value+"";else a+=""+t.Value+"";a+=""}else if(Array.isArray(s[o]))for(n=0;n"+s[o][n].toString()+"";else a+=""+s[o].toString()+"";return a+=""}(e,s);p.PerformAjax(n+"",r,a,o)},p.ExecCreate=function(e,s,r,a,o,t){var n=p.GetNameFromUrl(e),l="http://schemas.xmlsoap.org/ws/2004/09/transfer/Create"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S"+w(t)+"';for(var c in s)l+=""+s[c]+"";p.PerformAjax(l+"",r,a,o)},p.ExecCreateXml=function(e,s,r,a,o){var t=p.GetNameFromUrl(e);p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/transfer/Create"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60.000S'+s+"",r,a,o)},p.ExecDelete=function(e,s,r,a,o){var t="http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S"+w(s)+"";p.PerformAjax(t,r,a,o)},p.ExecGet=function(e,s,r,a){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/transfer/Get"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S",s,r,a)},p.ExecMethod=function(e,s,r,a,o,t,n){var l="";for(var c in r)if(null!=r[c])if(Array.isArray(r[c]))for(var d in r[c])l+=""+r[c][d]+"";else l+=""+r[c]+"";p.ExecMethodXml(e,s,l,a,o,t,n)},p.ExecMethodXml=function(e,s,r,a,o,t,n){p.PerformAjax(e+"/"+s+""+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S"+w(n)+"'+r+"",a,o,t)},p.ExecEnum=function(e,s,r,a){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate"+p.Address+""+e+""+p.NextMessageId+++'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S',s,r,a)},p.ExecPull=function(e,s,r,a,o){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull"+p.Address+""+e+""+p.NextMessageId+++'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S'+s+"99999999",r,a,o)},p.ParseWsman=function(s){try{s.childNodes||(s=function(e){{if(window.DOMParser)return(new DOMParser).parseFromString(e,"text/xml");var s=new ActiveXObject("Microsoft.XMLDOM");return s.async=!1,s.loadXML(e),s}}(s));var e,r={Header:{}},a=s.getElementsByTagName("Header")[0];if(!(a=a||s.getElementsByTagName("a:Header")[0]))return null;for(var o=0;o'+e.InstanceID+"";var s="";for(var r in e)if(e.hasOwnProperty(r)){if(s+='',e[r].ReferenceParameters){s+="",s+=""+e[r].Address+""+e[r].ReferenceParameters.ResourceURI+"";var a=e[r].ReferenceParameters.SelectorSet.Selector;if(Array.isArray(a))for(var o=0;o"+a[o].Value+"";else s+=""+a.Value+"";s+=""}else s+=e[r];s+=""}return s+=""}return p.NextMessageId=1,p.Address="/wsman",p.comm=CreateWsmanComm(e,s,r,a,o,t),p.PerformAjax=function(e,o,s,r,a){null==a&&(a=""),p.comm.PerformAjax('
"+e,function(e,s,r){if(200==s){var a=p.ParseWsman(e);a&&null!=a?o(p,a.Header.ResourceURI,a,200,r):o(p,null,{Header:{HttpError:s}},601,r)}else o(p,null,{Header:{HttpError:s}},s,r)},s,r)},p.CancelAllQueries=function(e){p.comm.CancelAllQueries(e)},p.GetNameFromUrl=function(e){var s=e.lastIndexOf("/");return-1==s?e:e.substring(s+1)},p.ExecSubscribe=function(e,s,r,a,o,t,n,l,d,c){var m="",i="";null!=d&&null!=c&&(m="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken"+d+''+c+"",i=''),l=null!=l&&null!=l?""+l+"":"";var u="http://schemas.xmlsoap.org/ws/2004/08/eventing/Subscribe"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"+w(n)+m+'
'+r+""+i+"PT0.000000S";p.PerformAjax(u+"
",a,o,t,'xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:se="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:m="http://x.com"')},p.ExecUnSubscribe=function(e,s,r,a,o){var t="http://schemas.xmlsoap.org/ws/2004/08/eventing/Unsubscribe"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"+w(o)+"";p.PerformAjax(t+"",s,r,a,'xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing"')},p.ExecPut=function(e,s,r,a,o,t){var n="http://schemas.xmlsoap.org/ws/2004/09/transfer/Put"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60.000S"+w(t)+""+function(e,s){if(!e||null==s)return"";var r=p.GetNameFromUrl(e),a="';for(var o in s)if(s.hasOwnProperty(o)&&0!==o.indexOf("__")&&0!==o.indexOf("@")&&void 0!==s[o]&&null!==s[o]&&"function"!=typeof s[o])if("object"==typeof s[o]&&s[o].ReferenceParameters){a+=""+s[o].Address+""+s[o].ReferenceParameters.ResourceURI+"";var t=s[o].ReferenceParameters.SelectorSet.Selector;if(Array.isArray(t))for(var n=0;n"+t[n].Value+"";else a+=""+t.Value+"";a+=""}else if(Array.isArray(s[o]))for(n=0;n"+s[o][n].toString()+"";else a+=""+s[o].toString()+"";return a+=""}(e,s);p.PerformAjax(n+"",r,a,o)},p.ExecCreate=function(e,s,r,a,o,t){var n=p.GetNameFromUrl(e),l="http://schemas.xmlsoap.org/ws/2004/09/transfer/Create"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S"+w(t)+"';for(var d in s)l+=""+s[d]+"";p.PerformAjax(l+"",r,a,o)},p.ExecCreateXml=function(e,s,r,a,o){var t=p.GetNameFromUrl(e);p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/transfer/Create"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60.000S'+s+"",r,a,o)},p.ExecDelete=function(e,s,r,a,o){var t="http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S"+w(s)+"";p.PerformAjax(t,r,a,o)},p.ExecGet=function(e,s,r,a){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/transfer/Get"+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S",s,r,a)},p.ExecMethod=function(e,s,r,a,o,t,n){var l="";for(var d in r)if(null!=r[d])if(Array.isArray(r[d]))for(var c in r[d])l+=""+r[d][c]+"";else l+=""+r[d]+"";p.ExecMethodXml(e,s,l,a,o,t,n)},p.ExecMethodXml=function(e,s,r,a,o,t,n){p.PerformAjax(e+"/"+s+""+p.Address+""+e+""+p.NextMessageId+++"http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S"+w(n)+"'+r+"",a,o,t)},p.ExecEnum=function(e,s,r,a){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate"+p.Address+""+e+""+p.NextMessageId+++'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S',s,r,a)},p.ExecPull=function(e,s,r,a,o){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull"+p.Address+""+e+""+p.NextMessageId+++'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousPT60S'+s+"99999999",r,a,o)},p.ParseWsman=function(s){try{s.childNodes||(s=function(e){{if(window.DOMParser)return(new DOMParser).parseFromString(e,"text/xml");var s=new ActiveXObject("Microsoft.XMLDOM");return s.async=!1,s.loadXML(e),s}}(s));var e,r={Header:{}},a=s.getElementsByTagName("Header")[0];if(!(a=a||s.getElementsByTagName("a:Header")[0]))return null;for(var o=0;o2->5->1" + "account-reset.html->2->5->1", + "message.handlebars->3->19", + "message2.handlebars->5->19" ] }, { @@ -16674,8 +16676,8 @@ "zh-chs": "进入登录页面", "zh-cht": "進入登入頁面", "xloc": [ - "message.handlebars->3->19", - "message2.handlebars->5->19" + "message.handlebars->3->20", + "message2.handlebars->5->20" ] }, { @@ -42116,7 +42118,7 @@ "ru": "macOS ARM (64bit)", "tr": "macOS ARM (64bit)", "zh-chs": "macOS ARM (64位)", - "zh-cht": "macOS ARM (64位)" + "zh-cht": "macOS ARM (64位)", "xloc": [ "default.handlebars->29->815" ] @@ -42125,7 +42127,6 @@ "en": "macOS x86 (64bit)", "cs": "macOS x86 (64bit)", "de": "macOS x86 (64bit)", - "en": "macOS x86 (64bit)", "es": "macOS x86 (64bit)", "fi": "macOS x86 (64-bittinen)", "fr": "macOS x86 (64bit)", @@ -42137,7 +42138,7 @@ "ru": "macOS x86 (64bit)", "tr": "macOS x86 (64bit)", "zh-chs": "macOS x86 (64位)", - "zh-cht": "macOS x86 (64位)" + "zh-cht": "macOS x86 (64位)", "xloc": [ "default.handlebars->29->814" ] @@ -42157,7 +42158,7 @@ "ru": "macOS x86-32bit", "tr": "macOS x86-32bit", "zh-chs": "macOS x86-32位", - "zh-cht": "macOS x86-32位" + "zh-cht": "macOS x86-32位", "xloc": [ "default.handlebars->29->24" ] @@ -42177,7 +42178,7 @@ "ru": "macOS x86-64bit", "tr": "macOS x86-64bit", "zh-chs": "macOS x86-64位", - "zh-cht": "macOS x86-64位" + "zh-cht": "macOS x86-64位", "xloc": [ "default.handlebars->29->29" ] @@ -43532,4 +43533,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/views/message.handlebars b/views/message.handlebars index 9b665c23..821a2c7a 100644 --- a/views/message.handlebars +++ b/views/message.handlebars @@ -77,10 +77,11 @@ case 11: { msg = "Sharing link not valid yet."; break; } case 12: { msg = "Sharing link is expired."; break; } case 13: { msg = "If you are an administrator, [login here].".replace('[', '').replace(']', ''); break; } + case 14: { msg = '' + "Click here to reset your account password." + ''; break; } } // Add login page link - if ((msgid != 11) && (msgid != 12) && (msgid != 13)) { msg += ' ' + "Go to login page" + '.' } + if ((msgid != 11) && (msgid != 12) && (msgid != 13) && (msgid != 14)) { msg += ' ' + "Go to login page" + '.' } QH('mainMessage', msg); function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); }; diff --git a/views/message2.handlebars b/views/message2.handlebars index ce7de3be..a6626c4e 100644 --- a/views/message2.handlebars +++ b/views/message2.handlebars @@ -78,10 +78,11 @@ case 11: { msg = "Sharing link not valid yet."; break; } case 12: { msg = "Sharing link is expired."; break; } case 13: { msg = "If you are an administrator, [login here].".replace('[', '').replace(']', ''); break; } + case 14: { msg = '' + "Click here to reset your account password." + ''; break; } } // Add login page link - if ((msgid != 11) && (msgid != 12) && (msgid != 13)) { msg += ' ' + "Go to login page" + '.' } + if ((msgid != 11) && (msgid != 12) && (msgid != 13) && (msgid != 14)) { msg += ' ' + "Go to login page" + '.' } QH('mainMessage', msg); function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); }; diff --git a/webserver.js b/webserver.js index c99d4680..ed54b1db 100644 --- a/webserver.js +++ b/webserver.js @@ -942,7 +942,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { req.session.messageid = 108; // Invalid token, try again. if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed 2FA for ' + xusername + ' from ' + cleanRemoteAddr(req.clientIp) + ' port ' + req.port); } parent.debug('web', 'handleLoginRequest: invalid 2FA token'); - obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + user.name], obj, { action: 'authfail', username: user.name, userid: 'user/' + domain.id + '/' + user.name, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp }); + obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { action: 'authfail', username: user.name, userid: user._id, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp }); obj.setbadLogin(req); } else { parent.debug('web', 'handleLoginRequest: 2FA token required'); @@ -1389,6 +1389,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } } else { obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) { + // Remove all accounts that start with ~ since they are special accounts. + var cleanDocs = []; + if ((err == null) && (docs.length > 0)) { + for (var i in docs) { + const user = docs[i]; + const locked = ((user.siteadmin != null) && (user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)); // No password recovery for locked accounts + const specialAccount = (user._id.split('/')[2].startsWith('~')); // No password recovery for special accounts + if ((specialAccount == false) && (locked == false)) { cleanDocs.push(user); } + } + } + docs = cleanDocs; + + // Check if we have any account that match this email address if ((err != null) || (docs.length == 0)) { parent.debug('web', 'handleResetAccountRequest: Account not found'); req.session.loginmode = '3'; @@ -1407,11 +1420,22 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // 2-step auth is required, but the token is not present or not valid. parent.debug('web', 'handleResetAccountRequest: Invalid 2FA token, try again'); if ((req.body.token != null) || (req.body.hwtoken != null)) { - req.session.messageid = 108; // Invalid token, try again. - obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + user.name], obj, { action: 'authfail', username: user.name, userid: 'user/' + domain.id + '/' + user.name, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp }); - obj.setbadLogin(req); + var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null)); + if ((req.body.hwtoken == '**sms**') && sms2fa) { + // Cause a token to be sent to the user's phone number + user.otpsms = { k: obj.common.zeroPad(getRandomSixDigitInteger(), 6), d: Date.now() }; + obj.db.SetUser(user); + parent.debug('web', 'Sending 2FA SMS for password recovery to: ' + user.phone); + parent.smsserver.sendToken(domain, user.phone, user.otpsms.k, obj.getLanguageCodes(req)); + req.session.messageid = 4; // SMS sent. + } else { + req.session.messageid = 108; // Invalid token, try again. + obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { action: 'authfail', username: user.name, userid: user._id, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp }); + obj.setbadLogin(req); + } } req.session.loginmode = '5'; + delete req.session.tokenemail; req.session.tokenemail = email; if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } } @@ -1419,7 +1443,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Send email to perform recovery. delete req.session.tokenemail; if (obj.parent.mailserver != null) { - obj.parent.mailserver.sendAccountResetMail(domain, user.name, user.email, obj.getLanguageCodes(req), req.query.key); + obj.parent.mailserver.sendAccountResetMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key); if (i == 0) { parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.'); req.session.loginmode = '1'; @@ -1439,7 +1463,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } else { // No second factor, send email to perform recovery. if (obj.parent.mailserver != null) { - obj.parent.mailserver.sendAccountResetMail(domain, user.name, user.email, obj.getLanguageCodes(req), req.query.key); + obj.parent.mailserver.sendAccountResetMail(domain, user.name, user._id, user.email, obj.getLanguageCodes(req), req.query.key); if (i == 0) { parent.debug('web', 'handleResetAccountRequest: Hold on, reset mail sent.'); req.session.loginmode = '1'; @@ -1544,13 +1568,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (req.query.c != null) { var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.mailserver.mailCookieEncryptionKey, 30); - if ((cookie != null) && (cookie.u != null) && (cookie.e != null)) { + if ((cookie != null) && (cookie.u != null) && (cookie.u.startsWith('user/')) && (cookie.e != null)) { var idsplit = cookie.u.split('/'); - if ((idsplit.length != 2) || (idsplit[0] != domain.id)) { + if ((idsplit.length != 3) || (idsplit[1] != domain.id)) { parent.debug('web', 'handleCheckMailRequest: Invalid domain.'); render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 1, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); } else { - obj.db.Get('user/' + cookie.u.toLowerCase(), function (err, docs) { + obj.db.Get(cookie.u, function (err, docs) { if (docs.length == 0) { parent.debug('web', 'handleCheckMailRequest: Invalid username.'); render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 2, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: encodeURIComponent(idsplit[1]).replace(/'/g, '%27') }, req, domain)); @@ -1600,36 +1624,40 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { parent.debug('web', 'handleCheckMailRequest: email not verified.'); render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 7, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: EscapeHtml(user.email), arg2: EscapeHtml(user.name) }, req, domain)); } else { - // Set a temporary password - obj.crypto.randomBytes(16, function (err, buf) { - var newpass = buf.toString('base64').split('=').join('').split('/').join(''); - require('./pass').hash(newpass, function (err, salt, hash, tag) { - var userinfo = null; - if (err) throw err; + if (req.query.confirm == 1) { + // Set a temporary password + obj.crypto.randomBytes(16, function (err, buf) { + var newpass = buf.toString('base64').split('=').join('').split('/').join('').split('+').join(''); + require('./pass').hash(newpass, function (err, salt, hash, tag) { + if (err) throw err; - // Change the password - userinfo = obj.users[user._id]; - userinfo.salt = salt; - userinfo.hash = hash; - delete userinfo.passtype; - userinfo.passchange = Math.floor(Date.now() / 1000); - delete userinfo.passhint; - //delete userinfo.otpsecret; // Currently a email password reset will turn off 2-step login. - obj.db.SetUser(userinfo); + // Change the password + var userinfo = obj.users[user._id]; + userinfo.salt = salt; + userinfo.hash = hash; + delete userinfo.passtype; + userinfo.passchange = Math.floor(Date.now() / 1000); + delete userinfo.passhint; + obj.db.SetUser(userinfo); - // Event the change - var event = { etype: 'user', userid: user._id, username: userinfo.name, account: obj.CloneSafeUser(userinfo), action: 'accountchange', msg: 'Password reset for user ' + EscapeHtml(user.name), domain: domain.id }; - if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come. - obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event); + // Event the change + var event = { etype: 'user', userid: user._id, username: userinfo.name, account: obj.CloneSafeUser(userinfo), action: 'accountchange', msg: 'Password reset for user ' + EscapeHtml(user.name), domain: domain.id }; + if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come. + obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event); - // Send the new password - render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 8, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: EscapeHtml(user.name), arg2: EscapeHtml(newpass) }, req, domain)); - parent.debug('web', 'handleCheckMailRequest: send temporary password.'); + // Send the new password + render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 8, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), arg1: EscapeHtml(user.name), arg2: EscapeHtml(newpass) }, req, domain)); + parent.debug('web', 'handleCheckMailRequest: send temporary password.'); - // Send to authlog - if (obj.parent.authlog) { obj.parent.authLog('https', 'Performed account reset for user ' + user.name); } - }, 0); - }); + // Send to authlog + if (obj.parent.authlog) { obj.parent.authLog('https', 'Performed account reset for user ' + user.name); } + }, 0); + }); + } else { + // Display a link for the user to confirm password reset + // We must do this because GMail will also load this URL a few seconds after the user does and we don't want to cause two password resets. + render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 14, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); + } } } else { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 1, msgid: 9, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); @@ -2498,8 +2526,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { var hwstate = null; if (hardwareKeyChallenge) { hwstate = obj.parent.encodeCookie({ u: req.session.tokenusername, p: req.session.tokenpassword, c: req.session.u2fchallenge }, obj.parent.loginCookieEncryptionKey) } - // Check if we can use OTP tokens with email - var otpemail = (parent.mailserver != null) && (req.session != null) && ((req.session.tokenemail == true) || (typeof req.session.tokenemail == 'string')); + // Check if we can use OTP tokens with email. We can't use email for 2FA password recovery (loginmode 5). + var otpemail = (loginmode != 5) && (parent.mailserver != null) && (req.session != null) && ((req.session.tokenemail == true) || (typeof req.session.tokenemail == 'string')); if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; } var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tokensms == true); if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }