Improved 2FA trusted cookie.

This commit is contained in:
Ylian Saint-Hilaire 2020-04-29 15:45:31 -07:00
parent 6eb0cf6fb9
commit d54dcdc6b8
3 changed files with 32 additions and 7 deletions

View File

@ -158,7 +158,7 @@
</tr> </tr>
<tr> <tr>
<td colspan="2" style="align-content:center"> <td colspan="2" style="align-content:center">
<label><input id=tokenInputRemember name=remembertoken type=checkbox />Remember this device for 30 days.</label> <label id=tokenInputRememberLabel><input id=tokenInputRemember name=remembertoken type=checkbox /><span id=tokenInputRememberSpan></span></label>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -302,6 +302,7 @@
var currentpanel = 0; var currentpanel = 0;
var otpemail = ('{{{otpemail}}}' === 'true'); var otpemail = ('{{{otpemail}}}' === 'true');
var otpsms = ('{{{otpsms}}}' === 'true'); var otpsms = ('{{{otpsms}}}' === 'true');
var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
// Display the right server message // Display the right server message
var messageid = parseInt('{{{messageid}}}'); var messageid = parseInt('{{{messageid}}}');
@ -328,6 +329,14 @@
Q('resetpasswordformargs').value = urlargs; 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() { function startup() {
if ((features & 32) == 0) { if ((features & 32) == 0) {
// Guard against other site's top frames (web bugs). // Guard against other site's top frames (web bugs).

View File

@ -154,7 +154,7 @@
</tr> </tr>
<tr> <tr>
<td colspan="2" style="align-content:center"> <td colspan="2" style="align-content:center">
<label><input id=tokenInputRemember name=remembertoken type=checkbox />Remember this device for 30 days.</label> <label id=tokenInputRememberLabel><input id=tokenInputRemember name=remembertoken type=checkbox /><span id=tokenInputRememberSpan></span></label>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -301,6 +301,7 @@
var publicKeyCredentialRequestOptions = null; var publicKeyCredentialRequestOptions = null;
var otpemail = ('{{{otpemail}}}' === 'true'); var otpemail = ('{{{otpemail}}}' === 'true');
var otpsms = ('{{{otpsms}}}' === 'true'); var otpsms = ('{{{otpsms}}}' === 'true');
var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
// Display the right server message // Display the right server message
var messageid = parseInt('{{{messageid}}}'); var messageid = parseInt('{{{messageid}}}');
@ -322,6 +323,14 @@
Q('termsLinkFooter').href += '?key=' + urlargs.key; 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 URL arguments are provided, add them to form posts
if (window.location.href.indexOf('?') > 0) { if (window.location.href.indexOf('?') > 0) {
var xurlargs = window.location.href.substring(window.location.href.indexOf('?')); var xurlargs = window.location.href.substring(window.location.href.indexOf('?'));

View File

@ -595,7 +595,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
for (var i in cookies) { for (var i in cookies) {
if (cookies[i].startsWith('twofactor=')) { if (cookies[i].startsWith('twofactor=')) {
var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(cookies[i].substring(10)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // 30 day timeout 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); }, randomWaitTime);
} else { } else {
// Check if we need to remember this device // Check if we need to remember this device
if (req.body.remembertoken === 'on') { if ((req.body.remembertoken === 'on') && ((domain.twofactorcookiedurationdays == null) || (domain.twofactorcookiedurationdays > 0))) {
const twoFactorCookie = obj.parent.encodeCookie({ userid: user._id /*, ip: cleanRemoteAddr(req.ip)*/ }, obj.parent.loginCookieEncryptionKey); var maxCookieAge = domain.twofactorcookiedurationdays;
res.cookie('twofactor', twoFactorCookie, { maxAge: (30 * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: 'strict', secure: true }); 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 // 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); var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tokensms == true);
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; } 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 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 // Handle a post request on the root