active_model_otp 2.3.1 → 2.3.2
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/.github/workflows/active_model_otp.yml +34 -8
- data/Appraisals +7 -0
- data/README.md +24 -2
- data/gemfiles/rails_7.0.gemfile +10 -0
- data/lib/active_model/one_time_password.rb +27 -45
- data/lib/active_model/otp/version.rb +1 -1
- data/test/models/after_user.rb +5 -0
- data/test/one_time_password_test.rb +30 -12
- data/test/schema.rb +9 -0
- data/test/test_helper.rb +1 -0
- metadata +6 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 98c3fad16e797e0483e6aeebcdae72e868dd699b82f07400ad0ee9b0261dbd2a
         | 
| 4 | 
            +
              data.tar.gz: 3454ea7c190b0f7f69b50cf617c132a46e1a22f7f5554e4159acec61dd40979f
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 93a2cec634c0b00a4480598f0dc5fe8bfcf27a9dcc35fb6603eb7bbdfb90fc4f85f94443fbe4b9df9acb6113facd7c14bc12368f0ad15f4df2c0cd5b5498f6bf
         | 
| 7 | 
            +
              data.tar.gz: 232296291239477d07f49ba75bc9db6288a8a3e039f6b1bc93bb3f9ce57d07d8e3624450ebc425e4b7532073f5937506f97f67ec014cc9c6e2312c30aeeb755e
         | 
| @@ -9,22 +9,48 @@ on: | |
| 9 9 | 
             
            jobs:
         | 
| 10 10 | 
             
              ci:
         | 
| 11 11 | 
             
                runs-on: ubuntu-latest
         | 
| 12 | 
            -
             | 
| 12 | 
            +
             | 
| 13 13 | 
             
                strategy:
         | 
| 14 14 | 
             
                  matrix:
         | 
| 15 | 
            -
                    gemfile: [rails_4.2, rails_5.0, rails_5.1, rails_5.2, rails_6.0, rails_6.1]
         | 
| 16 | 
            -
                    ruby-version: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0]
         | 
| 15 | 
            +
                    gemfile: [rails_4.2, rails_5.0, rails_5.1, rails_5.2, rails_6.0, rails_6.1, rails_7.0]
         | 
| 16 | 
            +
                    ruby-version: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1, 3.2]
         | 
| 17 17 | 
             
                    exclude:
         | 
| 18 | 
            -
                      - { gemfile:  | 
| 19 | 
            -
                      - { gemfile:  | 
| 20 | 
            -
                      - { gemfile: rails_6.0, ruby-version: 2.4 }
         | 
| 21 | 
            -
                      - { gemfile: rails_6.1, ruby-version: 2.4 }
         | 
| 18 | 
            +
                      - { gemfile: rails_4.2, ruby-version: 2.5 }
         | 
| 19 | 
            +
                      - { gemfile: rails_4.2, ruby-version: 2.6 }
         | 
| 22 20 | 
             
                      - { gemfile: rails_4.2, ruby-version: 2.7 }
         | 
| 23 21 | 
             
                      - { gemfile: rails_4.2, ruby-version: 3.0 }
         | 
| 22 | 
            +
                      - { gemfile: rails_4.2, ruby-version: 3.1 }
         | 
| 23 | 
            +
                      - { gemfile: rails_4.2, ruby-version: 3.2 }
         | 
| 24 | 
            +
                      - { gemfile: rails_5.0, ruby-version: 2.5 }
         | 
| 25 | 
            +
                      - { gemfile: rails_5.0, ruby-version: 2.6 }
         | 
| 26 | 
            +
                      - { gemfile: rails_5.0, ruby-version: 2.7 }
         | 
| 24 27 | 
             
                      - { gemfile: rails_5.0, ruby-version: 3.0 }
         | 
| 28 | 
            +
                      - { gemfile: rails_5.0, ruby-version: 3.1 }
         | 
| 29 | 
            +
                      - { gemfile: rails_5.0, ruby-version: 3.2 }
         | 
| 30 | 
            +
                      - { gemfile: rails_5.1, ruby-version: 2.6 }
         | 
| 31 | 
            +
                      - { gemfile: rails_5.1, ruby-version: 2.7 }
         | 
| 25 32 | 
             
                      - { gemfile: rails_5.1, ruby-version: 3.0 }
         | 
| 33 | 
            +
                      - { gemfile: rails_5.1, ruby-version: 3.1 }
         | 
| 34 | 
            +
                      - { gemfile: rails_5.1, ruby-version: 3.2 }
         | 
| 35 | 
            +
                      - { gemfile: rails_5.2, ruby-version: 2.7 }
         | 
| 26 36 | 
             
                      - { gemfile: rails_5.2, ruby-version: 3.0 }
         | 
| 27 | 
            -
             | 
| 37 | 
            +
                      - { gemfile: rails_5.2, ruby-version: 3.1 }
         | 
| 38 | 
            +
                      - { gemfile: rails_5.2, ruby-version: 3.2 }
         | 
| 39 | 
            +
                      - { gemfile: rails_6.0, ruby-version: 2.3 }
         | 
| 40 | 
            +
                      - { gemfile: rails_6.0, ruby-version: 2.4 }
         | 
| 41 | 
            +
                      - { gemfile: rails_6.0, ruby-version: 3.0 }
         | 
| 42 | 
            +
                      - { gemfile: rails_6.0, ruby-version: 3.1 }
         | 
| 43 | 
            +
                      - { gemfile: rails_6.0, ruby-version: 3.2 }
         | 
| 44 | 
            +
                      - { gemfile: rails_6.1, ruby-version: 2.3 }
         | 
| 45 | 
            +
                      - { gemfile: rails_6.1, ruby-version: 2.4 }
         | 
| 46 | 
            +
                      - { gemfile: rails_6.1, ruby-version: 3.0 }
         | 
| 47 | 
            +
                      - { gemfile: rails_6.1, ruby-version: 3.1 }
         | 
| 48 | 
            +
                      - { gemfile: rails_6.1, ruby-version: 3.2 }
         | 
| 49 | 
            +
                      - { gemfile: rails_7.0, ruby-version: 2.3 }
         | 
| 50 | 
            +
                      - { gemfile: rails_7.0, ruby-version: 2.4 }
         | 
| 51 | 
            +
                      - { gemfile: rails_7.0, ruby-version: 2.5 }
         | 
| 52 | 
            +
                      - { gemfile: rails_7.0, ruby-version: 2.6 }
         | 
| 53 | 
            +
             | 
| 28 54 | 
             
                env:
         | 
| 29 55 | 
             
                  BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
         | 
| 30 56 |  | 
    
        data/Appraisals
    CHANGED
    
    | @@ -34,3 +34,10 @@ appraise "rails-6.1" do | |
| 34 34 | 
             
              gem "activemodel-serializers-xml"
         | 
| 35 35 | 
             
              gem "sqlite3", "~> 1.4"
         | 
| 36 36 | 
             
            end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            appraise "rails-7.0" do
         | 
| 39 | 
            +
              gem "activerecord", "~> 7.0"
         | 
| 40 | 
            +
              gem "activemodel", "~> 7.0"
         | 
| 41 | 
            +
              gem "activemodel-serializers-xml"
         | 
| 42 | 
            +
              gem "sqlite3", "~> 1.6"
         | 
| 43 | 
            +
            end
         | 
    
        data/README.md
    CHANGED
    
    | @@ -98,9 +98,31 @@ sleep 30 # lets wait again | |
| 98 98 | 
             
            user.authenticate_otp('186522', drift: 60) # => true
         | 
| 99 99 | 
             
            ```
         | 
| 100 100 |  | 
| 101 | 
            +
            ### Preventing reuse of Time based OTP's
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            By keeping track of the last time a user's OTP was verified, we can prevent token reuse during the interval window (default 30 seconds). It is useful with SMS, that is commonly used in combination with `drift` to extend the life of the code.
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            ```ruby
         | 
| 106 | 
            +
            rails g migration AddLastOtpAtToUsers last_otp_at:integer
         | 
| 107 | 
            +
            =>
         | 
| 108 | 
            +
                  invoke  active_record
         | 
| 109 | 
            +
                  create    db/migrate/20220407010931_add_last_otp_at_to_users.rb
         | 
| 110 | 
            +
            ```
         | 
| 111 | 
            +
             | 
| 112 | 
            +
            ```ruby
         | 
| 113 | 
            +
            class User < ApplicationRecord
         | 
| 114 | 
            +
              has_one_time_password after_column_name: :last_otp_at
         | 
| 115 | 
            +
            end
         | 
| 116 | 
            +
            ```
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            ```ruby
         | 
| 119 | 
            +
            user.authenticate_otp('186522') # => true
         | 
| 120 | 
            +
            user.authenticate_otp('186522') # => false
         | 
| 121 | 
            +
            ```
         | 
| 122 | 
            +
             | 
| 101 123 | 
             
            ## Counter based OTP
         | 
| 102 124 |  | 
| 103 | 
            -
            An  | 
| 125 | 
            +
            An additional counter field is required in our ``User`` Model
         | 
| 104 126 |  | 
| 105 127 | 
             
            ```ruby
         | 
| 106 128 | 
             
            rails g migration AddCounterForOtpToUsers otp_counter:integer
         | 
| @@ -213,7 +235,7 @@ user.provisioning_uri(nil, issuer: 'MYAPP') #=> 'otpauth://totp/hello@heapsource | |
| 213 235 |  | 
| 214 236 | 
             
            This can then be rendered as a QR Code which can be scanned and added to the users list of OTP credentials.
         | 
| 215 237 |  | 
| 216 | 
            -
            ### Setting up a customer interval | 
| 238 | 
            +
            ### Setting up a customer interval
         | 
| 217 239 |  | 
| 218 240 | 
             
            If you define a custom interval for TOTP codes, just as `has_one_time_password interval: 10` (for example), remember to include the interval also in `provisioning_uri` method. If not defined, the default value is 30 seconds (according to ROTP gem: https://github.com/mdp/rotp/blob/master/lib/rotp/totp.rb#L9)
         | 
| 219 241 |  | 
| @@ -13,32 +13,20 @@ module ActiveModel | |
| 13 13 | 
             
                module ClassMethods
         | 
| 14 14 | 
             
                  def has_one_time_password(options = {})
         | 
| 15 15 | 
             
                    cattr_accessor :otp_column_name, :otp_counter_column_name,
         | 
| 16 | 
            -
                                   :otp_backup_codes_column_name
         | 
| 16 | 
            +
                                   :otp_backup_codes_column_name, :otp_after_column_name
         | 
| 17 17 | 
             
                    class_attribute :otp_digits, :otp_counter_based,
         | 
| 18 18 | 
             
                                    :otp_backup_codes_count, :otp_one_time_backup_codes,
         | 
| 19 19 | 
             
                                    :otp_interval
         | 
| 20 20 |  | 
| 21 | 
            -
                    self.otp_column_name = (
         | 
| 22 | 
            -
                      options[:column_name] || OTP_DEFAULT_COLUMN_NAME
         | 
| 23 | 
            -
                    ).to_s
         | 
| 21 | 
            +
                    self.otp_column_name = (options[:column_name] || OTP_DEFAULT_COLUMN_NAME).to_s
         | 
| 24 22 | 
             
                    self.otp_digits = options[:length] || OTP_DEFAULT_DIGITS
         | 
| 25 | 
            -
                    self.otp_counter_based =  | 
| 26 | 
            -
             | 
| 27 | 
            -
                    )
         | 
| 28 | 
            -
                    self.otp_counter_column_name = (
         | 
| 29 | 
            -
                      options[:counter_column_name] || OTP_DEFAULT_COUNTER_COLUMN_NAME
         | 
| 30 | 
            -
                    ).to_s
         | 
| 23 | 
            +
                    self.otp_counter_based = options[:counter_based] || OTP_COUNTER_ENABLED_BY_DEFAULT
         | 
| 24 | 
            +
                    self.otp_counter_column_name = (options[:counter_column_name] || OTP_DEFAULT_COUNTER_COLUMN_NAME).to_s
         | 
| 31 25 | 
             
                    self.otp_interval = options[:interval]
         | 
| 32 | 
            -
                    self. | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
                     | 
| 36 | 
            -
                    self.otp_backup_codes_count = (
         | 
| 37 | 
            -
                      options[:backup_codes_count] || OTP_DEFAULT_BACKUP_CODES_COUNT
         | 
| 38 | 
            -
                    )
         | 
| 39 | 
            -
                    self.otp_one_time_backup_codes = (
         | 
| 40 | 
            -
                      options[:one_time_backup_codes] || OTP_BACKUP_CODES_ENABLED_BY_DEFAULT
         | 
| 41 | 
            -
                    )
         | 
| 26 | 
            +
                    self.otp_after_column_name = options[:after_column_name]
         | 
| 27 | 
            +
                    self.otp_backup_codes_column_name = (options[:backup_codes_column_name] || OTP_DEFAULT_BACKUP_CODES_COLUMN_NAME).to_s
         | 
| 28 | 
            +
                    self.otp_backup_codes_count = options[:backup_codes_count] || OTP_DEFAULT_BACKUP_CODES_COUNT
         | 
| 29 | 
            +
                    self.otp_one_time_backup_codes = options[:one_time_backup_codes] || OTP_BACKUP_CODES_ENABLED_BY_DEFAULT
         | 
| 42 30 |  | 
| 43 31 | 
             
                    include InstanceMethodsOnActivation
         | 
| 44 32 |  | 
| @@ -95,13 +83,9 @@ module ActiveModel | |
| 95 83 | 
             
                    account ||= ""
         | 
| 96 84 |  | 
| 97 85 | 
             
                    if otp_counter_based
         | 
| 98 | 
            -
                      ROTP::HOTP
         | 
| 99 | 
            -
                        .new(otp_column, options)
         | 
| 100 | 
            -
                        .provisioning_uri(account, self.otp_counter)
         | 
| 86 | 
            +
                      ROTP::HOTP.new(otp_column, options).provisioning_uri(account, self.otp_counter)
         | 
| 101 87 | 
             
                    else
         | 
| 102 | 
            -
                      ROTP::TOTP
         | 
| 103 | 
            -
                        .new(otp_column, options)
         | 
| 104 | 
            -
                        .provisioning_uri(account)
         | 
| 88 | 
            +
                      ROTP::TOTP.new(otp_column, options).provisioning_uri(account)
         | 
| 105 89 | 
             
                    end
         | 
| 106 90 | 
             
                  end
         | 
| 107 91 |  | 
| @@ -133,6 +117,7 @@ module ActiveModel | |
| 133 117 | 
             
                    options ||= {}
         | 
| 134 118 | 
             
                    options[:except] = Array(options[:except])
         | 
| 135 119 | 
             
                    options[:except] << self.class.otp_column_name
         | 
| 120 | 
            +
             | 
| 136 121 | 
             
                    super(options)
         | 
| 137 122 | 
             
                  end
         | 
| 138 123 |  | 
| @@ -154,45 +139,42 @@ module ActiveModel | |
| 154 139 | 
             
                  def authenticate_hotp(code, options = {})
         | 
| 155 140 | 
             
                    hotp = ROTP::HOTP.new(otp_column, digits: otp_digits)
         | 
| 156 141 | 
             
                    result = hotp.verify(code, otp_counter)
         | 
| 142 | 
            +
             | 
| 157 143 | 
             
                    if result && options[:auto_increment]
         | 
| 158 144 | 
             
                      self.otp_counter += 1
         | 
| 159 145 | 
             
                      save if respond_to?(:changed?) && !new_record?
         | 
| 160 146 | 
             
                    end
         | 
| 147 | 
            +
             | 
| 161 148 | 
             
                    result
         | 
| 162 149 | 
             
                  end
         | 
| 163 150 |  | 
| 164 151 | 
             
                  def authenticate_totp(code, options = {})
         | 
| 165 | 
            -
                    totp = ROTP::TOTP.new(
         | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
                    )
         | 
| 170 | 
            -
             | 
| 171 | 
            -
                      totp.verify(code, drift_behind: drift)
         | 
| 172 | 
            -
                    else
         | 
| 173 | 
            -
                      totp.verify(code)
         | 
| 152 | 
            +
                    totp = ROTP::TOTP.new(otp_column, digits: otp_digits, interval: otp_interval)
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                    otp_after = public_send(otp_after_column_name) if otp_after_column_name_enabled?
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                    totp.verify(code, drift_behind: options[:drift] || 0, after: otp_after).tap do |updated_last_otp_at|
         | 
| 157 | 
            +
                      updated_last_otp_at && otp_after_column_name_enabled? && update(otp_after_column_name => updated_last_otp_at)
         | 
| 174 158 | 
             
                    end
         | 
| 175 159 | 
             
                  end
         | 
| 176 160 |  | 
| 161 | 
            +
                  def otp_after_column_name_enabled?
         | 
| 162 | 
            +
                    otp_after_column_name && respond_to?(otp_after_column_name)
         | 
| 163 | 
            +
                  end
         | 
| 164 | 
            +
             | 
| 177 165 | 
             
                  def hotp_code(options = {})
         | 
| 178 166 | 
             
                    if options[:auto_increment]
         | 
| 179 167 | 
             
                      self.otp_counter += 1
         | 
| 180 168 | 
             
                      save if respond_to?(:changed?) && !new_record?
         | 
| 181 169 | 
             
                    end
         | 
| 170 | 
            +
             | 
| 182 171 | 
             
                    ROTP::HOTP.new(otp_column, digits: otp_digits).at(otp_counter)
         | 
| 183 172 | 
             
                  end
         | 
| 184 173 |  | 
| 185 174 | 
             
                  def totp_code(options = {})
         | 
| 186 | 
            -
                    time =  | 
| 187 | 
            -
             | 
| 188 | 
            -
             | 
| 189 | 
            -
                             options
         | 
| 190 | 
            -
                           end
         | 
| 191 | 
            -
                    ROTP::TOTP.new(
         | 
| 192 | 
            -
                      otp_column,
         | 
| 193 | 
            -
                      digits: otp_digits,
         | 
| 194 | 
            -
                      interval: otp_interval
         | 
| 195 | 
            -
                    ).at(time)
         | 
| 175 | 
            +
                    time = options.is_a?(Hash) ? options.fetch(:time, Time.now) : options
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                    ROTP::TOTP.new(otp_column, digits: otp_digits, interval: otp_interval).at(time)
         | 
| 196 178 | 
             
                  end
         | 
| 197 179 |  | 
| 198 180 | 
             
                  def authenticate_backup_code(code)
         | 
| @@ -3,6 +3,8 @@ | |
| 3 3 | 
             
            require 'test_helper'
         | 
| 4 4 |  | 
| 5 5 | 
             
            class OtpTest < MiniTest::Test
         | 
| 6 | 
            +
              include ActiveSupport::Testing::TimeHelpers
         | 
| 7 | 
            +
             | 
| 6 8 | 
             
              def setup
         | 
| 7 9 | 
             
                @user = User.new
         | 
| 8 10 | 
             
                @user.email = 'roberto@heapsource.com'
         | 
| @@ -23,6 +25,10 @@ class OtpTest < MiniTest::Test | |
| 23 25 | 
             
                @opt_in = OptInTwoFactor.new
         | 
| 24 26 | 
             
                @opt_in.email = 'roberto@heapsource.com'
         | 
| 25 27 | 
             
                @opt_in.run_callbacks :create
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                @after_user = AfterUser.new
         | 
| 30 | 
            +
                @after_user.email = 'roberto@heapsource.com'
         | 
| 31 | 
            +
                @after_user.run_callbacks :create
         | 
| 26 32 | 
             
              end
         | 
| 27 33 |  | 
| 28 34 | 
             
              def test_authenticate_with_otp
         | 
| @@ -88,6 +94,18 @@ class OtpTest < MiniTest::Test | |
| 88 94 | 
             
                assert_equal true, @visitor.authenticate_otp(code, drift: 60)
         | 
| 89 95 | 
             
              end
         | 
| 90 96 |  | 
| 97 | 
            +
              def test_authenticate_with_otp_when_after_is_allowed
         | 
| 98 | 
            +
                code = @user.otp_code
         | 
| 99 | 
            +
                assert_equal true, @user.authenticate_otp(code)
         | 
| 100 | 
            +
                assert_equal true, @user.authenticate_otp(code)
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                code = @after_user.otp_code
         | 
| 103 | 
            +
                assert_equal true, @after_user.authenticate_otp(code)
         | 
| 104 | 
            +
                assert_equal false, @after_user.authenticate_otp(code)
         | 
| 105 | 
            +
                assert_equal false, @after_user.authenticate_otp('1111111')
         | 
| 106 | 
            +
                assert_equal false, @after_user.authenticate_otp(code)
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
             | 
| 91 109 | 
             
              def test_authenticate_with_backup_code
         | 
| 92 110 | 
             
                backup_code = @user.public_send(@user.otp_backup_codes_column_name).first
         | 
| 93 111 | 
             
                assert_equal true, @user.authenticate_otp(backup_code)
         | 
| @@ -115,7 +133,7 @@ class OtpTest < MiniTest::Test | |
| 115 133 |  | 
| 116 134 | 
             
              def test_otp_code_without_specific_length
         | 
| 117 135 | 
             
                assert_match(/^\d{6}$/, @user.otp_code(2160).to_s)
         | 
| 118 | 
            -
                 | 
| 136 | 
            +
                assert @user.otp_code(2160).to_s.length <= 6
         | 
| 119 137 | 
             
              end
         | 
| 120 138 |  | 
| 121 139 | 
             
              def test_provisioning_uri_with_provided_account
         | 
| @@ -146,13 +164,8 @@ class OtpTest < MiniTest::Test | |
| 146 164 | 
             
                  &issuer=Example$
         | 
| 147 165 | 
             
                }x
         | 
| 148 166 |  | 
| 149 | 
            -
                assert_match(
         | 
| 150 | 
            -
             | 
| 151 | 
            -
                )
         | 
| 152 | 
            -
             | 
| 153 | 
            -
                assert_match(
         | 
| 154 | 
            -
                  account, @visitor.provisioning_uri('roberto', issuer: 'Example')
         | 
| 155 | 
            -
                )
         | 
| 167 | 
            +
                assert_match account, @user.provisioning_uri('roberto', issuer: 'Example')
         | 
| 168 | 
            +
                assert_match account, @visitor.provisioning_uri('roberto', issuer: 'Example')
         | 
| 156 169 |  | 
| 157 170 | 
             
                assert_match email, @user.provisioning_uri(nil, issuer: 'Example')
         | 
| 158 171 | 
             
                assert_match email, @visitor.provisioning_uri(nil, issuer: 'Example')
         | 
| @@ -186,8 +199,10 @@ class OtpTest < MiniTest::Test | |
| 186 199 | 
             
                @interval_user.run_callbacks :create
         | 
| 187 200 | 
             
                otp_code = @interval_user.otp_code
         | 
| 188 201 | 
             
                2.times { assert_match(otp_code, @interval_user.otp_code) }
         | 
| 189 | 
            -
             | 
| 190 | 
            -
                 | 
| 202 | 
            +
             | 
| 203 | 
            +
                travel 5.seconds do
         | 
| 204 | 
            +
                  refute_match(otp_code, @interval_user.otp_code)
         | 
| 205 | 
            +
                end
         | 
| 191 206 | 
             
              end
         | 
| 192 207 |  | 
| 193 208 | 
             
              def test_otp_default_interval
         | 
| @@ -195,8 +210,11 @@ class OtpTest < MiniTest::Test | |
| 195 210 | 
             
                @default_interval_user.email = 'roberto@heapsource.com'
         | 
| 196 211 | 
             
                @default_interval_user.run_callbacks :create
         | 
| 197 212 | 
             
                otp_code = @default_interval_user.otp_code
         | 
| 213 | 
            +
             | 
| 198 214 | 
             
                2.times { assert_match(otp_code, @default_interval_user.otp_code) }
         | 
| 199 | 
            -
             | 
| 200 | 
            -
                 | 
| 215 | 
            +
             | 
| 216 | 
            +
                travel 5.seconds do
         | 
| 217 | 
            +
                  assert_match(otp_code, @default_interval_user.otp_code)
         | 
| 218 | 
            +
                end
         | 
| 201 219 | 
             
              end
         | 
| 202 220 | 
             
            end
         | 
    
        data/test/schema.rb
    CHANGED
    
    | @@ -24,4 +24,13 @@ ActiveRecord::Schema.define do | |
| 24 24 | 
             
                t.string :otp_secret_key
         | 
| 25 25 | 
             
                t.timestamps
         | 
| 26 26 | 
             
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              create_table :after_users, force: true do |t|
         | 
| 29 | 
            +
                t.string :key
         | 
| 30 | 
            +
                t.string :email
         | 
| 31 | 
            +
                t.integer :otp_counter
         | 
| 32 | 
            +
                t.string :otp_secret_key
         | 
| 33 | 
            +
                t.integer :last_otp_at
         | 
| 34 | 
            +
                t.timestamps
         | 
| 35 | 
            +
              end
         | 
| 27 36 | 
             
            end
         | 
    
        data/test/test_helper.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: active_model_otp
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2.3. | 
| 4 | 
            +
              version: 2.3.2
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Guillermo Iguaran
         | 
| @@ -10,7 +10,7 @@ authors: | |
| 10 10 | 
             
            autorequire:
         | 
| 11 11 | 
             
            bindir: bin
         | 
| 12 12 | 
             
            cert_chain: []
         | 
| 13 | 
            -
            date:  | 
| 13 | 
            +
            date: 2023-04-26 00:00:00.000000000 Z
         | 
| 14 14 | 
             
            dependencies:
         | 
| 15 15 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 16 16 | 
             
              name: activemodel
         | 
| @@ -135,10 +135,12 @@ files: | |
| 135 135 | 
             
            - gemfiles/rails_5.2.gemfile
         | 
| 136 136 | 
             
            - gemfiles/rails_6.0.gemfile
         | 
| 137 137 | 
             
            - gemfiles/rails_6.1.gemfile
         | 
| 138 | 
            +
            - gemfiles/rails_7.0.gemfile
         | 
| 138 139 | 
             
            - lib/active_model/one_time_password.rb
         | 
| 139 140 | 
             
            - lib/active_model/otp/version.rb
         | 
| 140 141 | 
             
            - lib/active_model_otp.rb
         | 
| 141 142 | 
             
            - test/models/activerecord_user.rb
         | 
| 143 | 
            +
            - test/models/after_user.rb
         | 
| 142 144 | 
             
            - test/models/default_interval_user.rb
         | 
| 143 145 | 
             
            - test/models/interval_user.rb
         | 
| 144 146 | 
             
            - test/models/member.rb
         | 
| @@ -167,12 +169,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 167 169 | 
             
                - !ruby/object:Gem::Version
         | 
| 168 170 | 
             
                  version: '0'
         | 
| 169 171 | 
             
            requirements: []
         | 
| 170 | 
            -
            rubygems_version: 3. | 
| 172 | 
            +
            rubygems_version: 3.4.1
         | 
| 171 173 | 
             
            signing_key:
         | 
| 172 174 | 
             
            specification_version: 4
         | 
| 173 175 | 
             
            summary: Adds methods to set and authenticate against one time passwords.
         | 
| 174 176 | 
             
            test_files:
         | 
| 175 177 | 
             
            - test/models/activerecord_user.rb
         | 
| 178 | 
            +
            - test/models/after_user.rb
         | 
| 176 179 | 
             
            - test/models/default_interval_user.rb
         | 
| 177 180 | 
             
            - test/models/interval_user.rb
         | 
| 178 181 | 
             
            - test/models/member.rb
         |