devise-multi-factor 3.1.5 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/devise/totp_controller.rb +5 -5
- data/app/controllers/devise/two_factor_authentication_controller.rb +5 -3
- data/config/locales/en.yml +3 -0
- data/devise-multi-factor.gemspec +1 -1
- data/lib/devise_multi_factor/hooks/two_factor_authenticatable.rb +2 -3
- data/lib/devise_multi_factor/remember_tfa_cookie.rb +39 -0
- data/lib/devise_multi_factor/version.rb +1 -1
- data/lib/devise_multi_factor.rb +1 -0
- data/spec/controllers/two_factor_authentication_controller_spec.rb +1 -1
- data/spec/features/two_factor_authenticatable_spec.rb +32 -32
- data/spec/lib/devise_multi_factor/tfa_remember_cookie_spec.rb +135 -0
- metadata +6 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b69e7dd9a398b8cfebfd6c3b1e17e4c94603f2097e56a54f78a5f4ce07ce9751
         | 
| 4 | 
            +
              data.tar.gz: 76f657bc840dc7b9b3d48fb3fe66876194ba4481e6849aac1b8f28b3a22eae52
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: f1dc861603dd878a0caddf74a316f7c385e3d103a0049de7cf5d1f2baadb29c7b7c1a1c25fa2c42a865c01eac178d3bd92621a53b278f6ec4f6c1f3fa0ec11d2
         | 
| 7 | 
            +
              data.tar.gz: 513a0cb006edb6613a7c3e0af9cf1ab8594ed2841429b09d7b34e538c2a8a2d614f001aa86c1b7a87ef8946afddfdbcae16beda276cd437d8c0ca61fa84b0741
         | 
| @@ -16,11 +16,11 @@ class Devise::TotpController < DeviseController | |
| 16 16 | 
             
                if resource.enroll_totp!(@otp_secret, params[:otp_attempt])
         | 
| 17 17 | 
             
                  after_two_factor_enroll_success_for(resource)
         | 
| 18 18 | 
             
                else
         | 
| 19 | 
            -
                  flash.now[:error] = ' | 
| 20 | 
            -
                  render_enroll_form
         | 
| 19 | 
            +
                  flash.now[:error] = I18n.t('devise.totp_setup.invalid_code')
         | 
| 20 | 
            +
                  render_enroll_form(status: :unprocessable_entity)
         | 
| 21 21 | 
             
                end
         | 
| 22 22 | 
             
              rescue ActiveSupport::MessageVerifier::InvalidSignature
         | 
| 23 | 
            -
                redirect_to send("new_#{resource_name}_two_factor_authentication_path"), flash: { error: ' | 
| 23 | 
            +
                redirect_to send("new_#{resource_name}_two_factor_authentication_path"), flash: { error: I18n.t('devise.totp_setup.invalid_signature') }
         | 
| 24 24 | 
             
              end
         | 
| 25 25 |  | 
| 26 26 | 
             
              def show
         | 
| @@ -40,9 +40,9 @@ class Devise::TotpController < DeviseController | |
| 40 40 | 
             
                  .to_data_url
         | 
| 41 41 | 
             
              end
         | 
| 42 42 |  | 
| 43 | 
            -
              def render_enroll_form
         | 
| 43 | 
            +
              def render_enroll_form(status: :ok)
         | 
| 44 44 | 
             
                @qr_code = generate_qr_code(@otp_secret)
         | 
| 45 | 
            -
                render :new
         | 
| 45 | 
            +
                render :new, status: status
         | 
| 46 46 | 
             
              end
         | 
| 47 47 |  | 
| 48 48 | 
             
              def verifier
         | 
| @@ -8,7 +8,7 @@ class Devise::TwoFactorAuthenticationController < DeviseController | |
| 8 8 | 
             
              end
         | 
| 9 9 |  | 
| 10 10 | 
             
              def update
         | 
| 11 | 
            -
                render | 
| 11 | 
            +
                render(:show, status: :unprocessable_entity) and return if params[:code].nil?
         | 
| 12 12 |  | 
| 13 13 | 
             
                if resource.authenticate_otp(params[:code])
         | 
| 14 14 | 
             
                  after_two_factor_success_for(resource)
         | 
| @@ -45,9 +45,11 @@ class Devise::TwoFactorAuthenticationController < DeviseController | |
| 45 45 | 
             
                expires_seconds = resource.class.remember_otp_session_for_seconds
         | 
| 46 46 |  | 
| 47 47 | 
             
                if expires_seconds && expires_seconds > 0
         | 
| 48 | 
            +
                  expires_at = expires_seconds.seconds.from_now
         | 
| 48 49 | 
             
                  cookies.signed[DeviseMultiFactor::REMEMBER_TFA_COOKIE_NAME] = {
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                       | 
| 50 | 
            +
                    value: DeviseMultiFactor::RememberTFACookie.new
         | 
| 51 | 
            +
                      .generate_cookie_data(resource, expires_at: expires_at),
         | 
| 52 | 
            +
                    expires: expires_at,
         | 
| 51 53 | 
             
                  }
         | 
| 52 54 | 
             
                end
         | 
| 53 55 | 
             
              end
         | 
    
        data/config/locales/en.yml
    CHANGED
    
    | @@ -6,3 +6,6 @@ en: | |
| 6 6 | 
             
                  max_login_attempts_reached: "Access completely denied as you have reached your attempts limit"
         | 
| 7 7 | 
             
                  contact_administrator: "Please contact your system administrator."
         | 
| 8 8 | 
             
                  code_has_been_sent: "Your authentication code has been sent."
         | 
| 9 | 
            +
                totp_setup:
         | 
| 10 | 
            +
                  invalid_code: "The authenticator code provided was invalid!"
         | 
| 11 | 
            +
                  invalid_signature: "There has been a problem in the configuration process, please try again."
         | 
    
        data/devise-multi-factor.gemspec
    CHANGED
    
    | @@ -7,7 +7,7 @@ Gem::Specification.new do |s| | |
| 7 7 | 
             
              s.version     = DeviseMultiFactor::VERSION.dup
         | 
| 8 8 | 
             
              s.authors     = ["Dmitrii Golub", "Alex Santos"]
         | 
| 9 9 | 
             
              s.email       = ["hello@alexcsantos.com"]
         | 
| 10 | 
            -
              s.homepage    = "https://github.com/Colex/ | 
| 10 | 
            +
              s.homepage    = "https://github.com/Colex/devise-multi-factor"
         | 
| 11 11 | 
             
              s.summary     = %q{Two factor authentication plugin for devise}
         | 
| 12 12 | 
             
              s.description = <<-EOF
         | 
| 13 13 | 
             
                ### Features ###
         | 
| @@ -1,8 +1,7 @@ | |
| 1 1 | 
             
            Warden::Manager.after_authentication do |resource, auth, options|
         | 
| 2 2 | 
             
              if auth.env["action_dispatch.cookies"]
         | 
| 3 | 
            -
                 | 
| 4 | 
            -
                 | 
| 5 | 
            -
                bypass_by_cookie = actual_cookie_value == expected_cookie_value
         | 
| 3 | 
            +
                cookie_value = auth.env["action_dispatch.cookies"].signed[DeviseMultiFactor::REMEMBER_TFA_COOKIE_NAME]
         | 
| 4 | 
            +
                bypass_by_cookie = DeviseMultiFactor::RememberTFACookie.new.valid_cookie_data?(resource, cookie_value)
         | 
| 6 5 | 
             
              end
         | 
| 7 6 |  | 
| 8 7 | 
             
              if resource.respond_to?(:need_two_factor_authentication?) && !bypass_by_cookie
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            module DeviseMultiFactor
         | 
| 2 | 
            +
              class RememberTFACookie
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def generate_cookie_data(resource, expires_at:)
         | 
| 5 | 
            +
                  { 'data' => generate_resource_data(resource) }
         | 
| 6 | 
            +
                    .merge('expires_at' => expires_at)
         | 
| 7 | 
            +
                    .to_json
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def valid_cookie_data?(resource, cookie_data)
         | 
| 11 | 
            +
                  return false if cookie_data.nil?
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  parsed_data = JSON.parse(cookie_data)
         | 
| 14 | 
            +
                  expires_at = parse_time(parsed_data['expires_at'])
         | 
| 15 | 
            +
                  return false if expires_at.nil? || expires_at < Time.current
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  expected_data = generate_resource_data(resource)
         | 
| 18 | 
            +
                  parsed_data['data'] == expected_data
         | 
| 19 | 
            +
                rescue JSON::ParserError
         | 
| 20 | 
            +
                  false
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                private
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def generate_resource_data(resource)
         | 
| 26 | 
            +
                  {
         | 
| 27 | 
            +
                    'resource_name' => resource.class.to_s,
         | 
| 28 | 
            +
                    'resource_id' => resource.public_send(Devise.second_factor_resource_id),
         | 
| 29 | 
            +
                    'remember_tfa_token' => resource.try(:remember_tfa_token) || '',
         | 
| 30 | 
            +
                  }
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def parse_time(time_str)
         | 
| 34 | 
            +
                  Time.parse(time_str)
         | 
| 35 | 
            +
                rescue StandardError
         | 
| 36 | 
            +
                  nil
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
    
        data/lib/devise_multi_factor.rb
    CHANGED
    
    | @@ -52,5 +52,6 @@ Devise.add_module :totp_enrollable, model: 'devise_multi_factor/models/totp_enro | |
| 52 52 |  | 
| 53 53 | 
             
            require 'devise_multi_factor/orm/active_record' if defined?(ActiveRecord::Base)
         | 
| 54 54 | 
             
            require 'devise_multi_factor/routes'
         | 
| 55 | 
            +
            require 'devise_multi_factor/remember_tfa_cookie'
         | 
| 55 56 | 
             
            require 'devise_multi_factor/models/two_factor_authenticatable'
         | 
| 56 57 | 
             
            require 'devise_multi_factor/rails'
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 3 | 
             
            describe Devise::TwoFactorAuthenticationController, type: :controller do
         | 
| 4 | 
            -
              describe 'is_fully_authenticated? | 
| 4 | 
            +
              describe '#is_fully_authenticated?' do
         | 
| 5 5 | 
             
                def post_code(code)
         | 
| 6 6 | 
             
                  if Rails::VERSION::MAJOR >= 5
         | 
| 7 7 | 
             
                    post :update, params: { code: code }
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 | 
             
            include AuthenticatedModelHelper
         | 
| 3 3 |  | 
| 4 | 
            -
            feature  | 
| 4 | 
            +
            feature 'User of two factor authentication' do
         | 
| 5 5 | 
             
              context 'sending two factor authentication code via SMS' do
         | 
| 6 6 | 
             
                shared_examples 'sends and authenticates code' do |user, type|
         | 
| 7 7 | 
             
                  before do
         | 
| @@ -48,62 +48,62 @@ feature "User of two factor authentication" do | |
| 48 48 | 
             
                it_behaves_like 'sends and authenticates code', create_user, 'encrypted'
         | 
| 49 49 | 
             
              end
         | 
| 50 50 |  | 
| 51 | 
            -
              scenario  | 
| 51 | 
            +
              scenario 'must be logged in' do
         | 
| 52 52 | 
             
                visit user_two_factor_authentication_path
         | 
| 53 53 |  | 
| 54 | 
            -
                expect(page).to have_content( | 
| 55 | 
            -
                expect(page).to have_content( | 
| 54 | 
            +
                expect(page).to have_content('Welcome Home')
         | 
| 55 | 
            +
                expect(page).to have_content('You are signed out')
         | 
| 56 56 | 
             
              end
         | 
| 57 57 |  | 
| 58 | 
            -
              context  | 
| 58 | 
            +
              context 'when logged in' do
         | 
| 59 59 | 
             
                let(:user) { create_user }
         | 
| 60 60 |  | 
| 61 61 | 
             
                background do
         | 
| 62 62 | 
             
                  login_as user
         | 
| 63 63 | 
             
                end
         | 
| 64 64 |  | 
| 65 | 
            -
                scenario  | 
| 66 | 
            -
                  visit dashboard_path +  | 
| 65 | 
            +
                scenario 'is redirected to TFA when path requires authentication' do
         | 
| 66 | 
            +
                  visit dashboard_path + '?A=param%20a&B=param%20b'
         | 
| 67 67 |  | 
| 68 | 
            -
                  expect(page).to_not have_content( | 
| 68 | 
            +
                  expect(page).to_not have_content('Your Personal Dashboard')
         | 
| 69 69 |  | 
| 70 | 
            -
                  fill_in  | 
| 71 | 
            -
                  click_button  | 
| 70 | 
            +
                  fill_in 'code', with: SMSProvider.last_message.body
         | 
| 71 | 
            +
                  click_button 'Submit'
         | 
| 72 72 |  | 
| 73 | 
            -
                  expect(page).to have_content( | 
| 74 | 
            -
                  expect(page).to have_content( | 
| 75 | 
            -
                  expect(page).to have_content( | 
| 76 | 
            -
                  expect(page).to have_content( | 
| 73 | 
            +
                  expect(page).to have_content('Your Personal Dashboard')
         | 
| 74 | 
            +
                  expect(page).to have_content('You are signed in as Marissa')
         | 
| 75 | 
            +
                  expect(page).to have_content('Param A is param a')
         | 
| 76 | 
            +
                  expect(page).to have_content('Param B is param b')
         | 
| 77 77 | 
             
                end
         | 
| 78 78 |  | 
| 79 | 
            -
                scenario  | 
| 79 | 
            +
                scenario 'is locked out after max failed attempts' do
         | 
| 80 80 | 
             
                  visit user_two_factor_authentication_path
         | 
| 81 81 |  | 
| 82 82 | 
             
                  max_attempts = User.max_login_attempts
         | 
| 83 83 |  | 
| 84 84 | 
             
                  max_attempts.times do
         | 
| 85 | 
            -
                    fill_in  | 
| 86 | 
            -
                    click_button  | 
| 85 | 
            +
                    fill_in 'code', with: "incorrect#{rand(100)}"
         | 
| 86 | 
            +
                    click_button 'Submit'
         | 
| 87 87 |  | 
| 88 | 
            -
                    within( | 
| 89 | 
            -
                      expect(page).to have_content( | 
| 88 | 
            +
                    within('.flash.alert') do
         | 
| 89 | 
            +
                      expect(page).to have_content('Attempt failed')
         | 
| 90 90 | 
             
                    end
         | 
| 91 91 | 
             
                  end
         | 
| 92 92 |  | 
| 93 | 
            -
                  expect(page).to have_content( | 
| 94 | 
            -
                  expect(page).to have_content( | 
| 93 | 
            +
                  expect(page).to have_content('Access completely denied')
         | 
| 94 | 
            +
                  expect(page).to have_content('You are signed out')
         | 
| 95 95 | 
             
                end
         | 
| 96 96 |  | 
| 97 | 
            -
                scenario  | 
| 97 | 
            +
                scenario 'cannot retry authentication after max attempts' do
         | 
| 98 98 | 
             
                  user.update_attribute(:second_factor_attempts_count, User.max_login_attempts)
         | 
| 99 99 |  | 
| 100 100 | 
             
                  visit user_two_factor_authentication_path
         | 
| 101 101 |  | 
| 102 | 
            -
                  expect(page).to have_content( | 
| 103 | 
            -
                  expect(page).to have_content( | 
| 102 | 
            +
                  expect(page).to have_content('Access completely denied')
         | 
| 103 | 
            +
                  expect(page).to have_content('You are signed out')
         | 
| 104 104 | 
             
                end
         | 
| 105 105 |  | 
| 106 | 
            -
                describe  | 
| 106 | 
            +
                describe 'rememberable TFA' do
         | 
| 107 107 | 
             
                  before do
         | 
| 108 108 | 
             
                    @original_remember_otp_session_for_seconds = User.remember_otp_session_for_seconds
         | 
| 109 109 | 
             
                    User.remember_otp_session_for_seconds = 30.days
         | 
| @@ -120,11 +120,11 @@ feature "User of two factor authentication" do | |
| 120 120 |  | 
| 121 121 | 
             
                    login_as user
         | 
| 122 122 | 
             
                    visit dashboard_path
         | 
| 123 | 
            -
                    expect(page).to have_content( | 
| 124 | 
            -
                    expect(page).to have_content( | 
| 123 | 
            +
                    expect(page).to have_content('Your Personal Dashboard')
         | 
| 124 | 
            +
                    expect(page).to have_content('You are signed in as Marissa')
         | 
| 125 125 | 
             
                  end
         | 
| 126 126 |  | 
| 127 | 
            -
                  scenario  | 
| 127 | 
            +
                  scenario 'requires TFA code again after 30 days' do
         | 
| 128 128 | 
             
                    sms_sign_in
         | 
| 129 129 |  | 
| 130 130 | 
             
                    logout
         | 
| @@ -132,8 +132,8 @@ feature "User of two factor authentication" do | |
| 132 132 | 
             
                    Timecop.travel(30.days.from_now)
         | 
| 133 133 | 
             
                    login_as user
         | 
| 134 134 | 
             
                    visit dashboard_path
         | 
| 135 | 
            -
                    expect(page).to have_content( | 
| 136 | 
            -
                    expect(page).to have_content( | 
| 135 | 
            +
                    expect(page).to have_content('You are signed in as Marissa')
         | 
| 136 | 
            +
                    expect(page).to have_content('Enter the code that was sent to you')
         | 
| 137 137 | 
             
                  end
         | 
| 138 138 |  | 
| 139 139 | 
             
                  scenario 'TFA should be different for different users' do
         | 
| @@ -172,7 +172,7 @@ feature "User of two factor authentication" do | |
| 172 172 | 
             
                    set_tfa_cookie(tfa_cookie1)
         | 
| 173 173 | 
             
                    login_as(user2)
         | 
| 174 174 | 
             
                    visit dashboard_path
         | 
| 175 | 
            -
                    expect(page).to have_content( | 
| 175 | 
            +
                    expect(page).to have_content('Enter the code that was sent to you')
         | 
| 176 176 | 
             
                  end
         | 
| 177 177 |  | 
| 178 178 | 
             
                  scenario 'Delete cookie when user logs out if enabled' do
         | 
| @@ -184,7 +184,7 @@ feature "User of two factor authentication" do | |
| 184 184 | 
             
                    login_as user
         | 
| 185 185 |  | 
| 186 186 | 
             
                    visit dashboard_path
         | 
| 187 | 
            -
                    expect(page).to have_content( | 
| 187 | 
            +
                    expect(page).to have_content('Enter the code that was sent to you')
         | 
| 188 188 | 
             
                  end
         | 
| 189 189 | 
             
                end
         | 
| 190 190 |  | 
| @@ -0,0 +1,135 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class MockUser
         | 
| 4 | 
            +
              def id
         | 
| 5 | 
            +
                15
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
            end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            class MockUserWithRememberTFAToken
         | 
| 10 | 
            +
              def id
         | 
| 11 | 
            +
                45
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def remember_tfa_token
         | 
| 15 | 
            +
                'generated_token'
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            describe DeviseMultiFactor::RememberTFACookie do
         | 
| 20 | 
            +
              subject(:remember_tfa_cookie) { described_class.new }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              describe '#generate_cookie_data' do
         | 
| 23 | 
            +
                describe 'when resource does not define remember_tfa_token method' do
         | 
| 24 | 
            +
                  let(:resource) { MockUser.new }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  it 'returns cookie value with expiration date' do
         | 
| 27 | 
            +
                    result = remember_tfa_cookie.generate_cookie_data(
         | 
| 28 | 
            +
                      resource,
         | 
| 29 | 
            +
                      expires_at: Time.utc(2022, 1, 17, 19, 28, 0),
         | 
| 30 | 
            +
                    )
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    expect(JSON.parse(result)).to eql(
         | 
| 33 | 
            +
                      'data' => {
         | 
| 34 | 
            +
                        'resource_name' => 'MockUser',
         | 
| 35 | 
            +
                        'resource_id' => 15,
         | 
| 36 | 
            +
                        'remember_tfa_token' => '',
         | 
| 37 | 
            +
                      },
         | 
| 38 | 
            +
                      'expires_at' => '2022-01-17T19:28:00.000Z',
         | 
| 39 | 
            +
                    )
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                describe 'when resource defines remember_tfa_token method' do
         | 
| 44 | 
            +
                  let(:resource) { MockUserWithRememberTFAToken.new }
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  it 'returns cookie value with expiration date and tfa remember token' do
         | 
| 47 | 
            +
                    result = remember_tfa_cookie.generate_cookie_data(
         | 
| 48 | 
            +
                      resource,
         | 
| 49 | 
            +
                      expires_at: Time.utc(2022, 1, 17, 19, 28, 0),
         | 
| 50 | 
            +
                    )
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    expect(JSON.parse(result)).to eql(
         | 
| 53 | 
            +
                      'data' => {
         | 
| 54 | 
            +
                        'resource_name' => 'MockUserWithRememberTFAToken',
         | 
| 55 | 
            +
                        'resource_id' => 45,
         | 
| 56 | 
            +
                        'remember_tfa_token' => 'generated_token',
         | 
| 57 | 
            +
                      },
         | 
| 58 | 
            +
                      'expires_at' => '2022-01-17T19:28:00.000Z',
         | 
| 59 | 
            +
                    )
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              describe '#valid_cookie_data?' do
         | 
| 65 | 
            +
                let(:cookie_data) do
         | 
| 66 | 
            +
                  {
         | 
| 67 | 
            +
                    'data' => {
         | 
| 68 | 
            +
                      'resource_name' => resource_name,
         | 
| 69 | 
            +
                      'resource_id' => resource_id,
         | 
| 70 | 
            +
                      'remember_tfa_token' => remember_tfa_token,
         | 
| 71 | 
            +
                    },
         | 
| 72 | 
            +
                    'expires_at' => '2022-01-17T19:28:00.000Z',
         | 
| 73 | 
            +
                  }.to_json
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
                let(:resource_name) { 'MockUserWithRememberTFAToken' }
         | 
| 76 | 
            +
                let(:resource_id) { 45 }
         | 
| 77 | 
            +
                let(:remember_tfa_token) { 'generated_token' }
         | 
| 78 | 
            +
                let(:resource) { MockUserWithRememberTFAToken.new }
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                describe 'when cookie data has expired' do
         | 
| 81 | 
            +
                  it 'returns false' do
         | 
| 82 | 
            +
                    Timecop.freeze(Time.utc(2022, 1, 17, 19, 29, 0)) do
         | 
| 83 | 
            +
                      result = remember_tfa_cookie.valid_cookie_data?(resource, cookie_data)
         | 
| 84 | 
            +
                      expect(result).to be(false)
         | 
| 85 | 
            +
                    end
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                describe 'when cookie data has not expired' do
         | 
| 90 | 
            +
                  let(:date_before_expiration) { Time.utc(2022, 1, 17, 19, 27, 0) }
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  describe 'when resource class does not match' do
         | 
| 93 | 
            +
                    let(:resource_name) { 'MockUser' }
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    it 'returns false' do
         | 
| 96 | 
            +
                      Timecop.freeze(date_before_expiration) do
         | 
| 97 | 
            +
                        result = remember_tfa_cookie.valid_cookie_data?(resource, cookie_data)
         | 
| 98 | 
            +
                        expect(result).to be(false)
         | 
| 99 | 
            +
                      end
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  describe 'when resource id does not match' do
         | 
| 104 | 
            +
                    let(:resource_id) { 46 }
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    it 'returns false' do
         | 
| 107 | 
            +
                      Timecop.freeze(date_before_expiration) do
         | 
| 108 | 
            +
                        result = remember_tfa_cookie.valid_cookie_data?(resource, cookie_data)
         | 
| 109 | 
            +
                        expect(result).to be(false)
         | 
| 110 | 
            +
                      end
         | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  describe 'when remember tfa token does not match' do
         | 
| 115 | 
            +
                    let(:remember_tfa_token) { '' }
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                    it 'returns false' do
         | 
| 118 | 
            +
                      Timecop.freeze(date_before_expiration) do
         | 
| 119 | 
            +
                        result = remember_tfa_cookie.valid_cookie_data?(resource, cookie_data)
         | 
| 120 | 
            +
                        expect(result).to be(false)
         | 
| 121 | 
            +
                      end
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  describe 'when all cookie data matches' do
         | 
| 126 | 
            +
                    it 'returns true' do
         | 
| 127 | 
            +
                      Timecop.freeze(date_before_expiration) do
         | 
| 128 | 
            +
                        result = remember_tfa_cookie.valid_cookie_data?(resource, cookie_data)
         | 
| 129 | 
            +
                        expect(result).to be(true)
         | 
| 130 | 
            +
                      end
         | 
| 131 | 
            +
                    end
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
              end
         | 
| 135 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: devise-multi-factor
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 3. | 
| 4 | 
            +
              version: 3.2.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Dmitrii Golub
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date:  | 
| 12 | 
            +
            date: 2022-01-17 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: rails
         | 
| @@ -220,6 +220,7 @@ files: | |
| 220 220 | 
             
            - lib/devise_multi_factor/models/two_factor_authenticatable.rb
         | 
| 221 221 | 
             
            - lib/devise_multi_factor/orm/active_record.rb
         | 
| 222 222 | 
             
            - lib/devise_multi_factor/rails.rb
         | 
| 223 | 
            +
            - lib/devise_multi_factor/remember_tfa_cookie.rb
         | 
| 223 224 | 
             
            - lib/devise_multi_factor/routes.rb
         | 
| 224 225 | 
             
            - lib/devise_multi_factor/schema.rb
         | 
| 225 226 | 
             
            - lib/devise_multi_factor/version.rb
         | 
| @@ -230,6 +231,7 @@ files: | |
| 230 231 | 
             
            - spec/features/two_factor_authenticatable_spec.rb
         | 
| 231 232 | 
             
            - spec/generators/active_record/devise_multi_factor_generator_spec.rb
         | 
| 232 233 | 
             
            - spec/lib/devise_multi_factor/models/two_factor_authenticatable_spec.rb
         | 
| 234 | 
            +
            - spec/lib/devise_multi_factor/tfa_remember_cookie_spec.rb
         | 
| 233 235 | 
             
            - spec/rails_app/.gitignore
         | 
| 234 236 | 
             
            - spec/rails_app/README.md
         | 
| 235 237 | 
             
            - spec/rails_app/Rakefile
         | 
| @@ -290,7 +292,7 @@ files: | |
| 290 292 | 
             
            - spec/support/features_spec_helper.rb
         | 
| 291 293 | 
             
            - spec/support/sms_provider.rb
         | 
| 292 294 | 
             
            - spec/support/totp_helper.rb
         | 
| 293 | 
            -
            homepage: https://github.com/Colex/ | 
| 295 | 
            +
            homepage: https://github.com/Colex/devise-multi-factor
         | 
| 294 296 | 
             
            licenses: []
         | 
| 295 297 | 
             
            metadata: {}
         | 
| 296 298 | 
             
            post_install_message: 
         | 
| @@ -308,7 +310,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 308 310 | 
             
                - !ruby/object:Gem::Version
         | 
| 309 311 | 
             
                  version: '0'
         | 
| 310 312 | 
             
            requirements: []
         | 
| 311 | 
            -
            rubygems_version: 3.0.3
         | 
| 313 | 
            +
            rubygems_version: 3.0.3.1
         | 
| 312 314 | 
             
            signing_key: 
         | 
| 313 315 | 
             
            specification_version: 4
         | 
| 314 316 | 
             
            summary: Two factor authentication plugin for devise
         |