devise-multi-factor 3.1.5

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.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +21 -0
  3. data/.github/workflows/gem-push.yml +42 -0
  4. data/.gitignore +23 -0
  5. data/.rubocop.yml +295 -0
  6. data/.travis.yml +28 -0
  7. data/CHANGELOG.md +119 -0
  8. data/Gemfile +32 -0
  9. data/LICENSE +19 -0
  10. data/README.md +322 -0
  11. data/Rakefile +12 -0
  12. data/app/controllers/devise/totp_controller.rb +79 -0
  13. data/app/controllers/devise/two_factor_authentication_controller.rb +84 -0
  14. data/app/views/devise/two_factor_authentication/max_login_attempts_reached.html.erb +3 -0
  15. data/app/views/devise/two_factor_authentication/new.html.erb +14 -0
  16. data/app/views/devise/two_factor_authentication/show.html.erb +19 -0
  17. data/config/locales/de.yml +8 -0
  18. data/config/locales/en.yml +8 -0
  19. data/config/locales/es.yml +8 -0
  20. data/config/locales/fr.yml +8 -0
  21. data/config/locales/ru.yml +8 -0
  22. data/devise-multi-factor.gemspec +40 -0
  23. data/lib/devise-multi-factor.rb +1 -0
  24. data/lib/devise_multi_factor.rb +56 -0
  25. data/lib/devise_multi_factor/controllers/helpers.rb +57 -0
  26. data/lib/devise_multi_factor/hooks/two_factor_authenticatable.rb +17 -0
  27. data/lib/devise_multi_factor/models/totp_enrollable.rb +7 -0
  28. data/lib/devise_multi_factor/models/two_factor_authenticatable.rb +142 -0
  29. data/lib/devise_multi_factor/orm/active_record.rb +14 -0
  30. data/lib/devise_multi_factor/rails.rb +7 -0
  31. data/lib/devise_multi_factor/routes.rb +15 -0
  32. data/lib/devise_multi_factor/schema.rb +23 -0
  33. data/lib/devise_multi_factor/version.rb +3 -0
  34. data/lib/generators/active_record/devise_multi_factor_generator.rb +13 -0
  35. data/lib/generators/active_record/templates/migration.rb +11 -0
  36. data/lib/generators/devise_multi_factor/devise_multi_factor_generator.rb +17 -0
  37. data/spec/controllers/two_factor_authentication_controller_spec.rb +41 -0
  38. data/spec/features/two_factor_authenticatable_spec.rb +237 -0
  39. data/spec/generators/active_record/devise_multi_factor_generator_spec.rb +34 -0
  40. data/spec/lib/devise_multi_factor/models/two_factor_authenticatable_spec.rb +282 -0
  41. data/spec/rails_app/.gitignore +3 -0
  42. data/spec/rails_app/README.md +3 -0
  43. data/spec/rails_app/Rakefile +7 -0
  44. data/spec/rails_app/app/assets/config/manifest.js +2 -0
  45. data/spec/rails_app/app/assets/javascripts/application.js +1 -0
  46. data/spec/rails_app/app/assets/stylesheets/application.css +4 -0
  47. data/spec/rails_app/app/controllers/application_controller.rb +3 -0
  48. data/spec/rails_app/app/controllers/home_controller.rb +10 -0
  49. data/spec/rails_app/app/helpers/application_helper.rb +8 -0
  50. data/spec/rails_app/app/mailers/.gitkeep +0 -0
  51. data/spec/rails_app/app/models/.gitkeep +0 -0
  52. data/spec/rails_app/app/models/admin.rb +6 -0
  53. data/spec/rails_app/app/models/encrypted_user.rb +7 -0
  54. data/spec/rails_app/app/models/guest_user.rb +7 -0
  55. data/spec/rails_app/app/models/test_user.rb +38 -0
  56. data/spec/rails_app/app/models/user.rb +18 -0
  57. data/spec/rails_app/app/views/home/dashboard.html.erb +11 -0
  58. data/spec/rails_app/app/views/home/index.html.erb +3 -0
  59. data/spec/rails_app/app/views/layouts/application.html.erb +20 -0
  60. data/spec/rails_app/config.ru +4 -0
  61. data/spec/rails_app/config/application.rb +61 -0
  62. data/spec/rails_app/config/boot.rb +10 -0
  63. data/spec/rails_app/config/database.yml +19 -0
  64. data/spec/rails_app/config/environment.rb +5 -0
  65. data/spec/rails_app/config/environments/development.rb +28 -0
  66. data/spec/rails_app/config/environments/production.rb +68 -0
  67. data/spec/rails_app/config/environments/test.rb +41 -0
  68. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  69. data/spec/rails_app/config/initializers/cookies_serializer.rb +3 -0
  70. data/spec/rails_app/config/initializers/devise.rb +258 -0
  71. data/spec/rails_app/config/initializers/inflections.rb +15 -0
  72. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  73. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  74. data/spec/rails_app/config/initializers/session_store.rb +8 -0
  75. data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
  76. data/spec/rails_app/config/locales/devise.en.yml +59 -0
  77. data/spec/rails_app/config/locales/en.yml +5 -0
  78. data/spec/rails_app/config/routes.rb +65 -0
  79. data/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb +42 -0
  80. data/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb +17 -0
  81. data/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb +7 -0
  82. data/spec/rails_app/db/migrate/20151224171231_add_encrypted_columns_to_user.rb +7 -0
  83. data/spec/rails_app/db/migrate/20151224180310_populate_otp_column.rb +19 -0
  84. data/spec/rails_app/db/migrate/20151228230340_remove_otp_secret_key_from_user.rb +5 -0
  85. data/spec/rails_app/db/migrate/20160209032439_devise_create_admins.rb +42 -0
  86. data/spec/rails_app/db/schema.rb +55 -0
  87. data/spec/rails_app/lib/assets/.gitkeep +0 -0
  88. data/spec/rails_app/lib/sms_provider.rb +17 -0
  89. data/spec/rails_app/public/404.html +26 -0
  90. data/spec/rails_app/public/422.html +26 -0
  91. data/spec/rails_app/public/500.html +25 -0
  92. data/spec/rails_app/public/favicon.ico +0 -0
  93. data/spec/rails_app/script/rails +6 -0
  94. data/spec/spec_helper.rb +26 -0
  95. data/spec/support/authenticated_model_helper.rb +29 -0
  96. data/spec/support/capybara.rb +3 -0
  97. data/spec/support/controller_helper.rb +16 -0
  98. data/spec/support/features_spec_helper.rb +42 -0
  99. data/spec/support/sms_provider.rb +5 -0
  100. data/spec/support/totp_helper.rb +11 -0
  101. metadata +315 -0
@@ -0,0 +1,42 @@
1
+ class DeviseCreateUsers < ActiveRecord::Migration[4.2]
2
+ def change
3
+ create_table(:users) do |t|
4
+ ## Database authenticatable
5
+ t.string :email, null: false, default: ""
6
+ t.string :encrypted_password, null: false, default: ""
7
+
8
+ ## Recoverable
9
+ t.string :reset_password_token
10
+ t.datetime :reset_password_sent_at
11
+
12
+ ## Rememberable
13
+ t.datetime :remember_created_at
14
+
15
+ ## Trackable
16
+ t.integer :sign_in_count, default: 0, null: false
17
+ t.datetime :current_sign_in_at
18
+ t.datetime :last_sign_in_at
19
+ t.string :current_sign_in_ip
20
+ t.string :last_sign_in_ip
21
+
22
+ ## Confirmable
23
+ # t.string :confirmation_token
24
+ # t.datetime :confirmed_at
25
+ # t.datetime :confirmation_sent_at
26
+ # t.string :unconfirmed_email # Only if using reconfirmable
27
+
28
+ ## Lockable
29
+ # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
30
+ # t.string :unlock_token # Only if unlock strategy is :email or :both
31
+ # t.datetime :locked_at
32
+
33
+
34
+ t.timestamps null: false
35
+ end
36
+
37
+ add_index :users, :email, unique: true
38
+ add_index :users, :reset_password_token, unique: true
39
+ # add_index :users, :confirmation_token, unique: true
40
+ # add_index :users, :unlock_token, unique: true
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ class TwoFactorAuthenticationAddToUsers < ActiveRecord::Migration[4.2]
2
+ def up
3
+ change_table :users do |t|
4
+ t.string :otp_secret_key
5
+ t.string :direct_otp
6
+ t.datetime :direct_otp_sent_at
7
+ t.integer :second_factor_attempts_count, :default => 0
8
+ end
9
+
10
+ add_index :users, :otp_secret_key, :unique => true
11
+ end
12
+
13
+ def down
14
+ remove_column :users, :otp_secret_key
15
+ remove_column :users, :second_factor_attempts_count
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ class AddNickanmeToUsers < ActiveRecord::Migration[4.2]
2
+ def change
3
+ change_table :users do |t|
4
+ t.column :nickname, :string, limit: 64
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ class AddEncryptedColumnsToUser < ActiveRecord::Migration[4.2]
2
+ def change
3
+ add_column :users, :encrypted_otp_secret_key, :string
4
+
5
+ add_index :users, :encrypted_otp_secret_key, unique: true
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ class PopulateOtpColumn < ActiveRecord::Migration[4.2]
2
+ def up
3
+ User.reset_column_information
4
+
5
+ User.find_each do |user|
6
+ user.otp_secret_key = user.read_attribute('otp_secret_key')
7
+ user.save!
8
+ end
9
+ end
10
+
11
+ def down
12
+ User.reset_column_information
13
+
14
+ User.find_each do |user|
15
+ user.otp_secret_key = ROTP::Base32.random_base32
16
+ user.save!
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ class RemoveOtpSecretKeyFromUser < ActiveRecord::Migration[4.2]
2
+ def change
3
+ remove_column :users, :otp_secret_key, :string
4
+ end
5
+ end
@@ -0,0 +1,42 @@
1
+ class DeviseCreateAdmins < ActiveRecord::Migration[4.2]
2
+ def change
3
+ create_table(:admins) do |t|
4
+ ## Database authenticatable
5
+ t.string :email, null: false, default: ""
6
+ t.string :encrypted_password, null: false, default: ""
7
+
8
+ ## Recoverable
9
+ t.string :reset_password_token
10
+ t.datetime :reset_password_sent_at
11
+
12
+ ## Rememberable
13
+ t.datetime :remember_created_at
14
+
15
+ ## Trackable
16
+ t.integer :sign_in_count, default: 0, null: false
17
+ t.datetime :current_sign_in_at
18
+ t.datetime :last_sign_in_at
19
+ t.string :current_sign_in_ip
20
+ t.string :last_sign_in_ip
21
+
22
+ ## Confirmable
23
+ # t.string :confirmation_token
24
+ # t.datetime :confirmed_at
25
+ # t.datetime :confirmation_sent_at
26
+ # t.string :unconfirmed_email # Only if using reconfirmable
27
+
28
+ ## Lockable
29
+ # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
30
+ # t.string :unlock_token # Only if unlock strategy is :email or :both
31
+ # t.datetime :locked_at
32
+
33
+
34
+ t.timestamps null: false
35
+ end
36
+
37
+ add_index :admins, :email, unique: true
38
+ add_index :admins, :reset_password_token, unique: true
39
+ # add_index :admins, :confirmation_token, unique: true
40
+ # add_index :admins, :unlock_token, unique: true
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # Note that this schema.rb definition is the authoritative source for your
6
+ # database schema. If you need to create the application database on another
7
+ # system, you should be using db:schema:load, not running all the migrations
8
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(version: 2016_02_09_032439) do
14
+
15
+ create_table "admins", force: :cascade do |t|
16
+ t.string "email", default: "", null: false
17
+ t.string "encrypted_password", default: "", null: false
18
+ t.string "reset_password_token"
19
+ t.datetime "reset_password_sent_at"
20
+ t.datetime "remember_created_at"
21
+ t.integer "sign_in_count", default: 0, null: false
22
+ t.datetime "current_sign_in_at"
23
+ t.datetime "last_sign_in_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
30
+ end
31
+
32
+ create_table "users", force: :cascade do |t|
33
+ t.string "email", default: "", null: false
34
+ t.string "encrypted_password", default: "", null: false
35
+ t.string "reset_password_token"
36
+ t.datetime "reset_password_sent_at"
37
+ t.datetime "remember_created_at"
38
+ t.integer "sign_in_count", default: 0, null: false
39
+ t.datetime "current_sign_in_at"
40
+ t.datetime "last_sign_in_at"
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.string "direct_otp"
46
+ t.datetime "direct_otp_sent_at"
47
+ t.integer "second_factor_attempts_count", default: 0
48
+ t.string "nickname", limit: 64
49
+ t.string "encrypted_otp_secret_key"
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
53
+ end
54
+
55
+ end
File without changes
@@ -0,0 +1,17 @@
1
+ require 'ostruct'
2
+
3
+ class SMSProvider
4
+ Message = Class.new(OpenStruct)
5
+
6
+ class_attribute :messages
7
+ self.messages = []
8
+
9
+ def self.send_message(opts = {})
10
+ self.messages << Message.new(opts)
11
+ end
12
+
13
+ def self.last_message
14
+ self.messages.last
15
+ end
16
+
17
+ end
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ </div>
24
+ </body>
25
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,26 @@
1
+ ENV["RAILS_ENV"] ||= "test"
2
+ require File.expand_path("../rails_app/config/environment.rb", __FILE__)
3
+
4
+ require 'rspec/rails'
5
+ require 'timecop'
6
+ require 'rack_session_access/capybara'
7
+
8
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
9
+ RSpec.configure do |config|
10
+ config.run_all_when_everything_filtered = true
11
+ config.filter_run :focus
12
+
13
+ config.use_transactional_examples = true
14
+
15
+ config.include Capybara::DSL
16
+
17
+ # Run specs in random order to surface order dependencies. If you find an
18
+ # order dependency and want to debug it, you can fix the order by providing
19
+ # the seed, which is printed after each run.
20
+ # --seed 1234
21
+ config.order = 'random'
22
+
23
+ config.after(:each) { Timecop.return }
24
+ end
25
+
26
+ Dir["#{Dir.pwd}/spec/support/**/*.rb"].each {|f| require f}
@@ -0,0 +1,29 @@
1
+ module AuthenticatedModelHelper
2
+ def build_guest_user
3
+ GuestUser.new
4
+ end
5
+
6
+ def create_user(type = 'encrypted', attributes = {})
7
+ User.create!(valid_attributes(attributes))
8
+ end
9
+
10
+ def create_admin
11
+ Admin.create!(valid_attributes.except(:nickname))
12
+ end
13
+
14
+ def valid_attributes(attributes={})
15
+ {
16
+ nickname: 'Marissa',
17
+ email: generate_unique_email,
18
+ password: 'password',
19
+ password_confirmation: 'password'
20
+ }.merge(attributes)
21
+ end
22
+
23
+ def generate_unique_email
24
+ email_id = User.order(id: :desc).first&.id.to_i + 1
25
+ "user#{email_id}@example.com"
26
+ end
27
+ end
28
+
29
+ RSpec.configuration.send(:include, AuthenticatedModelHelper)
@@ -0,0 +1,3 @@
1
+ require 'capybara/rspec'
2
+
3
+ Capybara.app = Dummy::Application
@@ -0,0 +1,16 @@
1
+ module ControllerHelper
2
+ def sign_in(user = create_user('not_encrypted'))
3
+ allow(warden).to receive(:authenticated?).with(:user).and_return(true)
4
+ allow(controller).to receive(:current_user).and_return(user)
5
+ warden.session(:user)[DeviseMultiFactor::NEED_AUTHENTICATION] = true
6
+ end
7
+ end
8
+
9
+ RSpec.configure do |config|
10
+ config.include Devise::Test::ControllerHelpers, type: :controller
11
+ config.include ControllerHelper, type: :controller
12
+
13
+ config.before(:example, type: :controller) do
14
+ @request.env['devise.mapping'] = Devise.mappings[:user]
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ require 'warden'
2
+
3
+ module FeaturesSpecHelper
4
+ def warden
5
+ request.env['warden']
6
+ end
7
+
8
+ def complete_sign_in_form_for(user)
9
+ fill_in "Email", with: user.email
10
+ fill_in "Password", with: 'password'
11
+ find('.actions input').click # 'Sign in' or 'Log in'
12
+ end
13
+
14
+ def set_cookie key, value
15
+ page.driver.browser.set_cookie [key, value].join('=')
16
+ end
17
+
18
+ def get_cookie key
19
+ Capybara.current_session.driver.request.cookies[key]
20
+ end
21
+
22
+ def set_tfa_cookie value
23
+ set_cookie DeviseMultiFactor::REMEMBER_TFA_COOKIE_NAME, value
24
+ end
25
+
26
+ def get_tfa_cookie
27
+ get_cookie DeviseMultiFactor::REMEMBER_TFA_COOKIE_NAME
28
+ end
29
+ end
30
+
31
+ RSpec.configure do |config|
32
+ config.include Warden::Test::Helpers, type: :feature
33
+ config.include FeaturesSpecHelper, type: :feature
34
+
35
+ config.before(:each) do
36
+ Warden.test_mode!
37
+ end
38
+
39
+ config.after(:each) do
40
+ Warden.test_reset!
41
+ end
42
+ end