devise-otp 1.1.0 → 2.0.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.erb_lint.yml +30 -0
  3. data/.rubocop.yml +18 -0
  4. data/CHANGELOG.md +42 -1
  5. data/Gemfile +7 -1
  6. data/README.md +5 -1
  7. data/app/controllers/devise_otp/devise/otp_credentials_controller.rb +30 -7
  8. data/app/controllers/devise_otp/devise/otp_persistence_controller.rb +53 -0
  9. data/app/controllers/devise_otp/devise/otp_tokens_controller.rb +2 -37
  10. data/app/views/devise/otp_credentials/refresh.html.erb +6 -6
  11. data/app/views/devise/otp_credentials/show.html.erb +10 -13
  12. data/app/views/devise/otp_tokens/_token_secret.html.erb +9 -9
  13. data/app/views/devise/otp_tokens/_trusted_devices.html.erb +7 -7
  14. data/app/views/devise/otp_tokens/edit.html.erb +9 -9
  15. data/app/views/devise/otp_tokens/recovery.html.erb +6 -6
  16. data/app/views/devise/otp_tokens/show.html.erb +6 -6
  17. data/bin/erb_lint +27 -0
  18. data/bin/rubocop +27 -0
  19. data/config/locales/en.yml +9 -7
  20. data/devise-otp.gemspec +3 -1
  21. data/lib/devise/strategies/database_authenticatable.rb +4 -17
  22. data/lib/devise-otp/version.rb +1 -1
  23. data/lib/devise-otp.rb +0 -1
  24. data/lib/devise_otp_authenticatable/controllers/helpers.rb +1 -2
  25. data/lib/devise_otp_authenticatable/controllers/public_helpers.rb +1 -2
  26. data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +7 -2
  27. data/lib/devise_otp_authenticatable/hooks/refreshable.rb +0 -1
  28. data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +18 -12
  29. data/lib/devise_otp_authenticatable/routes.rb +7 -7
  30. data/test/dummy/app/controllers/admin_posts_controller.rb +0 -1
  31. data/test/dummy/app/controllers/base_controller.rb +0 -2
  32. data/test/dummy/app/controllers/non_otp_posts_controller.rb +0 -1
  33. data/test/dummy/app/models/admin.rb +0 -1
  34. data/test/dummy/app/models/lockable_user.rb +8 -0
  35. data/test/dummy/app/models/non_otp_user.rb +0 -1
  36. data/test/dummy/app/models/rememberable_user.rb +8 -0
  37. data/test/dummy/app/views/layouts/application.html.erb +4 -2
  38. data/test/dummy/app/views/posts/show.html.erb +0 -1
  39. data/test/dummy/app/views/shared/_navbar.html.erb +15 -0
  40. data/test/dummy/config/application.rb +3 -0
  41. data/test/dummy/config/initializers/devise.rb +2 -2
  42. data/test/dummy/config/routes.rb +3 -0
  43. data/test/dummy/db/migrate/20250731000001_create_lockable_users.rb +9 -0
  44. data/test/dummy/db/migrate/20250731000002_add_devise_to_lockable_users.rb +52 -0
  45. data/test/dummy/db/migrate/20250731000003_devise_otp_add_to_lockable_users.rb +28 -0
  46. data/test/dummy/db/migrate/20250817221304_create_rememberable_users.rb +50 -0
  47. data/test/dummy/db/migrate/20250818030305_add_devise_otp_to_rememberable_users.rb +28 -0
  48. data/test/dummy/db/schema.rb +67 -1
  49. data/test/integration/disable_token_test.rb +0 -2
  50. data/test/integration/enable_otp_form_test.rb +12 -1
  51. data/test/integration/lockable_test.rb +143 -0
  52. data/test/integration/non_otp_user_models_test.rb +0 -2
  53. data/test/integration/otp_drift_test.rb +35 -0
  54. data/test/integration/persistence_test.rb +59 -4
  55. data/test/integration/refresh_test.rb +23 -14
  56. data/test/integration/rememberable_test.rb +143 -0
  57. data/test/integration/reset_token_test.rb +0 -2
  58. data/test/integration/sign_in_test.rb +15 -8
  59. data/test/integration/trackable_test.rb +0 -2
  60. data/test/integration_tests_helper.rb +22 -1
  61. data/test/models/otp_authenticatable_test.rb +48 -11
  62. data/test/test_helper.rb +1 -0
  63. metadata +34 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f329ff1fa9732961646ad4169f89921f3429a6aa86744486eafa21a891fd1628
4
- data.tar.gz: 8a4797664986ea6c11e21a50a43d5aeda471000ce5610e91451ec37f3d231121
3
+ metadata.gz: afd51a7a5178b6da891987b166c3f838833ec6c6c506ceb457c49c743b925244
4
+ data.tar.gz: 20e043c52ed71c4d6e8c5b9dd309badbf656d6d0d386cdae1269d8440a91d09e
5
5
  SHA512:
6
- metadata.gz: e42b34ea4259e698e75f6408b6d0a0021b39f934d960e039a305ee2f9dc7d443a8dbdc9f99e1df452b75602430e601bf7ece1214b11766ba558fb3806d07355c
7
- data.tar.gz: 8c1bde0740a5f96dfc440e976c568a8ef0b7f0ac91e7af620d14aea1e8208811e6339384dc9d7b6363c6f4ed9de363c1637422a82b5f5f5c6696fa17c9879764
6
+ metadata.gz: faee3c01bdf932a07843ea991f87cffa40081a1339647960382e7ad6532302aeb02cd0ac39de8a643a5b3a33f87a134a3bf11a30969b3c5a3d64c6bea3cb0a6f
7
+ data.tar.gz: f08c0a495128b23816751a3386082bf250d3791ce2dc25c0f4e4facd6e911053900aad3664f75938d3ab67c3757811f189f9a22f5b535c8095a679de3f901c60
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/.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/CHANGELOG.md CHANGED
@@ -1,6 +1,47 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## v2.0.0
4
+
5
+ Bug fixes:
6
+ - Fixed an issue where Turbo/Hotwire enabled applications return a JS error for failed OTP authentication;
7
+ - Fixed an issue with the "Remember me" functionality for users with OTP enabled;
8
+
9
+ Improvements:
10
+ - Add support for Lockable strategy to OTP credentials form;
11
+ - Use locales for "Enabled/Disabled" status;
12
+ - Fix spelling, spacing, and grammatical issues in the default locales file;
13
+
14
+ Code Quality:
15
+ - Use built-in functionality from rotp gem for handling the TOTP drift window;
16
+ - Use standard URL Helpers in database\_authenticatable;
17
+ - Refactor code for otp\_issuer method;
18
+ - Cleanup Ruby syntax in ERB views;
19
+ - Add Timecop for testing time based functionality;
20
+ - Add Rubocop and ERB Lint;
21
+
22
+ Maintenance
23
+ - Bump rqrcode to 3.0.0
24
+
25
+ Breaking changes
26
+ - Move browser persistence actions to dedicated controller
27
+ - Standardize browser persistence routes, and use buttons for controls
28
+
29
+ ### Upgrading
30
+
31
+ If you have customized the otp_tokens controller/views, or the locales file for browser persistence:
32
+ 1. Regenerate the "otp_tokens" controller and views
33
+
34
+ ```
35
+ rails g devise_otp:controllers
36
+ rails g devise_otp:views
37
+ ```
38
+
39
+ 2. Reapply any desired changes
40
+ - Note that the browser persistence controls now use "button_to", rather than "link_to"
41
+
42
+ 3. Update your locales file
43
+ - Move any \*\_persistence keys from devise.otp.otp_tokens/ to devise.otp.otp_persistence/
44
+
4
45
 
5
46
  ## 1.1.0
6
47
 
data/Gemfile CHANGED
@@ -13,4 +13,10 @@ gem "rdoc"
13
13
  gem "shoulda"
14
14
  gem "sprockets-rails"
15
15
  gem "sqlite3", "~> 2.1"
16
- gem "standardrb"
16
+
17
+ # Formatting gems
18
+ gem "standardrb", require: false
19
+ gem "rubocop-rails-omakase", require: false
20
+ gem "erb_lint", require: false
21
+
22
+ gem "debug"
data/README.md CHANGED
@@ -88,7 +88,7 @@ The install generator adds some options to the end of your Devise config file (`
88
88
 
89
89
  * `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
90
  * `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 minutes centered at the current time. (default: `3`)
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 "steps" of 30 second increments. (default: `3`)
92
92
  * `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
93
  * `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
94
  * `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)
@@ -115,6 +115,10 @@ To run the tests for devise-otp against your current Ruby/Rails configuration:
115
115
  - Return to the root directory of devise-otp
116
116
  - Run "rake test"
117
117
 
118
+ To check Ruby syntax and formatting:
119
+ - Rubocop: "./bin/rubocop"
120
+ - ERB Lint (for views): "./bin/erb\_lint -a --lint-all"
121
+
118
122
  ## Authors
119
123
 
120
124
  The project was originally started by Lele Forzani by forking [devise_google_authenticator](https://github.com/AsteriskLabs/devise_google_authenticator) and still contains some devise_google_authenticator code. It's now maintained by [Josef Strzibny](https://github.com/strzibny/) and [Laney Stroup](https://github.com/strouptl).
@@ -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
- if resource.otp_challenge_valid? && resource.validate_otp_token(@token, @recovery)
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
- otp_set_trusted_device_for(resource) if params[:enable_persistence] == "true"
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
- kind = (@token.blank? ? :token_blank : :token_invalid)
37
- otp_set_flash_message :alert, kind, :now => true
38
- render :show
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, :now => true
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,53 @@
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
+ # makes the current browser persistent
11
+ #
12
+ def create
13
+ if otp_set_trusted_device_for(resource)
14
+ otp_set_flash_message :success, :successfully_set_persistence
15
+ end
16
+
17
+ redirect_to otp_token_path_for(resource)
18
+ end
19
+
20
+ #
21
+ # clears persistence for the current browser
22
+ #
23
+ def destroy
24
+ if otp_clear_trusted_device_for(resource)
25
+ otp_set_flash_message :success, :successfully_cleared_persistence
26
+ end
27
+
28
+ redirect_to otp_token_path_for(resource)
29
+ end
30
+
31
+ #
32
+ # rehash the persistence secret, thus, making all the persistence cookies invalid
33
+ #
34
+ def reset
35
+ if otp_reset_persistence_for(resource)
36
+ otp_set_flash_message :notice, :successfully_reset_persistence
37
+ end
38
+
39
+ redirect_to otp_token_path_for(resource)
40
+ end
41
+
42
+ private
43
+
44
+ def ensure_credentials_refresh
45
+ ensure_resource!
46
+
47
+ if needs_credentials_refresh?(resource)
48
+ redirect_to refresh_otp_credential_path_for(resource)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ 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, :now => true
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><%= I18n.t('title', :scope => 'devise.otp.credentials_refresh') %></h2>
2
- <p><%= I18n.t('explain', :scope => 'devise.otp.credentials_refresh') %></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, :as => resource_name, :url => [:refresh, resource_name, :otp_credential], :html => { :method => :put, "data-turbo" => false }) do |f| %>
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, :disabled => :true%>
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, :autocomplete => :off, :autofocus => true %>
15
+ <%= f.password_field :refresh_password, autocomplete: :off, autofocus: true %>
16
16
  </div>
17
17
 
18
- <div><%= f.submit I18n.t(:go_on, :scope => 'devise.otp.credentials_refresh') %></div>
18
+ <div><%= f.submit t(:go_on, scope: 'devise.otp.credentials_refresh') %></div>
19
19
  <% end %>
@@ -1,31 +1,28 @@
1
- <h2><%= I18n.t('title', :scope => 'devise.otp.submit_token') %></h2>
2
- <p><%= I18n.t('explain', :scope => 'devise.otp.submit_token') %></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, :as => resource_name, :url => [resource_name, :otp_credential], :html => { :method => :put, "data-turbo" => false }) do |f| %>
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, I18n.t('recovery_prompt', :scope => 'devise.otp.submit_token') %><br />
12
- <%= text_field_tag :otp_recovery_counter, resource.otp_recovery_counter, :autocomplete => :off, :disabled => true, :size => 4 %>
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, I18n.t('prompt', :scope => 'devise.otp.submit_token') %><br />
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, :autocomplete => :off, :autofocus => true, :size => 6 %><br>
21
+ <%= text_field_tag :token, nil, autocomplete: :off, autofocus: true, size: 6 %><br>
21
22
 
22
- <%= label_tag :enable_persistence do %>
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 I18n.t('recovery_link', :scope => 'devise.otp.submit_token'), otp_credential_path_for(resource_name, :challenge => @challenge, :recovery => true) %></p>
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><%= I18n.t('title', :scope => 'devise.otp.token_secret') %></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><%= I18n.t('manual_provisioning', :scope => 'devise.otp.token_secret') %>:</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 I18n.t('reset_link', :scope => 'devise.otp.otp_tokens'), reset_otp_token_path_for(resource), :method => :post , :data => { "turbo-method": "POST" } %></p>
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
- <%= I18n.t('reset_explain', :scope => 'devise.otp.otp_tokens') %>
14
- <strong><%= I18n.t('reset_explain_warn', :scope => 'devise.otp.otp_tokens') %></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><%= I18n.t('title', :scope => 'devise.otp.otp_tokens.recovery') %></h3>
19
- <p><%= I18n.t('explain', :scope => 'devise.otp.otp_tokens.recovery') %></p>
20
- <p><%= link_to I18n.t('codes_list', :scope => 'devise.otp.otp_tokens.recovery'), recovery_otp_token_for(resource_name) %></p>
21
- <p><%= link_to I18n.t('download_codes', :scope => 'devise.otp.otp_tokens.recovery'), recovery_otp_token_for(resource_name, format: :text) %></p>
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><%= I18n.t('title', :scope => 'devise.otp.trusted_browsers') %></h3>
2
- <p><%= I18n.t('explain', :scope => 'devise.otp.trusted_browsers') %></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><%= I18n.t('browser_trusted', :scope => 'devise.otp.trusted_browsers') %></em></p>
6
- <p><%= link_to I18n.t('trust_remove', :scope => 'devise.otp.trusted_browsers'), persistence_otp_token_path_for(resource_name), :method => :post, :data => { "turbo-method": "POST" } %></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><%= I18n.t('browser_not_trusted', :scope => 'devise.otp.trusted_browsers') %></p>
9
- <p><%= link_to I18n.t('trust_add', :scope => 'devise.otp.trusted_browsers'), persistence_otp_token_path_for(resource_name) %></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 I18n.t('trust_clear', :scope => 'devise.otp.trusted_browsers'), persistence_otp_token_path_for(resource_name), :method => :delete, :data => { "turbo-method": "DELETE" } %></p>
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><%= I18n.t('title', :scope => 'devise.otp.edit_otp_token') %></h2>
2
- <p><%= I18n.t('explain', :scope => 'devise.otp.edit_otp_token') %></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><%= I18n.t('lead_in', :scope => 'devise.otp.edit_otp_token') %></h2>
4
+ <h2><%= t('lead_in', scope: 'devise.otp.edit_otp_token') %></h2>
5
5
 
6
- <p><%= I18n.t('step_1', :scope => 'devise.otp.edit_otp_token') %></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><%= I18n.t('manual_provisioning', :scope => 'devise.otp.token_secret') %>:</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><%= I18n.t('step_2', :scope => 'devise.otp.edit_otp_token') %></p>
15
+ <p><%= t('step_2', scope: 'devise.otp.edit_otp_token') %></p>
16
16
 
17
- <%= form_with(:url => [resource_name, :otp_token], :method => :put) do |f| %>
17
+ <%= form_with(url: [resource_name, :otp_token], method: :put) do |f| %>
18
18
 
19
19
  <p>
20
- <%= f.label :confirmation_code, I18n.t('confirmation_code', :scope => 'devise.otp.edit_otp_token') %>
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 I18n.t('submit', :scope => 'devise.otp.edit_otp_token') %></p>
24
+ <p><%= f.submit t('submit', scope: 'devise.otp.edit_otp_token') %></p>
25
25
 
26
26
  <% end %>
@@ -1,13 +1,13 @@
1
- <h2><%= I18n.t('title', :scope => 'devise.otp.otp_tokens.recovery') %></h2>
2
- <p><%= I18n.t('explain', :scope => 'devise.otp.otp_tokens.recovery') %></p>
1
+ <h2><%= t('title', scope: 'devise.otp.otp_tokens.recovery') %></h2>
2
+ <p><%= t('explain', scope: 'devise.otp.otp_tokens.recovery') %></p>
3
3
 
4
4
  <table>
5
5
  <caption>
6
6
  <thead>
7
- <tr>
8
- <th><%= I18n.t('sequence', :scope => 'devise.otp.otp_tokens.recovery') %></th>
9
- <th><%= I18n.t('code', :scope => 'devise.otp.otp_tokens.recovery') %></th>
10
- </tr>
7
+ <tr>
8
+ <th><%= t('sequence', scope: 'devise.otp.otp_tokens.recovery') %></th>
9
+ <th><%= t('code', scope: 'devise.otp.otp_tokens.recovery') %></th>
10
+ </tr>
11
11
  </thead>
12
12
  <tbody>
13
13
  <%- resource.next_otp_recovery_tokens.each do |seq, code| %>
@@ -1,14 +1,14 @@
1
- <h2><%= I18n.t('title', :scope => 'devise.otp.otp_tokens') %></h2>
1
+ <h2><%= t('title', scope: 'devise.otp.otp_tokens') %></h2>
2
2
 
3
- <p><strong>Status:</strong> <%= resource.otp_enabled? ? "Enabled" : "Disabled" %></p>
3
+ <p><strong>Status:</strong> <%= t(resource.otp_enabled? ? :enabled : :disabled, scope: 'devise.otp.general') %></p>
4
4
 
5
5
  <%- if resource.otp_enabled? %>
6
- <%= render :partial => 'token_secret' if resource.otp_enabled? %>
7
- <%= render :partial => 'trusted_devices' if trusted_devices_enabled? %>
6
+ <%= render partial: 'token_secret' if resource.otp_enabled? %>
7
+ <%= render partial: 'trusted_devices' if trusted_devices_enabled? %>
8
8
 
9
9
  <% unless otp_mandatory_on?(resource) %>
10
- <%= button_to I18n.t('disable_link', :scope => 'devise.otp.otp_tokens'), otp_token_path_for(resource), :method => :delete, :data => { "turbo-method": "DELETE" } %>
10
+ <%= button_to t('disable_link', scope: 'devise.otp.otp_tokens'), otp_token_path_for(resource), method: :delete, data: { turbo_method: :delete } %>
11
11
  <% end %>
12
12
  <% else %>
13
- <%= link_to I18n.t('enable_link', :scope => 'devise.otp.otp_tokens'), edit_otp_token_path_for(resource) %>
13
+ <%= link_to t('enable_link', scope: 'devise.otp.otp_tokens'), edit_otp_token_path_for(resource) %>
14
14
  <% end %>
data/bin/erb_lint ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'erb_lint' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("erb_lint", "erb_lint")
data/bin/rubocop ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("rubocop", "rubocop")