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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +21 -0
- data/.github/workflows/gem-push.yml +42 -0
- data/.gitignore +23 -0
- data/.rubocop.yml +295 -0
- data/.travis.yml +28 -0
- data/CHANGELOG.md +119 -0
- data/Gemfile +32 -0
- data/LICENSE +19 -0
- data/README.md +322 -0
- data/Rakefile +12 -0
- data/app/controllers/devise/totp_controller.rb +79 -0
- data/app/controllers/devise/two_factor_authentication_controller.rb +84 -0
- data/app/views/devise/two_factor_authentication/max_login_attempts_reached.html.erb +3 -0
- data/app/views/devise/two_factor_authentication/new.html.erb +14 -0
- data/app/views/devise/two_factor_authentication/show.html.erb +19 -0
- data/config/locales/de.yml +8 -0
- data/config/locales/en.yml +8 -0
- data/config/locales/es.yml +8 -0
- data/config/locales/fr.yml +8 -0
- data/config/locales/ru.yml +8 -0
- data/devise-multi-factor.gemspec +40 -0
- data/lib/devise-multi-factor.rb +1 -0
- data/lib/devise_multi_factor.rb +56 -0
- data/lib/devise_multi_factor/controllers/helpers.rb +57 -0
- data/lib/devise_multi_factor/hooks/two_factor_authenticatable.rb +17 -0
- data/lib/devise_multi_factor/models/totp_enrollable.rb +7 -0
- data/lib/devise_multi_factor/models/two_factor_authenticatable.rb +142 -0
- data/lib/devise_multi_factor/orm/active_record.rb +14 -0
- data/lib/devise_multi_factor/rails.rb +7 -0
- data/lib/devise_multi_factor/routes.rb +15 -0
- data/lib/devise_multi_factor/schema.rb +23 -0
- data/lib/devise_multi_factor/version.rb +3 -0
- data/lib/generators/active_record/devise_multi_factor_generator.rb +13 -0
- data/lib/generators/active_record/templates/migration.rb +11 -0
- data/lib/generators/devise_multi_factor/devise_multi_factor_generator.rb +17 -0
- data/spec/controllers/two_factor_authentication_controller_spec.rb +41 -0
- data/spec/features/two_factor_authenticatable_spec.rb +237 -0
- data/spec/generators/active_record/devise_multi_factor_generator_spec.rb +34 -0
- data/spec/lib/devise_multi_factor/models/two_factor_authenticatable_spec.rb +282 -0
- data/spec/rails_app/.gitignore +3 -0
- data/spec/rails_app/README.md +3 -0
- data/spec/rails_app/Rakefile +7 -0
- data/spec/rails_app/app/assets/config/manifest.js +2 -0
- data/spec/rails_app/app/assets/javascripts/application.js +1 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +4 -0
- data/spec/rails_app/app/controllers/application_controller.rb +3 -0
- data/spec/rails_app/app/controllers/home_controller.rb +10 -0
- data/spec/rails_app/app/helpers/application_helper.rb +8 -0
- data/spec/rails_app/app/mailers/.gitkeep +0 -0
- data/spec/rails_app/app/models/.gitkeep +0 -0
- data/spec/rails_app/app/models/admin.rb +6 -0
- data/spec/rails_app/app/models/encrypted_user.rb +7 -0
- data/spec/rails_app/app/models/guest_user.rb +7 -0
- data/spec/rails_app/app/models/test_user.rb +38 -0
- data/spec/rails_app/app/models/user.rb +18 -0
- data/spec/rails_app/app/views/home/dashboard.html.erb +11 -0
- data/spec/rails_app/app/views/home/index.html.erb +3 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +20 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +61 -0
- data/spec/rails_app/config/boot.rb +10 -0
- data/spec/rails_app/config/database.yml +19 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/development.rb +28 -0
- data/spec/rails_app/config/environments/production.rb +68 -0
- data/spec/rails_app/config/environments/test.rb +41 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/cookies_serializer.rb +3 -0
- data/spec/rails_app/config/initializers/devise.rb +258 -0
- data/spec/rails_app/config/initializers/inflections.rb +15 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +8 -0
- data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails_app/config/locales/devise.en.yml +59 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +65 -0
- data/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb +42 -0
- data/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb +17 -0
- data/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb +7 -0
- data/spec/rails_app/db/migrate/20151224171231_add_encrypted_columns_to_user.rb +7 -0
- data/spec/rails_app/db/migrate/20151224180310_populate_otp_column.rb +19 -0
- data/spec/rails_app/db/migrate/20151228230340_remove_otp_secret_key_from_user.rb +5 -0
- data/spec/rails_app/db/migrate/20160209032439_devise_create_admins.rb +42 -0
- data/spec/rails_app/db/schema.rb +55 -0
- data/spec/rails_app/lib/assets/.gitkeep +0 -0
- data/spec/rails_app/lib/sms_provider.rb +17 -0
- data/spec/rails_app/public/404.html +26 -0
- data/spec/rails_app/public/422.html +26 -0
- data/spec/rails_app/public/500.html +25 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/authenticated_model_helper.rb +29 -0
- data/spec/support/capybara.rb +3 -0
- data/spec/support/controller_helper.rb +16 -0
- data/spec/support/features_spec_helper.rb +42 -0
- data/spec/support/sms_provider.rb +5 -0
- data/spec/support/totp_helper.rb +11 -0
- metadata +315 -0
data/Gemfile
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
source 'https://rubygems.org'
|
|
2
|
+
|
|
3
|
+
# Specify your gem's dependencies in devise_ip_filter.gemspec
|
|
4
|
+
gemspec
|
|
5
|
+
|
|
6
|
+
rails_version = ENV["RAILS_VERSION"] || "default"
|
|
7
|
+
|
|
8
|
+
rails = case rails_version
|
|
9
|
+
when "master"
|
|
10
|
+
{github: "rails/rails"}
|
|
11
|
+
when "default"
|
|
12
|
+
"~> 5.2"
|
|
13
|
+
else
|
|
14
|
+
"~> #{rails_version}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
gem "rails", rails
|
|
18
|
+
|
|
19
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.2.0')
|
|
20
|
+
gem "test-unit", "~> 3.0"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
group :test, :development do
|
|
24
|
+
gem 'byebug'
|
|
25
|
+
gem 'lockbox'
|
|
26
|
+
gem 'sqlite3'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
group :test do
|
|
30
|
+
gem 'rack_session_access'
|
|
31
|
+
gem 'ammeter'
|
|
32
|
+
end
|
data/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (C) 2012 Dmitrii Golub
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
5
|
+
the Software without restriction, including without limitation the rights to
|
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
8
|
+
so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# Devise Multi Factor
|
|
2
|
+
|
|
3
|
+
This is a fork of the gem [two_factor_authentication](https://github.com/Houdini/two_factor_authentication) by Houdini. The name has been changed to `devise-multi-factor` to avoid conflicts of both gems.
|
|
4
|
+
This version uses [Lockbox](https://github.com/ankane/lockbox) to manage the encryption of the key.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
* Support for 2 types of OTP codes
|
|
9
|
+
1. Codes delivered directly to the user
|
|
10
|
+
2. TOTP (Google Authenticator) codes based on a shared secret (HMAC)
|
|
11
|
+
* Configurable OTP code digit length
|
|
12
|
+
* Configurable max login attempts
|
|
13
|
+
* Customizable logic to determine if a user needs two factor authentication
|
|
14
|
+
* Configurable period where users won't be asked for 2FA again
|
|
15
|
+
* TOTP secret key automatically encrypted in DB
|
|
16
|
+
* Enroll form for TOTP using `:totp_enrollable` on your model
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
### Initial Setup
|
|
21
|
+
|
|
22
|
+
In a Rails environment, require the gem in your Gemfile:
|
|
23
|
+
|
|
24
|
+
gem 'devise-multi-factor'
|
|
25
|
+
|
|
26
|
+
Once that's done, run:
|
|
27
|
+
|
|
28
|
+
bundle install
|
|
29
|
+
|
|
30
|
+
Note that Ruby 2.1 or greater is required.
|
|
31
|
+
|
|
32
|
+
### Installation
|
|
33
|
+
|
|
34
|
+
#### Automatic initial setup
|
|
35
|
+
|
|
36
|
+
To set up the model and database migration file automatically, run the
|
|
37
|
+
following command:
|
|
38
|
+
|
|
39
|
+
bundle exec rails g devise_multi_factor MODEL
|
|
40
|
+
|
|
41
|
+
Where MODEL is your model name (e.g. User or Admin). This generator will add
|
|
42
|
+
`:two_factor_authenticatable` to your model's Devise options and create a
|
|
43
|
+
migration in `db/migrate/`, which will add the following columns to your table:
|
|
44
|
+
|
|
45
|
+
- `:second_factor_attempts_count`
|
|
46
|
+
- `:encrypted_otp_secret_key`
|
|
47
|
+
- `:direct_otp`
|
|
48
|
+
- `:direct_otp_sent_at`
|
|
49
|
+
- `:totp_timestamp`
|
|
50
|
+
|
|
51
|
+
#### Manual initial setup
|
|
52
|
+
|
|
53
|
+
If you prefer to set up the model and migration manually, add the
|
|
54
|
+
`:two_factor_authenticatable` option to your existing devise options, such as:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
devise :database_authenticatable, :registerable, :recoverable, :rememberable,
|
|
58
|
+
:trackable, :validatable, :two_factor_authenticatable
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then create your migration file using the Rails generator, such as:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
rails g migration AddTwoFactorFieldsToUsers second_factor_attempts_count:integer encrypted_otp_secret_key:string direct_otp:string direct_otp_sent_at:datetime totp_timestamp:integer
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### Complete the setup
|
|
68
|
+
|
|
69
|
+
Run the migration with:
|
|
70
|
+
|
|
71
|
+
bundle exec rake db:migrate
|
|
72
|
+
|
|
73
|
+
Add the following line to your model to fully enable two-factor auth:
|
|
74
|
+
|
|
75
|
+
has_one_time_password
|
|
76
|
+
|
|
77
|
+
Set config values in `config/initializers/devise.rb`:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
config.max_login_attempts = 3 # Maximum second factor attempts count.
|
|
81
|
+
config.allowed_otp_drift_seconds = 30 # Allowed TOTP time drift between client and server.
|
|
82
|
+
config.otp_length = 6 # TOTP code length
|
|
83
|
+
config.direct_otp_valid_for = 5.minutes # Time before direct OTP becomes invalid
|
|
84
|
+
config.direct_otp_length = 6 # Direct OTP code length
|
|
85
|
+
config.remember_otp_session_for_seconds = 30.days # Time before browser has to perform 2fA again. Default is 0.
|
|
86
|
+
config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY']
|
|
87
|
+
config.second_factor_resource_id = 'id' # Field or method name used to set value for 2fA remember cookie
|
|
88
|
+
config.delete_cookie_on_logout = false # Delete cookie when user signs out, to force 2fA again on login
|
|
89
|
+
```
|
|
90
|
+
The `otp_secret_encryption_key` must be a random key that is not stored in the
|
|
91
|
+
DB, and is not checked in to your repo. It is recommended to store it in an
|
|
92
|
+
environment variable, and you can generate it with `bundle exec rake secret`.
|
|
93
|
+
|
|
94
|
+
Override the method in your model in order to send direct OTP codes. This is
|
|
95
|
+
automatically called when a user logs in unless they have TOTP enabled (see
|
|
96
|
+
below):
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
def send_two_factor_authentication_code(code)
|
|
100
|
+
# Send code via SMS, etc.
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Customisation and Usage
|
|
105
|
+
|
|
106
|
+
By default, second factor authentication is required for each user. You can
|
|
107
|
+
change that by overriding the following method in your model:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
def need_two_factor_authentication?(request)
|
|
111
|
+
request.ip != '127.0.0.1'
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
In the example above, two factor authentication will not be required for local
|
|
116
|
+
users.
|
|
117
|
+
|
|
118
|
+
This gem is compatible with [Google Authenticator](https://support.google.com/accounts/answer/1066447?hl=en).
|
|
119
|
+
To enable this a shared secret must be generated by invoking the following
|
|
120
|
+
method on your model:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
user.generate_totp_secret
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This must then be shared via a provisioning uri:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
user.provisioning_uri # This assumes a user model with an email attribute
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This provisioning uri can then be turned in to a QR code if desired so that
|
|
133
|
+
users may add the app to Google Authenticator easily. Once this is done, they
|
|
134
|
+
may retrieve a one-time password directly from the Google Authenticator app.
|
|
135
|
+
|
|
136
|
+
#### Overriding the view
|
|
137
|
+
|
|
138
|
+
The default view that shows the form can be overridden by adding a
|
|
139
|
+
file named `show.html.erb` (or `show.html.haml` if you prefer HAML)
|
|
140
|
+
inside `app/views/devise/two_factor_authentication/` and customizing it.
|
|
141
|
+
Below is an example using ERB:
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<h2>Hi, you received a code by email, please enter it below, thanks!</h2>
|
|
146
|
+
|
|
147
|
+
<%= form_tag([resource_name, :two_factor_authentication], :method => :put) do %>
|
|
148
|
+
<%= text_field_tag :code %>
|
|
149
|
+
<%= submit_tag "Log in!" %>
|
|
150
|
+
<% end %>
|
|
151
|
+
|
|
152
|
+
<%= link_to "Sign out", destroy_user_session_path, :method => :delete %>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Upgrading from version 1.X to 2.X
|
|
156
|
+
|
|
157
|
+
The following database fields are new in version 2.
|
|
158
|
+
|
|
159
|
+
- `direct_otp`
|
|
160
|
+
- `direct_otp_sent_at`
|
|
161
|
+
- `totp_timestamp`
|
|
162
|
+
|
|
163
|
+
To add them, generate a migration such as:
|
|
164
|
+
|
|
165
|
+
$ rails g migration AddTwoFactorFieldsToUsers direct_otp:string direct_otp_sent_at:datetime totp_timestamp:timestamp
|
|
166
|
+
|
|
167
|
+
The `otp_secret_key` is only required for users who use TOTP (Google Authenticator) codes,
|
|
168
|
+
so unless it has been shared with the user it should be set to `nil`. The
|
|
169
|
+
following pseudo-code is an example of how this might be done:
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
User.find_each do |user| do
|
|
173
|
+
if !uses_authenticator_app(user)
|
|
174
|
+
user.otp_secret_key = nil
|
|
175
|
+
user.save!
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### Adding the TOTP encryption option to an existing app
|
|
181
|
+
|
|
182
|
+
If you've already been using this gem, and want to start encrypting the OTP
|
|
183
|
+
secret key in the database (recommended), you'll need to perform the following
|
|
184
|
+
steps:
|
|
185
|
+
|
|
186
|
+
1. Generate a migration to add the necessary columns to your model's table:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
rails g migration AddEncryptionFieldsToUsers encrypted_otp_secret_key:string
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Open your migration file (it will be in the `db/migrate` directory and will be
|
|
193
|
+
named something like `20151230163930_add_encryption_fields_to_users.rb`), and
|
|
194
|
+
add `unique: true` to the `add_index` line so that it looks like this:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
add_index :users, :encrypted_otp_secret_key, unique: true
|
|
198
|
+
```
|
|
199
|
+
Save the file.
|
|
200
|
+
|
|
201
|
+
2. Run the migration: `bundle exec rake db:migrate`
|
|
202
|
+
|
|
203
|
+
2. Update the gem: `bundle update two_factor_authentication`
|
|
204
|
+
|
|
205
|
+
3. Add `encrypted: true` to `has_one_time_password` in your model.
|
|
206
|
+
For example: `has_one_time_password(encrypted: true)`
|
|
207
|
+
|
|
208
|
+
4. Generate a migration to populate the new encryption fields:
|
|
209
|
+
```
|
|
210
|
+
rails g migration PopulateEncryptedOtpFields
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Open the generated file, and replace its contents with the following:
|
|
214
|
+
```ruby
|
|
215
|
+
class PopulateEncryptedOtpFields < ActiveRecord::Migration
|
|
216
|
+
def up
|
|
217
|
+
User.reset_column_information
|
|
218
|
+
|
|
219
|
+
User.find_each do |user|
|
|
220
|
+
user.otp_secret_key = user.read_attribute('otp_secret_key')
|
|
221
|
+
user.save!
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def down
|
|
226
|
+
User.reset_column_information
|
|
227
|
+
|
|
228
|
+
User.find_each do |user|
|
|
229
|
+
user.otp_secret_key = ROTP::Base32.random_base32
|
|
230
|
+
user.save!
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
5. Generate a migration to remove the `:otp_secret_key` column:
|
|
237
|
+
```
|
|
238
|
+
rails g migration RemoveOtpSecretKeyFromUsers otp_secret_key:string
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
6. Run the migrations: `bundle exec rake db:migrate`
|
|
242
|
+
|
|
243
|
+
If, for some reason, you want to switch back to the old non-encrypted version,
|
|
244
|
+
use these steps:
|
|
245
|
+
|
|
246
|
+
1. Remove `(encrypted: true)` from `has_one_time_password`
|
|
247
|
+
|
|
248
|
+
2. Roll back the last 3 migrations (assuming you haven't added any new ones
|
|
249
|
+
after them):
|
|
250
|
+
```
|
|
251
|
+
bundle exec rake db:rollback STEP=3
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### Critical Security Note! Add before_action to your user registration controllers
|
|
255
|
+
|
|
256
|
+
You should have a file registrations_controller.rb in your controllers folder
|
|
257
|
+
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!
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
class RegistrationsController < Devise::RegistrationsController
|
|
261
|
+
before_action :two_factor_authenticate!, except: [:new, :create, :cancel]
|
|
262
|
+
end
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
### Example App
|
|
267
|
+
(This gem is not 100% compatible with this example app)
|
|
268
|
+
[TwoFactorAuthenticationExample](https://github.com/Houdini/TwoFactorAuthenticationExample)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
### Example user actions
|
|
272
|
+
|
|
273
|
+
to use an ENV VAR for the 2FA encryption key:
|
|
274
|
+
|
|
275
|
+
config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY']
|
|
276
|
+
|
|
277
|
+
to set up TOTP for Google Authenticator for user:
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
current_user.otp_secret_key = current_user.generate_totp_secret
|
|
281
|
+
current_user.save!
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
( encrypted db fields are set upon user model save action,
|
|
285
|
+
rails c access relies on setting env var: OTP_SECRET_ENCRYPTION_KEY )
|
|
286
|
+
|
|
287
|
+
to check if user has input the correct code (from the QR display page)
|
|
288
|
+
before saving the user model:
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
current_user.authenticate_totp('123456')
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
additional note:
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
current_user.otp_secret_key
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
This returns the OTP secret key in plaintext for the user (if you have set the env var) in the console
|
|
301
|
+
the string used for generating the QR given to the user for their Google Auth is something like:
|
|
302
|
+
|
|
303
|
+
otpauth://totp/LABEL?secret=p6wwetjnkjnrcmpd (example secret used here)
|
|
304
|
+
|
|
305
|
+
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
|
|
306
|
+
|
|
307
|
+
this returns true or false with an allowed_otp_drift_seconds 'grace period'
|
|
308
|
+
|
|
309
|
+
to set TOTP to DISABLED for a user account:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
current_user.second_factor_attempts_count=nil
|
|
313
|
+
current_user.encrypted_otp_secret_key=nil
|
|
314
|
+
current_user.direct_otp=nil
|
|
315
|
+
current_user.direct_otp_sent_at=nil
|
|
316
|
+
current_user.totp_timestamp=nil
|
|
317
|
+
current_user.direct_otp=nil
|
|
318
|
+
current_user.otp_secret_key=nil
|
|
319
|
+
current_user.save! (if in ruby code instead of console)
|
|
320
|
+
current_user.direct_otp? => false
|
|
321
|
+
current_user.totp_enabled? => false
|
|
322
|
+
```
|
data/Rakefile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
2
|
+
|
|
3
|
+
APP_RAKEFILE = File.expand_path("../spec/rails_app/Rakefile", __FILE__)
|
|
4
|
+
load 'rails/tasks/engine.rake'
|
|
5
|
+
|
|
6
|
+
require 'rspec/core/rake_task'
|
|
7
|
+
|
|
8
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
|
9
|
+
task :default => :spec
|
|
10
|
+
|
|
11
|
+
# To test against a specific version of Rails
|
|
12
|
+
# export RAILS_VERSION=3.2.0; bundle update; rake
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'devise/version'
|
|
2
|
+
|
|
3
|
+
class Devise::TotpController < DeviseController
|
|
4
|
+
prepend_before_action :authenticate_scope!
|
|
5
|
+
before_action :two_factor_authenticate!
|
|
6
|
+
|
|
7
|
+
def new
|
|
8
|
+
@otp_secret = resource.generate_totp_secret
|
|
9
|
+
@otp_secret_signature = sign_otp_secret(@otp_secret)
|
|
10
|
+
render_enroll_form
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create
|
|
14
|
+
@otp_secret_signature = params[:otp_secret_signature]
|
|
15
|
+
@otp_secret = verify_otp_secret(@otp_secret_signature)
|
|
16
|
+
if resource.enroll_totp!(@otp_secret, params[:otp_attempt])
|
|
17
|
+
after_two_factor_enroll_success_for(resource)
|
|
18
|
+
else
|
|
19
|
+
flash.now[:error] = 'The authenticator code provided was invalid!'
|
|
20
|
+
render_enroll_form
|
|
21
|
+
end
|
|
22
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
23
|
+
redirect_to send("new_#{resource_name}_two_factor_authentication_path"), flash: { error: 'There has been a problem in the configuration process, please try again.' }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def show
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def destroy
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def generate_qr_code(otp_secret)
|
|
35
|
+
return unless defined?(::RQRCode)
|
|
36
|
+
|
|
37
|
+
qr_code = RQRCode::QRCode
|
|
38
|
+
.new(resource.provisioning_uri(nil, otp_secret_key: @otp_secret, issuer: Devise.otp_issuer))
|
|
39
|
+
.as_png(resize_exactly_to: 246)
|
|
40
|
+
.to_data_url
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render_enroll_form
|
|
44
|
+
@qr_code = generate_qr_code(@otp_secret)
|
|
45
|
+
render :new
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def verifier
|
|
49
|
+
ActiveSupport::MessageVerifier.new(Devise.secret_key, digest: 'SHA256')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def verify_otp_secret(otp_secret_signature)
|
|
53
|
+
data = verifier.verify(otp_secret_signature)
|
|
54
|
+
valid = data[Devise.second_factor_resource_id] == resource[Devise.second_factor_resource_id]
|
|
55
|
+
raise ActiveSupport::MessageVerifier::InvalidSignature unless valid
|
|
56
|
+
|
|
57
|
+
data['otp_secret']
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def sign_otp_secret(otp_secret)
|
|
61
|
+
data = {
|
|
62
|
+
Devise.second_factor_resource_id => resource[Devise.second_factor_resource_id],
|
|
63
|
+
'otp_secret' => otp_secret,
|
|
64
|
+
}
|
|
65
|
+
verifier.generate(data)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def after_two_factor_enroll_success_for(resource)
|
|
69
|
+
redirect_to after_two_factor_enroll_success_path_for(resource), flash: { success: 'Multi-factor authentication successfully setup!' }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def after_two_factor_enroll_success_path_for(resource)
|
|
73
|
+
:root
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def authenticate_scope!
|
|
77
|
+
self.resource = send("current_#{resource_name}")
|
|
78
|
+
end
|
|
79
|
+
end
|