From 11c3653e8ab5b95dd066cd594a4ec496e2a4b137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benigno=20Herna=CC=81ndez?= Date: Fri, 29 Nov 2019 13:58:30 +0100 Subject: [PATCH 1/7] otp_secret_encryption_key can be a string or lambda --- lib/two_factor_authentication.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/two_factor_authentication.rb b/lib/two_factor_authentication.rb index 7b1bbbc1..47628b3d 100644 --- a/lib/two_factor_authentication.rb +++ b/lib/two_factor_authentication.rb @@ -24,14 +24,23 @@ module Devise mattr_accessor :remember_otp_session_for_seconds @@remember_otp_session_for_seconds = 0 - mattr_accessor :otp_secret_encryption_key - @@otp_secret_encryption_key = '' - mattr_accessor :second_factor_resource_id @@second_factor_resource_id = 'id' mattr_accessor :delete_cookie_on_logout @@delete_cookie_on_logout = false + + mattr_writer :otp_secret_encryption_key + @@otp_secret_encryption_key = '' + + def self.otp_secret_encryption_key + if @@otp_secret_encryption_key.respond_to?(:call) + @@otp_secret_encryption_key.call + else + @@otp_secret_encryption_key + end + end + delegate :otp_secret_encryption_key, to: 'self.class' end module TwoFactorAuthentication From 29c3e980e4f98bb11fadb72b070a103a451f26c2 Mon Sep 17 00:00:00 2001 From: bhernez Date: Fri, 29 Nov 2019 14:04:47 +0100 Subject: [PATCH 2/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f4f91d1a..0ca0fb74 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ config.otp_length = 6 # TOTP code length config.direct_otp_valid_for = 5.minutes # Time before direct OTP becomes invalid config.direct_otp_length = 6 # Direct OTP code length config.remember_otp_session_for_seconds = 30.days # Time before browser has to perform 2fA again. Default is 0. -config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY'] +config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY'] # Can be also a lambda config.second_factor_resource_id = 'id' # Field or method name used to set value for 2fA remember cookie config.delete_cookie_on_logout = false # Delete cookie when user signs out, to force 2fA again on login ``` From 6b34fd6175a2bb173055bdcd975d7e4e327c3d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benigno=20Herna=CC=81ndez?= Date: Fri, 6 Dec 2019 13:22:21 +0100 Subject: [PATCH 3/7] Use instance variables for configurations and expose attempts_left count --- .../models/two_factor_authenticatable.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index 6d73a0fb..0eda5550 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -35,8 +35,8 @@ def authenticate_direct_otp(code) def authenticate_totp(code, options = {}) totp_secret = options[:otp_secret_key] || otp_secret_key - digits = options[:otp_length] || self.class.otp_length - drift = options[:drift] || self.class.allowed_otp_drift_seconds + digits = options[:otp_length] || self.otp_length + drift = options[:drift] || self.allowed_otp_drift_seconds raise "authenticate_totp called with no otp_secret_key set" if totp_secret.nil? totp = ROTP::TOTP.new(totp_secret, digits: digits) new_timestamp = totp.verify( @@ -50,7 +50,7 @@ def authenticate_totp(code, options = {}) def provisioning_uri(account = nil, options = {}) totp_secret = options[:otp_secret_key] || otp_secret_key - options[:digits] ||= options[:otp_length] || self.class.otp_length + options[:digits] ||= options[:otp_length] || self.otp_length raise "provisioning_uri called with no otp_secret_key set" if totp_secret.nil? account ||= email if respond_to?(:email) ROTP::TOTP.new(totp_secret, options).provisioning_uri(account) @@ -78,7 +78,11 @@ def max_login_attempts? end def max_login_attempts - self.class.max_login_attempts + self.max_login_attempts + end + + def attempts_left + max_login_attempts.to_i - second_factor_attempts_count.to_i end def totp_enabled? @@ -100,7 +104,7 @@ def generate_totp_secret def create_direct_otp(options = {}) # Create a new random OTP and store it in the database - digits = options[:length] || self.class.direct_otp_length || 6 + digits = options[:length] || self.direct_otp_length || 6 update_attributes( direct_otp: random_base10(digits), direct_otp_sent_at: Time.now.utc @@ -118,7 +122,7 @@ def random_base10(digits) end def direct_otp_expired? - Time.now.utc > direct_otp_sent_at + self.class.direct_otp_valid_for + Time.now.utc > direct_otp_sent_at + self.direct_otp_valid_for end def clear_direct_otp From d8f0cfe2de8bf681db01595d094e316ba62537d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benigno=20Herna=CC=81ndez?= Date: Fri, 6 Dec 2019 14:36:34 +0100 Subject: [PATCH 4/7] Allow instance and class configurations --- .../hooks/two_factor_authenticatable.rb | 8 +++++++- .../models/two_factor_authenticatable.rb | 10 +++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb b/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb index 3ff03415..28bcb4aa 100644 --- a/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb @@ -13,5 +13,11 @@ end Warden::Manager.before_logout do |user, auth, _options| - auth.cookies.delete TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME if Devise.delete_cookie_on_logout + should_delete = Devise.delete_cookie_on_logout + + if user.respond_to?(:delete_cookie_on_logout?) + should_delete = user.delete_cookie_on_logout + end + + auth.cookies.delete TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME if should_delete end diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index 0eda5550..32848672 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -35,8 +35,8 @@ def authenticate_direct_otp(code) def authenticate_totp(code, options = {}) totp_secret = options[:otp_secret_key] || otp_secret_key - digits = options[:otp_length] || self.otp_length - drift = options[:drift] || self.allowed_otp_drift_seconds + digits = options[:otp_length] || (self.respond_to?(:otp_length) && self.otp_length) || self.class.otp_length + drift = options[:drift] || self.class.allowed_otp_drift_seconds raise "authenticate_totp called with no otp_secret_key set" if totp_secret.nil? totp = ROTP::TOTP.new(totp_secret, digits: digits) new_timestamp = totp.verify( @@ -50,7 +50,7 @@ def authenticate_totp(code, options = {}) def provisioning_uri(account = nil, options = {}) totp_secret = options[:otp_secret_key] || otp_secret_key - options[:digits] ||= options[:otp_length] || self.otp_length + options[:digits] ||= options[:otp_length] || (self.respond_to?(:otp_length) && self.otp_length) || self.class.otp_length raise "provisioning_uri called with no otp_secret_key set" if totp_secret.nil? account ||= email if respond_to?(:email) ROTP::TOTP.new(totp_secret, options).provisioning_uri(account) @@ -74,7 +74,7 @@ def send_two_factor_authentication_code(code) end def max_login_attempts? - second_factor_attempts_count.to_i >= max_login_attempts.to_i + second_factor_attempts_count.to_i > max_login_attempts.to_i end def max_login_attempts @@ -104,7 +104,7 @@ def generate_totp_secret def create_direct_otp(options = {}) # Create a new random OTP and store it in the database - digits = options[:length] || self.direct_otp_length || 6 + digits = options[:length] || (self.respond_to?(:direct_otp_length) && self.direct_otp_length) || self.class.direct_otp_length || 6 update_attributes( direct_otp: random_base10(digits), direct_otp_sent_at: Time.now.utc From 8cb180cce5bc402312113aa6fe52ad9c0a357a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benigno=20Herna=CC=81ndez?= Date: Fri, 6 Dec 2019 14:37:47 +0100 Subject: [PATCH 5/7] Allow instance and class configurations --- .../models/two_factor_authenticatable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index 32848672..da78e965 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -36,7 +36,7 @@ def authenticate_direct_otp(code) def authenticate_totp(code, options = {}) totp_secret = options[:otp_secret_key] || otp_secret_key digits = options[:otp_length] || (self.respond_to?(:otp_length) && self.otp_length) || self.class.otp_length - drift = options[:drift] || self.class.allowed_otp_drift_seconds + drift = options[:drift] || (self.respond_to?(:allowed_otp_drift_seconds) && self.allowed_otp_drift_seconds) || self.class.allowed_otp_drift_seconds raise "authenticate_totp called with no otp_secret_key set" if totp_secret.nil? totp = ROTP::TOTP.new(totp_secret, digits: digits) new_timestamp = totp.verify( From cf61c3358c8bf4b598b51472ff0b47850de1cb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benigno=20Herna=CC=81ndez?= Date: Fri, 6 Dec 2019 16:11:31 +0100 Subject: [PATCH 6/7] Make otp dynamic --- .../devise/two_factor_authentication_controller.rb | 6 +++++- .../models/two_factor_authenticatable.rb | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/controllers/devise/two_factor_authentication_controller.rb b/app/controllers/devise/two_factor_authentication_controller.rb index 0e7aed84..bebeabc1 100644 --- a/app/controllers/devise/two_factor_authentication_controller.rb +++ b/app/controllers/devise/two_factor_authentication_controller.rb @@ -42,7 +42,11 @@ def after_two_factor_success_for(resource) end def set_remember_two_factor_cookie(resource) - expires_seconds = resource.class.remember_otp_session_for_seconds + expires_seconds = if resource.respond_to?(:remember_otp_session_for_seconds) + resource.remember_otp_session_for_seconds + else + resource.class.remember_otp_session_for_seconds + end if expires_seconds && expires_seconds > 0 cookies.signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME] = { diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb index da78e965..c715a350 100644 --- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb @@ -170,13 +170,21 @@ def otp_encrypt(value) def encryption_options_for(value) { value: value, - key: Devise.otp_secret_encryption_key, + key: otp_secret_encryption_key, iv: iv_for_attribute, salt: salt_for_attribute, algorithm: 'aes-256-cbc' } end + def otp_secret_encryption_key + if self.respond_to?(:otp_secret_encryption_key) + self.otp_secret_encryption_key + else + Devise.otp_secret_encryption_key + end + end + def iv_for_attribute(algorithm = 'aes-256-cbc') iv = encrypted_otp_secret_key_iv From 789636edc79b9c306dabb87bd726ac222f8d54ec Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Thu, 3 Mar 2022 13:09:53 +0100 Subject: [PATCH 7/7] AIRDX-237 Fix for Redis Session Storage --- .../hooks/two_factor_authenticatable.rb | 5 +++-- lib/two_factor_authentication/version.rb | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb b/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb index 28bcb4aa..c50b81e2 100644 --- a/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb +++ b/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb @@ -1,7 +1,8 @@ Warden::Manager.after_authentication do |user, auth, options| - if auth.env["action_dispatch.cookies"] + cookie_jar = auth.cookies || auth.env["action_dispatch.cookies"] + if cookie_jar expected_cookie_value = "#{user.class}-#{user.public_send(Devise.second_factor_resource_id)}" - actual_cookie_value = auth.env["action_dispatch.cookies"].signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME] + actual_cookie_value = cookie_jar.signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME] bypass_by_cookie = actual_cookie_value == expected_cookie_value end diff --git a/lib/two_factor_authentication/version.rb b/lib/two_factor_authentication/version.rb index 239fae10..23f2b1f8 100644 --- a/lib/two_factor_authentication/version.rb +++ b/lib/two_factor_authentication/version.rb @@ -1,3 +1,3 @@ module TwoFactorAuthentication - VERSION = "2.2.0".freeze + VERSION = "2.3.0".freeze end