Improved 2FA trusted cookie.

Ylian Saint-Hilaire committed Apr 29, 2020 at 15:45 UTC d54dcdc6b8d399b465f2d29ef5f5cd369cd383f2
3 files changed +32 -7
views/login-mobile.handlebars
+10 -1
@@ -158,7 +158,7 @@
158 </tr>
159 <tr>
160 <td colspan="2" style="align-content:center">
161 - <label><input id=tokenInputRemember name=remembertoken type=checkbox />Remember this device for 30 days.</label>
161 + <label id=tokenInputRememberLabel><input id=tokenInputRemember name=remembertoken type=checkbox /><span id=tokenInputRememberSpan></span></label>
162 </td>
163 </tr>
164 <tr>
@@ -302,6 +302,7 @@
302 var currentpanel = 0;
303 var otpemail = ('{{{otpemail}}}' === 'true');
304 var otpsms = ('{{{otpsms}}}' === 'true');
305 + var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
306
307 // Display the right server message
308 var messageid = parseInt('{{{messageid}}}');
@@ -328,6 +329,14 @@
329 Q('resetpasswordformargs').value = urlargs;
330 }
331
332 + // Setup two factor cookie time
333 + if (twoFactorCookieDays > 0) {
334 + QV('tokenInputRememberLabel', true);
335 + QH('tokenInputRememberSpan', format("Remember this device for {0} days.", twoFactorCookieDays));
336 + } else {
337 + QV('tokenInputRememberLabel', false);
338 + }
339 +
340 function startup() {
341 if ((features & 32) == 0) {
342 // Guard against other site's top frames (web bugs).
views/login.handlebars
+10 -1
@@ -154,7 +154,7 @@
154 </tr>
155 <tr>
156 <td colspan="2" style="align-content:center">
157 - <label><input id=tokenInputRemember name=remembertoken type=checkbox />Remember this device for 30 days.</label>
157 + <label id=tokenInputRememberLabel><input id=tokenInputRemember name=remembertoken type=checkbox /><span id=tokenInputRememberSpan></span></label>
158 </td>
159 </tr>
160 <tr>
@@ -301,6 +301,7 @@
301 var publicKeyCredentialRequestOptions = null;
302 var otpemail = ('{{{otpemail}}}' === 'true');
303 var otpsms = ('{{{otpsms}}}' === 'true');
304 + var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
305
306 // Display the right server message
307 var messageid = parseInt('{{{messageid}}}');
@@ -322,6 +323,14 @@
323 Q('termsLinkFooter').href += '?key=' + urlargs.key;
324 }
325
326 + // Setup two factor cookie time
327 + if (twoFactorCookieDays > 0) {
328 + QV('tokenInputRememberLabel', true);
329 + QH('tokenInputRememberSpan', format("Remember this device for {0} days.", twoFactorCookieDays));
330 + } else {
331 + QV('tokenInputRememberLabel', false);
332 + }
333 +
334 // If URL arguments are provided, add them to form posts
335 if (window.location.href.indexOf('?') > 0) {
336 var xurlargs = window.location.href.substring(window.location.href.indexOf('?'));
webserver.js
+12 -5
@@ -595,7 +595,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
595 for (var i in cookies) {
596 if (cookies[i].startsWith('twofactor=')) {
597 var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(cookies[i].substring(10)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // 30 day timeout
598 - if ((twoFactorCookie != null) && (obj.args.cookieipcheck !== false) && ((twoFactorCookie.ip == null) || (twoFactorCookie.ip === cleanRemoteAddr(req.ip))) && (twoFactorCookie.userid == user._id)) { return false; }
598 + if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === cleanRemoteAddr(req.ip))) && (twoFactorCookie.userid == user._id)) { return false; }
599 }
600 }
601 }
@@ -851,9 +851,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
851 }, randomWaitTime);
852 } else {
853 // Check if we need to remember this device
854 - if (req.body.remembertoken === 'on') {
855 - const twoFactorCookie = obj.parent.encodeCookie({ userid: user._id /*, ip: cleanRemoteAddr(req.ip)*/ }, obj.parent.loginCookieEncryptionKey);
856 - res.cookie('twofactor', twoFactorCookie, { maxAge: (30 * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: 'strict', secure: true });
854 + if ((req.body.remembertoken === 'on') && ((domain.twofactorcookiedurationdays == null) || (domain.twofactorcookiedurationdays > 0))) {
855 + var maxCookieAge = domain.twofactorcookiedurationdays;
856 + if (typeof maxCookieAge != 'number') { maxCookieAge = 30; }
857 + console.log('maxCookieAge', maxCookieAge);
858 + const twoFactorCookie = obj.parent.encodeCookie({ userid: user._id, expire: maxCookieAge * 24 * 60 /*, ip: cleanRemoteAddr(req.ip)*/ }, obj.parent.loginCookieEncryptionKey);
859 + res.cookie('twofactor', twoFactorCookie, { maxAge: (maxCookieAge * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: 'strict', secure: true });
860 }
861
862 // Check if email address needs to be confirmed
@@ -1977,8 +1980,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1980 var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tokensms == true);
1981 if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
1982
1983 + // See if we support two-factor trusted cookies
1984 + var twoFactorCookieDays = 30;
1985 + if (typeof domain.twofactorcookiedurationdays == 'number') { twoFactorCookieDays = domain.twofactorcookiedurationdays; }
1986 +
1987 // Render the login page
1981 - 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));
1988 + 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));
1989 }
1990
1991 // Handle a post request on the root