two_factor_authentication 2.1.1 → 2.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/.travis.yml +10 -11
- data/Gemfile +1 -2
- data/README.md +156 -19
- data/lib/two_factor_authentication.rb +1 -2
- data/lib/two_factor_authentication/controllers/helpers.rb +1 -1
- data/lib/two_factor_authentication/models/two_factor_authenticatable.rb +8 -1
- data/lib/two_factor_authentication/orm/active_record.rb +2 -0
- data/lib/two_factor_authentication/version.rb +1 -1
- data/spec/controllers/two_factor_authentication_controller_spec.rb +10 -2
- data/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +10 -5
- data/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb +1 -1
- data/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb +1 -1
- data/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb +1 -1
- data/spec/rails_app/db/migrate/20151224171231_add_encrypted_columns_to_user.rb +1 -1
- data/spec/rails_app/db/migrate/20151224180310_populate_otp_column.rb +1 -1
- data/spec/rails_app/db/migrate/20151228230340_remove_otp_secret_key_from_user.rb +1 -1
- data/spec/rails_app/db/migrate/20160209032439_devise_create_admins.rb +1 -1
- data/spec/rails_app/db/schema.rb +27 -30
- data/spec/support/authenticated_model_helper.rb +1 -1
- data/spec/support/totp_helper.rb +1 -1
- data/two_factor_authentication.gemspec +2 -2
- metadata +8 -8
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 3d6d6636a281220ef8fd5aee19a4c4aaa4129797
         | 
| 4 | 
            +
              data.tar.gz: 4954c89fa183dcb47c3104bfb14035ceaf6d20f4
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: c559cf0c9c2c21519efdf25ce615ca5930dfc657564e4209b77298944ea3a2342414e7772b493db65c657c617641d0af4398a31bca0d3fbf81025e417fb688c2
         | 
| 7 | 
            +
              data.tar.gz: 7e0d5abb909bb53f04d3d0cb887a4e869944eda656318c7e782562ecdf7bd635c8079d144506b1633e71c83368604b989dd9b4a21f2d8c21cb41d1f2849b3b58
         | 
    
        data/.travis.yml
    CHANGED
    
    | @@ -1,29 +1,28 @@ | |
| 1 1 | 
             
            language: ruby
         | 
| 2 2 |  | 
| 3 3 | 
             
            env:
         | 
| 4 | 
            -
              - "RAILS_VERSION=4.0"
         | 
| 5 | 
            -
              - "RAILS_VERSION=4.1"
         | 
| 6 4 | 
             
              - "RAILS_VERSION=4.2"
         | 
| 5 | 
            +
              - "RAILS_VERSION=5.2"
         | 
| 7 6 | 
             
              - "RAILS_VERSION=master"
         | 
| 8 7 |  | 
| 9 8 | 
             
            rvm:
         | 
| 10 | 
            -
              - 2. | 
| 11 | 
            -
              - 2. | 
| 12 | 
            -
              - 2.3 | 
| 9 | 
            +
              - 2.3.8
         | 
| 10 | 
            +
              - 2.4.5
         | 
| 11 | 
            +
              - 2.5.3
         | 
| 13 12 |  | 
| 14 13 | 
             
            matrix:
         | 
| 14 | 
            +
              fast_finish: true
         | 
| 15 15 | 
             
              allow_failures:
         | 
| 16 16 | 
             
                - env: "RAILS_VERSION=master"
         | 
| 17 | 
            -
               | 
| 18 | 
            -
                - rvm: 2.1
         | 
| 19 | 
            -
                  env: RAILS_VERSION=master
         | 
| 17 | 
            +
              include:
         | 
| 20 18 | 
             
                - rvm: 2.2
         | 
| 21 | 
            -
                  env: RAILS_VERSION= | 
| 19 | 
            +
                  env: RAILS_VERSION=4.2
         | 
| 22 20 |  | 
| 23 21 | 
             
            before_install:
         | 
| 24 | 
            -
              - gem  | 
| 22 | 
            +
              - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
         | 
| 23 | 
            +
              - gem install bundler -v '< 2'
         | 
| 25 24 |  | 
| 26 25 | 
             
            before_script:
         | 
| 27 | 
            -
              - bundle exec rake app:db: | 
| 26 | 
            +
              - bundle exec rake app:db:setup
         | 
| 28 27 |  | 
| 29 28 | 
             
            script: bundle exec rake spec
         | 
    
        data/Gemfile
    CHANGED
    
    | @@ -9,7 +9,7 @@ rails = case rails_version | |
| 9 9 | 
             
                    when "master"
         | 
| 10 10 | 
             
                      {github: "rails/rails"}
         | 
| 11 11 | 
             
                    when "default"
         | 
| 12 | 
            -
                      "~>  | 
| 12 | 
            +
                      "~> 5.2"
         | 
| 13 13 | 
             
                    else
         | 
| 14 14 | 
             
                      "~> #{rails_version}"
         | 
| 15 15 | 
             
                    end
         | 
| @@ -27,5 +27,4 @@ end | |
| 27 27 | 
             
            group :test do
         | 
| 28 28 | 
             
              gem 'rack_session_access'
         | 
| 29 29 | 
             
              gem 'ammeter'
         | 
| 30 | 
            -
              gem 'pry'
         | 
| 31 30 | 
             
            end
         | 
    
        data/README.md
    CHANGED
    
    | @@ -54,7 +54,7 @@ migration in `db/migrate/`, which will add the following columns to your table: | |
| 54 54 | 
             
            #### Manual initial setup
         | 
| 55 55 |  | 
| 56 56 | 
             
            If you prefer to set up the model and migration manually, add the
         | 
| 57 | 
            -
            `: | 
| 57 | 
            +
            `:two_factor_authenticatable` option to your existing devise options, such as:
         | 
| 58 58 |  | 
| 59 59 | 
             
            ```ruby
         | 
| 60 60 | 
             
            devise :database_authenticatable, :registerable, :recoverable, :rememberable,
         | 
| @@ -225,24 +225,24 @@ steps: | |
| 225 225 | 
             
               Open the generated file, and replace its contents with the following:
         | 
| 226 226 | 
             
               ```ruby
         | 
| 227 227 | 
             
               class PopulateEncryptedOtpFields < ActiveRecord::Migration
         | 
| 228 | 
            -
             | 
| 229 | 
            -
             | 
| 230 | 
            -
             | 
| 231 | 
            -
             | 
| 232 | 
            -
             | 
| 233 | 
            -
             | 
| 234 | 
            -
             | 
| 235 | 
            -
             | 
| 236 | 
            -
             | 
| 237 | 
            -
             | 
| 238 | 
            -
             | 
| 239 | 
            -
             | 
| 240 | 
            -
             | 
| 241 | 
            -
             | 
| 242 | 
            -
             | 
| 243 | 
            -
             | 
| 244 | 
            -
             | 
| 245 | 
            -
             | 
| 228 | 
            +
                 def up
         | 
| 229 | 
            +
                   User.reset_column_information
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                   User.find_each do |user|
         | 
| 232 | 
            +
                     user.otp_secret_key = user.read_attribute('otp_secret_key')
         | 
| 233 | 
            +
                     user.save!
         | 
| 234 | 
            +
                   end
         | 
| 235 | 
            +
                 end
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                 def down
         | 
| 238 | 
            +
                   User.reset_column_information
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                   User.find_each do |user|
         | 
| 241 | 
            +
                     user.otp_secret_key = ROTP::Base32.random_base32
         | 
| 242 | 
            +
                     user.save!
         | 
| 243 | 
            +
                   end
         | 
| 244 | 
            +
                 end
         | 
| 245 | 
            +
               end
         | 
| 246 246 | 
             
               ```
         | 
| 247 247 |  | 
| 248 248 | 
             
            5. Generate a migration to remove the `:otp_secret_key` column:
         | 
| @@ -263,6 +263,143 @@ after them): | |
| 263 263 | 
             
               bundle exec rake db:rollback STEP=3
         | 
| 264 264 | 
             
               ```
         | 
| 265 265 |  | 
| 266 | 
            +
            #### Critical Security Note! Add before_action to your user registration controllers
         | 
| 267 | 
            +
             | 
| 268 | 
            +
            You should have a file registrations_controller.rb in your controllers folder
         | 
| 269 | 
            +
            to overwrite/customize user registrations. It should include the lines below, for 2FA protection of user model updates, meaning that users can only access the users/edit page after confirming 2FA fully, not simply by logging in. Otherwise the entire 2FA system can be bypassed!
         | 
| 270 | 
            +
             | 
| 271 | 
            +
               ```ruby
         | 
| 272 | 
            +
               class RegistrationsController < Devise::RegistrationsController
         | 
| 273 | 
            +
                 before_action :confirm_two_factor_authenticated, except: [:new, :create, :cancel]
         | 
| 274 | 
            +
               
         | 
| 275 | 
            +
                 protected
         | 
| 276 | 
            +
               
         | 
| 277 | 
            +
                 def confirm_two_factor_authenticated
         | 
| 278 | 
            +
                   return if is_fully_authenticated?
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                   flash[:error] = t('devise.errors.messages.user_not_authenticated')
         | 
| 281 | 
            +
                   redirect_to user_two_factor_authentication_url
         | 
| 282 | 
            +
                 end
         | 
| 283 | 
            +
               end
         | 
| 284 | 
            +
               ```
         | 
| 285 | 
            +
             | 
| 286 | 
            +
            #### Critical Security Note! Add 2FA validation to your custom user actions
         | 
| 287 | 
            +
             | 
| 288 | 
            +
            Make sure you are passing the 2FA secret codes securely and checking for them upon critical user actions, such as API key updates, user email or pgp pubkey updates, or any other changess to private/secure account-related details. Validate the secret during the initial 2FA key/secret verification by the user also, of course.
         | 
| 289 | 
            +
             | 
| 290 | 
            +
             For example, a simple account_controller.rb may look something like this:
         | 
| 291 | 
            +
             | 
| 292 | 
            +
               ```
         | 
| 293 | 
            +
               require 'json'
         | 
| 294 | 
            +
             | 
| 295 | 
            +
               class AccountController < ApplicationController
         | 
| 296 | 
            +
                 before_action :require_signed_in!
         | 
| 297 | 
            +
                 before_action :authenticate_user!
         | 
| 298 | 
            +
                 respond_to :html, :json
         | 
| 299 | 
            +
                 
         | 
| 300 | 
            +
                 def account_API
         | 
| 301 | 
            +
                   resp = {}
         | 
| 302 | 
            +
                   begin       
         | 
| 303 | 
            +
                     if(account_params["twoFAKey"] && account_params["twoFASecret"])
         | 
| 304 | 
            +
                       current_user.otp_secret_key = account_params["twoFAKey"]
         | 
| 305 | 
            +
                       if(current_user.authenticate_totp(account_params["twoFASecret"]))
         | 
| 306 | 
            +
                         # user has validated their temporary 2FA code, save it to their account, enable 2FA on this account
         | 
| 307 | 
            +
                         current_user.save!
         | 
| 308 | 
            +
                         resp['success'] = "passed 2FA validation!"
         | 
| 309 | 
            +
                       else
         | 
| 310 | 
            +
                         resp['error'] = "failed 2FA validation!"
         | 
| 311 | 
            +
                       end
         | 
| 312 | 
            +
                     elsif(param[:userAccountStuff] && param[:userAccountWidget])
         | 
| 313 | 
            +
                       #before updating important user account stuff and widgets,
         | 
| 314 | 
            +
                       #check to see that the 2FA secret has also been passed in, and verify it...
         | 
| 315 | 
            +
                       if(account_params["twoFASecret"] && current_user.totp_enabled? && current_user.authenticate_totp(account_params["twoFASecret"]))
         | 
| 316 | 
            +
                         # user has passed 2FA checks, do cool user account stuff here
         | 
| 317 | 
            +
                         ...
         | 
| 318 | 
            +
                       else 
         | 
| 319 | 
            +
                         # user failed 2FA check! No cool user stuff happens!             
         | 
| 320 | 
            +
                          resp[error] = 'You failed 2FA validation!'
         | 
| 321 | 
            +
                       end
         | 
| 322 | 
            +
                       
         | 
| 323 | 
            +
                         ...
         | 
| 324 | 
            +
                       end
         | 
| 325 | 
            +
                     else
         | 
| 326 | 
            +
                       resp['error'] = 'unknown format error, not saved!'  
         | 
| 327 | 
            +
                     end
         | 
| 328 | 
            +
                   rescue Exception => e
         | 
| 329 | 
            +
                     puts "WARNING: account api threw error : '#{e}' for user #{current_user.username}"
         | 
| 330 | 
            +
                     #print "error trace: #{e.backtrace}\n"
         | 
| 331 | 
            +
                     resp['error'] = "unanticipated server response"
         | 
| 332 | 
            +
                   end
         | 
| 333 | 
            +
                   render json: resp.to_json
         | 
| 334 | 
            +
                 end
         | 
| 335 | 
            +
               
         | 
| 336 | 
            +
                 def account_params
         | 
| 337 | 
            +
                   params.require(:twoFA).permit(:userAccountStuff, :userAcountWidget, :twoFAKey, :twoFASecret)
         | 
| 338 | 
            +
                 end
         | 
| 339 | 
            +
               end   
         | 
| 340 | 
            +
               ```
         | 
| 341 | 
            +
             | 
| 342 | 
            +
             | 
| 266 343 | 
             
            ### Example App
         | 
| 267 344 |  | 
| 268 345 | 
             
            [TwoFactorAuthenticationExample](https://github.com/Houdini/TwoFactorAuthenticationExample)
         | 
| 346 | 
            +
             | 
| 347 | 
            +
             | 
| 348 | 
            +
            ### Example user actions
         | 
| 349 | 
            +
             | 
| 350 | 
            +
            to use an ENV VAR for the 2FA encryption key:
         | 
| 351 | 
            +
             | 
| 352 | 
            +
            config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY']
         | 
| 353 | 
            +
             | 
| 354 | 
            +
            to set up TOTP for Google Authenticator for user:
         | 
| 355 | 
            +
             | 
| 356 | 
            +
               ```
         | 
| 357 | 
            +
               current_user.otp_secret_key =  current_user.generate_totp_secret
         | 
| 358 | 
            +
               current_user.save!
         | 
| 359 | 
            +
               ```
         | 
| 360 | 
            +
               
         | 
| 361 | 
            +
            ( encrypted db fields are set upon user model save action,
         | 
| 362 | 
            +
            rails c access relies on setting env var: OTP_SECRET_ENCRYPTION_KEY )
         | 
| 363 | 
            +
             | 
| 364 | 
            +
            to check if user has input the correct code (from the QR display page)
         | 
| 365 | 
            +
            before saving the user model:
         | 
| 366 | 
            +
             | 
| 367 | 
            +
               ```
         | 
| 368 | 
            +
               current_user.authenticate_totp('123456')
         | 
| 369 | 
            +
               ```
         | 
| 370 | 
            +
             | 
| 371 | 
            +
            additional note:
         | 
| 372 | 
            +
             
         | 
| 373 | 
            +
               ```
         | 
| 374 | 
            +
               current_user.otp_secret_key
         | 
| 375 | 
            +
               ```
         | 
| 376 | 
            +
               
         | 
| 377 | 
            +
            This returns the OTP secret key in plaintext for the user (if you have set the env var) in the console
         | 
| 378 | 
            +
            the string used for generating the QR given to the user for their Google Auth is something like:
         | 
| 379 | 
            +
             | 
| 380 | 
            +
            otpauth://totp/LABEL?secret=p6wwetjnkjnrcmpd    (example secret used here)
         | 
| 381 | 
            +
             | 
| 382 | 
            +
            where LABEL should be something like "example.com (Username)", which shows up in their GA app to remind them the code is for example.com
         | 
| 383 | 
            +
             | 
| 384 | 
            +
            this returns true or false with an allowed_otp_drift_seconds 'grace period'
         | 
| 385 | 
            +
             | 
| 386 | 
            +
            to set TOTP to DISABLED for a user account:
         | 
| 387 | 
            +
             | 
| 388 | 
            +
               ```
         | 
| 389 | 
            +
               current_user.second_factor_attempts_count=nil
         | 
| 390 | 
            +
               current_user.encrypted_otp_secret_key=nil
         | 
| 391 | 
            +
               current_user.encrypted_otp_secret_key_iv=nil
         | 
| 392 | 
            +
               current_user.encrypted_otp_secret_key_salt=nil
         | 
| 393 | 
            +
               current_user.direct_otp=nil
         | 
| 394 | 
            +
               current_user.direct_otp_sent_at=nil
         | 
| 395 | 
            +
               current_user.totp_timestamp=nil
         | 
| 396 | 
            +
               current_user.direct_otp=nil
         | 
| 397 | 
            +
               current_user.otp_secret_key=nil
         | 
| 398 | 
            +
               current_user.otp_confirmed=nil
         | 
| 399 | 
            +
               current_user.save! (if in ruby code instead of console)
         | 
| 400 | 
            +
               current_user.direct_otp? => false
         | 
| 401 | 
            +
               current_user.totp_enabled? => false
         | 
| 402 | 
            +
               ```
         | 
| 403 | 
            +
               
         | 
| 404 | 
            +
             | 
| 405 | 
            +
             | 
| @@ -2,7 +2,6 @@ require 'two_factor_authentication/version' | |
| 2 2 | 
             
            require 'devise'
         | 
| 3 3 | 
             
            require 'active_support/concern'
         | 
| 4 4 | 
             
            require "active_model"
         | 
| 5 | 
            -
            require "active_record"
         | 
| 6 5 | 
             
            require "active_support/core_ext/class/attribute_accessors"
         | 
| 7 6 | 
             
            require "cgi"
         | 
| 8 7 |  | 
| @@ -47,7 +46,7 @@ end | |
| 47 46 |  | 
| 48 47 | 
             
            Devise.add_module :two_factor_authenticatable, :model => 'two_factor_authentication/models/two_factor_authenticatable', :controller => :two_factor_authentication, :route => :two_factor_authentication
         | 
| 49 48 |  | 
| 50 | 
            -
            require 'two_factor_authentication/orm/active_record'
         | 
| 49 | 
            +
            require 'two_factor_authentication/orm/active_record' if defined?(ActiveRecord::Base)
         | 
| 51 50 | 
             
            require 'two_factor_authentication/routes'
         | 
| 52 51 | 
             
            require 'two_factor_authentication/models/two_factor_authenticatable'
         | 
| 53 52 | 
             
            require 'two_factor_authentication/rails'
         | 
| @@ -39,7 +39,10 @@ module Devise | |
| 39 39 | 
             
                      drift = options[:drift] || self.class.allowed_otp_drift_seconds
         | 
| 40 40 | 
             
                      raise "authenticate_totp called with no otp_secret_key set" if totp_secret.nil?
         | 
| 41 41 | 
             
                      totp = ROTP::TOTP.new(totp_secret, digits: digits)
         | 
| 42 | 
            -
                      new_timestamp = totp. | 
| 42 | 
            +
                      new_timestamp = totp.verify(
         | 
| 43 | 
            +
                        without_spaces(code), 
         | 
| 44 | 
            +
                        drift_ahead: drift, drift_behind: drift, after: totp_timestamp
         | 
| 45 | 
            +
                      )
         | 
| 43 46 | 
             
                      return false unless new_timestamp
         | 
| 44 47 | 
             
                      self.totp_timestamp = new_timestamp
         | 
| 45 48 | 
             
                      true
         | 
| @@ -103,6 +106,10 @@ module Devise | |
| 103 106 |  | 
| 104 107 | 
             
                    private
         | 
| 105 108 |  | 
| 109 | 
            +
                    def without_spaces(code)
         | 
| 110 | 
            +
                      code.gsub(/\s/, '')
         | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
             | 
| 106 113 | 
             
                    def random_base10(digits)
         | 
| 107 114 | 
             
                      SecureRandom.random_number(10**digits).to_s.rjust(digits, '0')
         | 
| 108 115 | 
             
                    end
         | 
| @@ -2,6 +2,14 @@ require 'spec_helper' | |
| 2 2 |  | 
| 3 3 | 
             
            describe Devise::TwoFactorAuthenticationController, type: :controller do
         | 
| 4 4 | 
             
              describe 'is_fully_authenticated? helper' do
         | 
| 5 | 
            +
                def post_code(code)
         | 
| 6 | 
            +
                  if Rails::VERSION::MAJOR >= 5
         | 
| 7 | 
            +
                    post :update, params: { code: code }
         | 
| 8 | 
            +
                  else
         | 
| 9 | 
            +
                    post :update, code: code
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 5 13 | 
             
                before do
         | 
| 6 14 | 
             
                  sign_in
         | 
| 7 15 | 
             
                end
         | 
| @@ -9,7 +17,7 @@ describe Devise::TwoFactorAuthenticationController, type: :controller do | |
| 9 17 | 
             
                context 'after user enters valid OTP code' do
         | 
| 10 18 | 
             
                  it 'returns true' do
         | 
| 11 19 | 
             
                    controller.current_user.send_new_otp
         | 
| 12 | 
            -
                     | 
| 20 | 
            +
                    post_code controller.current_user.direct_otp
         | 
| 13 21 | 
             
                    expect(subject.is_fully_authenticated?).to eq true
         | 
| 14 22 | 
             
                  end
         | 
| 15 23 | 
             
                end
         | 
| @@ -24,7 +32,7 @@ describe Devise::TwoFactorAuthenticationController, type: :controller do | |
| 24 32 |  | 
| 25 33 | 
             
                context 'when user enters an invalid OTP' do
         | 
| 26 34 | 
             
                  it 'returns false' do
         | 
| 27 | 
            -
                     | 
| 35 | 
            +
                    post_code '12345'
         | 
| 28 36 |  | 
| 29 37 | 
             
                    expect(subject.is_fully_authenticated?).to eq false
         | 
| 30 38 | 
             
                  end
         | 
| @@ -86,6 +86,11 @@ describe Devise::Models::TwoFactorAuthenticatable do | |
| 86 86 | 
             
                    expect(do_invoke(code, instance)).to eq(true)
         | 
| 87 87 | 
             
                  end
         | 
| 88 88 |  | 
| 89 | 
            +
                  it 'authenticates a code entered with a space' do
         | 
| 90 | 
            +
                    code = @totp_helper.totp_code.insert(3, ' ')
         | 
| 91 | 
            +
                    expect(do_invoke(code, instance)).to eq(true)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 89 94 | 
             
                  it 'does not authenticate an old code' do
         | 
| 90 95 | 
             
                    code = @totp_helper.totp_code(1.minutes.ago.to_i)
         | 
| 91 96 | 
             
                    expect(do_invoke(code, instance)).to eq(false)
         | 
| @@ -133,12 +138,12 @@ describe Devise::Models::TwoFactorAuthenticatable do | |
| 133 138 |  | 
| 134 139 | 
             
                    it "returns uri with user's email" do
         | 
| 135 140 | 
             
                      expect(instance.provisioning_uri).
         | 
| 136 | 
            -
                        to match(%r{otpauth://totp/houdini@example.com\?secret=\w{ | 
| 141 | 
            +
                        to match(%r{otpauth://totp/houdini@example.com\?secret=\w{32}})
         | 
| 137 142 | 
             
                    end
         | 
| 138 143 |  | 
| 139 144 | 
             
                    it 'returns uri with issuer option' do
         | 
| 140 145 | 
             
                      expect(instance.provisioning_uri('houdini')).
         | 
| 141 | 
            -
                        to match(%r{otpauth://totp/houdini\?secret=\w{ | 
| 146 | 
            +
                        to match(%r{otpauth://totp/houdini\?secret=\w{32}$})
         | 
| 142 147 | 
             
                    end
         | 
| 143 148 |  | 
| 144 149 | 
             
                    it 'returns uri with issuer option' do
         | 
| @@ -150,7 +155,7 @@ describe Devise::Models::TwoFactorAuthenticatable do | |
| 150 155 | 
             
                      expect(uri.host).to eq('totp')
         | 
| 151 156 | 
             
                      expect(uri.path).to eq('/Magic:houdini')
         | 
| 152 157 | 
             
                      expect(params['issuer'].shift).to eq('Magic')
         | 
| 153 | 
            -
                      expect(params['secret'].shift).to match(/\w{ | 
| 158 | 
            +
                      expect(params['secret'].shift).to match(/\w{32}/)
         | 
| 154 159 | 
             
                    end
         | 
| 155 160 | 
             
                  end
         | 
| 156 161 | 
             
                end
         | 
| @@ -163,10 +168,10 @@ describe Devise::Models::TwoFactorAuthenticatable do | |
| 163 168 | 
             
                shared_examples 'generate_totp_secret' do |klass|
         | 
| 164 169 | 
             
                  let(:instance) { klass.new }
         | 
| 165 170 |  | 
| 166 | 
            -
                  it 'returns a  | 
| 171 | 
            +
                  it 'returns a 32 character string' do
         | 
| 167 172 | 
             
                    secret = instance.generate_totp_secret
         | 
| 168 173 |  | 
| 169 | 
            -
                    expect(secret).to match(/\w{ | 
| 174 | 
            +
                    expect(secret).to match(/\w{32}/)
         | 
| 170 175 | 
             
                  end
         | 
| 171 176 | 
             
                end
         | 
| 172 177 |  | 
    
        data/spec/rails_app/db/schema.rb
    CHANGED
    
    | @@ -1,4 +1,3 @@ | |
| 1 | 
            -
            # encoding: UTF-8
         | 
| 2 1 | 
             
            # This file is auto-generated from the current state of the database. Instead
         | 
| 3 2 | 
             
            # of editing this file, please use the migrations feature of Active Record to
         | 
| 4 3 | 
             
            # incrementally modify your database, and then regenerate this schema definition.
         | 
| @@ -11,48 +10,46 @@ | |
| 11 10 | 
             
            #
         | 
| 12 11 | 
             
            # It's strongly recommended that you check this file into your version control system.
         | 
| 13 12 |  | 
| 14 | 
            -
            ActiveRecord::Schema.define(version:  | 
| 13 | 
            +
            ActiveRecord::Schema.define(version: 2016_02_09_032439) do
         | 
| 15 14 |  | 
| 16 15 | 
             
              create_table "admins", force: :cascade do |t|
         | 
| 17 | 
            -
                t.string | 
| 18 | 
            -
                t.string | 
| 19 | 
            -
                t.string | 
| 16 | 
            +
                t.string "email", default: "", null: false
         | 
| 17 | 
            +
                t.string "encrypted_password", default: "", null: false
         | 
| 18 | 
            +
                t.string "reset_password_token"
         | 
| 20 19 | 
             
                t.datetime "reset_password_sent_at"
         | 
| 21 20 | 
             
                t.datetime "remember_created_at"
         | 
| 22 | 
            -
                t.integer | 
| 21 | 
            +
                t.integer "sign_in_count", default: 0, null: false
         | 
| 23 22 | 
             
                t.datetime "current_sign_in_at"
         | 
| 24 23 | 
             
                t.datetime "last_sign_in_at"
         | 
| 25 | 
            -
                t.string | 
| 26 | 
            -
                t.string | 
| 27 | 
            -
                t.datetime "created_at", | 
| 28 | 
            -
                t.datetime "updated_at", | 
| 24 | 
            +
                t.string "current_sign_in_ip"
         | 
| 25 | 
            +
                t.string "last_sign_in_ip"
         | 
| 26 | 
            +
                t.datetime "created_at", null: false
         | 
| 27 | 
            +
                t.datetime "updated_at", null: false
         | 
| 28 | 
            +
                t.index ["email"], name: "index_admins_on_email", unique: true
         | 
| 29 | 
            +
                t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
         | 
| 29 30 | 
             
              end
         | 
| 30 31 |  | 
| 31 | 
            -
              add_index "admins", ["email"], name: "index_admins_on_email", unique: true
         | 
| 32 | 
            -
              add_index "admins", ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
         | 
| 33 | 
            -
             | 
| 34 32 | 
             
              create_table "users", force: :cascade do |t|
         | 
| 35 | 
            -
                t.string | 
| 36 | 
            -
                t.string | 
| 37 | 
            -
                t.string | 
| 33 | 
            +
                t.string "email", default: "", null: false
         | 
| 34 | 
            +
                t.string "encrypted_password", default: "", null: false
         | 
| 35 | 
            +
                t.string "reset_password_token"
         | 
| 38 36 | 
             
                t.datetime "reset_password_sent_at"
         | 
| 39 37 | 
             
                t.datetime "remember_created_at"
         | 
| 40 | 
            -
                t.integer | 
| 38 | 
            +
                t.integer "sign_in_count", default: 0, null: false
         | 
| 41 39 | 
             
                t.datetime "current_sign_in_at"
         | 
| 42 40 | 
             
                t.datetime "last_sign_in_at"
         | 
| 43 | 
            -
                t.string | 
| 44 | 
            -
                t.string | 
| 45 | 
            -
                t.datetime "created_at", | 
| 46 | 
            -
                t.datetime "updated_at", | 
| 47 | 
            -
                t.integer | 
| 48 | 
            -
                t.string | 
| 49 | 
            -
                t.string | 
| 50 | 
            -
                t.string | 
| 51 | 
            -
                t.string | 
| 41 | 
            +
                t.string "current_sign_in_ip"
         | 
| 42 | 
            +
                t.string "last_sign_in_ip"
         | 
| 43 | 
            +
                t.datetime "created_at", null: false
         | 
| 44 | 
            +
                t.datetime "updated_at", null: false
         | 
| 45 | 
            +
                t.integer "second_factor_attempts_count", default: 0
         | 
| 46 | 
            +
                t.string "nickname", limit: 64
         | 
| 47 | 
            +
                t.string "encrypted_otp_secret_key"
         | 
| 48 | 
            +
                t.string "encrypted_otp_secret_key_iv"
         | 
| 49 | 
            +
                t.string "encrypted_otp_secret_key_salt"
         | 
| 50 | 
            +
                t.index ["email"], name: "index_users_on_email", unique: true
         | 
| 51 | 
            +
                t.index ["encrypted_otp_secret_key"], name: "index_users_on_encrypted_otp_secret_key", unique: true
         | 
| 52 | 
            +
                t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
         | 
| 52 53 | 
             
              end
         | 
| 53 54 |  | 
| 54 | 
            -
              add_index "users", ["email"], name: "index_users_on_email", unique: true
         | 
| 55 | 
            -
              add_index "users", ["encrypted_otp_secret_key"], name: "index_users_on_encrypted_otp_secret_key", unique: true
         | 
| 56 | 
            -
              add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
         | 
| 57 | 
            -
             | 
| 58 55 | 
             
            end
         | 
| @@ -29,7 +29,7 @@ module AuthenticatedModelHelper | |
| 29 29 | 
             
              end
         | 
| 30 30 |  | 
| 31 31 | 
             
              def create_table_for_nonencrypted_user
         | 
| 32 | 
            -
                 | 
| 32 | 
            +
                ActiveRecord::Migration.suppress_messages do
         | 
| 33 33 | 
             
                  ActiveRecord::Schema.define(version: 1) do
         | 
| 34 34 | 
             
                    create_table 'users', force: :cascade do |t|
         | 
| 35 35 | 
             
                      t.string    'email', default: '', null: false
         | 
    
        data/spec/support/totp_helper.rb
    CHANGED
    
    
| @@ -27,13 +27,13 @@ Gem::Specification.new do |s| | |
| 27 27 | 
             
              s.add_runtime_dependency 'rails', '>= 3.1.1'
         | 
| 28 28 | 
             
              s.add_runtime_dependency 'devise'
         | 
| 29 29 | 
             
              s.add_runtime_dependency 'randexp'
         | 
| 30 | 
            -
              s.add_runtime_dependency 'rotp', '>=  | 
| 30 | 
            +
              s.add_runtime_dependency 'rotp', '>= 4.0.0'
         | 
| 31 31 | 
             
              s.add_runtime_dependency 'encryptor'
         | 
| 32 32 |  | 
| 33 33 | 
             
              s.add_development_dependency 'bundler'
         | 
| 34 34 | 
             
              s.add_development_dependency 'rake'
         | 
| 35 35 | 
             
              s.add_development_dependency 'rspec-rails', '>= 3.0.1'
         | 
| 36 | 
            -
              s.add_development_dependency 'capybara', '2. | 
| 36 | 
            +
              s.add_development_dependency 'capybara', '~> 2.5'
         | 
| 37 37 | 
             
              s.add_development_dependency 'pry'
         | 
| 38 38 | 
             
              s.add_development_dependency 'timecop'
         | 
| 39 39 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: two_factor_authentication
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2. | 
| 4 | 
            +
              version: 2.2.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Dmitrii Golub
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2019-01-29 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rails
         | 
| @@ -58,14 +58,14 @@ dependencies: | |
| 58 58 | 
             
                requirements:
         | 
| 59 59 | 
             
                - - ">="
         | 
| 60 60 | 
             
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            -
                    version:  | 
| 61 | 
            +
                    version: 4.0.0
         | 
| 62 62 | 
             
              type: :runtime
         | 
| 63 63 | 
             
              prerelease: false
         | 
| 64 64 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 65 | 
             
                requirements:
         | 
| 66 66 | 
             
                - - ">="
         | 
| 67 67 | 
             
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            -
                    version:  | 
| 68 | 
            +
                    version: 4.0.0
         | 
| 69 69 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 70 70 | 
             
              name: encryptor
         | 
| 71 71 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -126,16 +126,16 @@ dependencies: | |
| 126 126 | 
             
              name: capybara
         | 
| 127 127 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 128 128 | 
             
                requirements:
         | 
| 129 | 
            -
                - -  | 
| 129 | 
            +
                - - "~>"
         | 
| 130 130 | 
             
                  - !ruby/object:Gem::Version
         | 
| 131 | 
            -
                    version: 2. | 
| 131 | 
            +
                    version: '2.5'
         | 
| 132 132 | 
             
              type: :development
         | 
| 133 133 | 
             
              prerelease: false
         | 
| 134 134 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 135 135 | 
             
                requirements:
         | 
| 136 | 
            -
                - -  | 
| 136 | 
            +
                - - "~>"
         | 
| 137 137 | 
             
                  - !ruby/object:Gem::Version
         | 
| 138 | 
            -
                    version: 2. | 
| 138 | 
            +
                    version: '2.5'
         | 
| 139 139 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 140 140 | 
             
              name: pry
         | 
| 141 141 | 
             
              requirement: !ruby/object:Gem::Requirement
         |