diff --git a/views/login-mobile.handlebars b/views/login-mobile.handlebars index 78e404f5..c5fe0ff4 100644 --- a/views/login-mobile.handlebars +++ b/views/login-mobile.handlebars @@ -158,7 +158,7 @@ - + @@ -302,6 +302,7 @@ var currentpanel = 0; var otpemail = ('{{{otpemail}}}' === 'true'); var otpsms = ('{{{otpsms}}}' === 'true'); + var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}'); // Display the right server message var messageid = parseInt('{{{messageid}}}'); @@ -328,6 +329,14 @@ Q('resetpasswordformargs').value = urlargs; } + // Setup two factor cookie time + if (twoFactorCookieDays > 0) { + QV('tokenInputRememberLabel', true); + QH('tokenInputRememberSpan', format("Remember this device for {0} days.", twoFactorCookieDays)); + } else { + QV('tokenInputRememberLabel', false); + } + function startup() { if ((features & 32) == 0) { // Guard against other site's top frames (web bugs). diff --git a/views/login.handlebars b/views/login.handlebars index e947cd17..22b12ed1 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -154,7 +154,7 @@ - + @@ -301,6 +301,7 @@ var publicKeyCredentialRequestOptions = null; var otpemail = ('{{{otpemail}}}' === 'true'); var otpsms = ('{{{otpsms}}}' === 'true'); + var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}'); // Display the right server message var messageid = parseInt('{{{messageid}}}'); @@ -322,6 +323,14 @@ Q('termsLinkFooter').href += '?key=' + urlargs.key; } + // Setup two factor cookie time + if (twoFactorCookieDays > 0) { + QV('tokenInputRememberLabel', true); + QH('tokenInputRememberSpan', format("Remember this device for {0} days.", twoFactorCookieDays)); + } else { + QV('tokenInputRememberLabel', false); + } + // If URL arguments are provided, add them to form posts if (window.location.href.indexOf('?') > 0) { var xurlargs = window.location.href.substring(window.location.href.indexOf('?')); diff --git a/webserver.js b/webserver.js index 0f4fe730..cce10f8f 100644 --- a/webserver.js +++ b/webserver.js @@ -595,7 +595,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { for (var i in cookies) { if (cookies[i].startsWith('twofactor=')) { var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(cookies[i].substring(10)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // 30 day timeout - if ((twoFactorCookie != null) && (obj.args.cookieipcheck !== false) && ((twoFactorCookie.ip == null) || (twoFactorCookie.ip === cleanRemoteAddr(req.ip))) && (twoFactorCookie.userid == user._id)) { return false; } + if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === cleanRemoteAddr(req.ip))) && (twoFactorCookie.userid == user._id)) { return false; } } } } @@ -851,9 +851,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { }, randomWaitTime); } else { // Check if we need to remember this device - if (req.body.remembertoken === 'on') { - const twoFactorCookie = obj.parent.encodeCookie({ userid: user._id /*, ip: cleanRemoteAddr(req.ip)*/ }, obj.parent.loginCookieEncryptionKey); - res.cookie('twofactor', twoFactorCookie, { maxAge: (30 * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: 'strict', secure: true }); + if ((req.body.remembertoken === 'on') && ((domain.twofactorcookiedurationdays == null) || (domain.twofactorcookiedurationdays > 0))) { + var maxCookieAge = domain.twofactorcookiedurationdays; + if (typeof maxCookieAge != 'number') { maxCookieAge = 30; } + console.log('maxCookieAge', maxCookieAge); + const twoFactorCookie = obj.parent.encodeCookie({ userid: user._id, expire: maxCookieAge * 24 * 60 /*, ip: cleanRemoteAddr(req.ip)*/ }, obj.parent.loginCookieEncryptionKey); + res.cookie('twofactor', twoFactorCookie, { maxAge: (maxCookieAge * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: 'strict', secure: true }); } // Check if email address needs to be confirmed @@ -1977,8 +1980,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tokensms == true); if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; } + // See if we support two-factor trusted cookies + var twoFactorCookieDays = 30; + if (typeof domain.twofactorcookiedurationdays == 'number') { twoFactorCookieDays = domain.twofactorcookiedurationdays; } + // Render the login page - render(req, res, getRenderPage('login', req, domain), getRenderArgs({ loginmode: loginmode, rootCertLink: getRootCertLink(), newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge), messageid: msgid, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null, hwstate: hwstate, otpemail: otpemail, otpsms: otpsms }, domain)); + render(req, res, getRenderPage('login', req, domain), getRenderArgs({ loginmode: loginmode, rootCertLink: getRootCertLink(), newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge), messageid: msgid, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null, hwstate: hwstate, otpemail: otpemail, otpsms: otpsms, twoFactorCookieDays: twoFactorCookieDays }, domain)); } // Handle a post request on the root