authentication-zero 2.16.15 → 2.16.16

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 +4 -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 +29 -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: 99df48ab46b9859695eadb1675580d523f1953a3f211b648b3c752e22598557a
4
+ data.tar.gz: c9fa8cfc8785c16a3329737b89c43ded33f74b79857e52a32f5e1b00872e388f
5
5
  SHA512:
6
- metadata.gz: '0899627eceb12252ec843541a19ec9da8de2e0d4ab12e76e43f34e1117597be485f58a090a66f825f2a1fdb1affe5c83cb55502e2deb7a681e2d70e7578ede31'
7
- data.tar.gz: 8ee00bb247a6989e0c930b33ffefc5c546f6e2c4161d77297344343ec5585b6c80f5b11dd18cd06937b6264d4abb1d89b9f47d1ab80494201bf5d523cb289f86
6
+ metadata.gz: 72daf22b87d0960c34885348cdf1c24ae3d67dbc9e34c816ac0d1592fd9e46299776b624383d4d318034edfbcedfa5e894fa65495188afe94aee6b011d3b4281
7
+ data.tar.gz: 219aa2ebe407eacf4344c462e20aad1abea1389b96b629f6313486821d07f04ea3682e789accaef04fb2bf60365cacb229fddbbafbf8c6e2bb8172ab18195ec7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## Authentication Zero 2.16.16 ##
2
+
3
+ * Add recovery codes to two factor auth
4
+
1
5
  ## Authentication Zero 2.16.15 ##
2
6
 
3
7
  * 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.16)
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.16"
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
 
@@ -98,9 +97,13 @@ class AuthenticationGenerator < Rails::Generators::Base
98
97
 
99
98
  def create_views
100
99
  if options.api?
101
- directory "erb/user_mailer", "app/views/user_mailer"
100
+ template "erb/user_mailer/email_verification.html.erb", "app/views/user_mailer/email_verification.html.erb"
101
+ template "erb/user_mailer/password_reset.html.erb", "app/views/user_mailer/password_reset.html.erb"
102
102
  else
103
- directory "erb/user_mailer", "app/views/user_mailer"
103
+ template "erb/user_mailer/email_verification.html.erb", "app/views/user_mailer/email_verification.html.erb"
104
+ template "erb/user_mailer/password_reset.html.erb", "app/views/user_mailer/password_reset.html.erb"
105
+ template "erb/user_mailer/invitation_instructions.html.erb", "app/views/user_mailer/invitation_instructions.html.erb" if invitable?
106
+ template "erb/user_mailer/passwordless.html.erb", "app/views/user_mailer/passwordless.html.erb" if passwordless?
104
107
 
105
108
  directory "erb/home", "app/views/home"
106
109
 
@@ -133,6 +136,10 @@ class AuthenticationGenerator < Rails::Generators::Base
133
136
  route "resource :sudo, only: [:new, :create]", namespace: :sessions
134
137
  end
135
138
 
139
+ if invitable?
140
+ route "resource :invitation, only: [:new, :create]"
141
+ end
142
+
136
143
  if passwordless?
137
144
  route "resource :passwordless, only: [:new, :edit, :create]", namespace: :sessions
138
145
  end
@@ -148,8 +155,9 @@ class AuthenticationGenerator < Rails::Generators::Base
148
155
  end
149
156
 
150
157
  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
158
+ route "resources :recovery_codes, only: [:index, :create]", namespace: :two_factor_authentication
159
+ route "resource :totp, only: [:new, :create]", namespace: :two_factor_authentication
160
+ route "resource :challenge, only: [:new, :create]", namespace: :two_factor_authentication
153
161
  end
154
162
 
155
163
  if options.trackable?
@@ -160,11 +168,12 @@ class AuthenticationGenerator < Rails::Generators::Base
160
168
  route "resource :email_verification, only: [:show, :create]", namespace: :identity
161
169
  route "resource :email, only: [:edit, :update]", namespace: :identity
162
170
 
163
- route "resource :invitation, only: [:new, :create]" if invitable?
164
- route "resource :password, only: [:edit, :update]"
165
- route "resources :sessions, only: [:index, :show, :destroy]"
171
+ route "resource :password, only: [:edit, :update]"
172
+ route "resources :sessions, only: [:index, :show, :destroy]"
173
+
166
174
  route 'post "sign_up", to: "registrations#create"'
167
175
  route 'get "sign_up", to: "registrations#new"' unless options.api?
176
+
168
177
  route 'post "sign_in", to: "sessions#create"'
169
178
  route 'get "sign_in", to: "sessions#new"' unless options.api?
170
179
  end
@@ -213,11 +222,7 @@ class AuthenticationGenerator < Rails::Generators::Base
213
222
  options.sudoable? && !options.api?
214
223
  end
215
224
 
216
- def code_verifiable?
217
- options.code_verifiable? && options.api?
218
- end
219
-
220
225
  def redis?
221
- options.lockable? || options.ratelimit? || sudoable? || code_verifiable?
226
+ options.lockable? || options.ratelimit? || sudoable?
222
227
  end
223
228
  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.16
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