authentication-zero 2.16.14 → 2.16.16

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