diff --git a/CHANGES b/CHANGES index 29da2c6a4..80db1598e 100644 --- a/CHANGES +++ b/CHANGES @@ -1653,4 +1653,5 @@ * Fix issue where tar backups with files > 8GB was corrupt * Add SparkPost as mail relay backend * Add Wasabi storage backend +* TOTP tokens are now checked for with +- 60 seconds diff --git a/src/routes/developer.js b/src/routes/developer.js index 4e9ccc90f..7909710d8 100644 --- a/src/routes/developer.js +++ b/src/routes/developer.js @@ -20,7 +20,7 @@ function login(req, res, next) { if (!user.ghost && user.twoFactorAuthenticationEnabled) { if (!req.body.totpToken) return next(new HttpError(401, 'A totpToken must be provided')); - let verified = speakeasy.totp.verify({ secret: user.twoFactorAuthenticationSecret, encoding: 'base32', token: req.body.totpToken }); + let verified = speakeasy.totp.verify({ secret: user.twoFactorAuthenticationSecret, encoding: 'base32', token: req.body.totpToken, window: 2 }); if (!verified) return next(new HttpError(401, 'Invalid totpToken')); } diff --git a/src/routes/oauth2.js b/src/routes/oauth2.js index 1cc40f81d..97276e181 100644 --- a/src/routes/oauth2.js +++ b/src/routes/oauth2.js @@ -263,7 +263,7 @@ function login(req, res) { return res.redirect('/api/v1/session/login?' + failureQuery); } - let verified = speakeasy.totp.verify({ secret: req.user.twoFactorAuthenticationSecret, encoding: 'base32', token: req.body.totpToken }); + let verified = speakeasy.totp.verify({ secret: req.user.twoFactorAuthenticationSecret, encoding: 'base32', token: req.body.totpToken, window: 2 }); if (!verified) { let failureQuery = querystring.stringify({ error: 'The 2FA token is invalid', returnTo: returnTo }); return res.redirect('/api/v1/session/login?' + failureQuery); diff --git a/src/users.js b/src/users.js index 4424957f3..9c563b9cc 100644 --- a/src/users.js +++ b/src/users.js @@ -631,7 +631,7 @@ function enableTwoFactorAuthentication(userId, totpToken, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND)); if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error)); - var verified = speakeasy.totp.verify({ secret: result.twoFactorAuthenticationSecret, encoding: 'base32', token: totpToken }); + var verified = speakeasy.totp.verify({ secret: result.twoFactorAuthenticationSecret, encoding: 'base32', token: totpToken, window: 2 }); if (!verified) return callback(new UsersError(UsersError.BAD_TOKEN)); if (result.twoFactorAuthenticationEnabled) return callback(new UsersError(UsersError.ALREADY_EXISTS));