two_factor_authentication 1.1.4 → 1.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +13 -6
  3. data/CHANGELOG.md +109 -0
  4. data/Gemfile +8 -2
  5. data/README.md +182 -54
  6. data/app/controllers/devise/two_factor_authentication_controller.rb +1 -1
  7. data/config/locales/fr.yml +7 -0
  8. data/lib/generators/active_record/templates/migration.rb +6 -11
  9. data/lib/two_factor_authentication.rb +3 -0
  10. data/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb +26 -2
  11. data/lib/two_factor_authentication/models/two_factor_authenticatable.rb +89 -23
  12. data/lib/two_factor_authentication/schema.rb +12 -4
  13. data/lib/two_factor_authentication/version.rb +1 -1
  14. data/spec/controllers/two_factor_authentication_controller_spec.rb +33 -0
  15. data/spec/features/two_factor_authenticatable_spec.rb +164 -28
  16. data/spec/generators/active_record/two_factor_authentication_generator_spec.rb +36 -0
  17. data/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +213 -117
  18. data/spec/rails_app/app/models/encrypted_user.rb +14 -0
  19. data/spec/rails_app/app/models/user.rb +1 -2
  20. data/spec/rails_app/config/environments/test.rb +3 -0
  21. data/spec/rails_app/config/initializers/devise.rb +3 -1
  22. data/spec/rails_app/db/migrate/20151224171231_add_encrypted_columns_to_user.rb +9 -0
  23. data/spec/rails_app/db/migrate/20151224180310_populate_otp_column.rb +19 -0
  24. data/spec/rails_app/db/migrate/20151228230340_remove_otp_secret_key_from_user.rb +5 -0
  25. data/spec/rails_app/db/schema.rb +16 -14
  26. data/spec/spec_helper.rb +1 -0
  27. data/spec/support/authenticated_model_helper.rb +26 -2
  28. data/spec/support/controller_helper.rb +16 -0
  29. data/spec/support/features_spec_helper.rb +24 -1
  30. data/two_factor_authentication.gemspec +1 -0
  31. metadata +25 -3
  32. data/spec/controllers/two_factor_auth_spec.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6d9dcc7b4346ba04374293811dba157adfd55db2
4
- data.tar.gz: aaa9d10892c25956248fa6c2325c0632e34f4cf3
3
+ metadata.gz: 75e8b63e29715336ec78a4a06db141ec2fdddf68
4
+ data.tar.gz: c2b09dd1294a966ce75425efc97b6e2089e33689
5
5
  SHA512:
6
- metadata.gz: c5ac4fa24344211e03465875098f6fb5a09af71335747a9972829f5f304c8b10b12037fd581a47de6ad973cc562dba29c110ca3171d833b8d08d9fb8bd7faec9
7
- data.tar.gz: 8ce9ce6d83afafdd9014f7cf7c4aef87a0409a54f193a1943c3b071a0e86e3a60b93fe9a58e06beb380ecb42857988c521132fc4b44d16a926c8fbe2c729367c
6
+ metadata.gz: 6d38f91cbb77148bd9c401f129d977f5fa22281ddb20ec12e4a12f02b1e35acabd04048bc88bad4d7e4cc80311e43f8720f45b0eeea116323c96fdee78cfb733
7
+ data.tar.gz: e7a2a0026e4558a8e0e8efbb3257b0fec162f7d45390a7521f249332e273c53568b309044000fae9ecdfcd55c3cfe13a951d2895f5327bb54b692a4fe2723fcf
@@ -1,21 +1,28 @@
1
1
  language: ruby
2
2
 
3
3
  env:
4
- - "RAILS_VERSION=3.2.0"
5
- - "RAILS_VERSION=4.0.0"
6
- - "RAILS_VERSION=4.1.1"
7
- - "RAILS_VERSION=4.2.4"
4
+ - "RAILS_VERSION=3.2"
5
+ - "RAILS_VERSION=4.0"
6
+ - "RAILS_VERSION=4.1"
7
+ - "RAILS_VERSION=4.2"
8
8
  - "RAILS_VERSION=master"
9
9
 
10
10
  rvm:
11
- - 1.9.3
12
11
  - 2.0
13
12
  - 2.1
14
- - 2.2
13
+ - 2.2.2
15
14
 
16
15
  matrix:
17
16
  allow_failures:
18
17
  - env: "RAILS_VERSION=master"
18
+ exclude:
19
+ - rvm: 2.0
20
+ env: RAILS_VERSION=master
21
+ - rvm: 2.1
22
+ env: RAILS_VERSION=master
23
+
24
+ before_install:
25
+ - gem update bundler
19
26
 
20
27
  before_script:
21
28
  - bundle exec rake app:db:migrate
@@ -0,0 +1,109 @@
1
+ # Change Log
2
+
3
+ ## [Unreleased](https://github.com/houdini/two_factor_authentication/tree/HEAD)
4
+
5
+ [Full Changelog](https://github.com/houdini/two_factor_authentication/compare/v1.1.4...HEAD)
6
+
7
+ **Closed issues:**
8
+
9
+ - How should I integrate Devise two factor authentication with custom sessions controller? [\#60](https://github.com/Houdini/two_factor_authentication/issues/60)
10
+
11
+ **Merged pull requests:**
12
+
13
+ - Update bundler on Travis before installing gems [\#63](https://github.com/Houdini/two_factor_authentication/pull/63) ([monfresh](https://github.com/monfresh))
14
+ - Add support for OTP secret key encryption [\#62](https://github.com/Houdini/two_factor_authentication/pull/62) ([monfresh](https://github.com/monfresh))
15
+ - Allow executing code after sign in and before sign out [\#61](https://github.com/Houdini/two_factor_authentication/pull/61) ([monfresh](https://github.com/monfresh))
16
+
17
+ ## [v1.1.4](https://github.com/houdini/two_factor_authentication/tree/v1.1.4) (2016-01-01)
18
+ [Full Changelog](https://github.com/houdini/two_factor_authentication/compare/v1.1.3...v1.1.4)
19
+
20
+ **Closed issues:**
21
+
22
+ - Old OTP can be used after a new one has been generated [\#59](https://github.com/Houdini/two_factor_authentication/issues/59)
23
+ - Do we have any two\_factor\_method like authenticate\_user! [\#58](https://github.com/Houdini/two_factor_authentication/issues/58)
24
+ - Configuration [\#57](https://github.com/Houdini/two_factor_authentication/issues/57)
25
+
26
+ **Merged pull requests:**
27
+
28
+ - Abstract logic for two factor success and fail into separate methods.… [\#56](https://github.com/Houdini/two_factor_authentication/pull/56) ([kpheasey](https://github.com/kpheasey))
29
+ - Move require rotp library to the file where it is used [\#55](https://github.com/Houdini/two_factor_authentication/pull/55) ([gkopylov](https://github.com/gkopylov))
30
+ - Add support for remembering a user's 2FA session in a cookie [\#54](https://github.com/Houdini/two_factor_authentication/pull/54) ([boffbowsh](https://github.com/boffbowsh))
31
+ - Test against Ruby 2.2 and Rails 4.2 [\#53](https://github.com/Houdini/two_factor_authentication/pull/53) ([boffbowsh](https://github.com/boffbowsh))
32
+ - Eliminates appended '?' to redirects that have no query string [\#46](https://github.com/Houdini/two_factor_authentication/pull/46) ([daveriess](https://github.com/daveriess))
33
+
34
+ ## [v1.1.3](https://github.com/houdini/two_factor_authentication/tree/v1.1.3) (2014-12-14)
35
+ [Full Changelog](https://github.com/houdini/two_factor_authentication/compare/list...v1.1.3)
36
+
37
+ **Closed issues:**
38
+
39
+ - rails g two\_factor\_authentication MODEL does not append .rb to end of migration [\#40](https://github.com/Houdini/two_factor_authentication/issues/40)
40
+
41
+ **Merged pull requests:**
42
+
43
+ - Allows length of OTP to be configured [\#44](https://github.com/Houdini/two_factor_authentication/pull/44) ([amoose](https://github.com/amoose))
44
+ - Missing translation. [\#43](https://github.com/Houdini/two_factor_authentication/pull/43) ([sadfuzzy](https://github.com/sadfuzzy))
45
+ - Preserve query parameters in \_return\_to for redirect. [\#42](https://github.com/Houdini/two_factor_authentication/pull/42) ([omb-awong](https://github.com/omb-awong))
46
+ - Add file extension to ActiveRecord generator [\#41](https://github.com/Houdini/two_factor_authentication/pull/41) ([jackturnbull](https://github.com/jackturnbull))
47
+
48
+ ## [list](https://github.com/houdini/two_factor_authentication/tree/list) (2014-07-14)
49
+ [Full Changelog](https://github.com/houdini/two_factor_authentication/compare/v1.1.2...list)
50
+
51
+ ## [v1.1.2](https://github.com/houdini/two_factor_authentication/tree/v1.1.2) (2014-07-14)
52
+ [Full Changelog](https://github.com/houdini/two_factor_authentication/compare/v1.1.1...v1.1.2)
53
+
54
+ **Closed issues:**
55
+
56
+ - NoMethodError \(undefined method `scan' for nil:NilClass\) [\#37](https://github.com/Houdini/two_factor_authentication/issues/37)
57
+
58
+ **Merged pull requests:**
59
+
60
+ - Updated readme with rake task to update existing users with OTP secret k... [\#39](https://github.com/Houdini/two_factor_authentication/pull/39) ([Znow](https://github.com/Znow))
61
+ - Updated readme with view overriding [\#38](https://github.com/Houdini/two_factor_authentication/pull/38) ([Znow](https://github.com/Znow))
62
+
63
+ ## [v1.1.1](https://github.com/houdini/two_factor_authentication/tree/v1.1.1) (2014-05-31)
64
+ [Full Changelog](https://github.com/houdini/two_factor_authentication/compare/v1.1...v1.1.1)
65
+
66
+ **Closed issues:**
67
+
68
+ - Override views [\#36](https://github.com/Houdini/two_factor_authentication/issues/36)
69
+ - NoMethodError in Devise::TwoFactorAuthenticationController\#update [\#30](https://github.com/Houdini/two_factor_authentication/issues/30)
70
+
71
+ **Merged pull requests:**
72
+
73
+ - Use Strings and not Symbols for keys when storing variable in warden session [\#35](https://github.com/Houdini/two_factor_authentication/pull/35) ([karolsarnacki](https://github.com/karolsarnacki))
74
+ - Chore/extract reused hash key [\#34](https://github.com/Houdini/two_factor_authentication/pull/34) ([rud](https://github.com/rud))
75
+ - Pad OTP codes with less than 6 digits [\#31](https://github.com/Houdini/two_factor_authentication/pull/31) ([brissmyr](https://github.com/brissmyr))
76
+
77
+ ## [v1.1](https://github.com/houdini/two_factor_authentication/tree/v1.1) (2014-04-16)
78
+ **Closed issues:**
79
+
80
+ - Update [\#15](https://github.com/Houdini/two_factor_authentication/issues/15)
81
+ - Data in formats other than HTML left unprotected [\#6](https://github.com/Houdini/two_factor_authentication/issues/6)
82
+ - Wordlists [\#5](https://github.com/Houdini/two_factor_authentication/issues/5)
83
+ - devise - wrong number of arguments \(1 for 0\) [\#3](https://github.com/Houdini/two_factor_authentication/issues/3)
84
+ - gem? [\#1](https://github.com/Houdini/two_factor_authentication/issues/1)
85
+
86
+ **Merged pull requests:**
87
+
88
+ - added is\_fully\_authenticated helper for current version [\#28](https://github.com/Houdini/two_factor_authentication/pull/28) ([edg3r](https://github.com/edg3r))
89
+ - Adds integration spec to ensure authentication code is sent on sign in [\#27](https://github.com/Houdini/two_factor_authentication/pull/27) ([rossta](https://github.com/rossta))
90
+ - ensure return\_to location is properly stored [\#26](https://github.com/Houdini/two_factor_authentication/pull/26) ([rossta](https://github.com/rossta))
91
+ - travis badge in README [\#25](https://github.com/Houdini/two_factor_authentication/pull/25) ([rossta](https://github.com/rossta))
92
+ - Integration specs [\#24](https://github.com/Houdini/two_factor_authentication/pull/24) ([rossta](https://github.com/rossta))
93
+ - README updates [\#23](https://github.com/Houdini/two_factor_authentication/pull/23) ([rossta](https://github.com/rossta))
94
+ - extract method \#max\_login\_attempts [\#22](https://github.com/Houdini/two_factor_authentication/pull/22) ([rossta](https://github.com/rossta))
95
+ - extract method \#populate\_otp\_column [\#21](https://github.com/Houdini/two_factor_authentication/pull/21) ([rossta](https://github.com/rossta))
96
+ - specs for Model\#provisioning\_uri [\#20](https://github.com/Houdini/two_factor_authentication/pull/20) ([rossta](https://github.com/rossta))
97
+ - Provide options for \#provisioning\_uri [\#19](https://github.com/Houdini/two_factor_authentication/pull/19) ([rossta](https://github.com/rossta))
98
+ - Use time-based authentication codes [\#16](https://github.com/Houdini/two_factor_authentication/pull/16) ([mattmueller](https://github.com/mattmueller))
99
+ - Add ru locales and locales for max\_limit\_reached view [\#13](https://github.com/Houdini/two_factor_authentication/pull/13) ([edg3r](https://github.com/edg3r))
100
+ - Update README.md [\#11](https://github.com/Houdini/two_factor_authentication/pull/11) ([edg3r](https://github.com/edg3r))
101
+ - Changed route from user to admin\_user [\#10](https://github.com/Houdini/two_factor_authentication/pull/10) ([ilanstern](https://github.com/ilanstern))
102
+ - Changed :notice to :error when setting flash message on attempt failure. [\#9](https://github.com/Houdini/two_factor_authentication/pull/9) ([johnmichaelbradley](https://github.com/johnmichaelbradley))
103
+ - Typo and punctuation corrections. [\#8](https://github.com/Houdini/two_factor_authentication/pull/8) ([johnmichaelbradley](https://github.com/johnmichaelbradley))
104
+ - Respond with 401 for request non-HTML requests [\#7](https://github.com/Houdini/two_factor_authentication/pull/7) ([WojtekKruszewski](https://github.com/WojtekKruszewski))
105
+ - need\_two\_factor\_authentication? method should accept request param. [\#4](https://github.com/Houdini/two_factor_authentication/pull/4) ([VladimirMikhailov](https://github.com/VladimirMikhailov))
106
+ - Add generators to make it easier to install and fix deprecation warnings [\#2](https://github.com/Houdini/two_factor_authentication/pull/2) ([carvil](https://github.com/carvil))
107
+
108
+
109
+ \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in devise_ip_filter.gemspec
4
4
  gemspec
@@ -20,6 +20,12 @@ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.2.0')
20
20
  gem "test-unit", "~> 3.0"
21
21
  end
22
22
 
23
+ group :test, :development do
24
+ gem 'sqlite3'
25
+ end
26
+
23
27
  group :test do
24
- gem "sqlite3"
28
+ gem 'rack_session_access'
29
+ gem 'ammeter'
30
+ gem 'pry'
25
31
  end
data/README.md CHANGED
@@ -5,11 +5,12 @@
5
5
 
6
6
  ## Features
7
7
 
8
- * control sms code pattern
9
- * configure max login attempts
10
- * per user level control if he really need two factor authentication
11
- * your own sms logic
8
+ * configurable OTP code digit length
9
+ * configurable max login attempts
10
+ * customizable logic to determine if a user needs two factor authentication
11
+ * customizable logic for sending the OTP code to the user
12
12
  * configurable period where users won't be asked for 2FA again
13
+ * option to encrypt the OTP secret key in the database, with iv and salt
13
14
 
14
15
  ## Configuration
15
16
 
@@ -23,62 +24,73 @@ Once that's done, run:
23
24
 
24
25
  bundle install
25
26
 
26
- ### Automatic installation
27
+ Note that Ruby 2.0 or greater is required.
27
28
 
28
- In order to add two factor authentication to a model, run the command:
29
+ ### Installation
29
30
 
30
- bundle exec rails g two_factor_authentication MODEL
31
-
32
- Where MODEL is your model name (e.g. User or Admin). This generator will add `:two_factor_authenticatable` to your model
33
- and create a migration in `db/migrate/`, which will add `:otp_secret_key` and `:second_factor_attempts_count` to your table.
34
- Finally, run the migration with:
31
+ #### Automatic initial setup
32
+ To set up the model and database migration file automatically, run the
33
+ following command:
35
34
 
36
- bundle exec rake db:migrate
35
+ bundle exec rails g two_factor_authentication MODEL
37
36
 
38
- Add the following line to your model to fully enable two-factor auth:
37
+ Where MODEL is your model name (e.g. User or Admin). This generator will add
38
+ `:two_factor_authenticatable` to your model's Devise options and create a
39
+ migration in `db/migrate/`, which will add the following columns to your table:
39
40
 
40
- has_one_time_password
41
+ - `:second_factor_attempts_count`
42
+ - `:encrypted_otp_secret_key`
43
+ - `:encrypted_otp_secret_key_iv`
44
+ - `:encrypted_otp_secret_key_salt`
41
45
 
42
- Set config values, if desired:
46
+ #### Manual initial setup
47
+ If you prefer to set up the model and migration manually, add the
48
+ `:two_factor_authentication` option to your existing devise options, such as:
43
49
 
44
50
  ```ruby
45
- config.max_login_attempts = 3 # Maximum second factor attempts count
46
- config.allowed_otp_drift_seconds = 30 # Allowed time drift
47
- config.otp_length = 6 # OTP code length
48
- config.remember_otp_session_for_seconds = 30.days # Time before browser has to enter OTP code again
51
+ devise :database_authenticatable, :registerable, :recoverable, :rememberable,
52
+ :trackable, :validatable, :two_factor_authenticatable
49
53
  ```
50
54
 
51
- Override the method to send one-time passwords in your model, this is automatically called when a user logs in:
55
+ Then create your migration file using the Rails generator, such as:
52
56
 
53
- ```ruby
54
- def send_two_factor_authentication_code
55
- # use Model#otp_code and send via SMS, etc.
56
- end
57
+ ```
58
+ rails g migration AddTwoFactorFieldsToUsers second_factor_attempts_count:integer encrypted_otp_secret_key:string:index encrypted_otp_secret_key_iv:string encrypted_otp_secret_key_salt:string
57
59
  ```
58
60
 
59
- ### Manual installation
60
-
61
- To manually enable two factor authentication for the User model, you should add two_factor_authentication to your devise line, like:
61
+ Open your migration file (it will be in the `db/migrate` directory and will be
62
+ named something like `20151230163930_add_two_factor_fields_to_users.rb`), and
63
+ add `unique: true` to the `add_index` line so that it looks like this:
62
64
 
63
65
  ```ruby
64
- devise :database_authenticatable, :registerable,
65
- :recoverable, :rememberable, :trackable, :validatable, :two_factor_authenticatable
66
+ add_index :users, :encrypted_otp_secret_key, unique: true
66
67
  ```
68
+ Save the file.
69
+
70
+ #### Complete the setup
71
+ Run the migration with:
72
+
73
+ bundle exec rake db:migrate
67
74
 
68
75
  Add the following line to your model to fully enable two-factor auth:
69
76
 
70
- has_one_time_password
77
+ has_one_time_password(encrypted: true)
71
78
 
72
- Set config values to devise.rb, if desired:
79
+ Set config values in `config/initializers/devise.rb`:
73
80
 
74
81
  ```ruby
75
- config.max_login_attempts = 3 # Maximum second factor attempts count
76
- config.allowed_otp_drift_seconds = 30 # Allowed time drift
82
+ config.max_login_attempts = 3 # Maximum second factor attempts count.
83
+ config.allowed_otp_drift_seconds = 30 # Allowed time drift between client and server.
77
84
  config.otp_length = 6 # OTP code length
78
- config.remember_otp_session_for_seconds = 30.days # Time before browser has to enter OTP code again
85
+ config.remember_otp_session_for_seconds = 30.days # Time before browser has to enter OTP code again. Default is 0.
86
+ config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY']
79
87
  ```
88
+ The `otp_secret_encryption_key` must be a random key that is not stored in the
89
+ DB, and is not checked in to your repo. It is recommended to store it in an
90
+ environment variable, and you can generate it with `bundle exec rake secret`.
80
91
 
81
- Override the method to send one-time passwords in your model, this is automatically called when a user logs in:
92
+ Override the method to send one-time passwords in your model. This is
93
+ automatically called when a user logs in:
82
94
 
83
95
  ```ruby
84
96
  def send_two_factor_authentication_code
@@ -86,10 +98,10 @@ def send_two_factor_authentication_code
86
98
  end
87
99
  ```
88
100
 
89
-
90
101
  ### Customisation and Usage
91
102
 
92
- By default second factor authentication enabled for each user, you can change it with this method in your User model:
103
+ By default, second factor authentication is required for each user. You can
104
+ change that by overriding the following method in your model:
93
105
 
94
106
  ```ruby
95
107
  def need_two_factor_authentication?(request)
@@ -97,19 +109,29 @@ def need_two_factor_authentication?(request)
97
109
  end
98
110
  ```
99
111
 
100
- this will disable two factor authentication for local users
112
+ In the example above, two factor authentication will not be required for local
113
+ users.
101
114
 
102
- This gem is compatible with Google Authenticator (https://support.google.com/accounts/answer/1066447?hl=en). You can generate provisioning uris by invoking the following method on your model:
115
+ This gem is compatible with [Google Authenticator](https://support.google.com/accounts/answer/1066447?hl=en).
116
+ You can generate provisioning uris by invoking the following method on your model:
103
117
 
104
- user.provisioning_uri #This assumes a user model with an email attributes
118
+ ```ruby
119
+ user.provisioning_uri # This assumes a user model with an email attribute
120
+ ```
105
121
 
106
- This provisioning uri can then be turned in to a QR code if desired so that users may add the app to Google Authenticator easily. Once this is done they may retrieve a one-time password directly from the Google Authenticator app as well as through whatever method you define in `send_two_factor_authentication_code`
122
+ This provisioning uri can then be turned in to a QR code if desired so that
123
+ users may add the app to Google Authenticator easily. Once this is done, they
124
+ may retrieve a one-time password directly from the Google Authenticator app as
125
+ well as through whatever method you define in
126
+ `send_two_factor_authentication_code`.
107
127
 
108
128
  #### Overriding the view
109
129
 
110
- The default view that shows the form can be overridden by first adding a folder named: "two_factor_authentication" inside "app/views/devise", in here you want to create a "show.html.erb" view.
130
+ The default view that shows the form can be overridden by adding a
131
+ file named `show.html.erb` (or `show.html.haml` if you prefer HAML)
132
+ inside `app/views/devise/two_factor_authentication/` and customizing it.
133
+ Below is an example using ERB:
111
134
 
112
- The full path should be "app/views/devise/two_factor_authentication/show.html.erb"
113
135
 
114
136
  ```html
115
137
  <h2>Hi, you received a code by email, please enter it below, thanks!</h2>
@@ -125,22 +147,128 @@ The full path should be "app/views/devise/two_factor_authentication/show.html.er
125
147
 
126
148
  #### Updating existing users with OTP secret key
127
149
 
128
- If you have existing users that needs to be provided with a OTP secret key, so they can take benefit of the two factor authentication, create a rake. It could look like this one below:
150
+ If you have existing users that need to be provided with a OTP secret key, so
151
+ they can use two factor authentication, create a rake task. It could look like this one below:
129
152
 
130
153
  ```ruby
131
- desc "rake task to update users with otp secret key"
154
+ desc 'rake task to update users with otp secret key'
132
155
  task :update_users_with_otp_secret_key => :environment do
133
- users = User.all
134
-
135
- users.each do |user|
136
- key = ROTP::Base32.random_base32
137
- user.update_attributes(:otp_secret_key => key)
138
- user.save
139
- puts "Rake[:update_users_with_otp_secret_key] => User '#{user.email}' OTP secret key set to '#{key}'"
140
- end
156
+ User.find_each do |user|
157
+ user.otp_secret_key = ROTP::Base32.random_base32
158
+ user.save!
159
+ puts "Rake[:update_users_with_otp_secret_key] => OTP secret key set to '#{key}' for User '#{user.email}'"
160
+ end
161
+ end
162
+ ```
163
+ Then run the task with `bundle exec rake update_users_with_otp_secret_key`
164
+
165
+ #### Adding the OTP encryption option to an existing app
166
+
167
+ If you've already been using this gem, and want to start encrypting the OTP
168
+ secret key in the database (recommended), you'll need to perform the following
169
+ steps:
170
+
171
+ 1. Generate a migration to add the necessary columns to your model's table:
172
+
173
+ ```
174
+ rails g migration AddEncryptionFieldsToUsers encrypted_otp_secret_key:string:index encrypted_otp_secret_key_iv:string encrypted_otp_secret_key_salt:string
175
+ ```
176
+
177
+ Open your migration file (it will be in the `db/migrate` directory and will be
178
+ named something like `20151230163930_add_encryption_fields_to_users.rb`), and
179
+ add `unique: true` to the `add_index` line so that it looks like this:
180
+
181
+ ```ruby
182
+ add_index :users, :encrypted_otp_secret_key, unique: true
183
+ ```
184
+ Save the file.
185
+
186
+ 2. Run the migration: `bundle exec rake db:migrate`
187
+
188
+ 2. Update the gem: `bundle update two_factor_authentication`
189
+
190
+ 3. Add `encrypted: true` to `has_one_time_password` in your model.
191
+ For example: `has_one_time_password(encrypted: true)`
192
+
193
+ 4. Generate a migration to populate the new encryption fields:
194
+ ```
195
+ rails g migration PopulateEncryptedOtpFields
196
+ ```
197
+
198
+ Open the generated file, and replace its contents with the following:
199
+ ```ruby
200
+ class PopulateEncryptedOtpFields < ActiveRecord::Migration
201
+ def up
202
+ User.reset_column_information
203
+
204
+ User.find_each do |user|
205
+ user.otp_secret_key = user.read_attribute('otp_secret_key')
206
+ user.save!
207
+ end
208
+ end
209
+
210
+ def down
211
+ User.reset_column_information
212
+
213
+ User.find_each do |user|
214
+ user.otp_secret_key = ROTP::Base32.random_base32
215
+ user.save!
216
+ end
217
+ end
218
+ end
219
+ ```
220
+
221
+ 5. Generate a migration to remove the `:otp_secret_key` column:
222
+ ```
223
+ rails g migration RemoveOtpSecretKeyFromUsers otp_secret_key:string
224
+ ```
225
+
226
+ 6. Run the migrations: `bundle exec rake db:migrate`
227
+
228
+ If, for some reason, you want to switch back to the old non-encrypted version,
229
+ use these steps:
230
+
231
+ 1. Remove `(encrypted: true)` from `has_one_time_password`
232
+
233
+ 2. Roll back the last 3 migrations (assuming you haven't added any new ones
234
+ after them):
235
+ ```
236
+ bundle exec rake db:rollback STEP=3
237
+ ```
238
+
239
+ #### Executing some code after the user signs in and before they sign out
240
+
241
+ In some cases, you might want to perform some action right after the user signs
242
+ in, but before the OTP is sent, and also right before the user signs out. One
243
+ scenario where you would need this is if you are requiring users to confirm
244
+ their phone number first before they can receive an OTP. If they enter a wrong
245
+ number, then sign out or close the browser before they confirm, they won't be
246
+ able to confirm their real number. To solve this problem, we need to be able to
247
+ reset their unconfirmed number before they sign out or sign in, and before the
248
+ OTP code is sent.
249
+
250
+ To define this action, create a `#{user.class}OtpSender` class that takes the
251
+ current user as its parameter, and defines a `#reset_otp_state` instance method.
252
+ For example, if your user's class is `User`, you would create a `UserOtpSender`
253
+ class, like this:
254
+ ```ruby
255
+ class UserOtpSender
256
+ def initialize(user)
257
+ @user = user
258
+ end
259
+
260
+ def reset_otp_state
261
+ if @user.unconfirmed_mobile.present?
262
+ @user.update(unconfirmed_mobile: nil)
263
+ end
264
+ end
141
265
  end
142
266
  ```
267
+ If you have different types of users in your app (for example, User and Admin),
268
+ and you need different logic for each type of user, create a second class for
269
+ your admin user, such as `AdminOtpSender`, with its own logic for
270
+ `#reset_otp_state`.
143
271
 
144
- ### Example
272
+ ### Example App
145
273
 
146
274
  [TwoFactorAuthenticationExample](https://github.com/Houdini/TwoFactorAuthenticationExample)