authentication-zero 2.9.2 → 2.11.0

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +15 -0
  3. data/CHANGELOG.md +10 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +8 -3
  6. data/authentication-zero-api.md +0 -3
  7. data/lib/authentication_zero/version.rb +1 -1
  8. data/lib/generators/authentication/authentication_generator.rb +47 -73
  9. data/lib/generators/authentication/templates/controllers/api/application_controller.rb.tt +27 -0
  10. data/lib/generators/authentication/templates/controllers/api/identity/emails_controller.rb.tt +3 -2
  11. data/lib/generators/authentication/templates/controllers/api/identity/password_resets_controller.rb.tt +4 -4
  12. data/lib/generators/authentication/templates/controllers/api/sessions/sudos_controller.rb.tt +1 -1
  13. data/lib/generators/authentication/templates/controllers/html/application_controller.rb.tt +25 -0
  14. data/lib/generators/authentication/templates/controllers/html/identity/emails_controller.rb.tt +3 -2
  15. data/lib/generators/authentication/templates/controllers/html/identity/password_resets_controller.rb.tt +4 -4
  16. data/lib/generators/authentication/templates/controllers/html/sessions/sudos_controller.rb.tt +4 -4
  17. data/lib/generators/authentication/templates/controllers/html/sessions_controller.rb.tt +13 -0
  18. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/challenges_controller.rb.tt +28 -0
  19. data/lib/generators/authentication/templates/controllers/html/two_factor_authentication/totps_controller.rb.tt +27 -0
  20. data/lib/generators/authentication/templates/erb/identity/emails/edit.html.erb.tt +5 -0
  21. data/lib/generators/authentication/templates/erb/identity/password_resets/edit.html.erb.tt +1 -1
  22. data/lib/generators/authentication/templates/erb/passwords/edit.html.erb.tt +2 -2
  23. data/lib/generators/authentication/templates/erb/sessions/sudos/new.html.erb.tt +2 -2
  24. data/lib/generators/authentication/templates/erb/two_factor_authentication/challenges/new.html.erb.tt +16 -0
  25. data/lib/generators/authentication/templates/erb/two_factor_authentication/totps/new.html.erb.tt +33 -0
  26. data/lib/generators/authentication/templates/migrations/create_sessions_migration.rb.tt +0 -3
  27. data/lib/generators/authentication/templates/migrations/create_table_migration.rb.tt +7 -4
  28. data/lib/generators/authentication/templates/models/model.rb.tt +8 -8
  29. data/lib/generators/authentication/templates/models/session.rb.tt +10 -3
  30. data/lib/generators/authentication/templates/test_unit/application_system_test_case.rb.tt +15 -0
  31. data/lib/generators/authentication/templates/test_unit/controllers/api/identity/email_verifications_controller_test.rb.tt +8 -8
  32. data/lib/generators/authentication/templates/test_unit/controllers/api/identity/emails_controller_test.rb.tt +9 -11
  33. data/lib/generators/authentication/templates/test_unit/controllers/api/identity/password_resets_controller_test.rb.tt +0 -3
  34. data/lib/generators/authentication/templates/test_unit/controllers/api/passwords_controller_test.rb.tt +6 -6
  35. data/lib/generators/authentication/templates/test_unit/controllers/api/sessions_controller_test.rb.tt +7 -7
  36. data/lib/generators/authentication/templates/test_unit/controllers/html/identity/email_verifications_controller_test.rb.tt +0 -4
  37. data/lib/generators/authentication/templates/test_unit/controllers/html/identity/emails_controller_test.rb.tt +5 -16
  38. data/lib/generators/authentication/templates/test_unit/controllers/html/identity/password_resets_controller_test.rb.tt +0 -3
  39. data/lib/generators/authentication/templates/test_unit/controllers/html/passwords_controller_test.rb.tt +0 -4
  40. data/lib/generators/authentication/templates/test_unit/controllers/html/sessions_controller_test.rb.tt +0 -4
  41. data/lib/generators/authentication/templates/test_unit/system/identity/emails_test.rb.tt +1 -10
  42. data/lib/generators/authentication/templates/test_unit/system/identity/password_resets_test.rb.tt +0 -3
  43. data/lib/generators/authentication/templates/test_unit/system/passwords_test.rb.tt +0 -10
  44. data/lib/generators/authentication/templates/test_unit/system/sessions_test.rb.tt +0 -10
  45. data/lib/generators/authentication/templates/test_unit/test_helper.rb.tt +22 -0
  46. metadata +11 -5
  47. data/lib/generators/authentication/templates/test_unit/controllers/api/sessions/sudos_controller_test.rb.tt +0 -24
  48. data/lib/generators/authentication/templates/test_unit/controllers/html/sessions/sudos_controller_test.rb.tt +0 -26
  49. data/lib/generators/authentication/templates/test_unit/system/sessions/sudos_test.rb.tt +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1eb8eb320cf8198e9afebf59fd2365b2683e3b52840d8d455876a11b208e243c
4
- data.tar.gz: 3bc190759683e6e647a597bdb57e09e69243901a3390c401e09eac72ada96726
3
+ metadata.gz: 702b78645aff0919daf1e518101731363068e6aef74fac0591f3257b5bf6b7a3
4
+ data.tar.gz: d07d22eb48277537484ef5f5c1cd4fd78f65e65d739fb7031384e3a594248e3a
5
5
  SHA512:
6
- metadata.gz: 0c2996c6cf6779c22442f7155025bb99a470f26a0dc23218aea96f413fd5ae85a1ffdebd796922b77a09f60a833c4a0bb72bc9e964f5c0695066cf4074c3f4e9
7
- data.tar.gz: 8cbf3eb8525e08fc10a5c00a88e1a82d928a107c969e7b6f4e65789d0db1d79b3a19369331f914b5baf439230a6b0e246287f6875f1f4ab7d57c4f470e96c0a0
6
+ metadata.gz: 99224479fcc817abaeed4492a5a48d071e98e3e32fcea32ec56e77007031b186feb0978405e5ec5f90750cf434bc86a554df73dea88245aa6185f18a16d7d2e3
7
+ data.tar.gz: 344af675d6c106d41c3a34dc2fdd04d93ca6028c83bc5acda497791a70a220a7854455cb336345d00d9371b529b7581d64617f4c244b1c15182bf5a869ada997
data/.rubocop.yml ADDED
@@ -0,0 +1,15 @@
1
+ inherit_from: https://raw.githubusercontent.com/rails/rails/master/.rubocop.yml
2
+
3
+ Performance:
4
+ Exclude:
5
+ - 'test/**/*'
6
+
7
+ Style/FrozenStringLiteralComment:
8
+ Enabled: false
9
+
10
+ Style/StringLiterals:
11
+ Enabled: true
12
+ EnforcedStyle: double_quotes
13
+ Include:
14
+ - 'app/**/*'
15
+ - 'test/**/*'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## Authentication Zero 2.11.0 (March 27, 2022) ##
2
+
3
+ * Remove sudo from default generator
4
+ * Remove sudo_at from database
5
+ * Implement sudoable using redis
6
+
7
+ ## Authentication Zero 2.10.0 (March 2, 2022) ##
8
+
9
+ * Implement two-factor
10
+
1
11
  ## Authentication Zero 2.9.0 (March 2, 2022) ##
2
12
 
3
13
  * Implement trackable
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- authentication-zero (2.9.2)
4
+ authentication-zero (2.11.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -11,8 +11,9 @@ The purpose of authentication zero is to generate a pre-built authentication sys
11
11
  - Checks if a password has been found in any data breach (--pwned)
12
12
  - Authentication by cookie
13
13
  - Authentication by token (--api)
14
+ - Two factor authentication (--two-factor)
14
15
  - Social Login with OmniAuth (--omniauthable)
15
- - Ask password before sensitive data changes, aka: sudo
16
+ - Ask password before sensitive data changes, aka: sudo (--sudoable)
16
17
  - Reset the user password and send reset instructions
17
18
  - Reset the user password only from verified emails
18
19
  - Lock sending reset password email after many attempts (--lockable)
@@ -53,7 +54,7 @@ root "home#index"
53
54
  ```
54
55
 
55
56
  ```
56
- $ rails generate controller home index
57
+ rails generate controller home index
57
58
  ```
58
59
 
59
60
  Add these lines to your `app/views/home/index.html.erb`:
@@ -79,6 +80,10 @@ Add these lines to your `app/views/home/index.html.erb`:
79
80
  <%# link_to "Activity Log", authentications_events_path %>
80
81
  </div>
81
82
 
83
+ <div>
84
+ <%# link_to "Two-Factor Authentication", new_two_factor_authentication_totp_path %>
85
+ </div>
86
+
82
87
  <br>
83
88
 
84
89
  <%= button_to "Log out", Current.session, method: :delete %>
@@ -93,7 +98,7 @@ config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
93
98
  ## Usage
94
99
 
95
100
  ```
96
- $ rails generate authentication user
101
+ rails generate authentication user
97
102
  ```
98
103
 
99
104
  Then run `bundle install` again!
@@ -78,7 +78,6 @@ This endpoint will return `201 Created` with the current JSON representation of
78
78
  "user_id": 1,
79
79
  "user_agent": "insomnia/2022.1.0",
80
80
  "ip_address": "127.0.0.1",
81
- "sudo_at": "2022-03-04T17:20:33.632Z",
82
81
  "created_at": "2022-03-04T17:20:33.632Z",
83
82
  "updated_at": "2022-03-04T17:20:33.632Z"
84
83
  },
@@ -87,7 +86,6 @@ This endpoint will return `201 Created` with the current JSON representation of
87
86
  "user_id": 1,
88
87
  "user_agent": "insomnia/2022.1.0",
89
88
  "ip_address": "127.0.0.1",
90
- "sudo_at": "2022-03-04T17:14:03.386Z",
91
89
  "created_at": "2022-03-04T17:14:03.386Z",
92
90
  "updated_at": "2022-03-04T17:14:03.386Z"
93
91
  }
@@ -106,7 +104,6 @@ This endpoint will return `201 Created` with the current JSON representation of
106
104
  "user_id": 1,
107
105
  "user_agent": "insomnia/2022.1.0",
108
106
  "ip_address": "127.0.0.1",
109
- "sudo_at": "2022-03-04T17:14:03.386Z",
110
107
  "created_at": "2022-03-04T17:14:03.386Z",
111
108
  "updated_at": "2022-03-04T17:14:03.386Z"
112
109
  }
@@ -1,3 +1,3 @@
1
1
  module AuthenticationZero
2
- VERSION = "2.9.2"
2
+ VERSION = "2.11.0"
3
3
  end
@@ -5,17 +5,19 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
5
5
 
6
6
  class_option :api, type: :boolean, desc: "Generates API authentication"
7
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"
8
9
  class_option :lockable, type: :boolean, desc: "Add password reset locking"
9
10
  class_option :ratelimit, type: :boolean, desc: "Add request rate limiting"
10
11
  class_option :omniauthable, type: :boolean, desc: "Add social login support"
11
12
  class_option :trackable, type: :boolean, desc: "Add activity log support"
13
+ class_option :two_factor, type: :boolean, desc: "Add two factor authentication"
12
14
 
13
15
  source_root File.expand_path("templates", __dir__)
14
16
 
15
17
  def add_gems
16
18
  uncomment_lines "Gemfile", /"bcrypt"/
17
- uncomment_lines "Gemfile", /"redis"/ if options.lockable?
18
- uncomment_lines "Gemfile", /"kredis"/ if options.lockable?
19
+ uncomment_lines "Gemfile", /"redis"/ if redis?
20
+ uncomment_lines "Gemfile", /"kredis"/ if redis?
19
21
 
20
22
  if options.pwned?
21
23
  gem "pwned", comment: "Use Pwned to check if a password has been found in any of the huge data breaches [https://github.com/philnash/pwned]"
@@ -29,15 +31,20 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
29
31
  gem "omniauth", comment: "Use OmniAuth to support multi-provider authentication [https://github.com/omniauth/omniauth]"
30
32
  gem "omniauth-rails_csrf_protection", comment: "Provides a mitigation against CVE-2015-9284 [https://github.com/cookpad/omniauth-rails_csrf_protection]"
31
33
  end
34
+
35
+ if two_factor?
36
+ gem "rotp", comment: "Use rotp for generating and validating one time passwords [https://github.com/mdp/rotp]"
37
+ gem "rqrcode", comment: "Use rqrcode for creating and rendering QR codes into various formats [https://github.com/whomwah/rqrcode]"
38
+ end
32
39
  end
33
40
 
34
41
  def create_configuration_files
35
- copy_file "config/redis/shared.yml", "config/redis/shared.yml" if options.lockable?
36
- copy_file "config/initializers/omniauth.rb", "config/initializers/omniauth.rb" if omniauthable?
42
+ copy_file "config/redis/shared.yml", "config/redis/shared.yml" if redis?
43
+ copy_file "config/initializers/omniauth.rb", "config/initializers/omniauth.rb" if omniauthable?
37
44
  end
38
45
 
39
46
  def add_environment_configurations
40
- ratelimit_code = <<~CODE
47
+ ratelimit_code = <<~CODE
41
48
  # Rate limit general requests by IP address in a rate of 1000 requests per hour
42
49
  config.middleware.use(Rack::Ratelimit, name: "General", rate: [1000, 1.hour], redis: Redis.new, logger: Rails.logger) { |env| ActionDispatch::Request.new(env).ip }
43
50
  CODE
@@ -63,69 +70,15 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
63
70
  template "test_unit/fixtures.yml", "test/fixtures/#{fixture_file_name}.yml"
64
71
  end
65
72
 
66
- def add_application_controller_methods
67
- api_code = <<~CODE
68
- include ActionController::HttpAuthentication::Token::ControllerMethods
69
-
70
- before_action :set_current_request_details
71
- before_action :authenticate
72
-
73
- def require_sudo
74
- if Current.session.sudo_at < 30.minutes.ago
75
- render json: { error: "Enter your password to continue" }, status: :forbidden
76
- end
77
- end
78
-
79
- private
80
- def authenticate
81
- if session = authenticate_with_http_token { |token, _| Session.find_signed(token) }
82
- Current.session = session
83
- else
84
- request_http_token_authentication
85
- end
86
- end
87
-
88
- def set_current_request_details
89
- Current.user_agent = request.user_agent
90
- Current.ip_address = request.ip
91
- end
92
- CODE
93
-
94
- html_code = <<~CODE
95
- before_action :set_current_request_details
96
- before_action :authenticate
97
-
98
- def require_sudo
99
- if Current.session.sudo_at < 30.minutes.ago
100
- redirect_to new_sessions_sudo_path(proceed_to_url: request.url)
101
- end
102
- end
103
-
104
- private
105
- def authenticate
106
- if session = Session.find_by_id(cookies.signed[:session_token])
107
- Current.session = session
108
- else
109
- redirect_to sign_in_path
110
- end
111
- end
112
-
113
- def set_current_request_details
114
- Current.user_agent = request.user_agent
115
- Current.ip_address = request.ip
116
- end
117
- CODE
118
-
119
- inject_code = options.api? ? api_code : html_code
120
- inject_into_class "app/controllers/application_controller.rb", "ApplicationController", optimize_indentation(inject_code, 2), verbose: false
121
- end
122
-
123
73
  def create_controllers
74
+ template "controllers/#{format_folder}/application_controller.rb", "app/controllers/application_controller.rb", force: true
75
+
124
76
  directory "controllers/#{format_folder}/identity", "app/controllers/identity"
77
+ directory "controllers/#{format_folder}/two_factor_authentication", "app/controllers/two_factor_authentication" if two_factor?
78
+ template "controllers/#{format_folder}/sessions_controller.rb", "app/controllers/sessions_controller.rb"
125
79
  template "controllers/#{format_folder}/passwords_controller.rb", "app/controllers/passwords_controller.rb"
126
80
  template "controllers/#{format_folder}/registrations_controller.rb", "app/controllers/registrations_controller.rb"
127
- template "controllers/#{format_folder}/sessions_controller.rb", "app/controllers/sessions_controller.rb"
128
- template "controllers/#{format_folder}/sessions/sudos_controller.rb", "app/controllers/sessions/sudos_controller.rb"
81
+ template "controllers/#{format_folder}/sessions/sudos_controller.rb", "app/controllers/sessions/sudos_controller.rb" if options.sudoable?
129
82
  template "controllers/#{format_folder}/sessions/omniauth_controller.rb", "app/controllers/sessions/omniauth_controller.rb" if omniauthable?
130
83
  template "controllers/#{format_folder}/authentications/events_controller.rb", "app/controllers/authentications/events_controller.rb" if options.trackable?
131
84
  end
@@ -141,7 +94,13 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
141
94
  directory "erb/identity", "app/views/identity"
142
95
  directory "erb/passwords", "app/views/passwords"
143
96
  directory "erb/registrations", "app/views/registrations"
144
- directory "erb/sessions", "app/views/sessions"
97
+
98
+ template "erb/sessions/index.html.erb", "app/views/sessions/index.html.erb"
99
+ template "erb/sessions/new.html.erb", "app/views/sessions/new.html.erb"
100
+
101
+ directory "erb/sessions/sudos", "app/views/sessions/sudos" if options.sudoable?
102
+
103
+ directory "erb/two_factor_authentication", "app/views/two_factor_authentication" if two_factor?
145
104
  directory "erb/authentications/events", "app/views/authentications/events" if options.trackable?
146
105
  end
147
106
  end
@@ -153,29 +112,36 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
153
112
  def add_routes
154
113
  if omniauthable?
155
114
  route "post '/auth/:provider/callback', to: 'sessions/omniauth#create'"
156
- route "get '/auth/:provider/callback', to: 'sessions/omniauth#create'"
157
- route "get '/auth/failure', to: 'sessions/omniauth#failure'"
115
+ route "get '/auth/:provider/callback', to: 'sessions/omniauth#create'"
116
+ route "get '/auth/failure', to: 'sessions/omniauth#failure'"
117
+ end
118
+
119
+ if two_factor?
120
+ route "resource :totp, only: [:new, :create]", namespace: :two_factor_authentication
121
+ route "resource :challenge, only: [:new, :create]", namespace: :two_factor_authentication
158
122
  end
159
123
 
160
124
  if options.trackable?
161
125
  route "resources :events, only: :index", namespace: :authentications
162
126
  end
163
127
 
164
- route "resource :password_reset, only: [:new, :edit, :create, :update]", namespace: :identity
128
+ route "resource :password_reset, only: [:new, :edit, :create, :update]", namespace: :identity
165
129
  route "resource :email_verification, only: [:edit, :create]", namespace: :identity
166
- route "resource :email, only: [:edit, :update]", namespace: :identity
167
- route "resource :sudo, only: [:new, :create]", namespace: :sessions
130
+ route "resource :email, only: [:edit, :update]", namespace: :identity
131
+ route "resource :sudo, only: [:new, :create]", namespace: :sessions if options.sudoable?
132
+ route "resource :password, only: [:edit, :update]"
168
133
  route "resources :sessions, only: [:index, :show, :destroy]"
169
- route "resource :password, only: [:edit, :update]"
170
134
  route "post 'sign_up', to: 'registrations#create'"
171
- route "get 'sign_up', to: 'registrations#new'" unless options.api?
135
+ route "get 'sign_up', to: 'registrations#new'" unless options.api?
172
136
  route "post 'sign_in', to: 'sessions#create'"
173
- route "get 'sign_in', to: 'sessions#new'" unless options.api?
137
+ route "get 'sign_in', to: 'sessions#new'" unless options.api?
174
138
  end
175
139
 
176
140
  def create_test_files
177
141
  directory "test_unit/controllers/#{format_folder}", "test/controllers"
178
142
  directory "test_unit/system", "test/system" unless options.api?
143
+ template "test_unit/test_helper.rb", "test/test_helper.rb", force: true
144
+ template "test_unit/application_system_test_case.rb", "test/application_system_test_case.rb", force: true unless options.api?
179
145
  end
180
146
 
181
147
  private
@@ -186,4 +152,12 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
186
152
  def omniauthable?
187
153
  options.omniauthable? && !options.api?
188
154
  end
155
+
156
+ def two_factor?
157
+ options.two_factor? && !options.api?
158
+ end
159
+
160
+ def redis?
161
+ options.lockable? || options.sudoable?
162
+ end
189
163
  end
@@ -0,0 +1,27 @@
1
+ class ApplicationController < ActionController::API
2
+ include ActionController::HttpAuthentication::Token::ControllerMethods
3
+
4
+ before_action :set_current_request_details
5
+ before_action :authenticate
6
+ <%- if options.sudoable? %>
7
+ def require_sudo
8
+ unless Current.session.sudo?
9
+ render json: { error: "Enter your password to continue" }, status: :forbidden
10
+ end
11
+ end
12
+ <%- end -%>
13
+
14
+ private
15
+ def authenticate
16
+ if session = authenticate_with_http_token { |token, _| Session.find_signed(token) }
17
+ Current.session = session
18
+ else
19
+ request_http_token_authentication
20
+ end
21
+ end
22
+
23
+ def set_current_request_details
24
+ Current.user_agent = request.user_agent
25
+ Current.ip_address = request.ip
26
+ end
27
+ end
@@ -1,9 +1,10 @@
1
1
  class Identity::EmailsController < ApplicationController
2
- before_action :require_sudo
3
2
  before_action :set_<%= singular_table_name %>
4
3
 
5
4
  def update
6
- if @<%= singular_table_name %>.update(<%= "#{singular_table_name}_params" %>)
5
+ if !@<%= singular_table_name %>.authenticate(params[:current_password])
6
+ render json: { error: "The password you entered is incorrect" }, status: :bad_request
7
+ elsif @<%= singular_table_name %>.update(<%= "#{singular_table_name}_params" %>)
7
8
  render json: @<%= singular_table_name %>
8
9
  else
9
10
  render json: @<%= singular_table_name %>.errors, status: :unprocessable_entity
@@ -1,9 +1,9 @@
1
1
  class Identity::PasswordResetsController < ApplicationController
2
2
  skip_before_action :authenticate
3
3
 
4
- <% if options.lockable? -%>
4
+ <%- if options.lockable? -%>
5
5
  before_action :require_locking, only: :create
6
- <% end -%>
6
+ <%- end -%>
7
7
  before_action :set_<%= singular_table_name %>, only: :update
8
8
 
9
9
  def create
@@ -32,11 +32,11 @@ class Identity::PasswordResetsController < ApplicationController
32
32
  def <%= "#{singular_table_name}_params" %>
33
33
  params.permit(:password, :password_confirmation)
34
34
  end
35
- <% if options.lockable? %>
35
+ <%- if options.lockable? %>
36
36
  def require_locking
37
37
  Locking.lock_on("password_reset_lock:#{request.remote_ip}", wait: 1.hour, attempts: 10) do
38
38
  render json: { error: "You've exceeded the maximum number of attempts" }, status: :too_many_requests
39
39
  end
40
40
  end
41
- <% end -%>
41
+ <%- end -%>
42
42
  end
@@ -3,7 +3,7 @@ class Sessions::SudosController < ApplicationController
3
3
  session = Current.session
4
4
 
5
5
  if session.<%= singular_table_name %>.authenticate(params[:password])
6
- session.update! sudo_at: Time.current
6
+ session.sudo.mark expires_in: 30.minutes
7
7
  else
8
8
  render json: { error: "The password you entered is incorrect" }, status: :bad_request
9
9
  end
@@ -0,0 +1,25 @@
1
+ class ApplicationController < ActionController::Base
2
+ before_action :set_current_request_details
3
+ before_action :authenticate
4
+ <%- if options.sudoable? %>
5
+ def require_sudo
6
+ unless Current.session.sudo?
7
+ redirect_to new_sessions_sudo_path(proceed_to_url: request.url)
8
+ end
9
+ end
10
+ <%- end -%>
11
+
12
+ private
13
+ def authenticate
14
+ if session = Session.find_by_id(cookies.signed[:session_token])
15
+ Current.session = session
16
+ else
17
+ redirect_to sign_in_path
18
+ end
19
+ end
20
+
21
+ def set_current_request_details
22
+ Current.user_agent = request.user_agent
23
+ Current.ip_address = request.ip
24
+ end
25
+ end
@@ -1,12 +1,13 @@
1
1
  class Identity::EmailsController < ApplicationController
2
- before_action :require_sudo
3
2
  before_action :set_<%= singular_table_name %>
4
3
 
5
4
  def edit
6
5
  end
7
6
 
8
7
  def update
9
- if @<%= singular_table_name %>.update(<%= "#{singular_table_name}_params" %>)
8
+ if !@<%= singular_table_name %>.authenticate(params[:current_password])
9
+ redirect_to edit_identity_email_path, alert: "The password you entered is incorrect"
10
+ elsif @<%= singular_table_name %>.update(<%= "#{singular_table_name}_params" %>)
10
11
  redirect_to root_path, notice: "Your email has been changed"
11
12
  else
12
13
  render :edit, status: :unprocessable_entity
@@ -1,9 +1,9 @@
1
1
  class Identity::PasswordResetsController < ApplicationController
2
2
  skip_before_action :authenticate
3
3
 
4
- <% if options.lockable? -%>
4
+ <%- if options.lockable? -%>
5
5
  before_action :require_locking, only: :create
6
- <% end -%>
6
+ <%- end -%>
7
7
  before_action :set_<%= singular_table_name %>, only: %i[ edit update ]
8
8
 
9
9
  def new
@@ -39,11 +39,11 @@ class Identity::PasswordResetsController < ApplicationController
39
39
  def <%= "#{singular_table_name}_params" %>
40
40
  params.permit(:password, :password_confirmation)
41
41
  end
42
- <% if options.lockable? %>
42
+ <%- if options.lockable? %>
43
43
  def require_locking
44
44
  Locking.lock_on("password_reset_lock:#{request.remote_ip}", wait: 1.hour, attempts: 10) do
45
45
  redirect_to new_identity_password_reset_path, alert: "You've exceeded the maximum number of attempts"
46
46
  end
47
47
  end
48
- <% end -%>
48
+ <%- end -%>
49
49
  end
@@ -5,12 +5,12 @@ class Sessions::SudosController < ApplicationController
5
5
  def create
6
6
  session = Current.session
7
7
 
8
- <% if omniauthable? -%>
8
+ <%- if omniauthable? -%>
9
9
  if session.<%= singular_table_name %>.authenticate(params[:password]) || session.<%= singular_table_name %>.provider
10
- <% else -%>
10
+ <%- else -%>
11
11
  if session.<%= singular_table_name %>.authenticate(params[:password])
12
- <% end -%>
13
- session.update!(sudo_at: Time.current); redirect_to(params[:proceed_to_url])
12
+ <%- end -%>
13
+ session.sudo.mark(expires_in: 30.minutes); redirect_to(params[:proceed_to_url])
14
14
  else
15
15
  redirect_to new_sessions_sudo_path(proceed_to_url: params[:proceed_to_url]), alert: "The password you entered is incorrect"
16
16
  end
@@ -15,10 +15,23 @@ class SessionsController < ApplicationController
15
15
  <%= singular_table_name %> = <%= class_name %>.find_by(email: params[:email])
16
16
 
17
17
  if <%= singular_table_name %> && <%= singular_table_name %>.authenticate(params[:password])
18
+ <%- if two_factor? -%>
19
+ if <%= singular_table_name %>.otp_secret
20
+ signed_id = <%= singular_table_name %>.signed_id(purpose: :authentication_challenge, expires_in: 20.minutes)
21
+
22
+ redirect_to new_two_factor_authentication_challenge_path(token: signed_id)
23
+ else
24
+ @session = <%= singular_table_name %>.sessions.create!
25
+ cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }
26
+
27
+ redirect_to root_path, notice: "Signed in successfully"
28
+ end
29
+ <%- else -%>
18
30
  @session = <%= singular_table_name %>.sessions.create!
19
31
  cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }
20
32
 
21
33
  redirect_to root_path, notice: "Signed in successfully"
34
+ <%- end -%>
22
35
  else
23
36
  redirect_to sign_in_path(email_hint: params[:email]), alert: "That email or password is incorrect"
24
37
  end
@@ -0,0 +1,28 @@
1
+ class TwoFactorAuthentication::ChallengesController < ApplicationController
2
+ skip_before_action :authenticate
3
+
4
+ before_action :set_<%= singular_table_name %>
5
+
6
+ def new
7
+ end
8
+
9
+ def create
10
+ @totp = ROTP::TOTP.new(@<%= singular_table_name %>.otp_secret, issuer: "YourAppName")
11
+
12
+ if @totp.verify(params[:code], drift_behind: 15)
13
+ session = @<%= singular_table_name %>.sessions.create!
14
+ cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
15
+
16
+ redirect_to root_path, notice: "Signed in successfully"
17
+ else
18
+ redirect_to new_two_factor_authentication_challenge_path(token: params[:token]), alert: "That code didn't work. Please try again"
19
+ end
20
+ end
21
+
22
+ private
23
+ def set_<%= singular_table_name %>
24
+ @<%= singular_table_name %> = <%= class_name %>.find_signed!(params[:token], purpose: :authentication_challenge)
25
+ rescue
26
+ redirect_to sign_in_path, alert: "That's taking too long. Please re-enter your password and try again"
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ class TwoFactorAuthentication::TotpsController < ApplicationController
2
+ before_action :set_<%= singular_table_name %>
3
+ before_action :set_totp
4
+
5
+ def new
6
+ @qr_code = RQRCode::QRCode.new(@totp.provisioning_uri(@<%= singular_table_name %>.email))
7
+ end
8
+
9
+ def create
10
+ if !@<%= singular_table_name %>.authenticate(params[:current_password])
11
+ redirect_to two_factor_authentication_totp_path, alert: "The password you entered is incorrect"
12
+ elsif @totp.verify(params[:code], drift_behind: 15)
13
+ @<%= singular_table_name %>.update! otp_secret: params[:secret]
14
+ redirect_to root_path, notice: "2FA is enabled on your account"
15
+ else
16
+ redirect_to two_factor_authentication_totp_path, alert: "That code didn't work. Please try again"
17
+ end
18
+ end
19
+
20
+ def set_<%= singular_table_name %>
21
+ @<%= singular_table_name %> = Current.<%= singular_table_name %>
22
+ end
23
+
24
+ def set_totp
25
+ @totp = ROTP::TOTP.new(params[:secret] || ROTP::Base32.random, issuer: "YourAppName")
26
+ end
27
+ end
@@ -21,6 +21,11 @@
21
21
  </div>
22
22
  <%% end %>
23
23
 
24
+ <div>
25
+ <%%= form.label :current_password, style: "display: block" %>
26
+ <%%= form.password_field :current_password, required: true, autofocus: true, autocomplete: "current-password" %>
27
+ </div>
28
+
24
29
  <div>
25
30
  <%%= form.label :email, "New email", style: "display: block" %>
26
31
  <%%= form.email_field :email %>
@@ -13,7 +13,7 @@
13
13
  </div>
14
14
  <%% end %>
15
15
 
16
- <%%= hidden_field_tag :token, params[:token] %>
16
+ <%%= form.hidden_field :token, value: params[:token] %>
17
17
 
18
18
  <div>
19
19
  <%%= form.label :password, "New password", style: "display: block" %>
@@ -16,8 +16,8 @@
16
16
  <%% end %>
17
17
 
18
18
  <div>
19
- <%%= label_tag :current_password, nil, style: "display: block" %>
20
- <%%= password_field_tag :current_password, nil, required: true, autofocus: true, autocomplete: "current-password" %>
19
+ <%%= form.label :current_password, style: "display: block" %>
20
+ <%%= form.password_field :current_password, required: true, autofocus: true, autocomplete: "current-password" %>
21
21
  </div>
22
22
 
23
23
  <div>
@@ -4,10 +4,10 @@
4
4
 
5
5
  <%%= form_with(url: sessions_sudo_path) do |form| %>
6
6
 
7
- <%%= hidden_field_tag :proceed_to_url, params[:proceed_to_url] %>
7
+ <%%= form.hidden_field :proceed_to_url, value: params[:proceed_to_url] %>
8
8
 
9
9
  <div>
10
- <%%= password_field_tag :password, nil, required: true, autofocus: true, autocomplete: "current-password" %>
10
+ <%%= form.password_field :password, required: true, autofocus: true, autocomplete: "current-password" %>
11
11
  </div>
12
12
 
13
13
  <div>
@@ -0,0 +1,16 @@
1
+ <p style="color: red"><%%= alert %></p>
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>
16
+ <%% end %>