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