authentication-zero 2.16.14 → 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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +21 -34
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +8 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +2 -2
  7. data/lib/authentication_zero/version.rb +1 -1
  8. data/lib/generators/authentication/authentication_generator.rb +38 -23
  9. data/lib/generators/authentication/templates/controllers/api/identity/email_verifications_controller.rb.tt +1 -11
  10. data/lib/generators/authentication/templates/controllers/api/identity/password_resets_controller.rb.tt +5 -1
  11. data/lib/generators/authentication/templates/controllers/html/identity/email_verifications_controller.rb.tt +1 -1
  12. data/lib/generators/authentication/templates/controllers/html/identity/password_resets_controller.rb.tt +1 -1
  13. data/lib/generators/authentication/templates/controllers/html/masquerades_controller.rb.tt +20 -0
  14. data/lib/generators/authentication/templates/controllers/html/sessions/passwordlesses_controller.rb.tt +1 -1
  15. data/lib/generators/authentication/templates/controllers/html/sessions_controller.rb.tt +1 -1
  16. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenges_controller.rb.tt +33 -9
  17. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/recovery_codes_controller.rb.tt +31 -0
  18. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/totps_controller.rb.tt +8 -7
  19. data/lib/generators/authentication/templates/erb/home/index.html.erb.tt +9 -0
  20. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/_recovery_code_form.html.erb.tt +19 -0
  21. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/_totp_form.html.erb.tt +20 -0
  22. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/new.html.erb.tt +4 -13
  23. data/lib/generators/authentication/templates/erb/two_factor_authentication/recovery_codes/_recovery_code.html.erb.tt +5 -0
  24. data/lib/generators/authentication/templates/erb/two_factor_authentication/recovery_codes/index.html.erb.tt +16 -0
  25. data/lib/generators/authentication/templates/erb/two_factor_authentication/totps/new.html.erb.tt +2 -2
  26. data/lib/generators/authentication/templates/erb/user_mailer/email_verification.html.erb.tt +1 -5
  27. data/lib/generators/authentication/templates/mailers/user_mailer.rb.tt +0 -4
  28. data/lib/generators/authentication/templates/migrations/create_recovery_codes_migration.rb.tt +11 -0
  29. data/lib/generators/authentication/templates/models/recovery_code.rb.tt +3 -0
  30. data/lib/generators/authentication/templates/models/user.rb.tt +4 -4
  31. data/lib/generators/authentication/templates/test_unit/controllers/api/identity/password_resets_controller_test.rb.tt +7 -0
  32. metadata +11 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0f83db44ee39461a039edbf34e4ab4b1af7698fc537361607b120310e3d828e
4
- data.tar.gz: ecbf7f8578a9612f30bd7d19d9c482c7d9dbf62e2a28fa148f25c7cb5eb3bad6
3
+ metadata.gz: 99df48ab46b9859695eadb1675580d523f1953a3f211b648b3c752e22598557a
4
+ data.tar.gz: c9fa8cfc8785c16a3329737b89c43ded33f74b79857e52a32f5e1b00872e388f
5
5
  SHA512:
6
- metadata.gz: c8b1ed8282c418a5941295a522640ec2bcbc43fcd5dd8f6743333a860051c7c2858301d019f4f3400e217e2a385527809ab7bee61ef49306e4f6aba88bbdad38
7
- data.tar.gz: 48220896d353c7aa5c37584f84ec4bd75d189f2205440fe754256fd2cfb885d31a2e87859d8756ec54502ef1e5f6cad51eede0662b664a26dc9f2daa066ea8e6
6
+ metadata.gz: 72daf22b87d0960c34885348cdf1c24ae3d67dbc9e34c816ac0d1592fd9e46299776b624383d4d318034edfbcedfa5e894fa65495188afe94aee6b011d3b4281
7
+ data.tar.gz: 219aa2ebe407eacf4344c462e20aad1abea1389b96b629f6313486821d07f04ea3682e789accaef04fb2bf60365cacb229fddbbafbf8c6e2bb8172ab18195ec7
@@ -6,12 +6,11 @@
6
6
  # run tests and linters.
7
7
  name: "Generate sample app and run tests"
8
8
  on: [push]
9
+
9
10
  jobs:
10
11
  test_html:
11
12
  name: 🧪 Run HTML Tests
12
13
  runs-on: ubuntu-latest
13
- env:
14
- RAILS_ENV: test
15
14
  steps:
16
15
  - name: Checkout code
17
16
  uses: actions/checkout@v3
@@ -22,45 +21,39 @@ jobs:
22
21
  bundler-cache: true
23
22
 
24
23
  - name: Install the latest Rails gem
25
- run: gem install rails
24
+ run: gem install rails -v "~> 7.0.0"
25
+
26
+ - name: Install Rubocop
27
+ run: gem install rubocop rubocop-performance rubocop-minitest rubocop-packaging rubocop-minitest rubocop-rails
26
28
 
27
29
  - name: Create fresh Rails app and run generator
28
30
  run: |
29
31
  rails new test-app
32
+ cp .rubocop.yml test-app/.rubocop.yml
30
33
  cd test-app
31
- wget https://raw.githubusercontent.com/lazaronixon/authentication-zero/master/.rubocop.yml
32
- bundle add authentication-zero --github ${{ github.repository }} --branch ${{ github.ref_name }}
34
+ bundle add authentication-zero --path ..
33
35
  bin/rails generate authentication
34
36
  bundle install
35
37
  bin/rails db:migrate
36
38
 
37
-
38
- - name: Install Rubocop
39
- run: gem install rubocop rubocop-performance rubocop-minitest rubocop-packaging rubocop-minitest rubocop-rails
40
-
41
39
  - name: Rubocop
42
- run: |
43
- cd test-app
44
- rubocop
40
+ run: cd test-app && rubocop
45
41
 
46
- - name: Install brakeman
42
+ - name: Install Brakeman
47
43
  run: gem install brakeman
48
44
 
49
45
  - name: Brakeman
50
- run: |
51
- cd test-app
52
- brakeman
46
+ run: cd test-app && brakeman
53
47
 
54
48
  - name: Tests
55
49
  run: |
56
50
  cd test-app
57
51
  bin/rails test
58
52
  bin/rails test:system
53
+
59
54
  test_api:
60
55
  name: 🧪 Run API Tests
61
56
  runs-on: ubuntu-latest
62
- env:
63
- RAILS_ENV: test
64
57
  steps:
65
58
  - name: Checkout code
66
59
  uses: actions/checkout@v3
@@ -71,36 +64,30 @@ jobs:
71
64
  bundler-cache: true
72
65
 
73
66
  - name: Install the latest Rails gem
74
- run: gem install rails
67
+ run: gem install rails -v "~> 7.0.0"
68
+
69
+ - name: Install Rubocop
70
+ run: gem install rubocop rubocop-performance rubocop-minitest rubocop-packaging rubocop-minitest rubocop-rails
75
71
 
76
72
  - name: Create fresh Rails app and run generator
77
73
  run: |
78
74
  rails new test-app
75
+ cp .rubocop.yml test-app/.rubocop.yml
79
76
  cd test-app
80
- wget https://raw.githubusercontent.com/lazaronixon/authentication-zero/master/.rubocop.yml
81
- bundle add authentication-zero --github ${{ github.repository }} --branch ${{ github.ref_name }}
77
+ bundle add authentication-zero --path ..
82
78
  bin/rails generate authentication --api
83
79
  bundle install
84
80
  bin/rails db:migrate
85
81
 
86
- - name: Install Rubocop
87
- run: gem install rubocop rubocop-performance rubocop-minitest rubocop-packaging rubocop-minitest rubocop-rails
88
-
89
82
  - name: Rubocop
90
- run: |
91
- cd test-app
92
- rubocop
83
+ run: cd test-app && rubocop
93
84
 
94
- - name: Install brakeman
85
+ - name: Install Brakeman
95
86
  run: gem install brakeman
96
87
 
97
88
  - name: Brakeman
98
- run: |
99
- cd test-app
100
- brakeman
89
+ run: cd test-app && brakeman
101
90
 
102
91
  - name: Tests
103
- run: |
104
- cd test-app
105
- bin/rails test
92
+ run: cd test-app && bin/rails test
106
93
 
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.1.2
1
+ 3.2.2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## Authentication Zero 2.16.16 ##
2
+
3
+ * Add recovery codes to two factor auth
4
+
5
+ ## Authentication Zero 2.16.15 ##
6
+
7
+ * Add sign-in as button functionallity (--masqueradable)
8
+
1
9
  ## Authentication Zero 2.16.14 ##
2
10
 
3
11
  * Remove password requirements
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- authentication-zero (2.16.14)
4
+ authentication-zero (2.16.16)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -28,11 +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
+ - Sign-in as button functionallity (--masqueradable)
34
35
  - Verify email using a link with token
35
- - Verify email using a six random digits code for api (--code-verifiable)
36
36
  - Ask password before sensitive data changes, aka: sudo (--sudoable)
37
37
  - Reset the user password and send reset instructions
38
38
  - Reset the user password only from verified emails
@@ -1,3 +1,3 @@
1
1
  module AuthenticationZero
2
- VERSION = "2.16.14"
2
+ VERSION = "2.16.16"
3
3
  end
@@ -3,17 +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"
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"
17
17
 
18
18
  source_root File.expand_path("templates", __dir__)
19
19
 
@@ -86,6 +86,7 @@ class AuthenticationGenerator < Rails::Generators::Base
86
86
  template "controllers/#{format_folder}/sessions_controller.rb", "app/controllers/sessions_controller.rb"
87
87
  template "controllers/#{format_folder}/passwords_controller.rb", "app/controllers/passwords_controller.rb"
88
88
  template "controllers/#{format_folder}/invitations_controller.rb", "app/controllers/invitations_controller.rb" if invitable?
89
+ template "controllers/#{format_folder}/masquerades_controller.rb", "app/controllers/masquerades_controller.rb" if masqueradable?
89
90
  template "controllers/#{format_folder}/registrations_controller.rb", "app/controllers/registrations_controller.rb"
90
91
  template "controllers/#{format_folder}/home_controller.rb", "app/controllers/home_controller.rb" unless options.api?
91
92
  template "controllers/#{format_folder}/sessions/sudos_controller.rb", "app/controllers/sessions/sudos_controller.rb" if sudoable?
@@ -96,9 +97,13 @@ class AuthenticationGenerator < Rails::Generators::Base
96
97
 
97
98
  def create_views
98
99
  if options.api?
99
- 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"
100
102
  else
101
- 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?
102
107
 
103
108
  directory "erb/home", "app/views/home"
104
109
 
@@ -131,10 +136,18 @@ class AuthenticationGenerator < Rails::Generators::Base
131
136
  route "resource :sudo, only: [:new, :create]", namespace: :sessions
132
137
  end
133
138
 
139
+ if invitable?
140
+ route "resource :invitation, only: [:new, :create]"
141
+ end
142
+
134
143
  if passwordless?
135
144
  route "resource :passwordless, only: [:new, :edit, :create]", namespace: :sessions
136
145
  end
137
146
 
147
+ if masqueradable?
148
+ route 'post "users/:user_id/masquerade", to: "masquerades#create", as: :user_masquerade'
149
+ end
150
+
138
151
  if omniauthable?
139
152
  route 'post "/auth/:provider/callback", to: "sessions/omniauth#create"'
140
153
  route 'get "/auth/:provider/callback", to: "sessions/omniauth#create"'
@@ -142,8 +155,9 @@ class AuthenticationGenerator < Rails::Generators::Base
142
155
  end
143
156
 
144
157
  if two_factor?
145
- route "resource :totp, only: [:new, :create]", namespace: :two_factor_authentication
146
- 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
147
161
  end
148
162
 
149
163
  if options.trackable?
@@ -154,11 +168,12 @@ class AuthenticationGenerator < Rails::Generators::Base
154
168
  route "resource :email_verification, only: [:show, :create]", namespace: :identity
155
169
  route "resource :email, only: [:edit, :update]", namespace: :identity
156
170
 
157
- route "resource :invitation, only: [:new, :create]" if invitable?
158
- route "resource :password, only: [:edit, :update]"
159
- route "resources :sessions, only: [:index, :show, :destroy]"
171
+ route "resource :password, only: [:edit, :update]"
172
+ route "resources :sessions, only: [:index, :show, :destroy]"
173
+
160
174
  route 'post "sign_up", to: "registrations#create"'
161
175
  route 'get "sign_up", to: "registrations#new"' unless options.api?
176
+
162
177
  route 'post "sign_in", to: "sessions#create"'
163
178
  route 'get "sign_in", to: "sessions#new"' unless options.api?
164
179
  end
@@ -199,15 +214,15 @@ class AuthenticationGenerator < Rails::Generators::Base
199
214
  options.invitable? && !options.api?
200
215
  end
201
216
 
202
- def sudoable?
203
- options.sudoable? && !options.api?
217
+ def masqueradable?
218
+ options.masqueradable? && !options.api?
204
219
  end
205
220
 
206
- def code_verifiable?
207
- options.code_verifiable? && options.api?
221
+ def sudoable?
222
+ options.sudoable? && !options.api?
208
223
  end
209
224
 
210
225
  def redis?
211
- options.lockable? || options.ratelimit? || sudoable? || code_verifiable?
226
+ options.lockable? || options.ratelimit? || sudoable?
212
227
  end
213
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
 
@@ -0,0 +1,20 @@
1
+ class MasqueradesController < ApplicationController
2
+ before_action :authorize
3
+ before_action :set_user
4
+
5
+ def create
6
+ session = @user.sessions.create!
7
+ cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
8
+
9
+ redirect_to root_path, notice: "Signed in successfully"
10
+ end
11
+
12
+ private
13
+ def set_user
14
+ @user = User.find(params[:user_id])
15
+ end
16
+
17
+ def authorize
18
+ redirect_to(root_path, alert: "You must be in development") unless Rails.env.development?
19
+ end
20
+ end
@@ -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
@@ -24,10 +24,19 @@
24
24
  <%%= link_to "Send invitation", new_invitation_path %>
25
25
  </div>
26
26
  <%- end -%>
27
+ <%- if masqueradable? %>
28
+ <div>
29
+ <%%= button_to "Signin as last user", user_masquerade_path(User.last) %>
30
+ </div>
31
+ <%- end -%>
27
32
  <%- if two_factor? %>
28
33
  <div>
29
34
  <%%= link_to "Two-Factor Authentication", new_two_factor_authentication_totp_path %>
30
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 %>
31
40
  <%- end -%>
32
41
 
33
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.14
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:
@@ -52,6 +52,7 @@ files:
52
52
  - lib/generators/authentication/templates/controllers/html/identity/emails_controller.rb.tt
53
53
  - lib/generators/authentication/templates/controllers/html/identity/password_resets_controller.rb.tt
54
54
  - lib/generators/authentication/templates/controllers/html/invitations_controller.rb.tt
55
+ - lib/generators/authentication/templates/controllers/html/masquerades_controller.rb.tt
55
56
  - lib/generators/authentication/templates/controllers/html/passwords_controller.rb.tt
56
57
  - lib/generators/authentication/templates/controllers/html/registrations_controller.rb.tt
57
58
  - lib/generators/authentication/templates/controllers/html/sessions/omniauth_controller.rb.tt
@@ -59,6 +60,7 @@ files:
59
60
  - lib/generators/authentication/templates/controllers/html/sessions/sudos_controller.rb.tt
60
61
  - lib/generators/authentication/templates/controllers/html/sessions_controller.rb.tt
61
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
62
64
  - lib/generators/authentication/templates/controllers/html/two_factor_authentication/totps_controller.rb.tt
63
65
  - lib/generators/authentication/templates/erb/authentications/events/index.html.erb.tt
64
66
  - lib/generators/authentication/templates/erb/home/index.html.erb.tt
@@ -72,7 +74,11 @@ files:
72
74
  - lib/generators/authentication/templates/erb/sessions/new.html.erb.tt
73
75
  - lib/generators/authentication/templates/erb/sessions/passwordlesses/new.html.erb.tt
74
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
75
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
76
82
  - lib/generators/authentication/templates/erb/two_factor_authentication/totps/new.html.erb.tt
77
83
  - lib/generators/authentication/templates/erb/user_mailer/email_verification.html.erb.tt
78
84
  - lib/generators/authentication/templates/erb/user_mailer/invitation_instructions.html.erb.tt
@@ -82,6 +88,7 @@ files:
82
88
  - lib/generators/authentication/templates/migrations/create_email_verification_tokens_migration.rb.tt
83
89
  - lib/generators/authentication/templates/migrations/create_events_migration.rb.tt
84
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
85
92
  - lib/generators/authentication/templates/migrations/create_sessions_migration.rb.tt
86
93
  - lib/generators/authentication/templates/migrations/create_sign_in_tokens_migration.rb.tt
87
94
  - lib/generators/authentication/templates/migrations/create_users_migration.rb.tt
@@ -89,6 +96,7 @@ files:
89
96
  - lib/generators/authentication/templates/models/email_verification_token.rb.tt
90
97
  - lib/generators/authentication/templates/models/event.rb.tt
91
98
  - lib/generators/authentication/templates/models/password_reset_token.rb.tt
99
+ - lib/generators/authentication/templates/models/recovery_code.rb.tt
92
100
  - lib/generators/authentication/templates/models/session.rb.tt
93
101
  - lib/generators/authentication/templates/models/sign_in_token.rb.tt
94
102
  - lib/generators/authentication/templates/models/user.rb.tt
@@ -135,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
143
  - !ruby/object:Gem::Version
136
144
  version: '0'
137
145
  requirements: []
138
- rubygems_version: 3.3.7
146
+ rubygems_version: 3.4.10
139
147
  signing_key:
140
148
  specification_version: 4
141
149
  summary: An authentication system generator for Rails applications