authentication-zero 2.16.19 → 2.16.21

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