authentication-zero 2.16.15 → 2.16.17

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +1 -2
  5. data/lib/authentication_zero/version.rb +1 -1
  6. data/lib/generators/authentication/authentication_generator.rb +31 -24
  7. data/lib/generators/authentication/templates/controllers/api/identity/email_verifications_controller.rb.tt +1 -11
  8. data/lib/generators/authentication/templates/controllers/api/identity/password_resets_controller.rb.tt +5 -1
  9. data/lib/generators/authentication/templates/controllers/html/identity/email_verifications_controller.rb.tt +1 -1
  10. data/lib/generators/authentication/templates/controllers/html/identity/password_resets_controller.rb.tt +1 -1
  11. data/lib/generators/authentication/templates/controllers/html/sessions/passwordlesses_controller.rb.tt +1 -1
  12. data/lib/generators/authentication/templates/controllers/html/sessions_controller.rb.tt +1 -1
  13. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenges_controller.rb.tt +33 -9
  14. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/recovery_codes_controller.rb.tt +31 -0
  15. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/totps_controller.rb.tt +8 -7
  16. data/lib/generators/authentication/templates/erb/home/index.html.erb.tt +4 -0
  17. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/_recovery_code_form.html.erb.tt +19 -0
  18. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/_totp_form.html.erb.tt +20 -0
  19. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/new.html.erb.tt +4 -13
  20. data/lib/generators/authentication/templates/erb/two_factor_authentication/recovery_codes/_recovery_code.html.erb.tt +5 -0
  21. data/lib/generators/authentication/templates/erb/two_factor_authentication/recovery_codes/index.html.erb.tt +16 -0
  22. data/lib/generators/authentication/templates/erb/two_factor_authentication/totps/new.html.erb.tt +2 -2
  23. data/lib/generators/authentication/templates/erb/user_mailer/email_verification.html.erb.tt +1 -5
  24. data/lib/generators/authentication/templates/mailers/user_mailer.rb.tt +0 -4
  25. data/lib/generators/authentication/templates/migrations/create_recovery_codes_migration.rb.tt +11 -0
  26. data/lib/generators/authentication/templates/models/recovery_code.rb.tt +3 -0
  27. data/lib/generators/authentication/templates/models/user.rb.tt +4 -4
  28. data/lib/generators/authentication/templates/test_unit/controllers/api/identity/password_resets_controller_test.rb.tt +7 -0
  29. metadata +9 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85fbe83273139a893939d4f20c3a06de269e0cea2b5fe3a7994f8d66919c3532
4
- data.tar.gz: 63396f6a2ceb39fc3a49be717a6ffc7097c29e6782b7a351ca0d6f3bb7f2cae6
3
+ metadata.gz: 54633f078d0da7d8832e1e8067b1a6462ddcf567351b5f15e548f62ab0307d86
4
+ data.tar.gz: ca024f87f0c49e1534679fedadc7d3944209be7c955a3bc46e254e3d4c790f73
5
5
  SHA512:
6
- metadata.gz: '0899627eceb12252ec843541a19ec9da8de2e0d4ab12e76e43f34e1117597be485f58a090a66f825f2a1fdb1affe5c83cb55502e2deb7a681e2d70e7578ede31'
7
- data.tar.gz: 8ee00bb247a6989e0c930b33ffefc5c546f6e2c4161d77297344343ec5585b6c80f5b11dd18cd06937b6264d4abb1d89b9f47d1ab80494201bf5d523cb289f86
6
+ metadata.gz: 2646e6b0e95f86ab931fa4338cf587894265078e346a3cb5a3b919bc3b5aba7a0eab9bb8a72e16dcce96efbec43764f4d47df08d6c2011d57994e958a6b295cc
7
+ data.tar.gz: 449db5cde90d0ba2bdbfc4df356c2eca24eab9464f88f149229b4cddaf63ce03cb1bcdf211af60af443db1a411ecfd800e98f871b6c717747da8863620ce33a7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## Authentication Zero 2.16.16 ##
2
+
3
+ * Add recovery codes to two factor auth
4
+ * Removed code-verifiable strategy
5
+ * Respond password reset edit api with no_content
6
+
1
7
  ## Authentication Zero 2.16.15 ##
2
8
 
3
9
  * Add sign-in as button functionallity (--masqueradable)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- authentication-zero (2.16.15)
4
+ authentication-zero (2.16.17)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -28,12 +28,11 @@ Since Authentication Zero generates this code into your application instead of b
28
28
  - Authentication by cookie
29
29
  - Authentication by token (--api)
30
30
  - Passwordless authentication (--passwordless)
31
- - Two factor authentication (--two-factor)
31
+ - Two factor authentication + recovery codes (--two-factor)
32
32
  - Social Login with OmniAuth (--omniauthable)
33
33
  - Send invitations (--invitable)
34
34
  - Sign-in as button functionallity (--masqueradable)
35
35
  - Verify email using a link with token
36
- - Verify email using a six random digits code for api (--code-verifiable)
37
36
  - Ask password before sensitive data changes, aka: sudo (--sudoable)
38
37
  - Reset the user password and send reset instructions
39
38
  - Reset the user password only from verified emails
@@ -1,3 +1,3 @@
1
1
  module AuthenticationZero
2
- VERSION = "2.16.15"
2
+ VERSION = "2.16.17"
3
3
  end
@@ -3,18 +3,17 @@ require "rails/generators/active_record"
3
3
  class AuthenticationGenerator < Rails::Generators::Base
4
4
  include ActiveRecord::Generators::Migration
5
5
 
6
- class_option :api, type: :boolean, desc: "Generates API authentication"
7
- class_option :pwned, type: :boolean, desc: "Add pwned password validation"
8
- class_option :code_verifiable, type: :boolean, desc: "Add email verification using a code for api"
9
- class_option :sudoable, type: :boolean, desc: "Add password request before sensitive data changes"
10
- class_option :lockable, type: :boolean, desc: "Add password reset locking"
11
- class_option :ratelimit, type: :boolean, desc: "Add request rate limiting"
12
- class_option :passwordless, type: :boolean, desc: "Add passwordless sign"
13
- class_option :omniauthable, type: :boolean, desc: "Add social login support"
14
- class_option :trackable, type: :boolean, desc: "Add activity log support"
15
- class_option :two_factor, type: :boolean, desc: "Add two factor authentication"
16
- class_option :invitable, type: :boolean, desc: "Add sending invitations"
17
- class_option :masqueradable, type: :boolean, desc: "Add sign-in as button functionallity"
6
+ class_option :api, type: :boolean, desc: "Generates API authentication"
7
+ class_option :pwned, type: :boolean, desc: "Add pwned password validation"
8
+ class_option :sudoable, type: :boolean, desc: "Add password request before sensitive data changes"
9
+ class_option :lockable, type: :boolean, desc: "Add password reset locking"
10
+ class_option :ratelimit, type: :boolean, desc: "Add request rate limiting"
11
+ class_option :passwordless, type: :boolean, desc: "Add passwordless sign"
12
+ class_option :omniauthable, type: :boolean, desc: "Add social login support"
13
+ class_option :trackable, type: :boolean, desc: "Add activity log support"
14
+ class_option :two_factor, type: :boolean, desc: "Add two factor authentication"
15
+ class_option :invitable, type: :boolean, desc: "Add sending invitations"
16
+ class_option :masqueradable, type: :boolean, desc: "Add sign-in as button functionallity"
18
17
 
19
18
  source_root File.expand_path("templates", __dir__)
20
19
 
@@ -63,6 +62,7 @@ class AuthenticationGenerator < Rails::Generators::Base
63
62
  migration_template "migrations/create_password_reset_tokens_migration.rb", "#{db_migrate_path}/create_password_reset_tokens.rb"
64
63
  migration_template "migrations/create_sign_in_tokens_migration.rb", "#{db_migrate_path}/create_sign_in_tokens_migration.rb" if passwordless?
65
64
  migration_template "migrations/create_events_migration.rb", "#{db_migrate_path}/create_events.rb" if options.trackable?
65
+ migration_template "migrations/create_recovery_codes_migration.rb", "#{db_migrate_path}/create_recovery_codes.rb" if two_factor?
66
66
  end
67
67
 
68
68
  def create_models
@@ -73,6 +73,7 @@ class AuthenticationGenerator < Rails::Generators::Base
73
73
  template "models/sign_in_token.rb", "app/models/sign_in_token.rb" if passwordless?
74
74
  template "models/current.rb", "app/models/current.rb"
75
75
  template "models/event.rb", "app/models/event.rb" if options.trackable?
76
+ template "models/recovery_code.rb", "app/models/recovery_code.rb" if two_factor?
76
77
  end
77
78
 
78
79
  def create_fixture_file
@@ -98,9 +99,13 @@ class AuthenticationGenerator < Rails::Generators::Base
98
99
 
99
100
  def create_views
100
101
  if options.api?
101
- directory "erb/user_mailer", "app/views/user_mailer"
102
+ template "erb/user_mailer/email_verification.html.erb", "app/views/user_mailer/email_verification.html.erb"
103
+ template "erb/user_mailer/password_reset.html.erb", "app/views/user_mailer/password_reset.html.erb"
102
104
  else
103
- directory "erb/user_mailer", "app/views/user_mailer"
105
+ template "erb/user_mailer/email_verification.html.erb", "app/views/user_mailer/email_verification.html.erb"
106
+ template "erb/user_mailer/password_reset.html.erb", "app/views/user_mailer/password_reset.html.erb"
107
+ template "erb/user_mailer/invitation_instructions.html.erb", "app/views/user_mailer/invitation_instructions.html.erb" if invitable?
108
+ template "erb/user_mailer/passwordless.html.erb", "app/views/user_mailer/passwordless.html.erb" if passwordless?
104
109
 
105
110
  directory "erb/home", "app/views/home"
106
111
 
@@ -133,6 +138,10 @@ class AuthenticationGenerator < Rails::Generators::Base
133
138
  route "resource :sudo, only: [:new, :create]", namespace: :sessions
134
139
  end
135
140
 
141
+ if invitable?
142
+ route "resource :invitation, only: [:new, :create]"
143
+ end
144
+
136
145
  if passwordless?
137
146
  route "resource :passwordless, only: [:new, :edit, :create]", namespace: :sessions
138
147
  end
@@ -148,8 +157,9 @@ class AuthenticationGenerator < Rails::Generators::Base
148
157
  end
149
158
 
150
159
  if two_factor?
151
- route "resource :totp, only: [:new, :create]", namespace: :two_factor_authentication
152
- route "resource :challenge, only: [:new, :create]", namespace: :two_factor_authentication
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
153
163
  end
154
164
 
155
165
  if options.trackable?
@@ -160,11 +170,12 @@ class AuthenticationGenerator < Rails::Generators::Base
160
170
  route "resource :email_verification, only: [:show, :create]", namespace: :identity
161
171
  route "resource :email, only: [:edit, :update]", namespace: :identity
162
172
 
163
- route "resource :invitation, only: [:new, :create]" if invitable?
164
- route "resource :password, only: [:edit, :update]"
165
- route "resources :sessions, only: [:index, :show, :destroy]"
173
+ route "resource :password, only: [:edit, :update]"
174
+ route "resources :sessions, only: [:index, :show, :destroy]"
175
+
166
176
  route 'post "sign_up", to: "registrations#create"'
167
177
  route 'get "sign_up", to: "registrations#new"' unless options.api?
178
+
168
179
  route 'post "sign_in", to: "sessions#create"'
169
180
  route 'get "sign_in", to: "sessions#new"' unless options.api?
170
181
  end
@@ -213,11 +224,7 @@ class AuthenticationGenerator < Rails::Generators::Base
213
224
  options.sudoable? && !options.api?
214
225
  end
215
226
 
216
- def code_verifiable?
217
- options.code_verifiable? && options.api?
218
- end
219
-
220
227
  def redis?
221
- options.lockable? || options.ratelimit? || sudoable? || code_verifiable?
228
+ options.lockable? || options.ratelimit? || sudoable?
222
229
  end
223
230
  end
@@ -13,18 +13,8 @@ class Identity::EmailVerificationsController < ApplicationController
13
13
 
14
14
  private
15
15
  def set_user
16
- <%- if code_verifiable? -%>
17
- verified_user = User.find_by(email: params[:email])
18
-
19
- if verified_user && verified_user.verification_code.value == params[:token]
20
- @user = verified_user
21
- else
22
- render json: { error: "That email verification code is invalid" }, status: :bad_request
23
- end
24
- <%- else -%>
25
16
  @token = EmailVerificationToken.find_signed!(params[:sid]); @user = @token.user
26
- rescue
17
+ rescue StandardError
27
18
  render json: { error: "That email verification link is invalid" }, status: :bad_request
28
- <%- end -%>
29
19
  end
30
20
  end
@@ -6,6 +6,10 @@ class Identity::PasswordResetsController < ApplicationController
6
6
  <%- end -%>
7
7
  before_action :set_user, only: :update
8
8
 
9
+ def edit
10
+ head :no_content
11
+ end
12
+
9
13
  def create
10
14
  if @user = User.find_by(email: params[:email], verified: true)
11
15
  UserMailer.with(user: @user).password_reset.deliver_later
@@ -25,7 +29,7 @@ class Identity::PasswordResetsController < ApplicationController
25
29
  private
26
30
  def set_user
27
31
  @token = PasswordResetToken.find_signed!(params[:sid]); @user = @token.user
28
- rescue
32
+ rescue StandardError
29
33
  render json: { error: "That password reset link is invalid" }, status: :bad_request
30
34
  end
31
35
 
@@ -16,7 +16,7 @@ class Identity::EmailVerificationsController < ApplicationController
16
16
  private
17
17
  def set_user
18
18
  @token = EmailVerificationToken.find_signed!(params[:sid]); @user = @token.user
19
- rescue
19
+ rescue StandardError
20
20
  redirect_to edit_identity_email_path, alert: "That email verification link is invalid"
21
21
  end
22
22
 
@@ -32,7 +32,7 @@ class Identity::PasswordResetsController < ApplicationController
32
32
  private
33
33
  def set_user
34
34
  @token = PasswordResetToken.find_signed!(params[:sid]); @user = @token.user
35
- rescue
35
+ rescue StandardError
36
36
  redirect_to new_identity_password_reset_path, alert: "That password reset link is invalid"
37
37
  end
38
38
 
@@ -28,7 +28,7 @@ class Sessions::PasswordlessesController < ApplicationController
28
28
  private
29
29
  def set_user
30
30
  @token = SignInToken.find_signed!(params[:sid]); @user = @token.user
31
- rescue
31
+ rescue StandardError
32
32
  redirect_to new_sessions_passwordless_path, alert: "That sign in link is invalid"
33
33
  end
34
34
 
@@ -16,7 +16,7 @@ class SessionsController < ApplicationController
16
16
 
17
17
  if user && user.authenticate(params[:password])
18
18
  <%- if two_factor? -%>
19
- if user.otp_secret
19
+ if user.otp_secret.present?
20
20
  signed_id = user.signed_id(purpose: :authentication_challenge, expires_in: 20.minutes)
21
21
  redirect_to new_two_factor_authentication_challenge_path(token: signed_id)
22
22
  else
@@ -7,22 +7,46 @@ class TwoFactorAuthentication::ChallengesController < ApplicationController
7
7
  end
8
8
 
9
9
  def create
10
- @totp = ROTP::TOTP.new(@user.otp_secret, issuer: "YourAppName")
11
-
12
- if @totp.verify(params[:code], drift_behind: 15)
13
- session = @user.sessions.create!
14
- cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
15
-
16
- redirect_to root_path, notice: "Signed in successfully"
10
+ if params[:scheme_type] == "recovery_codes"
11
+ verify_recovery_code
17
12
  else
18
- redirect_to new_two_factor_authentication_challenge_path(token: params[:token]), alert: "That code didn't work. Please try again"
13
+ verify_time_based_one_time_password
19
14
  end
20
15
  end
21
16
 
22
17
  private
23
18
  def set_user
24
19
  @user = User.find_signed!(params[:token], purpose: :authentication_challenge)
25
- rescue
20
+ rescue StandardError
26
21
  redirect_to sign_in_path, alert: "That's taking too long. Please re-enter your password and try again"
27
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(token: params[:token], scheme_type: params[:scheme_type]), alert: "That code didn't work. Please try again"
51
+ end
28
52
  end
@@ -0,0 +1,31 @@
1
+ class TwoFactorAuthentication::RecoveryCodesController < ApplicationController
2
+ before_action :set_user
3
+
4
+ def index
5
+ if Current.user.recovery_codes.exists?
6
+ @recovery_codes = @user.recovery_codes
7
+ else
8
+ @recovery_codes = @user.recovery_codes.create!(new_recovery_codes)
9
+ end
10
+ end
11
+
12
+ def create
13
+ @user.recovery_codes.delete_all
14
+ @user.recovery_codes.create!(new_recovery_codes)
15
+
16
+ redirect_to two_factor_authentication_recovery_codes_path, notice: "Your new recovery codes have been generated"
17
+ end
18
+
19
+ private
20
+ def set_user
21
+ @user = Current.user
22
+ end
23
+
24
+ def new_recovery_codes
25
+ 10.times.map { { code: new_recovery_code } }
26
+ end
27
+
28
+ def new_recovery_code
29
+ SecureRandom.alphanumeric(10).insert(5, "-").downcase
30
+ end
31
+ end
@@ -9,17 +9,18 @@ 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 root_path, notice: "2FA is enabled on your account"
12
+ redirect_to two_factor_authentication_recovery_codes_path
13
13
  else
14
14
  redirect_to new_two_factor_authentication_totp_path, alert: "That code didn't work. Please try again"
15
15
  end
16
16
  end
17
17
 
18
- def set_user
19
- @user = Current.user
20
- end
18
+ private
19
+ def set_user
20
+ @user = Current.user
21
+ end
21
22
 
22
- def set_totp
23
- @totp = ROTP::TOTP.new(params[:secret] || ROTP::Base32.random, issuer: "YourAppName")
24
- end
23
+ def set_totp
24
+ @totp = ROTP::TOTP.new(params[:secret] || ROTP::Base32.random, issuer: "YourAppName")
25
+ end
25
26
  end
@@ -33,6 +33,10 @@
33
33
  <div>
34
34
  <%%= link_to "Two-Factor Authentication", new_two_factor_authentication_totp_path %>
35
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 %>
36
40
  <%- end -%>
37
41
 
38
42
  <br>
@@ -0,0 +1,19 @@
1
+ <%%= form_with(url: two_factor_authentication_challenge_path) do |form| %>
2
+ <%%= form.hidden_field :token, value: params[:token] %>
3
+ <%%= form.hidden_field :scheme_type, value: "recovery_codes" %>
4
+
5
+ <div>
6
+ <%%= form.label :code do %>
7
+ <h1>OK, enter one of your recovery codes below:</h1>
8
+ <%% end %>
9
+ <%%= form.text_field :code, autofocus: true, required: true, autocomplete: :off %>
10
+ </div>
11
+
12
+ <div>
13
+ <%%= form.submit "Continue" %>
14
+ </div>
15
+ <%% end %>
16
+
17
+ <div>
18
+ <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>
19
+ </div>
@@ -0,0 +1,20 @@
1
+ <%%= form_with(url: two_factor_authentication_challenge_path) do |form| %>
2
+ <%%= form.hidden_field :token, value: params[:token] %>
3
+ <%%= form.hidden_field :scheme_type, value: "totp" %>
4
+
5
+ <div>
6
+ <%%= form.label :code do %>
7
+ <h1>Next, open the 2FA authenticator app on your phone and type the six digit code below:</h1>
8
+ <%% end %>
9
+ <%%= form.text_field :code, autofocus: true, required: true, autocomplete: :off %>
10
+ </div>
11
+
12
+ <div>
13
+ <%%= form.submit "Verify" %>
14
+ </div>
15
+ <%% end %>
16
+
17
+ <div>
18
+ <p><strong>Don't have your phone?</strong></p>
19
+ <%%= link_to "Use a recovery code to access your account.", new_two_factor_authentication_challenge_path(token: params[:token], scheme_type: "recovery_codes") %>
20
+ </div>
@@ -1,16 +1,7 @@
1
1
  <p style="color: red"><%%= alert %></p>
2
2
 
3
- <%%= form_with(url: two_factor_authentication_challenge_path) do |form| %>
4
- <%%= form.hidden_field :token, value: params[:token] %>
5
-
6
- <div>
7
- <%%= form.label :code do %>
8
- <h1>Next, open the 2FA authenticator app on your phone and type the six digit code below:</h1>
9
- <%% end %>
10
- <%%= form.text_field :code, autofocus: true, required: true, autocomplete: :off %>
11
- </div>
12
-
13
- <div>
14
- <%%= form.submit "Verify" %>
15
- </div>
3
+ <%% if params[:scheme_type] == "recovery_codes" %>
4
+ <%%= render "recovery_code_form" %>
5
+ <%% else %>
6
+ <%%= render "totp_form" %>
16
7
  <%% end %>
@@ -0,0 +1,5 @@
1
+ <%% if recovery_code.used? %>
2
+ <li><del><%%= recovery_code.code %></del></li>
3
+ <%% else %>
4
+ <li><%%= recovery_code.code %></li>
5
+ <%% end %>
@@ -0,0 +1,16 @@
1
+ <p style="color: green"><%%= notice %></p>
2
+
3
+ <h1>Two-factor recovery codes</h1>
4
+ <p>Recovery codes provide a way to log in if you lose your phone (or don't have it with you). Save these and keep them somewhere safe.</p>
5
+
6
+ <ul><%%= render @recovery_codes %></ul>
7
+
8
+ <%%= link_to "OK, I'm done", root_path %>
9
+
10
+ <hr>
11
+
12
+ <h2>Need new recovery codes?</h2>
13
+
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
+
16
+ <%%= button_to "Generate new recovery codes", two_factor_authentication_recovery_codes_path %>
@@ -10,8 +10,8 @@
10
10
  <p>Next, open the authenticator app, tap "Scan QR code" or "+", and, when it asks, point your phone's camera at this QR code picture below.</p>
11
11
 
12
12
  <figure>
13
- <%%= image_tag @qr_code.as_png(resize_exactly_to: 200).to_data_url%>
14
- <figcaption>Point your camera here</figcaption>
13
+ <%%= image_tag @qr_code.as_png(resize_exactly_to: 200).to_data_url%>
14
+ <figcaption>Point your camera here</figcaption>
15
15
  </figure>
16
16
 
17
17
  <%%= form_with(url: two_factor_authentication_totp_path) do |form| %>
@@ -2,13 +2,9 @@
2
2
 
3
3
  <p>This is to confirm that <%%= @user.email %> is the email you want to use on your account. If you ever lose your password, that's where we'll email a reset link.</p>
4
4
 
5
- <p><strong>You must <%= code_verifiable? ? "put the code" : "hit the link" %> below to confirm that you received this email.</strong></p>
5
+ <p><strong>You must hit the link below to confirm that you received this email.</strong></p>
6
6
 
7
- <%- if code_verifiable? -%>
8
- <strong><%%= @user.verification_code.value %></strong>
9
- <%- else -%>
10
7
  <%%= link_to "Yes, use this email for my account", identity_email_verification_url(sid: @signed_id) %>
11
- <%- end -%>
12
8
 
13
9
  <hr>
14
10
 
@@ -8,11 +8,7 @@ class UserMailer < ApplicationMailer
8
8
 
9
9
  def email_verification
10
10
  @user = params[:user]
11
- <%- if code_verifiable? -%>
12
- @user.verification_code.value = rand.to_s[2..7]
13
- <%- else -%>
14
11
  @signed_id = @user.email_verification_tokens.create.signed_id(expires_in: 2.days)
15
- <%- end -%>
16
12
 
17
13
  mail to: @user.email, subject: "Verify your email"
18
14
  end
@@ -0,0 +1,11 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :recovery_codes do |t|
4
+ t.references :user, null: false, foreign_key: true
5
+ t.string :code, null: false
6
+ t.boolean :used, null: false, default: false
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ class RecoveryCode < ApplicationRecord
2
+ belongs_to :user
3
+ end
@@ -3,6 +3,9 @@ class User < ApplicationRecord
3
3
 
4
4
  has_many :email_verification_tokens, dependent: :destroy
5
5
  has_many :password_reset_tokens, dependent: :destroy
6
+ <%- if two_factor? -%>
7
+ has_many :recovery_codes, dependent: :destroy
8
+ <%- end -%>
6
9
  <%- if passwordless? -%>
7
10
  has_many :sign_in_tokens, dependent: :destroy
8
11
  <%- end -%>
@@ -11,9 +14,6 @@ class User < ApplicationRecord
11
14
  <%- if options.trackable? -%>
12
15
  has_many :events, dependent: :destroy
13
16
  <%- end -%>
14
- <%- if code_verifiable? %>
15
- kredis_string :verification_code, expires_in: 2.days
16
- <%- end -%>
17
17
 
18
18
  validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
19
19
  validates :password, allow_nil: true, length: { minimum: 12 }
@@ -30,7 +30,7 @@ class User < ApplicationRecord
30
30
  end
31
31
 
32
32
  after_update if: :password_digest_previously_changed? do
33
- sessions.where.not(id: Current.session).destroy_all
33
+ sessions.where.not(id: Current.session).delete_all
34
34
  end
35
35
  <%- if options.trackable? %>
36
36
  after_update if: :email_previously_changed? do
@@ -5,6 +5,13 @@ class Identity::PasswordResetsControllerTest < ActionDispatch::IntegrationTest
5
5
  @user = users(:lazaro_nixon)
6
6
  end
7
7
 
8
+ test "should get edit" do
9
+ sid = @user.password_reset_tokens.create.signed_id(expires_in: 20.minutes)
10
+
11
+ get edit_identity_password_reset_url(sid: sid)
12
+ assert_response :no_content
13
+ end
14
+
8
15
  test "should send a password reset email" do
9
16
  assert_enqueued_email_with UserMailer, :password_reset, args: { user: @user } do
10
17
  post identity_password_reset_url, params: { email: @user.email }
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.15
4
+ version: 2.16.17
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-06 00:00:00.000000000 Z
11
+ date: 2023-04-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -60,6 +60,7 @@ files:
60
60
  - lib/generators/authentication/templates/controllers/html/sessions/sudos_controller.rb.tt
61
61
  - lib/generators/authentication/templates/controllers/html/sessions_controller.rb.tt
62
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
63
64
  - lib/generators/authentication/templates/controllers/html/two_factor_authentication/totps_controller.rb.tt
64
65
  - lib/generators/authentication/templates/erb/authentications/events/index.html.erb.tt
65
66
  - lib/generators/authentication/templates/erb/home/index.html.erb.tt
@@ -73,7 +74,11 @@ files:
73
74
  - lib/generators/authentication/templates/erb/sessions/new.html.erb.tt
74
75
  - lib/generators/authentication/templates/erb/sessions/passwordlesses/new.html.erb.tt
75
76
  - 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
76
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
77
82
  - lib/generators/authentication/templates/erb/two_factor_authentication/totps/new.html.erb.tt
78
83
  - lib/generators/authentication/templates/erb/user_mailer/email_verification.html.erb.tt
79
84
  - lib/generators/authentication/templates/erb/user_mailer/invitation_instructions.html.erb.tt
@@ -83,6 +88,7 @@ files:
83
88
  - lib/generators/authentication/templates/migrations/create_email_verification_tokens_migration.rb.tt
84
89
  - lib/generators/authentication/templates/migrations/create_events_migration.rb.tt
85
90
  - lib/generators/authentication/templates/migrations/create_password_reset_tokens_migration.rb.tt
91
+ - lib/generators/authentication/templates/migrations/create_recovery_codes_migration.rb.tt
86
92
  - lib/generators/authentication/templates/migrations/create_sessions_migration.rb.tt
87
93
  - lib/generators/authentication/templates/migrations/create_sign_in_tokens_migration.rb.tt
88
94
  - lib/generators/authentication/templates/migrations/create_users_migration.rb.tt
@@ -90,6 +96,7 @@ files:
90
96
  - lib/generators/authentication/templates/models/email_verification_token.rb.tt
91
97
  - lib/generators/authentication/templates/models/event.rb.tt
92
98
  - lib/generators/authentication/templates/models/password_reset_token.rb.tt
99
+ - lib/generators/authentication/templates/models/recovery_code.rb.tt
93
100
  - lib/generators/authentication/templates/models/session.rb.tt
94
101
  - lib/generators/authentication/templates/models/sign_in_token.rb.tt
95
102
  - lib/generators/authentication/templates/models/user.rb.tt