authentication-zero 2.16.19 → 2.16.21

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +6 -6
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +3 -2
  6. data/lib/authentication_zero/version.rb +1 -1
  7. data/lib/generators/authentication/authentication_generator.rb +58 -5
  8. data/lib/generators/authentication/templates/config/initializers/webauthn.rb +4 -0
  9. data/lib/generators/authentication/templates/config/redis/shared.yml +0 -5
  10. data/lib/generators/authentication/templates/controllers/api/application_controller.rb.tt +10 -10
  11. data/lib/generators/authentication/templates/controllers/api/identity/email_verifications_controller.rb.tt +1 -1
  12. data/lib/generators/authentication/templates/controllers/api/identity/emails_controller.rb.tt +1 -5
  13. data/lib/generators/authentication/templates/controllers/api/identity/password_resets_controller.rb.tt +1 -1
  14. data/lib/generators/authentication/templates/controllers/html/application_controller.rb.tt +17 -16
  15. data/lib/generators/authentication/templates/controllers/html/identity/email_verifications_controller.rb.tt +1 -1
  16. data/lib/generators/authentication/templates/controllers/html/identity/emails_controller.rb.tt +1 -5
  17. data/lib/generators/authentication/templates/controllers/html/identity/password_resets_controller.rb.tt +1 -1
  18. data/lib/generators/authentication/templates/controllers/html/sessions/passwordlesses_controller.rb.tt +1 -1
  19. data/lib/generators/authentication/templates/controllers/html/sessions_controller.rb.tt +1 -1
  20. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenge/recovery_codes_controller.rb.tt +30 -0
  21. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenge/security_keys_controller.rb.tt +46 -0
  22. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenge/totps_controller.rb.tt +32 -0
  23. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/{recovery_codes_controller.rb.tt → profile/recovery_codes_controller.rb.tt} +2 -2
  24. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/profile/security_keys_controller.rb.tt +62 -0
  25. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/{totps_controller.rb.tt → profile/totps_controller.rb.tt} +3 -3
  26. data/lib/generators/authentication/templates/erb/home/index.html.erb.tt +46 -41
  27. data/lib/generators/authentication/templates/erb/two_factor_authentication/{challenges/_recovery_code_form.html.erb.tt → challenge/recovery_codes/new.html.erb.tt} +3 -3
  28. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenge/security_keys/new.html.erb.tt +12 -0
  29. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenge/totps/new.html.erb.tt +26 -0
  30. data/lib/generators/authentication/templates/erb/two_factor_authentication/{recovery_codes → profile/recovery_codes}/index.html.erb.tt +1 -1
  31. data/lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/_form_confirm.html.erb.tt +13 -0
  32. data/lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/_form_edit.html.erb.tt +17 -0
  33. data/lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/_security_key.html.erb.tt +1 -0
  34. data/lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/edit.html.erb.tt +3 -0
  35. data/lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/index.html.erb.tt +10 -0
  36. data/lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/new.html.erb.tt +12 -0
  37. data/lib/generators/authentication/templates/erb/two_factor_authentication/{totps → profile/totps}/new.html.erb.tt +1 -1
  38. data/lib/generators/authentication/templates/javascript/controllers/application.js.tt +15 -0
  39. data/lib/generators/authentication/templates/migrations/create_security_keys_migration.rb.tt +13 -0
  40. data/lib/generators/authentication/templates/migrations/create_users_migration.rb.tt +3 -0
  41. data/lib/generators/authentication/templates/models/security_key.rb.tt +3 -0
  42. data/lib/generators/authentication/templates/models/user.rb.tt +9 -2
  43. metadata +24 -11
  44. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenges_controller.rb.tt +0 -52
  45. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/_totp_form.html.erb.tt +0 -19
  46. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/new.html.erb.tt +0 -7
  47. /data/lib/generators/authentication/templates/erb/two_factor_authentication/{recovery_codes → profile/recovery_codes}/_recovery_code.html.erb.tt +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cde9e441220d034130e39d950ed95456464b09c239596df6b1a84743292d5501
4
- data.tar.gz: df3da73d206c8a5b7d824afabf0c4b1882e61f8102e841a3cabaf6d6e366dac6
3
+ metadata.gz: c87cbd4d780d1170998c49949d6f28dbe0305df16880f012a7fd6a9cd19ce698
4
+ data.tar.gz: bb6dbd332c8d4fc221d4de3e8264204d0c999f1707f7529b3d547cb2eb4f5fe4
5
5
  SHA512:
6
- metadata.gz: 2b8fa852a07b2d4714f3b37b4632d5b3c214f405d4ba45882b76a175a9d853859852f564229c1e1a0457ceb495d0cae4a7146bea2ab51c6265558d0a0cb87cc4
7
- data.tar.gz: 1749b558ec810c4ec0c36505a030a62c3134c47619e5afeafc198afa3aabf4762719cf05465647be7960ff4a3f53950a6664a872df9407e59d9b0d4bf762ba50
6
+ metadata.gz: dc453d93036ca66a4df78da04c2fb1da2a21e862a409bad8608ec1661b315c9670e126b5de14ccd1b0a6b4fedb0445f258b0a472cb6f81c90ef588a178484cfc
7
+ data.tar.gz: c1d014b975a078302d919ba3ec52b4b2b4f0c94017cb04178785ae28bb5e5c8ff6e5d5e2fb84a8dc551cf3b5dc757c392af3061718ecf4e3a64c176292faaad2
@@ -26,6 +26,9 @@ jobs:
26
26
  - name: Install Rubocop
27
27
  run: gem install rubocop rubocop-performance rubocop-minitest rubocop-packaging rubocop-minitest rubocop-rails
28
28
 
29
+ - name: Install Brakeman
30
+ run: gem install brakeman
31
+
29
32
  - name: Create fresh Rails app and run generator
30
33
  run: |
31
34
  rails new test-app
@@ -39,9 +42,6 @@ jobs:
39
42
  - name: Rubocop
40
43
  run: cd test-app && rubocop
41
44
 
42
- - name: Install Brakeman
43
- run: gem install brakeman
44
-
45
45
  - name: Brakeman
46
46
  run: cd test-app && brakeman
47
47
 
@@ -69,6 +69,9 @@ jobs:
69
69
  - name: Install Rubocop
70
70
  run: gem install rubocop rubocop-performance rubocop-minitest rubocop-packaging rubocop-minitest rubocop-rails
71
71
 
72
+ - name: Install Brakeman
73
+ run: gem install brakeman
74
+
72
75
  - name: Create fresh Rails app and run generator
73
76
  run: |
74
77
  rails new test-app
@@ -82,9 +85,6 @@ jobs:
82
85
  - name: Rubocop
83
86
  run: cd test-app && rubocop
84
87
 
85
- - name: Install Brakeman
86
- run: gem install brakeman
87
-
88
88
  - name: Brakeman
89
89
  run: cd test-app && brakeman
90
90
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## Authentication Zero 2.16.21 ##
2
+
3
+ * Add two factor authentication using a hardware security key (--webauthn)
4
+ * Move two factor authentication to new namespaces
5
+
1
6
  ## Authentication Zero 2.16.18 ##
2
7
 
3
8
  * Use session to store the token for the 2fa challenge
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- authentication-zero (2.16.19)
4
+ authentication-zero (2.16.21)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -20,7 +20,7 @@ Since Authentication Zero generates this code into your application instead of b
20
20
 
21
21
  ## Features
22
22
 
23
- - **Simplest code ever (~200 lines of code)**
23
+ - **Simple code**
24
24
  - **Inspired by hey.com**
25
25
  - Sign up
26
26
  - Email and password validations
@@ -29,6 +29,7 @@ Since Authentication Zero generates this code into your application instead of b
29
29
  - Authentication by token (--api)
30
30
  - Passwordless authentication (--passwordless)
31
31
  - Two factor authentication + recovery codes (--two-factor)
32
+ - Two factor authentication using a hardware security key (--webauthn)
32
33
  - Social Login with OmniAuth (--omniauthable)
33
34
  - Send invitations (--invitable)
34
35
  - Sign-in as button functionallity (--masqueradable)
@@ -36,7 +37,7 @@ Since Authentication Zero generates this code into your application instead of b
36
37
  - Ask password before sensitive data changes, aka: sudo (--sudoable)
37
38
  - Reset the user password and send reset instructions
38
39
  - Reset the user password only from verified emails
39
- - Lock mechanism to prevent spamming (--lockable)
40
+ - Lock mechanism to prevent email bombing (--lockable)
40
41
  - Rate limiting for your app, 1000 reqs/minute (--ratelimit)
41
42
  - Send e-mail confirmation when your email has been changed
42
43
  - Manage multiple sessions & devices
@@ -1,3 +1,3 @@
1
1
  module AuthenticationZero
2
- VERSION = "2.16.19"
2
+ VERSION = "2.16.21"
3
3
  end
@@ -12,6 +12,7 @@ class AuthenticationGenerator < Rails::Generators::Base
12
12
  class_option :omniauthable, type: :boolean, desc: "Add social login support"
13
13
  class_option :trackable, type: :boolean, desc: "Add activity log support"
14
14
  class_option :two_factor, type: :boolean, desc: "Add two factor authentication"
15
+ class_option :webauthn, type: :boolean, desc: "Add two factor authentication using a hardware security key"
15
16
  class_option :invitable, type: :boolean, desc: "Add sending invitations"
16
17
  class_option :masqueradable, type: :boolean, desc: "Add sign-in as button functionallity"
17
18
 
@@ -42,6 +43,10 @@ class AuthenticationGenerator < Rails::Generators::Base
42
43
  gem "rotp", comment: "Use rotp for generating and validating one time passwords [https://github.com/mdp/rotp]"
43
44
  gem "rqrcode", comment: "Use rqrcode for creating and rendering QR codes into various formats [https://github.com/whomwah/rqrcode]"
44
45
  end
46
+
47
+ if webauthn?
48
+ gem "webauthn", comment: "Use webauthn for making rails become a conformant web authn relying party [https://github.com/cedarcode/webauthn-ruby]"
49
+ end
45
50
  end
46
51
 
47
52
  def add_environment_configurations
@@ -53,6 +58,7 @@ class AuthenticationGenerator < Rails::Generators::Base
53
58
  def create_configuration_files
54
59
  copy_file "config/redis/shared.yml", "config/redis/shared.yml" if redis?
55
60
  copy_file "config/initializers/omniauth.rb", "config/initializers/omniauth.rb" if omniauthable?
61
+ copy_file "config/initializers/webauthn.rb", "config/initializers/webauthn.rb" if webauthn?
56
62
  end
57
63
 
58
64
  def create_migrations
@@ -63,6 +69,7 @@ class AuthenticationGenerator < Rails::Generators::Base
63
69
  migration_template "migrations/create_sign_in_tokens_migration.rb", "#{db_migrate_path}/create_sign_in_tokens_migration.rb" if passwordless?
64
70
  migration_template "migrations/create_events_migration.rb", "#{db_migrate_path}/create_events.rb" if options.trackable?
65
71
  migration_template "migrations/create_recovery_codes_migration.rb", "#{db_migrate_path}/create_recovery_codes.rb" if two_factor?
72
+ migration_template "migrations/create_security_keys_migration.rb", "#{db_migrate_path}/create_security_keys.rb" if webauthn?
66
73
  end
67
74
 
68
75
  def create_models
@@ -74,6 +81,7 @@ class AuthenticationGenerator < Rails::Generators::Base
74
81
  template "models/current.rb", "app/models/current.rb"
75
82
  template "models/event.rb", "app/models/event.rb" if options.trackable?
76
83
  template "models/recovery_code.rb", "app/models/recovery_code.rb" if two_factor?
84
+ template "models/security_key.rb", "app/models/security_key.rb" if webauthn?
77
85
  end
78
86
 
79
87
  def create_fixture_file
@@ -84,7 +92,17 @@ class AuthenticationGenerator < Rails::Generators::Base
84
92
  template "controllers/#{format_folder}/application_controller.rb", "app/controllers/application_controller.rb", force: true
85
93
 
86
94
  directory "controllers/#{format_folder}/identity", "app/controllers/identity"
87
- directory "controllers/#{format_folder}/two_factor_authentication", "app/controllers/two_factor_authentication" if two_factor?
95
+
96
+ if two_factor?
97
+ template "controllers/html/two_factor_authentication/profile/totps_controller.rb", "app/controllers/two_factor_authentication/profile/totps_controller.rb"
98
+ template "controllers/html/two_factor_authentication/profile/recovery_codes_controller.rb", "app/controllers/two_factor_authentication/profile/recovery_codes_controller.rb"
99
+ template "controllers/html/two_factor_authentication/profile/security_keys_controller.rb", "app/controllers/two_factor_authentication/profile/security_keys_controller.rb" if webauthn?
100
+
101
+ template "controllers/html/two_factor_authentication/challenge/totps_controller.rb", "app/controllers/two_factor_authentication/challenge/totps_controller.rb"
102
+ template "controllers/html/two_factor_authentication/challenge/recovery_codes_controller.rb", "app/controllers/two_factor_authentication/challenge/recovery_codes_controller.rb"
103
+ template "controllers/html/two_factor_authentication/challenge/security_keys_controller.rb", "app/controllers/two_factor_authentication/challenge/security_keys_controller.rb" if webauthn?
104
+ end
105
+
88
106
  template "controllers/#{format_folder}/sessions_controller.rb", "app/controllers/sessions_controller.rb"
89
107
  template "controllers/#{format_folder}/passwords_controller.rb", "app/controllers/passwords_controller.rb"
90
108
  template "controllers/#{format_folder}/invitations_controller.rb", "app/controllers/invitations_controller.rb" if invitable?
@@ -97,6 +115,16 @@ class AuthenticationGenerator < Rails::Generators::Base
97
115
  template "controllers/#{format_folder}/authentications/events_controller.rb", "app/controllers/authentications/events_controller.rb" if options.trackable?
98
116
  end
99
117
 
118
+ def install_javascript_dependencies
119
+ return if options.api?
120
+ template "javascript/controllers/application.js", "app/javascript/controllers/application.js"
121
+
122
+ if webauthn?
123
+ run "bin/importmap pin stimulus-web-authn" if importmaps?
124
+ run "yarn add stimulus-web-authn" if node?
125
+ end
126
+ end
127
+
100
128
  def create_views
101
129
  if options.api?
102
130
  template "erb/user_mailer/email_verification.html.erb", "app/views/user_mailer/email_verification.html.erb"
@@ -122,8 +150,17 @@ class AuthenticationGenerator < Rails::Generators::Base
122
150
 
123
151
  directory "erb/sessions/passwordlesses", "app/views/sessions/passwordlesses" if passwordless?
124
152
 
125
- directory "erb/two_factor_authentication", "app/views/two_factor_authentication" if two_factor?
126
153
  directory "erb/authentications/events", "app/views/authentications/events" if options.trackable?
154
+
155
+ if two_factor?
156
+ directory "erb/two_factor_authentication/profile/totps", "app/views/two_factor_authentication/profile/totps"
157
+ directory "erb/two_factor_authentication/profile/recovery_codes", "app/views/two_factor_authentication/profile/recovery_codes"
158
+ directory "erb/two_factor_authentication/profile/security_keys", "app/views/two_factor_authentication/profile/security_keys" if webauthn?
159
+
160
+ directory "erb/two_factor_authentication/challenge/totps", "app/views/two_factor_authentication/challenge/totps"
161
+ directory "erb/two_factor_authentication/challenge/recovery_codes", "app/views/two_factor_authentication/challenge/recovery_codes"
162
+ directory "erb/two_factor_authentication/challenge/security_keys", "app/views/two_factor_authentication/challenge/security_keys" if webauthn?
163
+ end
127
164
  end
128
165
  end
129
166
 
@@ -157,9 +194,13 @@ class AuthenticationGenerator < Rails::Generators::Base
157
194
  end
158
195
 
159
196
  if two_factor?
160
- route "resources :recovery_codes, only: [:index, :create]", namespace: :two_factor_authentication
161
- route "resource :totp, only: [:new, :create]", namespace: :two_factor_authentication
162
- route "resource :challenge, only: [:new, :create]", namespace: :two_factor_authentication
197
+ route "resources :recovery_codes, only: [:index, :create]", namespace: [:two_factor_authentication, :profile]
198
+ route "resource :totp, only: [:new, :create]", namespace: [:two_factor_authentication, :profile]
199
+ route "resources :security_keys", namespace: [:two_factor_authentication, :profile] if webauthn?
200
+
201
+ route "resource :recovery_codes, only: [:new, :create]", namespace: [:two_factor_authentication, :challenge]
202
+ route "resource :totp, only: [:new, :create]", namespace: [:two_factor_authentication, :challenge]
203
+ route "resource :security_keys, only: [:new, :create]", namespace: [:two_factor_authentication, :challenge] if webauthn?
163
204
  end
164
205
 
165
206
  if options.trackable?
@@ -212,6 +253,10 @@ class AuthenticationGenerator < Rails::Generators::Base
212
253
  options.two_factor? && !options.api?
213
254
  end
214
255
 
256
+ def webauthn?
257
+ options.webauthn? && two_factor?
258
+ end
259
+
215
260
  def invitable?
216
261
  options.invitable? && !options.api?
217
262
  end
@@ -227,4 +272,12 @@ class AuthenticationGenerator < Rails::Generators::Base
227
272
  def redis?
228
273
  options.lockable? || options.ratelimit? || sudoable?
229
274
  end
275
+
276
+ def importmaps?
277
+ Rails.root.join("config/importmap.rb").exist?
278
+ end
279
+
280
+ def node?
281
+ Rails.root.join("package.json").exist?
282
+ end
230
283
  end
@@ -0,0 +1,4 @@
1
+ WebAuthn.configure do |config|
2
+ config.origin = "http://localhost:3000"
3
+ config.rp_name = "Example Inc."
4
+ end
@@ -6,10 +6,5 @@ development: &development
6
6
  url: <%= ENV.fetch("REDIS_URL", "redis://127.0.0.1:6379/0") %>
7
7
  timeout: 1
8
8
 
9
- # You can also specify host, port, and db instead of url
10
- # host: <%= ENV.fetch("REDIS_SHARED_HOST", "127.0.0.1") %>
11
- # port: <%= ENV.fetch("REDIS_SHARED_PORT", "6379") %>
12
- # db: <%= ENV.fetch("REDIS_SHARED_DB", "11") %>
13
-
14
9
  test:
15
10
  <<: *development
@@ -3,16 +3,6 @@ class ApplicationController < ActionController::API
3
3
 
4
4
  before_action :set_current_request_details
5
5
  before_action :authenticate
6
- <%- if options.lockable? %>
7
- def require_lock(wait: 1.hour, attempts: 10)
8
- counter = Kredis.counter("require_lock:#{request.remote_ip}:#{controller_path}:#{action_name}", expires_in: wait)
9
- counter.increment
10
-
11
- if counter.value > attempts
12
- render json: { error: "You've exceeded the maximum number of attempts" }, status: :too_many_requests
13
- end
14
- end
15
- <%- end -%>
16
6
 
17
7
  private
18
8
  def authenticate
@@ -27,4 +17,14 @@ class ApplicationController < ActionController::API
27
17
  Current.user_agent = request.user_agent
28
18
  Current.ip_address = request.ip
29
19
  end
20
+ <%- if options.lockable? %>
21
+ def require_lock(wait: 1.hour, attempts: 10)
22
+ counter = Kredis.counter("require_lock:#{request.remote_ip}:#{controller_path}:#{action_name}", expires_in: wait)
23
+ counter.increment
24
+
25
+ if counter.value > attempts
26
+ render json: { error: "You've exceeded the maximum number of attempts" }, status: :too_many_requests
27
+ end
28
+ end
29
+ <%- end -%>
30
30
  end
@@ -13,7 +13,7 @@ class Identity::EmailVerificationsController < ApplicationController
13
13
 
14
14
  private
15
15
  def set_user
16
- @token = EmailVerificationToken.find_signed!(params[:sid]); @user = @token.user
16
+ token = EmailVerificationToken.find_signed!(params[:sid]); @user = token.user
17
17
  rescue StandardError
18
18
  render json: { error: "That email verification link is invalid" }, status: :bad_request
19
19
  end
@@ -4,7 +4,7 @@ class Identity::EmailsController < ApplicationController
4
4
  def update
5
5
  if !@user.authenticate(params[:current_password])
6
6
  render json: { error: "The password you entered is incorrect" }, status: :bad_request
7
- elsif @user.update(user_params)
7
+ elsif @user.update(email: params[:email])
8
8
  render_show
9
9
  else
10
10
  render json: @user.errors, status: :unprocessable_entity
@@ -16,10 +16,6 @@ class Identity::EmailsController < ApplicationController
16
16
  @user = Current.user
17
17
  end
18
18
 
19
- def user_params
20
- params.permit(:email)
21
- end
22
-
23
19
  def render_show
24
20
  if @user.email_previously_changed?
25
21
  resend_email_verification; render(json: @user)
@@ -28,7 +28,7 @@ class Identity::PasswordResetsController < ApplicationController
28
28
 
29
29
  private
30
30
  def set_user
31
- @token = PasswordResetToken.find_signed!(params[:sid]); @user = @token.user
31
+ token = PasswordResetToken.find_signed!(params[:sid]); @user = token.user
32
32
  rescue StandardError
33
33
  render json: { error: "That password reset link is invalid" }, status: :bad_request
34
34
  end
@@ -1,22 +1,6 @@
1
1
  class ApplicationController < ActionController::Base
2
2
  before_action :set_current_request_details
3
3
  before_action :authenticate
4
- <%- if sudoable? %>
5
- def require_sudo
6
- return if Current.session.sudo?
7
- redirect_to new_sessions_sudo_path(proceed_to_url: request.url)
8
- end
9
- <%- end -%>
10
- <%- if options.lockable? %>
11
- def require_lock(wait: 1.hour, attempts: 10)
12
- counter = Kredis.counter("require_lock:#{request.remote_ip}:#{controller_path}:#{action_name}", expires_in: wait)
13
- counter.increment
14
-
15
- if counter.value > attempts
16
- redirect_to root_path, alert: "You've exceeded the maximum number of attempts"
17
- end
18
- end
19
- <%- end -%>
20
4
 
21
5
  private
22
6
  def authenticate
@@ -31,4 +15,21 @@ class ApplicationController < ActionController::Base
31
15
  Current.user_agent = request.user_agent
32
16
  Current.ip_address = request.ip
33
17
  end
18
+ <%- if options.lockable? %>
19
+ def require_lock(wait: 1.hour, attempts: 10)
20
+ counter = Kredis.counter("require_lock:#{request.remote_ip}:#{controller_path}:#{action_name}", expires_in: wait)
21
+ counter.increment
22
+
23
+ if counter.value > attempts
24
+ redirect_to root_path, alert: "You've exceeded the maximum number of attempts"
25
+ end
26
+ end
27
+ <%- end -%>
28
+ <%- if sudoable? %>
29
+ def require_sudo
30
+ unless Current.session.sudo?
31
+ redirect_to new_sessions_sudo_path(proceed_to_url: request.original_url)
32
+ end
33
+ end
34
+ <%- end -%>
34
35
  end
@@ -15,7 +15,7 @@ class Identity::EmailVerificationsController < ApplicationController
15
15
 
16
16
  private
17
17
  def set_user
18
- @token = EmailVerificationToken.find_signed!(params[:sid]); @user = @token.user
18
+ token = EmailVerificationToken.find_signed!(params[:sid]); @user = token.user
19
19
  rescue StandardError
20
20
  redirect_to edit_identity_email_path, alert: "That email verification link is invalid"
21
21
  end
@@ -7,7 +7,7 @@ class Identity::EmailsController < ApplicationController
7
7
  def update
8
8
  if !@user.authenticate(params[:current_password])
9
9
  redirect_to edit_identity_email_path, alert: "The password you entered is incorrect"
10
- elsif @user.update(user_params)
10
+ elsif @user.update(email: params[:email])
11
11
  redirect_to_root
12
12
  else
13
13
  render :edit, status: :unprocessable_entity
@@ -19,10 +19,6 @@ class Identity::EmailsController < ApplicationController
19
19
  @user = Current.user
20
20
  end
21
21
 
22
- def user_params
23
- params.permit(:email)
24
- end
25
-
26
22
  def redirect_to_root
27
23
  if @user.email_previously_changed?
28
24
  resend_email_verification
@@ -31,7 +31,7 @@ class Identity::PasswordResetsController < ApplicationController
31
31
 
32
32
  private
33
33
  def set_user
34
- @token = PasswordResetToken.find_signed!(params[:sid]); @user = @token.user
34
+ token = PasswordResetToken.find_signed!(params[:sid]); @user = token.user
35
35
  rescue StandardError
36
36
  redirect_to new_identity_password_reset_path, alert: "That password reset link is invalid"
37
37
  end
@@ -27,7 +27,7 @@ class Sessions::PasswordlessesController < ApplicationController
27
27
 
28
28
  private
29
29
  def set_user
30
- @token = SignInToken.find_signed!(params[:sid]); @user = @token.user
30
+ token = SignInToken.find_signed!(params[:sid]); @user = token.user
31
31
  rescue StandardError
32
32
  redirect_to new_sessions_passwordless_path, alert: "That sign in link is invalid"
33
33
  end
@@ -18,7 +18,7 @@ class SessionsController < ApplicationController
18
18
  <%- if two_factor? -%>
19
19
  if user.otp_secret.present?
20
20
  session[:challenge_token] = user.signed_id(purpose: :authentication_challenge, expires_in: 20.minutes)
21
- redirect_to new_two_factor_authentication_challenge_path
21
+ redirect_to new_two_factor_authentication_challenge_totp_path
22
22
  else
23
23
  @session = user.sessions.create!
24
24
  cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }
@@ -0,0 +1,30 @@
1
+ class TwoFactorAuthentication::Challenge::RecoveryCodesController < ApplicationController
2
+ skip_before_action :authenticate
3
+
4
+ before_action :set_user
5
+
6
+ def new
7
+ end
8
+
9
+ def create
10
+ if recover_code = @user.recovery_codes.find_by(code: params[:code], used: false)
11
+ recover_code.update!(used: true); sign_in_and_redirect_to_root
12
+ else
13
+ redirect_to new_two_factor_authentication_challenge_recovery_codes_path, alert: "That code didn't work. Please try again"
14
+ end
15
+ end
16
+
17
+ private
18
+ def set_user
19
+ @user = User.find_signed!(session[:challenge_token], purpose: :authentication_challenge)
20
+ rescue StandardError
21
+ redirect_to sign_in_path, alert: "That's taking too long. Please re-enter your password and try again"
22
+ end
23
+
24
+ def sign_in_and_redirect_to_root
25
+ session = @user.sessions.create!
26
+ cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
27
+
28
+ redirect_to root_path, notice: "Signed in successfully"
29
+ end
30
+ end
@@ -0,0 +1,46 @@
1
+ class TwoFactorAuthentication::Challenge::SecurityKeysController < ApplicationController
2
+ skip_before_action :authenticate
3
+
4
+ before_action :set_user
5
+
6
+ def new
7
+ respond_to do |format|
8
+ format.html
9
+ format.json { render json: options_for_get }
10
+ end
11
+ end
12
+
13
+ def create
14
+ if @user.security_keys.exists?(external_id: credential.id)
15
+ sign_in_and_redirect_to_root
16
+ else
17
+ render json: { error: "Verification failed: #{e.message}" }, status: :unprocessable_entity
18
+ end
19
+ end
20
+
21
+ private
22
+ def set_user
23
+ @user = User.find_signed!(session[:challenge_token], purpose: :authentication_challenge)
24
+ rescue StandardError
25
+ redirect_to sign_in_path, alert: "That's taking too long. Please re-enter your password and try again"
26
+ end
27
+
28
+ def sign_in_and_redirect_to_root
29
+ session = @user.sessions.create!
30
+ cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
31
+
32
+ render json: { status: "ok", location: root_url }, status: :created
33
+ end
34
+
35
+ def options_for_get
36
+ WebAuthn::Credential.options_for_get(allow: external_ids)
37
+ end
38
+
39
+ def external_ids
40
+ @user.security_keys.pluck(:external_id)
41
+ end
42
+
43
+ def credential
44
+ @credential ||= WebAuthn::Credential.from_get(params.require(:credential))
45
+ end
46
+ end
@@ -0,0 +1,32 @@
1
+ class TwoFactorAuthentication::Challenge::TotpsController < ApplicationController
2
+ skip_before_action :authenticate
3
+
4
+ before_action :set_user
5
+
6
+ def new
7
+ end
8
+
9
+ def create
10
+ @totp = ROTP::TOTP.new(@user.otp_secret, issuer: "YourAppName")
11
+
12
+ if @totp.verify(params[:code], drift_behind: 15)
13
+ sign_in_and_redirect_to_root
14
+ else
15
+ redirect_to new_two_factor_authentication_challenge_totp_path, alert: "That code didn't work. Please try again"
16
+ end
17
+ end
18
+
19
+ private
20
+ def set_user
21
+ @user = User.find_signed!(session[:challenge_token], purpose: :authentication_challenge)
22
+ rescue StandardError
23
+ redirect_to sign_in_path, alert: "That's taking too long. Please re-enter your password and try again"
24
+ end
25
+
26
+ def sign_in_and_redirect_to_root
27
+ session = @user.sessions.create!
28
+ cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
29
+
30
+ redirect_to root_path, notice: "Signed in successfully"
31
+ end
32
+ end
@@ -1,4 +1,4 @@
1
- class TwoFactorAuthentication::RecoveryCodesController < ApplicationController
1
+ class TwoFactorAuthentication::Profile::RecoveryCodesController < ApplicationController
2
2
  before_action :set_user
3
3
 
4
4
  def index
@@ -13,7 +13,7 @@ class TwoFactorAuthentication::RecoveryCodesController < ApplicationController
13
13
  @user.recovery_codes.delete_all
14
14
  @user.recovery_codes.create!(new_recovery_codes)
15
15
 
16
- redirect_to two_factor_authentication_recovery_codes_path, notice: "Your new recovery codes have been generated"
16
+ redirect_to two_factor_authentication_profile_recovery_codes_path, notice: "Your new recovery codes have been generated"
17
17
  end
18
18
 
19
19
  private
@@ -0,0 +1,62 @@
1
+ class TwoFactorAuthentication::Profile::SecurityKeysController < ApplicationController
2
+ before_action :set_user
3
+ before_action :set_security_key, only: %i[ edit update destroy ]
4
+
5
+ def index
6
+ @security_keys = @user.security_keys
7
+ end
8
+
9
+ def new
10
+ respond_to do |format|
11
+ format.html
12
+ format.json { render json: options_for_create }
13
+ end
14
+ end
15
+
16
+ def edit
17
+ end
18
+
19
+ def create
20
+ @security_key = @user.security_keys.create!(credential_params)
21
+ render json: { status: "ok", location: edit_two_factor_authentication_profile_security_key_url(@security_key, confirmation: true) }, status: :created
22
+ end
23
+
24
+ def update
25
+ @security_key.update! name: params[:name]
26
+ redirect_to two_factor_authentication_profile_security_keys_path, notice: "Your changes have been saved"
27
+ end
28
+
29
+ def destroy
30
+ @security_key.destroy
31
+ redirect_to two_factor_authentication_profile_security_keys_path, notice: "#{@security_key.name} has been removed"
32
+ end
33
+
34
+ private
35
+ def set_user
36
+ @user = Current.user
37
+ end
38
+
39
+ def set_security_key
40
+ @security_key = @user.security_keys.find(params[:id])
41
+ end
42
+
43
+ def options_for_create
44
+ WebAuthn::Credential.options_for_create(user: user_info, exclude: external_ids)
45
+ end
46
+
47
+ def user_info
48
+ { id: @user.webauthn_id, name: @user.email }
49
+ end
50
+
51
+ def external_ids
52
+ @user.security_keys.pluck(:external_id)
53
+ end
54
+
55
+ def credential_params
56
+ { external_id: credential.id, name: "security key" }
57
+ end
58
+
59
+ def credential
60
+ @credential ||= WebAuthn::Credential.from_create(params.require(:credential))
61
+ end
62
+ end
@@ -1,4 +1,4 @@
1
- class TwoFactorAuthentication::TotpsController < ApplicationController
1
+ class TwoFactorAuthentication::Profile::TotpsController < ApplicationController
2
2
  before_action :set_user
3
3
  before_action :set_totp
4
4
 
@@ -9,9 +9,9 @@ class TwoFactorAuthentication::TotpsController < ApplicationController
9
9
  def create
10
10
  if @totp.verify(params[:code], drift_behind: 15)
11
11
  @user.update! otp_secret: params[:secret]
12
- redirect_to two_factor_authentication_recovery_codes_path
12
+ redirect_to two_factor_authentication_profile_recovery_codes_path
13
13
  else
14
- redirect_to new_two_factor_authentication_totp_path, alert: "That code didn't work. Please try again"
14
+ redirect_to new_two_factor_authentication_profile_totp_path, alert: "That code didn't work. Please try again"
15
15
  end
16
16
  end
17
17
 
@@ -1,47 +1,52 @@
1
1
  <p style="color: green"><%%= notice %></p>
2
2
 
3
- <%% if Current.user.present? %>
4
- <p>Signed as <%%= Current.user.email %></p>
5
-
6
- <div>
7
- <%%= link_to "Change email address", edit_identity_email_path %>
8
- </div>
9
-
10
- <div>
11
- <%%= link_to "Change password", edit_password_path %>
12
- </div>
13
-
14
- <div>
15
- <%%= link_to "Devices & Sessions", sessions_path %>
16
- </div>
17
- <%- if options.trackable? %>
18
- <div>
19
- <%%= link_to "Activity Log", authentications_events_path %>
20
- </div>
21
- <%- end -%>
22
- <%- if invitable? %>
23
- <div>
24
- <%%= link_to "Send invitation", new_invitation_path %>
25
- </div>
26
- <%- end -%>
27
- <%- if masqueradable? %>
28
- <div>
29
- <%%= button_to "Signin as last user", user_masquerade_path(User.last) %>
30
- </div>
31
- <%- end -%>
32
- <%- if two_factor? %>
33
- <div>
34
- <%%= link_to "Two-Factor Authentication", new_two_factor_authentication_totp_path %>
35
- </div>
36
-
37
- <%% if Current.user.otp_secret.present? %>
38
- <div><%%= link_to "Recovery Codes", two_factor_authentication_recovery_codes_path %></div>
39
- <%% end %>
3
+ <p>Signed as <%%= Current.user.email %></p>
4
+
5
+ <h2>Login and verification</h2>
6
+
7
+ <div>
8
+ <%%= link_to "Change password", edit_password_path %>
9
+ </div>
10
+
11
+ <div>
12
+ <%%= link_to "Change email address", edit_identity_email_path %>
13
+ </div>
14
+ <%- if two_factor? %>
15
+ <div>
16
+ <%%= link_to "Two-Factor Authentication", new_two_factor_authentication_profile_totp_path %>
17
+ </div>
18
+
19
+ <%% if Current.user.otp_secret.present? %>
20
+ <div><%%= link_to "Recovery Codes", two_factor_authentication_profile_recovery_codes_path %></div>
21
+ <%- if webauthn? -%>
22
+ <div><%%= link_to "Security keys", two_factor_authentication_profile_security_keys_path %></div>
40
23
  <%- end -%>
24
+ <%% end %>
25
+ <%- end -%>
26
+ <%- if invitable? %>
27
+ <div>
28
+ <%%= link_to "Send invitation", new_invitation_path %>
29
+ </div>
30
+ <%- end -%>
31
+ <%- if masqueradable? %>
32
+ <div>
33
+ <%%= button_to "Signin as last user", user_masquerade_path(User.last) %>
34
+ </div>
35
+ <%- end -%>
36
+
37
+ <h2>Access history</h2>
41
38
 
42
- <br>
39
+ <div>
40
+ <%%= link_to "Devices & Sessions", sessions_path %>
41
+ </div>
42
+ <%- if options.trackable? %>
43
+ <div>
44
+ <%%= link_to "Activity Log", authentications_events_path %>
45
+ </div>
46
+ <%- end -%>
43
47
 
48
+ <br>
49
+
50
+ <div>
44
51
  <%%= button_to "Log out", Current.session, method: :delete %>
45
- <%% else %>
46
- Please <%%= link_to "sign in", sign_in_path %> or <%%= link_to "sign up", sign_up_path %>.
47
- <%% end %>
52
+ </div>
@@ -1,6 +1,6 @@
1
- <%%= form_with(url: two_factor_authentication_challenge_path) do |form| %>
2
- <%%= form.hidden_field :scheme_type, value: "recovery_codes" %>
1
+ <p style="color: red"><%%= alert %></p>
3
2
 
3
+ <%%= form_with(url: two_factor_authentication_challenge_recovery_codes_path) do |form| %>
4
4
  <div>
5
5
  <%%= form.label :code do %>
6
6
  <h1>OK, enter one of your recovery codes below:</h1>
@@ -14,5 +14,5 @@
14
14
  <%% end %>
15
15
 
16
16
  <div>
17
- <p>To access your account, enter one of the recovery codes (e.g., XXXXX-XXXXX) you saved when you set up your two-factor authentication device.</p>
17
+ <p>To access your account, enter one of the recovery codes (e.g., xxxxxxxxxx) you saved when you set up your two-factor authentication device.</p>
18
18
  </div>
@@ -0,0 +1,12 @@
1
+ <div data-controller="web-authn"
2
+ data-web-authn-loading-class="web-authn-loading"
3
+ data-web-authn-fallback-url-value="<%%= new_two_factor_authentication_challenge_totp_path %>"
4
+ data-web-authn-challenge-url-value="<%%= new_two_factor_authentication_challenge_security_keys_url %>"
5
+ data-web-authn-verification-url-value="<%%= two_factor_authentication_challenge_security_keys_url %>">
6
+
7
+ <p data-web-authn-target="error" style="color: red" hidden></p>
8
+
9
+ <h1>Verify with your security key.</h1>
10
+ <%%= button_tag "Use security key", type: :button, data: { web_authn_target: "button", action: "web-authn#getCredential" } %>
11
+ <p>Have your security key ready. If it's the USB kind insert it now and then, if it has one, press the activation button when asked.</p>
12
+ <div>
@@ -0,0 +1,26 @@
1
+ <p style="color: red"><%%= alert %></p>
2
+
3
+ <%%= form_with(url: two_factor_authentication_challenge_totp_path) do |form| %>
4
+ <div>
5
+ <%%= form.label :code do %>
6
+ <h1>Next, open the 2FA authenticator app on your phone and type the six digit code below:</h1>
7
+ <%% end %>
8
+ <%%= form.text_field :code, autofocus: true, required: true, autocomplete: :off %>
9
+ </div>
10
+
11
+ <div>
12
+ <%%= form.submit "Verify" %>
13
+ </div>
14
+ <%% end %>
15
+
16
+ <div>
17
+ <p><strong>Don't have your phone?</strong></p>
18
+ <div>
19
+ <%%= link_to "Use a recovery code to access your account.", new_two_factor_authentication_challenge_recovery_codes_path %>
20
+ </div>
21
+ <%- if webauthn? %>
22
+ <%% if @user.security_keys.exists? %>
23
+ <div><%%= link_to "Use a security key to access your account.", new_two_factor_authentication_challenge_security_keys_path %></div>
24
+ <%% end %>
25
+ <%- end -%>
26
+ </div>
@@ -13,4 +13,4 @@
13
13
 
14
14
  <p>If you think your codes have fallen into the wrong hands, you can get a new set. Be sure to save the new ones because the old codes will stop working.</p>
15
15
 
16
- <%%= button_to "Generate new recovery codes", two_factor_authentication_recovery_codes_path %>
16
+ <%%= button_to "Generate new recovery codes", two_factor_authentication_profile_recovery_codes_path %>
@@ -0,0 +1,13 @@
1
+ <%%= form_with(url: two_factor_authentication_profile_security_key_path, method: :patch) do |form| %>
2
+ <p>One more step. Please give this security key a nickname to help you remember it.</p>
3
+
4
+ <div>
5
+ <%%= form.label :name, style: "display: block" %>
6
+ <%%= form.text_field :name, autofocus: true, required: true %>
7
+ <div>e.g., Macbook Touch ID</div>
8
+ </div>
9
+
10
+ <div>
11
+ <%%= form.submit "Save" %>
12
+ </div>
13
+ <%% end %>
@@ -0,0 +1,17 @@
1
+ <%%= form_with(url: two_factor_authentication_profile_security_key_path, method: :patch) do |form| %>
2
+ <div>
3
+ <%%= form.label :name, style: "display: block" %>
4
+ <%%= form.text_field :name, value: @security_key.name, autofocus: true, required: true %>
5
+ <div>e.g., Macbook Touch ID</div>
6
+ </div>
7
+
8
+ <div>
9
+ <%%= form.submit "Save changes" %>
10
+ </div>
11
+ <%% end %>
12
+
13
+ <br>
14
+
15
+ <div>
16
+ <%%= button_to "Remove this security key...", two_factor_authentication_profile_security_key_path(@security_key), method: :delete %>
17
+ </div>
@@ -0,0 +1 @@
1
+ <li><%%= link_to security_key.name, edit_two_factor_authentication_profile_security_key_path(security_key) %></li>
@@ -0,0 +1,3 @@
1
+ <h1>Edit security key</h1>
2
+
3
+ <%%= render params[:confirmation] ? "form_confirm" : "form_edit" %>
@@ -0,0 +1,10 @@
1
+ <p style="color: green"><%%= notice %></p>
2
+
3
+ <h1>Security keys</h1>
4
+ <p>A security key is a hardware device used to verify your identity. For example, a built-in fingerprint reader, a plug-in USB key, or a login system like Windows Hello.</p>
5
+
6
+ <ul><%%= render @security_keys %></ul>
7
+
8
+ <br>
9
+
10
+ <%%= link_to "Add security key", new_two_factor_authentication_profile_security_key_path %>
@@ -0,0 +1,12 @@
1
+ <div data-controller="web-authn"
2
+ data-web-authn-loading-class="web-authn-loading"
3
+ data-web-authn-challenge-url-value="<%%= new_two_factor_authentication_profile_security_key_url %>"
4
+ data-web-authn-verification-url-value="<%%= two_factor_authentication_profile_security_keys_url %>">
5
+
6
+ <p data-web-authn-target="error" style="color: red" hidden></p>
7
+
8
+ <h1>Add a security key</h1>
9
+ <p>Have your security key ready. If it's the USB kind insert it now and then, if it has one, press the activation button when asked.</p>
10
+
11
+ <%%= button_tag "I'm ready, let's go", type: :button, data: { web_authn_target: "button", action: "web-authn#createCredential" } %>
12
+ </div>
@@ -14,7 +14,7 @@
14
14
  <figcaption>Point your camera here</figcaption>
15
15
  </figure>
16
16
 
17
- <%%= form_with(url: two_factor_authentication_totp_path) do |form| %>
17
+ <%%= form_with(url: two_factor_authentication_profile_totp_path) do |form| %>
18
18
  <%%= form.hidden_field :secret, value: @totp.secret %>
19
19
 
20
20
  <div>
@@ -0,0 +1,15 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+ <%- if webauthn? -%>
3
+ import WebAuthnController from "stimulus-web-authn"
4
+ <%- end -%>
5
+
6
+ const application = Application.start()
7
+ <%- if webauthn? -%>
8
+ application.register("web-authn", WebAuthnController)
9
+ <%- end -%>
10
+
11
+ // Configure Stimulus development experience
12
+ application.debug = false
13
+ window.Stimulus = application
14
+
15
+ export { application }
@@ -0,0 +1,13 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :security_keys do |t|
4
+ t.references :user, null: false, foreign_key: true
5
+ t.string :name, null: false
6
+ t.string :external_id, null: false
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :security_keys, :external_id, unique: true
12
+ end
13
+ end
@@ -12,6 +12,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
12
12
  t.string :provider
13
13
  t.string :uid
14
14
  <%- end -%>
15
+ <%- if webauthn? %>
16
+ t.string :webauthn_id
17
+ <%- end -%>
15
18
 
16
19
  t.timestamps
17
20
  end
@@ -0,0 +1,3 @@
1
+ class SecurityKey < ApplicationRecord
2
+ belongs_to :user
3
+ end
@@ -3,14 +3,16 @@ class User < ApplicationRecord
3
3
 
4
4
  has_many :email_verification_tokens, dependent: :destroy
5
5
  has_many :password_reset_tokens, dependent: :destroy
6
+ has_many :sessions, dependent: :destroy
6
7
  <%- if two_factor? -%>
7
8
  has_many :recovery_codes, dependent: :destroy
8
9
  <%- end -%>
10
+ <%- if webauthn? -%>
11
+ has_many :security_keys, dependent: :destroy
12
+ <%- end -%>
9
13
  <%- if passwordless? -%>
10
14
  has_many :sign_in_tokens, dependent: :destroy
11
15
  <%- end -%>
12
-
13
- has_many :sessions, dependent: :destroy
14
16
  <%- if options.trackable? -%>
15
17
  has_many :events, dependent: :destroy
16
18
  <%- end -%>
@@ -28,6 +30,11 @@ class User < ApplicationRecord
28
30
  before_validation if: :email_changed?, on: :update do
29
31
  self.verified = false
30
32
  end
33
+ <%- if webauthn? %>
34
+ before_validation on: :create do
35
+ self.webauthn_id = WebAuthn.generate_user_id
36
+ end
37
+ <%- end -%>
31
38
 
32
39
  after_update if: :password_digest_previously_changed? do
33
40
  sessions.where.not(id: Current.session).delete_all
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authentication-zero
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.16.19
4
+ version: 2.16.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nixon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-07 00:00:00.000000000 Z
11
+ date: 2023-04-12 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -36,6 +36,7 @@ files:
36
36
  - lib/generators/authentication/USAGE
37
37
  - lib/generators/authentication/authentication_generator.rb
38
38
  - lib/generators/authentication/templates/config/initializers/omniauth.rb
39
+ - lib/generators/authentication/templates/config/initializers/webauthn.rb
39
40
  - lib/generators/authentication/templates/config/redis/shared.yml
40
41
  - lib/generators/authentication/templates/controllers/api/application_controller.rb.tt
41
42
  - lib/generators/authentication/templates/controllers/api/authentications/events_controller.rb.tt
@@ -59,9 +60,12 @@ files:
59
60
  - lib/generators/authentication/templates/controllers/html/sessions/passwordlesses_controller.rb.tt
60
61
  - lib/generators/authentication/templates/controllers/html/sessions/sudos_controller.rb.tt
61
62
  - lib/generators/authentication/templates/controllers/html/sessions_controller.rb.tt
62
- - lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenges_controller.rb.tt
63
- - lib/generators/authentication/templates/controllers/html/two_factor_authentication/recovery_codes_controller.rb.tt
64
- - lib/generators/authentication/templates/controllers/html/two_factor_authentication/totps_controller.rb.tt
63
+ - lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenge/recovery_codes_controller.rb.tt
64
+ - lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenge/security_keys_controller.rb.tt
65
+ - lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenge/totps_controller.rb.tt
66
+ - lib/generators/authentication/templates/controllers/html/two_factor_authentication/profile/recovery_codes_controller.rb.tt
67
+ - lib/generators/authentication/templates/controllers/html/two_factor_authentication/profile/security_keys_controller.rb.tt
68
+ - lib/generators/authentication/templates/controllers/html/two_factor_authentication/profile/totps_controller.rb.tt
65
69
  - lib/generators/authentication/templates/erb/authentications/events/index.html.erb.tt
66
70
  - lib/generators/authentication/templates/erb/home/index.html.erb.tt
67
71
  - lib/generators/authentication/templates/erb/identity/emails/edit.html.erb.tt
@@ -74,21 +78,29 @@ files:
74
78
  - lib/generators/authentication/templates/erb/sessions/new.html.erb.tt
75
79
  - lib/generators/authentication/templates/erb/sessions/passwordlesses/new.html.erb.tt
76
80
  - lib/generators/authentication/templates/erb/sessions/sudos/new.html.erb.tt
77
- - lib/generators/authentication/templates/erb/two_factor_authentication/challenges/_recovery_code_form.html.erb.tt
78
- - lib/generators/authentication/templates/erb/two_factor_authentication/challenges/_totp_form.html.erb.tt
79
- - lib/generators/authentication/templates/erb/two_factor_authentication/challenges/new.html.erb.tt
80
- - lib/generators/authentication/templates/erb/two_factor_authentication/recovery_codes/_recovery_code.html.erb.tt
81
- - lib/generators/authentication/templates/erb/two_factor_authentication/recovery_codes/index.html.erb.tt
82
- - lib/generators/authentication/templates/erb/two_factor_authentication/totps/new.html.erb.tt
81
+ - lib/generators/authentication/templates/erb/two_factor_authentication/challenge/recovery_codes/new.html.erb.tt
82
+ - lib/generators/authentication/templates/erb/two_factor_authentication/challenge/security_keys/new.html.erb.tt
83
+ - lib/generators/authentication/templates/erb/two_factor_authentication/challenge/totps/new.html.erb.tt
84
+ - lib/generators/authentication/templates/erb/two_factor_authentication/profile/recovery_codes/_recovery_code.html.erb.tt
85
+ - lib/generators/authentication/templates/erb/two_factor_authentication/profile/recovery_codes/index.html.erb.tt
86
+ - lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/_form_confirm.html.erb.tt
87
+ - lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/_form_edit.html.erb.tt
88
+ - lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/_security_key.html.erb.tt
89
+ - lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/edit.html.erb.tt
90
+ - lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/index.html.erb.tt
91
+ - lib/generators/authentication/templates/erb/two_factor_authentication/profile/security_keys/new.html.erb.tt
92
+ - lib/generators/authentication/templates/erb/two_factor_authentication/profile/totps/new.html.erb.tt
83
93
  - lib/generators/authentication/templates/erb/user_mailer/email_verification.html.erb.tt
84
94
  - lib/generators/authentication/templates/erb/user_mailer/invitation_instructions.html.erb.tt
85
95
  - lib/generators/authentication/templates/erb/user_mailer/password_reset.html.erb.tt
86
96
  - lib/generators/authentication/templates/erb/user_mailer/passwordless.html.erb.tt
97
+ - lib/generators/authentication/templates/javascript/controllers/application.js.tt
87
98
  - lib/generators/authentication/templates/mailers/user_mailer.rb.tt
88
99
  - lib/generators/authentication/templates/migrations/create_email_verification_tokens_migration.rb.tt
89
100
  - lib/generators/authentication/templates/migrations/create_events_migration.rb.tt
90
101
  - lib/generators/authentication/templates/migrations/create_password_reset_tokens_migration.rb.tt
91
102
  - lib/generators/authentication/templates/migrations/create_recovery_codes_migration.rb.tt
103
+ - lib/generators/authentication/templates/migrations/create_security_keys_migration.rb.tt
92
104
  - lib/generators/authentication/templates/migrations/create_sessions_migration.rb.tt
93
105
  - lib/generators/authentication/templates/migrations/create_sign_in_tokens_migration.rb.tt
94
106
  - lib/generators/authentication/templates/migrations/create_users_migration.rb.tt
@@ -97,6 +109,7 @@ files:
97
109
  - lib/generators/authentication/templates/models/event.rb.tt
98
110
  - lib/generators/authentication/templates/models/password_reset_token.rb.tt
99
111
  - lib/generators/authentication/templates/models/recovery_code.rb.tt
112
+ - lib/generators/authentication/templates/models/security_key.rb.tt
100
113
  - lib/generators/authentication/templates/models/session.rb.tt
101
114
  - lib/generators/authentication/templates/models/sign_in_token.rb.tt
102
115
  - lib/generators/authentication/templates/models/user.rb.tt
@@ -1,52 +0,0 @@
1
- class TwoFactorAuthentication::ChallengesController < ApplicationController
2
- skip_before_action :authenticate
3
-
4
- before_action :set_user
5
-
6
- def new
7
- end
8
-
9
- def create
10
- if params[:scheme_type] == "recovery_codes"
11
- verify_recovery_code
12
- else
13
- verify_time_based_one_time_password
14
- end
15
- end
16
-
17
- private
18
- def set_user
19
- @user = User.find_signed!(session[:challenge_token], purpose: :authentication_challenge)
20
- rescue StandardError
21
- redirect_to sign_in_path, alert: "That's taking too long. Please re-enter your password and try again"
22
- end
23
-
24
- def verify_recovery_code
25
- if recover_code = @user.recovery_codes.find_by(code: params[:code], used: false)
26
- recover_code.update!(used: true); sign_in_and_redirect_to_root
27
- else
28
- redirect_to_authentication_challenge
29
- end
30
- end
31
-
32
- def verify_time_based_one_time_password
33
- @totp = ROTP::TOTP.new(@user.otp_secret, issuer: "YourAppName")
34
-
35
- if @totp.verify(params[:code], drift_behind: 15)
36
- sign_in_and_redirect_to_root
37
- else
38
- redirect_to_authentication_challenge
39
- end
40
- end
41
-
42
- def sign_in_and_redirect_to_root
43
- session = @user.sessions.create!
44
- cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
45
-
46
- redirect_to root_path, notice: "Signed in successfully"
47
- end
48
-
49
- def redirect_to_authentication_challenge
50
- redirect_to new_two_factor_authentication_challenge_path(scheme_type: params[:scheme_type]), alert: "That code didn't work. Please try again"
51
- end
52
- end
@@ -1,19 +0,0 @@
1
- <%%= form_with(url: two_factor_authentication_challenge_path) do |form| %>
2
- <%%= form.hidden_field :scheme_type, value: "totp" %>
3
-
4
- <div>
5
- <%%= form.label :code do %>
6
- <h1>Next, open the 2FA authenticator app on your phone and type the six digit code below:</h1>
7
- <%% end %>
8
- <%%= form.text_field :code, autofocus: true, required: true, autocomplete: :off %>
9
- </div>
10
-
11
- <div>
12
- <%%= form.submit "Verify" %>
13
- </div>
14
- <%% end %>
15
-
16
- <div>
17
- <p><strong>Don't have your phone?</strong></p>
18
- <%%= link_to "Use a recovery code to access your account.", new_two_factor_authentication_challenge_path(scheme_type: "recovery_codes") %>
19
- </div>
@@ -1,7 +0,0 @@
1
- <p style="color: red"><%%= alert %></p>
2
-
3
- <%% if params[:scheme_type] == "recovery_codes" %>
4
- <%%= render "recovery_code_form" %>
5
- <%% else %>
6
- <%%= render "totp_form" %>
7
- <%% end %>