two_factor_authentication 1.1.4 → 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
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)