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.
- checksums.yaml +4 -4
- data/.github/workflows/CI.yml +21 -34
- data/.ruby-version +1 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/README.md +2 -2
- data/lib/authentication_zero/version.rb +1 -1
- data/lib/generators/authentication/authentication_generator.rb +38 -23
- data/lib/generators/authentication/templates/controllers/api/identity/email_verifications_controller.rb.tt +1 -11
- data/lib/generators/authentication/templates/controllers/api/identity/password_resets_controller.rb.tt +5 -1
- data/lib/generators/authentication/templates/controllers/html/identity/email_verifications_controller.rb.tt +1 -1
- data/lib/generators/authentication/templates/controllers/html/identity/password_resets_controller.rb.tt +1 -1
- data/lib/generators/authentication/templates/controllers/html/masquerades_controller.rb.tt +20 -0
- data/lib/generators/authentication/templates/controllers/html/sessions/passwordlesses_controller.rb.tt +1 -1
- data/lib/generators/authentication/templates/controllers/html/sessions_controller.rb.tt +1 -1
- data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenges_controller.rb.tt +33 -9
- data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/recovery_codes_controller.rb.tt +31 -0
- data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/totps_controller.rb.tt +8 -7
- data/lib/generators/authentication/templates/erb/home/index.html.erb.tt +9 -0
- data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/_recovery_code_form.html.erb.tt +19 -0
- data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/_totp_form.html.erb.tt +20 -0
- data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/new.html.erb.tt +4 -13
- data/lib/generators/authentication/templates/erb/two_factor_authentication/recovery_codes/_recovery_code.html.erb.tt +5 -0
- data/lib/generators/authentication/templates/erb/two_factor_authentication/recovery_codes/index.html.erb.tt +16 -0
- data/lib/generators/authentication/templates/erb/two_factor_authentication/totps/new.html.erb.tt +2 -2
- data/lib/generators/authentication/templates/erb/user_mailer/email_verification.html.erb.tt +1 -5
- data/lib/generators/authentication/templates/mailers/user_mailer.rb.tt +0 -4
- data/lib/generators/authentication/templates/migrations/create_recovery_codes_migration.rb.tt +11 -0
- data/lib/generators/authentication/templates/models/recovery_code.rb.tt +3 -0
- data/lib/generators/authentication/templates/models/user.rb.tt +4 -4
- data/lib/generators/authentication/templates/test_unit/controllers/api/identity/password_resets_controller_test.rb.tt +7 -0
- metadata +11 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 99df48ab46b9859695eadb1675580d523f1953a3f211b648b3c752e22598557a
|
|
4
|
+
data.tar.gz: c9fa8cfc8785c16a3329737b89c43ded33f74b79857e52a32f5e1b00872e388f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 72daf22b87d0960c34885348cdf1c24ae3d67dbc9e34c816ac0d1592fd9e46299776b624383d4d318034edfbcedfa5e894fa65495188afe94aee6b011d3b4281
|
|
7
|
+
data.tar.gz: 219aa2ebe407eacf4344c462e20aad1abea1389b96b629f6313486821d07f04ea3682e789accaef04fb2bf60365cacb229fddbbafbf8c6e2bb8172ab18195ec7
|
data/.github/workflows/CI.yml
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
+
3.2.2
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
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
|
|
@@ -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,
|
|
7
|
-
class_option :pwned,
|
|
8
|
-
class_option :
|
|
9
|
-
class_option :
|
|
10
|
-
class_option :
|
|
11
|
-
class_option :
|
|
12
|
-
class_option :
|
|
13
|
-
class_option :
|
|
14
|
-
class_option :
|
|
15
|
-
class_option :
|
|
16
|
-
class_option :
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
146
|
-
route "resource
|
|
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 :
|
|
158
|
-
route "
|
|
159
|
-
|
|
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
|
|
203
|
-
options.
|
|
217
|
+
def masqueradable?
|
|
218
|
+
options.masqueradable? && !options.api?
|
|
204
219
|
end
|
|
205
220
|
|
|
206
|
-
def
|
|
207
|
-
options.
|
|
221
|
+
def sudoable?
|
|
222
|
+
options.sudoable? && !options.api?
|
|
208
223
|
end
|
|
209
224
|
|
|
210
225
|
def redis?
|
|
211
|
-
options.lockable? || options.ratelimit? || sudoable?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
private
|
|
19
|
+
def set_user
|
|
20
|
+
@user = Current.user
|
|
21
|
+
end
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
4
|
-
<%%=
|
|
5
|
-
|
|
6
|
-
|
|
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,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 %>
|
data/lib/generators/authentication/templates/erb/two_factor_authentication/totps/new.html.erb.tt
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
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
|
|
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
|
|
@@ -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).
|
|
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.
|
|
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-
|
|
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.
|
|
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
|