authentication-zero 2.9.2 → 2.11.0

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