devise-otp 1.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.erb_lint.yml +30 -0
- data/.github/workflows/ci.yml +4 -2
- data/.rubocop.yml +18 -0
- data/Appraisals +4 -17
- data/CHANGELOG.md +59 -1
- data/Gemfile +7 -1
- data/README.md +37 -14
- data/app/controllers/devise_otp/devise/otp_credentials_controller.rb +30 -7
- data/app/controllers/devise_otp/devise/otp_persistence_controller.rb +60 -0
- data/app/controllers/devise_otp/devise/otp_tokens_controller.rb +2 -37
- data/app/views/devise/otp_credentials/refresh.html.erb +6 -6
- data/app/views/devise/otp_credentials/show.html.erb +10 -13
- data/app/views/devise/otp_tokens/_token_secret.html.erb +9 -9
- data/app/views/devise/otp_tokens/_trusted_devices.html.erb +7 -7
- data/app/views/devise/otp_tokens/edit.html.erb +9 -9
- data/app/views/devise/otp_tokens/recovery.html.erb +8 -6
- data/app/views/devise/otp_tokens/show.html.erb +6 -6
- data/bin/appraisal +16 -0
- data/bin/erb_lint +16 -0
- data/bin/rake +16 -0
- data/bin/rubocop +16 -0
- data/config/locales/{en.yml → devise-otp.en.yml} +10 -8
- data/devise-otp.gemspec +2 -2
- data/gemfiles/rails_8.0.gemfile +5 -1
- data/gemfiles/{rails_7.1.gemfile → rails_8.1.gemfile} +7 -7
- data/lib/devise/strategies/database_authenticatable.rb +4 -17
- data/lib/devise-otp/version.rb +1 -1
- data/lib/devise-otp.rb +1 -2
- data/lib/devise_otp_authenticatable/controllers/helpers.rb +1 -2
- data/lib/devise_otp_authenticatable/controllers/public_helpers.rb +1 -2
- data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +7 -2
- data/lib/devise_otp_authenticatable/hooks/refreshable.rb +0 -1
- data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +18 -12
- data/lib/devise_otp_authenticatable/routes.rb +7 -7
- data/test/dummy/app/assets/stylesheets/application.css +15 -1
- data/test/dummy/app/controllers/admin_posts_controller.rb +0 -1
- data/test/dummy/app/controllers/base_controller.rb +0 -2
- data/test/dummy/app/controllers/non_otp_posts_controller.rb +0 -1
- data/test/dummy/app/models/admin.rb +0 -1
- data/test/dummy/app/models/lockable_user.rb +8 -0
- data/test/dummy/app/models/non_otp_user.rb +0 -1
- data/test/dummy/app/models/rememberable_user.rb +8 -0
- data/test/dummy/app/views/layouts/application.html.erb +4 -2
- data/test/dummy/app/views/posts/show.html.erb +0 -1
- data/test/dummy/app/views/shared/_navbar.html.erb +23 -0
- data/test/dummy/config/application.rb +3 -0
- data/test/dummy/config/initializers/devise.rb +2 -2
- data/test/dummy/config/routes.rb +3 -0
- data/test/dummy/db/migrate/20250731000001_create_lockable_users.rb +9 -0
- data/test/dummy/db/migrate/20250731000002_add_devise_to_lockable_users.rb +52 -0
- data/test/dummy/db/migrate/20250731000003_devise_otp_add_to_lockable_users.rb +28 -0
- data/test/dummy/db/migrate/20250817221304_create_rememberable_users.rb +50 -0
- data/test/dummy/db/migrate/20250818030305_add_devise_otp_to_rememberable_users.rb +28 -0
- data/test/dummy/db/schema.rb +110 -44
- data/test/integration/disable_token_test.rb +0 -2
- data/test/integration/enable_otp_form_test.rb +12 -1
- data/test/integration/lockable_test.rb +143 -0
- data/test/integration/non_otp_user_models_test.rb +0 -2
- data/test/integration/otp_drift_test.rb +35 -0
- data/test/integration/persistence_test.rb +96 -5
- data/test/integration/refresh_test.rb +23 -14
- data/test/integration/rememberable_test.rb +143 -0
- data/test/integration/reset_token_test.rb +0 -2
- data/test/integration/sign_in_test.rb +15 -8
- data/test/integration/trackable_test.rb +0 -2
- data/test/integration_tests_helper.rb +22 -1
- data/test/models/otp_authenticatable_test.rb +48 -11
- data/test/test_helper.rb +1 -0
- metadata +26 -9
- data/gemfiles/rails_7.2.gemfile +0 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d19fbc8b7c55dd43387e81ac13a7f1807f1a5c700b987c97d99f37985984831c
|
|
4
|
+
data.tar.gz: 8a396782dc0fd10ab4fe45a246f5a7460af6144de7b92834dc6ff755fb5afe43
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5e92163f98f4ffbf5ee3f649991df5fc40df6f2531fdf7d3d1081d7174e36598e4751de6b258ed10287da5853ff02ea87170c11c9b62122cc6fd5ffec35cd59d
|
|
7
|
+
data.tar.gz: f2640e640e6c72a299c78bf87e7ae6dea9137f2d0fdf9a0a9e0345edac6c4858418f6e4285357ae6266ac3bbeeb1c1c0c65fd23b3554d7b19369d2232f58b58b
|
data/.erb_lint.yml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
EnableDefaultLinters: true
|
|
3
|
+
glob: "**/*.{html,text,js}{+*,}.erb"
|
|
4
|
+
linters:
|
|
5
|
+
# ErbSafety:
|
|
6
|
+
# enabled: true
|
|
7
|
+
# better_html_config: .better-html.yml
|
|
8
|
+
SelfClosingTag:
|
|
9
|
+
enabled: false
|
|
10
|
+
Rubocop:
|
|
11
|
+
enabled: true
|
|
12
|
+
rubocop_config:
|
|
13
|
+
inherit_from:
|
|
14
|
+
- .rubocop.yml
|
|
15
|
+
Layout/InitialIndentation:
|
|
16
|
+
Enabled: false
|
|
17
|
+
Layout/LineLength:
|
|
18
|
+
Enabled: false
|
|
19
|
+
Layout/TrailingEmptyLines:
|
|
20
|
+
Enabled: false
|
|
21
|
+
Layout/TrailingWhitespace:
|
|
22
|
+
Enabled: false
|
|
23
|
+
Naming/FileName:
|
|
24
|
+
Enabled: false
|
|
25
|
+
Style/FrozenStringLiteralComment:
|
|
26
|
+
Enabled: false
|
|
27
|
+
Lint/UselessAssignment:
|
|
28
|
+
Enabled: false
|
|
29
|
+
Rails/OutputSafety:
|
|
30
|
+
Enabled: false
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -12,17 +12,19 @@ jobs:
|
|
|
12
12
|
fail-fast: false
|
|
13
13
|
matrix:
|
|
14
14
|
ruby:
|
|
15
|
+
- '4.0'
|
|
15
16
|
- '3.4'
|
|
16
17
|
- '3.3'
|
|
17
18
|
- '3.2'
|
|
18
19
|
- 'head'
|
|
19
20
|
rails:
|
|
21
|
+
- rails_8.1
|
|
20
22
|
- rails_8.0
|
|
21
|
-
- rails_7.2
|
|
22
|
-
- rails_7.1
|
|
23
23
|
exclude:
|
|
24
24
|
- ruby: '3.1'
|
|
25
25
|
rails: 'rails_8.0'
|
|
26
|
+
- ruby: '3.1'
|
|
27
|
+
rails: 'rails_8.1'
|
|
26
28
|
|
|
27
29
|
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
|
28
30
|
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.rails }}.gemfile
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Omakase Ruby styling for Rails
|
|
2
|
+
inherit_gem:
|
|
3
|
+
rubocop-rails-omakase: rubocop.yml
|
|
4
|
+
|
|
5
|
+
# Excluded directories
|
|
6
|
+
AllCops:
|
|
7
|
+
Exclude:
|
|
8
|
+
- "lib/generators/active_record/templates/migration.rb"
|
|
9
|
+
|
|
10
|
+
# Disabled checks
|
|
11
|
+
Style/StringLiterals:
|
|
12
|
+
Enabled: false
|
|
13
|
+
|
|
14
|
+
Layout/SpaceInsideArrayLiteralBrackets:
|
|
15
|
+
Enabled: false
|
|
16
|
+
|
|
17
|
+
Layout/SpaceInsideHashLiteralBraces:
|
|
18
|
+
Enabled: false
|
data/Appraisals
CHANGED
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
appraise 'rails_7.1' do
|
|
4
|
-
gem 'rails', '~> 7.1.0'
|
|
5
|
-
gem 'sqlite3', '~> 1.5.0'
|
|
6
|
-
|
|
7
|
-
# Fix:
|
|
8
|
-
# warning: logger was loaded from the standard library, but will no longer be part of the default gems since Ruby 3.5.0.
|
|
9
|
-
# Add logger to your Gemfile or gemspec.
|
|
10
|
-
install_if '-> { Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0") }' do
|
|
11
|
-
gem 'logger'
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
appraise 'rails_7.2' do
|
|
16
|
-
gem 'rails', '~> 7.2.0'
|
|
17
|
-
gem 'sqlite3', '~> 1.5.0'
|
|
18
|
-
end
|
|
19
|
-
|
|
20
3
|
appraise 'rails_8.0' do
|
|
21
4
|
gem 'rails', '~> 8.0.0'
|
|
22
5
|
end
|
|
6
|
+
|
|
7
|
+
appraise 'rails_8.1' do
|
|
8
|
+
gem 'rails', '~> 8.1.0'
|
|
9
|
+
end
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## v2.1.0
|
|
4
|
+
|
|
5
|
+
Bug fixes
|
|
6
|
+
- Fix issue with refreshing credentials when enabling browser persistence;
|
|
7
|
+
- Add back link from "View recovery codes" page;
|
|
8
|
+
|
|
9
|
+
Improvements
|
|
10
|
+
- Simplify "View recovery codes" verbiage;
|
|
11
|
+
- Move locale file to devise-otp.en.yml;
|
|
12
|
+
|
|
13
|
+
Maintenance
|
|
14
|
+
- Disable tests for Rails 7.1 (EOL on 10/2025) and 7.2 (EOL on 8/9/2026)
|
|
15
|
+
|
|
16
|
+
Documentation
|
|
17
|
+
- Switch to native CSS import in README for Propshaft compatibility;
|
|
18
|
+
- Add Getting Started section to README with sample link to OTP Token page;
|
|
19
|
+
|
|
20
|
+
## v2.0.0
|
|
21
|
+
|
|
22
|
+
Bug fixes:
|
|
23
|
+
- Fixed an issue where Turbo/Hotwire enabled applications return a JS error for failed OTP authentication;
|
|
24
|
+
- Fixed an issue with the "Remember me" functionality for users with OTP enabled;
|
|
25
|
+
|
|
26
|
+
Improvements:
|
|
27
|
+
- Add support for Lockable strategy to OTP credentials form;
|
|
28
|
+
- Use locales for "Enabled/Disabled" status;
|
|
29
|
+
- Fix spelling, spacing, and grammatical issues in the default locales file;
|
|
30
|
+
|
|
31
|
+
Code Quality:
|
|
32
|
+
- Use built-in functionality from rotp gem for handling the TOTP drift window;
|
|
33
|
+
- Use standard URL Helpers in database\_authenticatable;
|
|
34
|
+
- Refactor code for otp\_issuer method;
|
|
35
|
+
- Cleanup Ruby syntax in ERB views;
|
|
36
|
+
- Add Timecop for testing time based functionality;
|
|
37
|
+
- Add Rubocop and ERB Lint;
|
|
38
|
+
|
|
39
|
+
Maintenance
|
|
40
|
+
- Bump rqrcode to 3.0.0
|
|
41
|
+
|
|
42
|
+
Breaking changes
|
|
43
|
+
- Move browser persistence actions to dedicated controller
|
|
44
|
+
- Standardize browser persistence routes, and use buttons for controls
|
|
45
|
+
|
|
46
|
+
### Upgrading
|
|
47
|
+
|
|
48
|
+
If you have customized the otp_tokens controller/views, or the locales file for browser persistence:
|
|
49
|
+
1. Regenerate the "otp_tokens" controller and views
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
rails g devise_otp:controllers
|
|
53
|
+
rails g devise_otp:views
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
2. Reapply any desired changes
|
|
57
|
+
- Note that the browser persistence controls now use "button_to", rather than "link_to"
|
|
58
|
+
|
|
59
|
+
3. Update your locales file
|
|
60
|
+
- Move any \*\_persistence keys from devise.otp.otp_tokens/ to devise.otp.otp_persistence/
|
|
61
|
+
|
|
4
62
|
|
|
5
63
|
## 1.1.0
|
|
6
64
|
|
data/Gemfile
CHANGED
|
@@ -6,6 +6,7 @@ gemspec
|
|
|
6
6
|
gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
|
|
7
7
|
|
|
8
8
|
gem "capybara"
|
|
9
|
+
gem "debug"
|
|
9
10
|
gem "minitest-reporters", ">= 0.5.0"
|
|
10
11
|
gem "puma"
|
|
11
12
|
gem "rake"
|
|
@@ -13,4 +14,9 @@ gem "rdoc"
|
|
|
13
14
|
gem "shoulda"
|
|
14
15
|
gem "sprockets-rails"
|
|
15
16
|
gem "sqlite3", "~> 2.1"
|
|
16
|
-
gem "
|
|
17
|
+
gem "timecop", "~> 0.9.10"
|
|
18
|
+
|
|
19
|
+
# Formatting gems
|
|
20
|
+
gem "erb_lint", require: false
|
|
21
|
+
gem "standardrb", require: false
|
|
22
|
+
gem "rubocop-rails-omakase", require: false
|
data/README.md
CHANGED
|
@@ -50,23 +50,31 @@ Run the following generator to add the necessary configuration options to Devise
|
|
|
50
50
|
|
|
51
51
|
rails g devise_otp:install
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
## Getting Started
|
|
54
|
+
|
|
55
|
+
Create your Devise user model if you have not done so
|
|
56
|
+
|
|
57
|
+
rails g devise MODEL
|
|
58
|
+
|
|
59
|
+
Add Devise OTP to the model
|
|
54
60
|
|
|
55
61
|
rails g devise_otp MODEL
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
Add a link to the OTP Token settings to your navigation menu (or equivalent) for the user model
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
<%= link_to "OTP Token", user_otp_token_path %>
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
NOTE: replace "user" with the lowercase name of the model (same as the main devise gem)
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
## Default CSS
|
|
64
70
|
|
|
65
|
-
|
|
71
|
+
To use the default CSS for devise-otp, just import the devise-otp.css file into your application.css file (or equivalent):
|
|
66
72
|
|
|
67
|
-
|
|
73
|
+
@import url("devise-otp.css");
|
|
68
74
|
|
|
69
|
-
|
|
75
|
+
NOTE: You can also copy/paste the styles from [devise-otp.css](https://github.com/wmlele/devise-otp/blob/master/app/assets/stylesheets/devise-otp.css) into your stylesheet.
|
|
76
|
+
|
|
77
|
+
## Custom views
|
|
70
78
|
|
|
71
79
|
If you want to customise your views, you can use the following generator to eject the default view files:
|
|
72
80
|
|
|
@@ -74,11 +82,11 @@ If you want to customise your views, you can use the following generator to ejec
|
|
|
74
82
|
|
|
75
83
|
By default, the files live within the Devise namespace (`app/views/devise`, but if you want to move them or want to match the Devise configuration, set `config.otp_controller_path` in your initializers.
|
|
76
84
|
|
|
77
|
-
|
|
85
|
+
## I18n
|
|
78
86
|
|
|
79
87
|
The install generator also installs an english copy of a Devise OTP i18n file. This can be modified (or used to create other language versions) and is located at: _config/locales/devise.otp.en.yml_
|
|
80
88
|
|
|
81
|
-
|
|
89
|
+
## QR codes
|
|
82
90
|
|
|
83
91
|
Devise OTP generates QR Codes directly as SVG's via the [rqrcode](https://github.com/whomwah/rqrcode), so there are no JavaScript (or Sprockets) dependencies.
|
|
84
92
|
|
|
@@ -88,7 +96,7 @@ The install generator adds some options to the end of your Devise config file (`
|
|
|
88
96
|
|
|
89
97
|
* `config.otp_mandatory`: OTP is mandatory, users are going to be asked to enroll the next time they sign in, before they can successfully complete the session establishment.
|
|
90
98
|
* `config.otp_authentication_timeout`: How long the user has to authenticate with their token. (defaults to `3.minutes`)
|
|
91
|
-
* `config.otp_drift_window`: A window which provides allowance for drift between a user's token device clock (and therefore their OTP tokens) and the authentication server's clock. Expressed in
|
|
99
|
+
* `config.otp_drift_window`: A window which provides allowance for drift between a user's token device clock (and therefore their OTP tokens) and the authentication server's clock. Expressed in "steps" of 30 second increments. (default: `3`)
|
|
92
100
|
* `config.otp_credentials_refresh`: Users that have logged in longer than this time ago, are going to be asked their password (and an OTP challenge, if enabled) before they can see or change their otp informations. (defaults to `15.minutes`)
|
|
93
101
|
* `config.otp_recovery_tokens`: Whether the users are given a list of one-time recovery tokens, for emergency access (default: `10`, set to `false` to disable)
|
|
94
102
|
* `config.otp_trust_persistence`: The user is allowed to set his browser as "trusted", no more OTP challenges will be asked for that browser, for a limited time. (default: `1.month`, set to false to disable setting the browser as trusted)
|
|
@@ -110,10 +118,25 @@ To run the devise-otp dummy application in the development environment:
|
|
|
110
118
|
- Run the rails console or server (e.g. "rails c")
|
|
111
119
|
|
|
112
120
|
To run the tests for devise-otp against your current Ruby/Rails configuration:
|
|
113
|
-
- Navigate to the dummy app directory
|
|
114
|
-
|
|
121
|
+
- Navigate to the dummy app directory
|
|
122
|
+
|
|
123
|
+
cd test/dummy
|
|
124
|
+
|
|
125
|
+
- Create and migrate the database for the test environment
|
|
126
|
+
|
|
127
|
+
RAILS\_ENV=test rails db:drop
|
|
128
|
+
RAILS\_ENV=test rails db:create
|
|
129
|
+
RAILS\_ENV=test rails db:migrate
|
|
130
|
+
|
|
115
131
|
- Return to the root directory of devise-otp
|
|
116
|
-
|
|
132
|
+
|
|
133
|
+
- Execute the tests
|
|
134
|
+
|
|
135
|
+
rake test
|
|
136
|
+
|
|
137
|
+
To check Ruby syntax and formatting:
|
|
138
|
+
- Rubocop: "./bin/rubocop"
|
|
139
|
+
- ERB Lint (for views): "./bin/erb\_lint -a --lint-all"
|
|
117
140
|
|
|
118
141
|
## Authors
|
|
119
142
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
module DeviseOtp
|
|
2
2
|
module Devise
|
|
3
3
|
class OtpCredentialsController < DeviseController
|
|
4
|
+
include ::Devise::Controllers::Rememberable
|
|
4
5
|
helper_method :new_session_path
|
|
5
6
|
|
|
6
7
|
prepend_before_action :authenticate_scope!, only: [:get_refresh, :set_refresh]
|
|
7
8
|
prepend_before_action :require_no_authentication, only: [:show, :update]
|
|
8
9
|
before_action :set_challenge, only: [:show, :update]
|
|
10
|
+
before_action :set_remember_me, only: [:show, :update]
|
|
9
11
|
before_action :set_recovery, only: [:show, :update]
|
|
10
12
|
before_action :set_resource, only: [:show, :update]
|
|
11
13
|
before_action :set_token, only: [:update]
|
|
@@ -26,16 +28,31 @@ module DeviseOtp
|
|
|
26
28
|
# signs the resource in, if the OTP token is valid and the user has a valid challenge
|
|
27
29
|
#
|
|
28
30
|
def update
|
|
29
|
-
|
|
31
|
+
# The valid_for_authentication? method must be executed with the validation result as
|
|
32
|
+
# a block (as the DatabaseAuthenticatable strategy does via the validate method of the
|
|
33
|
+
# Authenticatable strategy). If true, and the account is not locked, then
|
|
34
|
+
# authentication will proceed as normal. If false, then the valid_for_authentication?
|
|
35
|
+
# method will increment the failed attempts and/or lock the account as specified.
|
|
36
|
+
|
|
37
|
+
if resource.valid_for_authentication? { resource.validate_otp_token(@token, @recovery) }
|
|
30
38
|
sign_in(resource_name, resource)
|
|
31
39
|
|
|
32
|
-
|
|
40
|
+
remember_me(resource) if resource.devise_modules.include?(:rememberable) and @remember_me
|
|
33
41
|
otp_refresh_credentials_for(resource)
|
|
34
42
|
respond_with resource, location: after_sign_in_path_for(resource)
|
|
43
|
+
elsif resource.devise_modules.include?(:lockable) and resource.access_locked?
|
|
44
|
+
otp_set_flash_message :alert, resource.unauthenticated_message, scope: "devise.failure"
|
|
45
|
+
redirect_to new_session_path(resource_name)
|
|
35
46
|
else
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
if resource.devise_modules.include?(:lockable) and resource.unauthenticated_message == :last_attempt
|
|
48
|
+
otp_set_flash_message :alert, :last_attempt, scope: "devise.failure", now: true
|
|
49
|
+
elsif @token.blank?
|
|
50
|
+
otp_set_flash_message :alert, :token_blank, now: true
|
|
51
|
+
else
|
|
52
|
+
otp_set_flash_message :alert, :token_invalid, now: true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
render :show, status: :unprocessable_entity
|
|
39
56
|
end
|
|
40
57
|
end
|
|
41
58
|
|
|
@@ -70,6 +87,10 @@ module DeviseOtp
|
|
|
70
87
|
end
|
|
71
88
|
end
|
|
72
89
|
|
|
90
|
+
def set_remember_me
|
|
91
|
+
@remember_me = (params[:remember_me] == "true")
|
|
92
|
+
end
|
|
93
|
+
|
|
73
94
|
def set_recovery
|
|
74
95
|
@recovery = (recovery_enabled? && params[:recovery] == "true")
|
|
75
96
|
end
|
|
@@ -90,6 +111,8 @@ module DeviseOtp
|
|
|
90
111
|
def skip_challenge_if_trusted_browser
|
|
91
112
|
if is_otp_trusted_browser_for?(resource)
|
|
92
113
|
sign_in(resource_name, resource)
|
|
114
|
+
|
|
115
|
+
remember_me(resource) if resource.devise_modules.include?(:rememberable) and @remember_me
|
|
93
116
|
otp_refresh_credentials_for(resource)
|
|
94
117
|
redirect_to after_sign_in_path_for(resource)
|
|
95
118
|
end
|
|
@@ -101,8 +124,8 @@ module DeviseOtp
|
|
|
101
124
|
end
|
|
102
125
|
|
|
103
126
|
def failed_refresh
|
|
104
|
-
otp_set_flash_message :alert, :invalid_refresh, :
|
|
105
|
-
render :refresh
|
|
127
|
+
otp_set_flash_message :alert, :invalid_refresh, now: true
|
|
128
|
+
render :refresh, status: :unprocessable_entity
|
|
106
129
|
end
|
|
107
130
|
|
|
108
131
|
def self.controller_path
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module DeviseOtp
|
|
2
|
+
module Devise
|
|
3
|
+
class OtpPersistenceController < DeviseController
|
|
4
|
+
include ::Devise::Controllers::Helpers
|
|
5
|
+
|
|
6
|
+
prepend_before_action :ensure_credentials_refresh
|
|
7
|
+
prepend_before_action :authenticate_scope!
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# fallback in case of redirect after authentication
|
|
11
|
+
#
|
|
12
|
+
def show
|
|
13
|
+
redirect_to otp_token_path_for(resource)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#
|
|
17
|
+
# makes the current browser persistent
|
|
18
|
+
#
|
|
19
|
+
def create
|
|
20
|
+
if otp_set_trusted_device_for(resource)
|
|
21
|
+
otp_set_flash_message :success, :successfully_set_persistence
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
redirect_to otp_token_path_for(resource)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# clears persistence for the current browser
|
|
29
|
+
#
|
|
30
|
+
def destroy
|
|
31
|
+
if otp_clear_trusted_device_for(resource)
|
|
32
|
+
otp_set_flash_message :success, :successfully_cleared_persistence
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
redirect_to otp_token_path_for(resource)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# rehash the persistence secret, thus, making all the persistence cookies invalid
|
|
40
|
+
#
|
|
41
|
+
def reset
|
|
42
|
+
if otp_reset_persistence_for(resource)
|
|
43
|
+
otp_set_flash_message :notice, :successfully_reset_persistence
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
redirect_to otp_token_path_for(resource)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def ensure_credentials_refresh
|
|
52
|
+
ensure_resource!
|
|
53
|
+
|
|
54
|
+
if needs_credentials_refresh?(resource)
|
|
55
|
+
redirect_to refresh_otp_credential_path_for(resource)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -6,8 +6,6 @@ module DeviseOtp
|
|
|
6
6
|
prepend_before_action :ensure_credentials_refresh
|
|
7
7
|
prepend_before_action :authenticate_scope!
|
|
8
8
|
|
|
9
|
-
protect_from_forgery except: [:clear_persistence, :delete_persistence]
|
|
10
|
-
|
|
11
9
|
#
|
|
12
10
|
# Displays the status of OTP authentication
|
|
13
11
|
#
|
|
@@ -35,8 +33,8 @@ module DeviseOtp
|
|
|
35
33
|
otp_set_flash_message :success, :successfully_updated
|
|
36
34
|
redirect_to otp_token_path_for(resource)
|
|
37
35
|
else
|
|
38
|
-
otp_set_flash_message :danger, :could_not_confirm, :
|
|
39
|
-
render :edit
|
|
36
|
+
otp_set_flash_message :danger, :could_not_confirm, now: true
|
|
37
|
+
render :edit, status: :unprocessable_entity
|
|
40
38
|
end
|
|
41
39
|
end
|
|
42
40
|
|
|
@@ -51,39 +49,6 @@ module DeviseOtp
|
|
|
51
49
|
redirect_to otp_token_path_for(resource)
|
|
52
50
|
end
|
|
53
51
|
|
|
54
|
-
#
|
|
55
|
-
# makes the current browser persistent
|
|
56
|
-
#
|
|
57
|
-
def get_persistence
|
|
58
|
-
if otp_set_trusted_device_for(resource)
|
|
59
|
-
otp_set_flash_message :success, :successfully_set_persistence
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
redirect_to otp_token_path_for(resource)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
# clears persistence for the current browser
|
|
67
|
-
#
|
|
68
|
-
def clear_persistence
|
|
69
|
-
if otp_clear_trusted_device_for(resource)
|
|
70
|
-
otp_set_flash_message :success, :successfully_cleared_persistence
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
redirect_to otp_token_path_for(resource)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
#
|
|
77
|
-
# rehash the persistence secret, thus, making all the persistence cookies invalid
|
|
78
|
-
#
|
|
79
|
-
def delete_persistence
|
|
80
|
-
if otp_reset_persistence_for(resource)
|
|
81
|
-
otp_set_flash_message :notice, :successfully_reset_persistence
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
redirect_to otp_token_path_for(resource)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
52
|
def recovery
|
|
88
53
|
respond_to do |format|
|
|
89
54
|
format.html
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
<h2><%=
|
|
2
|
-
<p><%=
|
|
1
|
+
<h2><%= t('title', scope: 'devise.otp.credentials_refresh') %></h2>
|
|
2
|
+
<p><%= t('explain', scope: 'devise.otp.credentials_refresh') %></p>
|
|
3
3
|
|
|
4
|
-
<%= form_for(resource, :
|
|
4
|
+
<%= form_for(resource, as: resource_name, url: [:refresh, resource_name, :otp_credential], html: { method: :put }) do |f| %>
|
|
5
5
|
|
|
6
6
|
<%= render "devise/shared/error_messages", resource: resource %>
|
|
7
7
|
|
|
8
8
|
<div>
|
|
9
9
|
<%= f.label :email %><br />
|
|
10
|
-
<%= f.text_field :email, :
|
|
10
|
+
<%= f.text_field :email, disabled: true %>
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
13
|
<div>
|
|
14
14
|
<%= f.label :password %><br />
|
|
15
|
-
<%= f.password_field :refresh_password, :
|
|
15
|
+
<%= f.password_field :refresh_password, autocomplete: :off, autofocus: true %>
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
<div><%= f.submit
|
|
18
|
+
<div><%= f.submit t(:go_on, scope: 'devise.otp.credentials_refresh') %></div>
|
|
19
19
|
<% end %>
|
|
@@ -1,31 +1,28 @@
|
|
|
1
|
-
<h2><%=
|
|
2
|
-
<p><%=
|
|
1
|
+
<h2><%= t('title', scope: 'devise.otp.submit_token') %></h2>
|
|
2
|
+
<p><%= t('explain', scope: 'devise.otp.submit_token') %></p>
|
|
3
3
|
|
|
4
|
-
<%= form_for(resource, :
|
|
4
|
+
<%= form_for(resource, as: resource_name, url: [resource_name, :otp_credential], html: { method: :put }) do |f| %>
|
|
5
5
|
|
|
6
6
|
<%= hidden_field_tag :challenge, @challenge %>
|
|
7
7
|
<%= hidden_field_tag :recovery, @recovery %>
|
|
8
|
+
<%= hidden_field_tag :remember_me, @remember_me %>
|
|
8
9
|
|
|
9
10
|
<% if @recovery %>
|
|
10
11
|
<p>
|
|
11
|
-
<%= label_tag :token,
|
|
12
|
-
<%= text_field_tag :otp_recovery_counter, resource.otp_recovery_counter, :
|
|
12
|
+
<%= label_tag :token, t('recovery_prompt', scope: 'devise.otp.submit_token') %><br />
|
|
13
|
+
<%= text_field_tag :otp_recovery_counter, resource.otp_recovery_counter, autocomplete: :off, disabled: true, size: 4 %>
|
|
13
14
|
</p>
|
|
14
15
|
<% else %>
|
|
15
16
|
<p>
|
|
16
|
-
<%= label_tag :token,
|
|
17
|
+
<%= label_tag :token, t('prompt', scope: 'devise.otp.submit_token') %><br />
|
|
17
18
|
</p>
|
|
18
19
|
<% end %>
|
|
19
20
|
|
|
20
|
-
<%= text_field_tag :token, nil, :
|
|
21
|
+
<%= text_field_tag :token, nil, autocomplete: :off, autofocus: true, size: 6 %><br>
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
<%= check_box_tag :enable_persistence, true, false %> <%= I18n.t('remember', :scope => 'devise.otp.general') %>
|
|
24
|
-
<% end %>
|
|
25
|
-
|
|
26
|
-
<p><%= f.submit I18n.t('submit', :scope => 'devise.otp.submit_token') %></p>
|
|
23
|
+
<p><%= f.submit t('submit', scope: 'devise.otp.submit_token') %></p>
|
|
27
24
|
|
|
28
25
|
<% if !@recovery && recovery_enabled? %>
|
|
29
|
-
<p><%= link_to
|
|
26
|
+
<p><%= link_to t('recovery_link', scope: 'devise.otp.submit_token'), otp_credential_path_for(resource_name, challenge: @challenge, recovery: true) %></p>
|
|
30
27
|
<% end %>
|
|
31
28
|
<% end %>
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
<h3><%=
|
|
1
|
+
<h3><%= t('title', scope: 'devise.otp.token_secret') %></h3>
|
|
2
2
|
|
|
3
3
|
<%= otp_authenticator_token_image(resource) %>
|
|
4
4
|
|
|
5
5
|
<p>
|
|
6
|
-
<strong><%=
|
|
6
|
+
<strong><%= t('manual_provisioning', scope: 'devise.otp.token_secret') %>:</strong>
|
|
7
7
|
<code><%= resource.otp_auth_secret %></code>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
<p><%= button_to
|
|
10
|
+
<p><%= button_to t('reset_link', scope: 'devise.otp.otp_tokens'), reset_otp_token_path_for(resource), method: :post, data: { turbo_method: :post } %></p>
|
|
11
11
|
|
|
12
12
|
<p>
|
|
13
|
-
<%=
|
|
14
|
-
<strong><%=
|
|
13
|
+
<%= t('reset_explain', scope: 'devise.otp.otp_tokens') %>
|
|
14
|
+
<strong><%= t('reset_explain_warn', scope: 'devise.otp.otp_tokens') %></strong>
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
17
|
<%- if recovery_enabled? %>
|
|
18
|
-
<h3><%=
|
|
19
|
-
<p><%=
|
|
20
|
-
<p><%= link_to
|
|
21
|
-
<p><%= link_to
|
|
18
|
+
<h3><%= t('title', scope: 'devise.otp.otp_tokens.recovery') %></h3>
|
|
19
|
+
<p><%= t('explain', scope: 'devise.otp.otp_tokens.recovery') %></p>
|
|
20
|
+
<p><%= link_to t('codes_list', scope: 'devise.otp.otp_tokens.recovery'), recovery_otp_token_for(resource_name) %></p>
|
|
21
|
+
<p><%= link_to t('download_codes', scope: 'devise.otp.otp_tokens.recovery'), recovery_otp_token_for(resource_name, format: :text) %></p>
|
|
22
22
|
<% end %>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
<h3><%=
|
|
2
|
-
<p><%=
|
|
1
|
+
<h3><%= t('title', scope: 'devise.otp.trusted_browsers') %></h3>
|
|
2
|
+
<p><%= t('explain', scope: 'devise.otp.trusted_browsers') %></p>
|
|
3
3
|
|
|
4
4
|
<%- if is_otp_trusted_browser_for? resource %>
|
|
5
|
-
<p><em><%=
|
|
6
|
-
<p><%=
|
|
5
|
+
<p><em><%= t('browser_trusted', scope: 'devise.otp.trusted_browsers') %></em></p>
|
|
6
|
+
<p><%= button_to t('trust_remove', scope: 'devise.otp.trusted_browsers'), otp_persistence_path_for(resource_name), method: :delete %></p>
|
|
7
7
|
<% else %>
|
|
8
|
-
<p><%=
|
|
9
|
-
<p><%=
|
|
8
|
+
<p><%= t('browser_not_trusted', scope: 'devise.otp.trusted_browsers') %></p>
|
|
9
|
+
<p><%= button_to t('trust_add', scope: 'devise.otp.trusted_browsers'), otp_persistence_path_for(resource_name) %></p>
|
|
10
10
|
<% end %>
|
|
11
11
|
|
|
12
|
-
<p><%= button_to
|
|
12
|
+
<p><%= button_to t('trust_clear', scope: 'devise.otp.trusted_browsers'), reset_otp_persistence_path_for(resource_name), method: :delete %></p>
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
<h2><%=
|
|
2
|
-
<p><%=
|
|
1
|
+
<h2><%= t('title', scope: 'devise.otp.edit_otp_token') %></h2>
|
|
2
|
+
<p><%= t('explain', scope: 'devise.otp.edit_otp_token') %></p>
|
|
3
3
|
|
|
4
|
-
<h2><%=
|
|
4
|
+
<h2><%= t('lead_in', scope: 'devise.otp.edit_otp_token') %></h2>
|
|
5
5
|
|
|
6
|
-
<p><%=
|
|
6
|
+
<p><%= t('step_1', scope: 'devise.otp.edit_otp_token') %></p>
|
|
7
7
|
|
|
8
8
|
<%= otp_authenticator_token_image(resource) %>
|
|
9
9
|
|
|
10
10
|
<p>
|
|
11
|
-
<strong><%=
|
|
11
|
+
<strong><%= t('manual_provisioning', scope: 'devise.otp.token_secret') %>:</strong>
|
|
12
12
|
<code><%= resource.otp_auth_secret %></code>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
<p><%=
|
|
15
|
+
<p><%= t('step_2', scope: 'devise.otp.edit_otp_token') %></p>
|
|
16
16
|
|
|
17
|
-
<%= form_with(:
|
|
17
|
+
<%= form_with(url: [resource_name, :otp_token], method: :put) do |f| %>
|
|
18
18
|
|
|
19
19
|
<p>
|
|
20
|
-
<%= f.label :confirmation_code,
|
|
20
|
+
<%= f.label :confirmation_code, t('confirmation_code', scope: 'devise.otp.edit_otp_token') %>
|
|
21
21
|
<%= f.text_field :confirmation_code %>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
|
-
<p><%= f.submit
|
|
24
|
+
<p><%= f.submit t('submit', scope: 'devise.otp.edit_otp_token') %></p>
|
|
25
25
|
|
|
26
26
|
<% end %>
|