rails_base 0.75.6 → 0.81.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/rails_base/rails_base_query_checker.js +36 -0
  3. data/app/controllers/rails_base/admin_controller.rb +54 -9
  4. data/app/controllers/rails_base/mfa/evaluation_controller.rb +59 -0
  5. data/app/controllers/rails_base/mfa/register/sms_controller.rb +45 -0
  6. data/app/controllers/rails_base/mfa/register/totp_controller.rb +42 -0
  7. data/app/controllers/rails_base/mfa/validate/sms_controller.rb +83 -0
  8. data/app/controllers/rails_base/mfa/validate/totp_controller.rb +35 -0
  9. data/app/controllers/rails_base/secondary_authentication_controller.rb +40 -96
  10. data/app/controllers/rails_base/user_settings_controller.rb +11 -1
  11. data/app/controllers/rails_base/users/registrations_controller.rb +1 -1
  12. data/app/controllers/rails_base/users/sessions_controller.rb +17 -11
  13. data/app/controllers/rails_base_application_controller.rb +96 -1
  14. data/app/jobs/twilio_job.rb +1 -1
  15. data/app/mailers/rails_base/email_verification_mailer.rb +6 -4
  16. data/app/mailers/rails_base/event_mailer.rb +4 -2
  17. data/app/mailers/rails_base/mailer_kwarg_inject.rb +31 -0
  18. data/app/models/rails_base/user_constants.rb +6 -3
  19. data/app/models/rails_base/user_helper/totp/backup_method_options.rb +33 -0
  20. data/app/models/rails_base/user_helper/totp/class_options.rb +35 -0
  21. data/app/models/rails_base/user_helper/totp/consume_method_options.rb +60 -0
  22. data/app/models/rails_base/user_helper/totp.rb +41 -0
  23. data/app/models/user.rb +28 -13
  24. data/app/services/rails_base/authentication/constants.rb +1 -1
  25. data/app/services/rails_base/authentication/decision_twofa_type.rb +63 -30
  26. data/app/services/rails_base/authentication/send_forgot_password.rb +0 -1
  27. data/app/services/rails_base/authentication/single_sign_on_send.rb +1 -1
  28. data/app/services/rails_base/authentication/sso_verify_email.rb +3 -1
  29. data/app/services/rails_base/authentication/update_phone_send_verification.rb +2 -2
  30. data/app/services/rails_base/authentication/verify_forgot_password.rb +8 -11
  31. data/app/services/rails_base/mfa/decision.rb +70 -0
  32. data/app/services/rails_base/mfa/encrypt_token.rb +34 -0
  33. data/app/services/rails_base/mfa/sms/remove.rb +35 -0
  34. data/app/services/rails_base/{authentication/send_login_mfa_to_user.rb → mfa/sms/send.rb} +19 -13
  35. data/app/services/rails_base/mfa/sms/validate.rb +105 -0
  36. data/app/services/rails_base/mfa/strategy/base.rb +44 -0
  37. data/app/services/rails_base/mfa/strategy/every_request.rb +14 -0
  38. data/app/services/rails_base/mfa/strategy/skip_every_request.rb +14 -0
  39. data/app/services/rails_base/mfa/strategy/time_based.rb +24 -0
  40. data/app/services/rails_base/mfa/totp/helper.rb +21 -0
  41. data/app/services/rails_base/mfa/totp/otp_metadata.rb +19 -0
  42. data/app/services/rails_base/mfa/totp/remove.rb +40 -0
  43. data/app/services/rails_base/mfa/totp/validate_code.rb +52 -0
  44. data/app/services/rails_base/mfa/totp/validate_temporary_code.rb +37 -0
  45. data/app/services/rails_base/mfa.rb +18 -0
  46. data/app/services/rails_base/name_change.rb +3 -3
  47. data/app/views/layouts/rails_base/application.html.erb +10 -6
  48. data/app/views/rails_base/devise/passwords/new.html.erb +1 -1
  49. data/app/views/rails_base/mfa/_switch_mfa_type.html.erb +17 -0
  50. data/app/views/rails_base/mfa/validate/sms/sms_event_input.html.erb +2 -0
  51. data/app/views/rails_base/mfa/validate/totp/totp_event_input.html.erb +1 -0
  52. data/app/views/rails_base/secondary_authentication/reset_password_input.html.erb +4 -0
  53. data/app/views/rails_base/shared/_enable_mfa_auth_modal.html.erb +1 -1
  54. data/app/views/rails_base/shared/_logged_in_header.html.erb +1 -25
  55. data/app/views/rails_base/shared/_modify_mfa_auth_modal.html.erb +102 -3
  56. data/app/views/rails_base/shared/_request_link_alert.html.erb +48 -0
  57. data/app/views/rails_base/shared/mfa/sms/_login_input.html.erb +13 -0
  58. data/app/views/rails_base/shared/mfa/totp/_login_input.html.erb +22 -0
  59. data/app/views/rails_base/shared/totp/_add_authenticator.html.erb +76 -0
  60. data/app/views/rails_base/shared/totp/_add_authenticator_modal.html.erb +25 -0
  61. data/app/views/rails_base/shared/totp/_confirm_code.html.erb +31 -0
  62. data/app/views/rails_base/shared/totp/_confirm_code_ajax.html.erb +3 -0
  63. data/app/views/rails_base/shared/totp/_confirm_code_rest.html.erb +5 -0
  64. data/app/views/rails_base/shared/totp/_remove_authenticator_modal.html.erb +50 -0
  65. data/app/views/rails_base/user_settings/index.html.erb +102 -3
  66. data/config/initializers/admin_action_helper.rb +44 -8
  67. data/config/routes.rb +42 -7
  68. data/db/migrate/20240808013706_add_totp_to_users.rb +9 -0
  69. data/db/migrate/20240825012724_reconfigure_mfa_variable_names.rb +10 -0
  70. data/lib/rails_base/admin/action_helper.rb +0 -1
  71. data/lib/rails_base/admin/default_index_tile.rb +3 -3
  72. data/lib/rails_base/config.rb +26 -22
  73. data/lib/rails_base/configuration/admin.rb +5 -5
  74. data/lib/rails_base/configuration/base.rb +1 -0
  75. data/lib/rails_base/configuration/mfa.rb +27 -60
  76. data/lib/rails_base/configuration/totp.rb +82 -0
  77. data/lib/rails_base/configuration/twilio.rb +85 -0
  78. data/lib/rails_base/mfa_event.rb +186 -0
  79. data/lib/rails_base/request_link.rb +27 -0
  80. data/lib/rails_base/version.rb +3 -3
  81. data/lib/rails_base.rb +2 -0
  82. data/lib/twilio_helper.rb +3 -3
  83. metadata +131 -64
  84. data/app/controllers/rails_base/mfa_auth_controller.rb +0 -50
  85. data/app/services/rails_base/authentication/mfa_set_encrypt_token.rb +0 -32
  86. data/app/services/rails_base/authentication/mfa_validator.rb +0 -88
  87. data/app/views/rails_base/mfa_auth/mfa_code.html.erb +0 -11
  88. data/app/views/rails_base/secondary_authentication/forgot_password.html.erb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7be240ad162cad85b3a70e4163f99eea08a0eec55d8ce97ab6a9e83f59d6559a
4
- data.tar.gz: 8357b2607d6eae820b73be0e794ad6cab80912d7cf02d6524f4ba95d47626caf
3
+ metadata.gz: 2e6dfb9b0dfa087ba1c2378c08703360360da41dfc5f676feb29c94a3542cfaf
4
+ data.tar.gz: 0bae19f4b8e3b06b42b3b7d91fd407028676034f5cb4c102898848747238d205
5
5
  SHA512:
6
- metadata.gz: 11ff5f552ba5e901555fdfd4df622a0c3bccef9e0e1f169fcfd50778143814ba7796483c2876121c67885ffc7dc9145c5ebc12954f47016497924a53c5c43d0a
7
- data.tar.gz: 2ae800d156442306b2f34374c1e0c5ab417e233c57c1aa341c516da5df110c2af3c0227dcee5112fb28ba51ed549b8cd598b0f15e38b77a3bad5c4f27d7bb02d
6
+ metadata.gz: e4c16fdf660ed93e2672208de8b86c0b766f374defb44cd3259ee96441071b8e1821246a766d6b2237c95250c6e3c21c1858eeeeb714079f1993aa13016676fd
7
+ data.tar.gz: fe5cd28864e828717ddf81a3fbfc3c0af190e4ca888154ad3af177fc8ca67e2bc9c908e29e6215e7e675533fdc005082a30de09bd27f25f25ad997adede714c5
@@ -0,0 +1,36 @@
1
+
2
+ function _railsBase_urlParams(param){
3
+ urlParams = new URLSearchParams(window.location.search);
4
+ return urlParams.get(param);
5
+ }
6
+
7
+ function _railsBase_goToStandardizedCollapse(q_param, identifier, function_base_name, function_yield){
8
+ param = _railsBase_urlParams(q_param)
9
+ if(param==null){
10
+ return false
11
+ }
12
+
13
+ // Let callee decide if they want to continue
14
+ if(typeof(function_yield) === "function") {
15
+ if (function_yield(param) != true) {
16
+ // Callee does not want to continue
17
+ return
18
+ }
19
+ } else {
20
+ // No function provided. Since the param was present, we will continue as expected
21
+ }
22
+
23
+ // Scroll to top of provided class
24
+ $('html, body').animate({
25
+ scrollTop: $(`${identifier}`).offset().top
26
+ }, 'slow');
27
+
28
+
29
+ // function name declared for the collapsable options
30
+ // Toggle it and open it up
31
+ console.log(`trying to open ${function_base_name}_collapse_toggle()`)
32
+ eval(`${function_base_name}_collapse_toggle()`)
33
+
34
+ return param
35
+ }
36
+
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsBase
2
4
  class AdminController < RailsBaseApplicationController
3
5
  before_action :authenticate_user!, except: [:sso_retrieve]
@@ -9,9 +11,10 @@ module RailsBase
9
11
 
10
12
  # GET admin
11
13
  def index
14
+ add_mfa_event_to_session(event: RailsBase::MfaEvent.admin_actions(user: current_user))
12
15
  end
13
16
 
14
- # GET admin
17
+ # GET admin/config
15
18
  def show_config
16
19
  unless RailsBase.config.admin.config_page?(current_user)
17
20
  flash[:alert] = 'You do not have correct permissions to view admin config'
@@ -36,7 +39,7 @@ module RailsBase
36
39
  uses: Authentication::Constants::SSO_SEND_USES,
37
40
  reason: Authentication::Constants::SSO_REASON,
38
41
  expires_at: Authentication::Constants::SSO_EXPIRES.from_now,
39
- url_redirect: RailsBase.url_routes.user_settings_path
42
+ url_redirect: RailsBase.url_routes.authenticated_root_path
40
43
  }
41
44
 
42
45
  status = RailsBase::Authentication::SingleSignOnSend.call(local_params)
@@ -50,6 +53,7 @@ module RailsBase
50
53
  redirect_to RailsBase.url_routes.admin_base_path
51
54
  end
52
55
 
56
+ # TODO: Move this to a different controller
53
57
  #GET auth/sso/:data
54
58
  def sso_retrieve
55
59
  local_params = {
@@ -153,6 +157,14 @@ module RailsBase
153
157
 
154
158
  # POST admin/update
155
159
  def update_attribute
160
+ unless RailsBase.config.admin.view_admin_page?(current_user)
161
+ session.clear
162
+ sign_out(current_user)
163
+ logger.warn("Unauthorized user has tried to update attributes. Fail Quickly!")
164
+ render json: { success: false, message: "Unauthorized action. You have been signed out" }, status: 404
165
+ return
166
+ end
167
+
156
168
  update = RailsBase::AdminUpdateAttribute.call(params: params, admin_user: admin_user)
157
169
  if update.success?
158
170
  @_admin_action_struct = RailsBase::AdminStruct.new(update.original_attribute, update.attribute, update.model)
@@ -161,12 +173,17 @@ module RailsBase
161
173
  @_admin_action_struct = false
162
174
  render json: { success: false, message: update.message }, status: 404
163
175
  end
176
+
177
+ session[:mfa_randomized_token] = nil
164
178
  end
165
179
 
180
+ # POST admin/update/name
166
181
  def update_name
167
182
  unless RailsBase.config.admin.name_tile_users?(admin_user)
168
- flash[:alert] = 'You do not have correct permissions to change a users name'
169
- redirect_to RailsBase.url_routes.admin_base_path
183
+ session.clear
184
+ sign_out(current_user)
185
+ logger.warn("Unauthorized user has tried to update attributes. Fail Quickly!")
186
+ render json: { success: false, message: "Unauthorized action. You have been signed out" }, status: 404
170
187
  return
171
188
  end
172
189
  user = User.find(params[:id])
@@ -186,12 +203,17 @@ module RailsBase
186
203
  @_admin_action_struct = false
187
204
  render json: { success: false, message: "Failed to change #{user.id} name" }, status: 404
188
205
  end
206
+
207
+ session[:mfa_randomized_token] = nil
189
208
  end
190
209
 
210
+ # POST admin/update/email
191
211
  def update_email
192
212
  unless RailsBase.config.admin.email_tile_users?(admin_user)
193
- flash[:alert] = 'You do not have correct permissions to change a users name'
194
- redirect_to RailsBase.url_routes.admin_base_path
213
+ session.clear
214
+ sign_out(current_user)
215
+ logger.warn("Unauthorized user has tried to update emai. Fail Quickly!")
216
+ render json: { success: false, message: "Unauthorized action. You have been signed out" }, status: 404
195
217
  return
196
218
  end
197
219
 
@@ -205,9 +227,20 @@ module RailsBase
205
227
  @_admin_action_struct = false
206
228
  render json: { success: false, message: result.message }, status: 404
207
229
  end
230
+
231
+ session[:mfa_randomized_token] = nil
208
232
  end
209
233
 
234
+ # POST admin/update/phone
210
235
  def update_phone
236
+ unless RailsBase.config.admin.view_admin_page?(current_user)
237
+ session.clear
238
+ sign_out(current_user)
239
+ logger.warn("Unauthorized user has tried to update phone. Fail Quickly!")
240
+ render json: { success: false, message: "Unauthorized action. You have been signed out" }, status: 400
241
+ return
242
+ end
243
+
211
244
  begin
212
245
  params[:value] = params[:phone_number].gsub(/\D/,'')
213
246
  rescue
@@ -220,6 +253,14 @@ module RailsBase
220
253
 
221
254
  # POST admin/validate_intent/send
222
255
  def send_2fa
256
+ unless RailsBase.config.admin.view_admin_page?(current_user)
257
+ session.clear
258
+ sign_out(current_user)
259
+ logger.warn("Unauthorized user has tried to send 2fa. Fail Quickly!")
260
+ render json: { success: false, message: "Unauthorized action. You have been signed out" }, status: 404
261
+ return
262
+ end
263
+
223
264
  reason = "#{SESSION_REASON_BASE}-#{SecureRandom.uuid}"
224
265
  result = AdminRiskyMfaSend.call(user: admin_user, reason: reason)
225
266
  if result.success?
@@ -232,6 +273,8 @@ module RailsBase
232
273
 
233
274
  # POST admin/validate_intent/verify
234
275
  def verify_2fa
276
+ return unless validate_mfa_with_event_json!(mfa_event_name: RailsBase::MfaEvent::ADMIN_VERIFY)
277
+
235
278
  unless modify_id = params[:modify_id]
236
279
  logger.warn("Failed to find #{modify_id} in payload")
237
280
  render json: { success: false, message: 'Hmm. Something fishy happend. Failed to find text to modify' }, status: 404
@@ -251,13 +294,14 @@ module RailsBase
251
294
  end
252
295
 
253
296
  params = {
297
+ mfa_event: @__rails_base_mfa_event,
254
298
  session_mfa_user_id: admin_user.id,
255
299
  current_user: admin_user,
256
300
  input_reason: session_reason,
257
301
  params: parse_mfa_to_obj
258
302
  }
259
- result = RailsBase::Authentication::MfaValidator.call(params)
260
- encrypt = RailsBase::Authentication::MfaSetEncryptToken.call(user: admin_user, purpose: session_reason, expires_at: 1.minute.from_now)
303
+ result = RailsBase::Mfa::Sms::Validate.call(params)
304
+ encrypt = RailsBase::Mfa::EncryptToken.call(user: admin_user, purpose: session_reason, expires_at: 1.minute.from_now)
261
305
 
262
306
  begin
263
307
  html = render_to_string(partial: render_partial, locals: { user: user, modify_id: modify_id })
@@ -266,9 +310,10 @@ module RailsBase
266
310
  logger.warn("Failed to render html correctly")
267
311
  html = nil
268
312
  end
313
+
269
314
  if html.nil?
270
315
  logger.warn("Failed to find render html correctly")
271
- render json: { success: false, message: 'Apologies. Wee are struggling to render the page. Please try again later' }, status: 500
316
+ render json: { success: false, message: 'Apologies. We are struggling to render the page. Please try again later' }, status: 500
272
317
  return
273
318
  end
274
319
 
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsBase::Mfa
4
+ class EvaluationController < RailsBaseApplicationController
5
+ before_action :authenticate_user!, only: [:mfa_evaluate_authenticated]
6
+ before_action :validate_mfa_with_event!
7
+ OTP_TEMPLATE = "rails_base/mfa/validate/totp/totp_event_input"
8
+ SMS_TEMPLATE = "rails_base/mfa/validate/sms/sms_event_input"
9
+
10
+ # GET mfa/:event
11
+ def mfa_with_event
12
+ user = User.find(@__rails_base_mfa_event.user_id)
13
+ decision = RailsBase::Mfa::Decision.(user: user)
14
+ mfa_type = mfa_decision(provided: params[:type], default: decision.mfa_type, allowed: decision.mfa_options)
15
+
16
+ if @__rails_base_mfa_event.phone_number
17
+ phone_number = @__rails_base_mfa_event.phone_number
18
+ else
19
+ phone_number = User.find(@__rails_base_mfa_event.user_id).phone_number
20
+ end
21
+
22
+ @masked_phone = User.masked_number(phone_number)
23
+ @mfa_options = decision.mfa_options.map do |type|
24
+ next if type == mfa_type
25
+
26
+ {
27
+ text: "Switch MFA to #{type}",
28
+ ** RailsBase::Mfa.mfa_link(mfa_event: @__rails_base_mfa_event.event, mfa: type)
29
+ }
30
+ end.compact
31
+
32
+ case mfa_type
33
+ when RailsBase::Mfa::OTP
34
+ render OTP_TEMPLATE
35
+ when RailsBase::Mfa::SMS
36
+ render SMS_TEMPLATE
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def mfa_decision(provided:, default:, allowed:)
43
+ if Array === @__rails_base_mfa_event.only_mfa
44
+ logger.warn("MFA Event is forcing one of #{@__rails_base_mfa_event.only_mfa}")
45
+ return @__rails_base_mfa_event.only_mfa.sample.to_sym
46
+ end
47
+
48
+ # Nothing was provided by the user
49
+ return default if provided.nil?
50
+
51
+ # Provided input is an allowed type for the current user
52
+ return provided.to_sym if allowed.include?(provided.to_sym)
53
+
54
+ flash[:alert] = "Unknown MFA type #{provided}. Using #{default} instead"
55
+
56
+ return default
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsBase::Mfa::Register
4
+ class SmsController < RailsBaseApplicationController
5
+ before_action :authenticate_user!
6
+ before_action ->() { validate_mfa_with_event!(mfa_event_name: RailsBase::MfaEvent::ENABLE_SMS_EVENT) }, only: [:sms_confirmation]
7
+ before_action ->() { validate_mfa_with_event!(mfa_event_name: RailsBase::MfaEvent::DISABLE_SMS_EVENT) }, only: [:sms_removal]
8
+ before_action :json_validate_current_user!, only: [:sms_registration]
9
+
10
+ # POST mfa/register/sms
11
+ def sms_registration
12
+ result = RailsBase::Authentication::UpdatePhoneSendVerification.call(user: current_user, phone_number: params[:phone_number])
13
+ if result.failure?
14
+ render :json => { error: I18n.t('request_response.teapot.fail'), msg: result.message }.to_json, :status => 418
15
+ return
16
+ end
17
+ session[:mfa_randomized_token] = result.mfa_randomized_token
18
+ render :json => { status: :success, message: I18n.t('request_response.teapot.valid') }
19
+ end
20
+
21
+ # POST mfa/register/sms/validate
22
+ def sms_confirmation
23
+ mfa_validity = RailsBase::Mfa::Sms::Validate.call(mfa_event: @__rails_base_mfa_event, current_user: current_user, params: params, session_mfa_user_id: @__rails_base_mfa_event.user_id)
24
+ if mfa_validity.failure?
25
+ redirect_to RailsBase.url_routes.user_settings_path, alert: I18n.t('authentication.confirm_phone_registration.fail', message: mfa_validity.message)
26
+ return
27
+ end
28
+
29
+ current_user.update!(mfa_sms_enabled: true)
30
+ redirect_to RailsBase.url_routes.user_settings_path, notice: "Successfully added SMS as an MFA option on your account"
31
+ end
32
+
33
+ # DELETE mfa/register/sms
34
+ def sms_removal
35
+ result = RailsBase::Mfa::Sms::Remove.(mfa_event: @__rails_base_mfa_event, current_user: current_user, session_mfa_user_id: @__rails_base_mfa_event.user_id, password: params[:password], sms_code: params[:sms_code])
36
+ if result.success?
37
+ flash[:notice] = "Successfully removed SMS as an MFA option on your account"
38
+ else
39
+ flash[:alert] = result.message
40
+ end
41
+
42
+ redirect_to RailsBase.url_routes.user_settings_path
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsBase::Mfa::Register
4
+ class TotpController < RailsBaseApplicationController
5
+ before_action :authenticate_user!
6
+
7
+ # DELETE mfa/register/totp
8
+ def totp_remove
9
+ result = RailsBase::Mfa::Totp::Remove.(password: params[:password], user: current_user, otp_code: params[:totp_code])
10
+
11
+ if result.success?
12
+ flash[:notice] = "Successfully Removed TOTP Authentication to #{RailsBase.app_name}"
13
+ else
14
+ flash[:alert] = "Something Went Wrong! #{result.message}"
15
+ end
16
+
17
+ redirect_to RailsBase.url_routes.user_settings_path
18
+ end
19
+
20
+ # POST mfa/register/totp
21
+ def totp_secret
22
+ result = RailsBase::Mfa::Totp::OtpMetadata.(user: current_user)
23
+ if result.success?
24
+ render json: result.metadata
25
+ else
26
+ render json: { status: result.message }, status: 400
27
+ end
28
+ end
29
+
30
+ # POST mfa/register/totp/validate
31
+ def totp_validate
32
+ result = RailsBase::Mfa::Totp::ValidateTemporaryCode.(user: current_user, otp_code: params[:totp_code])
33
+ if result.success?
34
+ flash[:notice] = "Successfully added an Authenticator for TOTP to #{RailsBase.app_name}"
35
+ else
36
+ flash[:alert] = "Something Went Wrong! Failed to add an Authenticator for TOTP to #{RailsBase.app_name}. Please try again"
37
+ end
38
+
39
+ redirect_to RailsBase.url_routes.user_settings_path
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsBase::Mfa::Validate
4
+ class SmsController < RailsBaseApplicationController
5
+ before_action :validate_mfa_with_event!, only: [:sms_event_input, :sms_event]
6
+
7
+ # POST mfa/validate/sms/:mfa_event/send
8
+ def sms_event_send
9
+ if soft_mfa_with_event
10
+ user = User.find(@__rails_base_mfa_event.user_id)
11
+ else
12
+ if request.format.json?
13
+ render json: { message: @__rails_base_mfa_event_invalid_reason }, status: 400
14
+ else
15
+ flash[:alert] = @__rails_base_mfa_event_invalid_reason
16
+ redirect = @__rails_base_mfa_event&.invalid_redirect || RailsBase.url_routes.new_user_session_path
17
+
18
+ redirect_to redirect, email: params.dig(:user,:email)
19
+ end
20
+ return
21
+ end
22
+
23
+ if request.format.json?
24
+ # When json, this will always come from an authenticated user
25
+ # otherwise kick them out now!
26
+ return unless authenticate_user!
27
+
28
+ user = current_user
29
+ end
30
+
31
+ result = RailsBase::Mfa::Sms::Send.call(expires_at: 5.minutes.from_now, phone_number: @__rails_base_mfa_event.phone_number, user: user)
32
+
33
+ if result.success?
34
+ flash[:notice] = msg = "SMS Code succesfully sent. Please check messages"
35
+ status = 200
36
+ else
37
+ flash[:alert] = msg = "Unable to complete Request. #{result.message}"
38
+ status = 400
39
+ end
40
+
41
+ if request.format.json?
42
+ render json: { message: msg }, status: status
43
+ flash.clear
44
+ else
45
+ redirect_to RailsBase.url_routes.mfa_with_event_path(mfa_event: @__rails_base_mfa_event.event, type: RailsBase::Mfa::SMS)
46
+ end
47
+ end
48
+
49
+ # GET mfa/validate/sms/:mfa_event
50
+ def sms_event_input
51
+ if @__rails_base_mfa_event.phone_number
52
+ phone_number = @__rails_base_mfa_event.phone_number
53
+ else
54
+ phone_number = User.find(@__rails_base_mfa_event.user_id).phone_number
55
+ end
56
+
57
+ @masked_phone = User.masked_number(phone_number)
58
+ end
59
+
60
+ # POST mfa/validate/sms/:mfa_event
61
+ def sms_event
62
+ mfa_validity = RailsBase::Mfa::Sms::Validate.call(mfa_event: @__rails_base_mfa_event, params: params, session_mfa_user_id: @__rails_base_mfa_event.user_id)
63
+ if mfa_validity.failure?
64
+ redirect_to(mfa_validity.redirect_url, alert: mfa_validity.message)
65
+ return
66
+ end
67
+
68
+ mfa_validity.user.set_last_mfa_sms_login!
69
+ if @__rails_base_mfa_event.sign_in_user
70
+ logger.info("Logging User in")
71
+ sign_in(mfa_validity.user)
72
+ end
73
+
74
+ if @__rails_base_mfa_event.set_satiated_on_success
75
+ logger.info("Satiating MFA Event")
76
+ @__rails_base_mfa_event.satiated!
77
+ end
78
+
79
+ add_mfa_event_to_session(event: @__rails_base_mfa_event)
80
+ redirect_to @__rails_base_mfa_event.redirect, notice: @__rails_base_mfa_event.flash_notice
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsBase::Mfa::Validate
4
+ class TotpController < RailsBaseApplicationController
5
+ before_action :validate_mfa_with_event!
6
+
7
+ # GET mfa/validate/totp/:event
8
+ def totp_event_input; end
9
+
10
+ # POST mfa/validate/totp/:event
11
+ def totp_event
12
+ user = User.find(@__rails_base_mfa_event.user_id)
13
+ mfa_validity = ::RailsBase::Mfa::Totp::ValidateCode.(user: user, otp_code: params[:totp_code])
14
+ if mfa_validity.failure?
15
+ redirect_to(RailsBase.url_routes.mfa_with_event_path(mfa_event: @__rails_base_mfa_event.event, type: RailsBase::Mfa::OTP), alert: mfa_validity.message)
16
+ return
17
+ end
18
+
19
+ user.set_last_mfa_otp_login!
20
+
21
+ if @__rails_base_mfa_event.sign_in_user
22
+ logger.info("Logging User in")
23
+ sign_in(mfa_validity.user)
24
+ end
25
+
26
+ if @__rails_base_mfa_event.set_satiated_on_success
27
+ logger.info("Satiating MFA Event")
28
+ @__rails_base_mfa_event.satiated!
29
+ end
30
+
31
+ add_mfa_event_to_session(event: @__rails_base_mfa_event)
32
+ redirect_to @__rails_base_mfa_event.redirect, notice: @__rails_base_mfa_event.flash_notice
33
+ end
34
+ end
35
+ end
@@ -2,13 +2,11 @@ module RailsBase
2
2
  class SecondaryAuthenticationController < RailsBaseApplicationController
3
3
  before_action :authenticate_user!, only: [:remove_phone_mfa, :confirm_phone_registration]
4
4
 
5
- before_action :validate_token!, only: [:resend_email, :wait, :confirm_phone_registration]
6
-
7
- before_action :json_validate_current_user!, only: [:phone_registration]
5
+ before_action :validate_mfa_token!, only: [:resend_email, :wait, :confirm_phone_registration]
8
6
 
9
7
  # GET auth/wait
10
8
  def static
11
- return unless validate_token!(purpose: Authentication::Constants::SSOVE_PURPOSE)
9
+ return unless validate_mfa_token!(purpose: Authentication::Constants::SSOVE_PURPOSE)
12
10
 
13
11
  if flash[:notice].nil? && flash[:alert].nil?
14
12
  flash[:notice] = Authentication::Constants::STATIC_WAIT_FLASH
@@ -18,11 +16,6 @@ module RailsBase
18
16
  def remove_me
19
17
  end
20
18
 
21
- def testing_route
22
- Rails.logger.error("This will cause an error to be thrown")
23
- raise ArgumentError, 'Boo'
24
- end
25
-
26
19
  # POST auth/resend_email
27
20
  def resend_email
28
21
  user = User.find @token_verifier.user_id
@@ -51,7 +44,7 @@ module RailsBase
51
44
 
52
45
  # GET auth/login
53
46
  def after_email_login_session_new
54
- return unless validate_token!(purpose: Authentication::Constants::SSOVE_PURPOSE)
47
+ return unless validate_mfa_token!(purpose: Authentication::Constants::SSOVE_PURPOSE)
55
48
 
56
49
  @user = User.new
57
50
  if flash[:alert].nil? && flash[:notice].nil?
@@ -61,7 +54,7 @@ module RailsBase
61
54
 
62
55
  # POST auth/login
63
56
  def after_email_login_session_create
64
- return unless validate_token!(purpose: Authentication::Constants::SSOVE_PURPOSE)
57
+ return unless validate_mfa_token!(purpose: Authentication::Constants::SSOVE_PURPOSE)
65
58
 
66
59
  flash[:notice] = nil
67
60
  flash[:alert] = nil
@@ -78,37 +71,6 @@ module RailsBase
78
71
  redirect_to RailsBase.url_routes.authenticated_root_path
79
72
  end
80
73
 
81
- # POST auth/phone
82
- def phone_registration
83
- result = Authentication::UpdatePhoneSendVerification.call(user: current_user, phone_number: params[:phone_number])
84
- if result.failure?
85
- render :json => { error: I18n.t('request_response.teapot.fail'), msg: result.message }.to_json, :status => 418
86
- return
87
- end
88
- session[:mfa_randomized_token] = result.mfa_randomized_token
89
-
90
- render :json => { status: :success, message: I18n.t('request_response.teapot.valid') }
91
- end
92
-
93
- # POST auth/phone/mfa
94
- def confirm_phone_registration
95
- mfa_validity = Authentication::MfaValidator.call(current_user: current_user, params: params, session_mfa_user_id: @token_verifier.user_id)
96
- if mfa_validity.failure?
97
- redirect_to RailsBase.url_routes.authenticated_root_path, alert: I18n.t('authentication.confirm_phone_registration.fail', message: mfa_validity.message)
98
- return
99
- end
100
-
101
- current_user.update!(mfa_enabled: true)
102
-
103
- redirect_to RailsBase.url_routes.authenticated_root_path, notice: I18n.t('authentication.confirm_phone_registration.valid')
104
- end
105
-
106
- # DELETE auth/phone/disable
107
- def remove_phone_mfa
108
- current_user.update!(mfa_enabled: false, last_mfa_login: nil)
109
- redirect_to RailsBase.url_routes.authenticated_root_path, notice: I18n.t('authentication.remove_phone_mfa')
110
- end
111
-
112
74
  # GET auth/email/forgot/:data
113
75
  def forgot_password
114
76
  result = Authentication::VerifyForgotPassword.call(data: params[:data])
@@ -117,52 +79,52 @@ module RailsBase
117
79
  redirect_to result.redirect_url, alert: result.message
118
80
  return
119
81
  end
120
- session[:mfa_randomized_token] = result.encrypted_val
121
- flash[:notice] =
122
- if @mfa_flow = result.mfa_flow
123
- I18n.t('authentication.forgot_password.2fa')
124
- else
125
- I18n.t('authentication.forgot_password.base')
126
- end
127
- @user = result.user
128
- @data = params[:data]
129
- end
130
82
 
131
- # POST auth/email/forgot/:data
132
- def forgot_password_with_mfa
133
- return unless validate_token!(purpose: Authentication::Constants::VFP_PURPOSE)
83
+ event = RailsBase::MfaEvent.forgot_password(user: result.user, data: params[:data])
84
+ if result.mfa_flow
85
+ flash[:notice] = "MFA required to reset password"
86
+ redirect_to(RailsBase.url_routes.mfa_with_event_path(mfa_event: event.event))
87
+ else
88
+ # Requirements to continue were satiatet..we can let the user reset their password
89
+ event.satiated!
90
+ flash[:notice] = "Datum valid. Reset your password"
91
+ redirect_to(RailsBase.url_routes.reset_password_input_path(data: params[:data]))
92
+ end
134
93
 
135
- # datum is expired because it was used with #forgot_password method
136
- # we dont care, we just want to ensure the correct user (multiple verification ways)
137
- # -- validate user by datum
138
- # -- validate user by short lived token
139
- # -- validate user by mfa_token
140
- # -- When all match by user and within the lifetime of the short lived token... we b gucci uber super secure/over engineered
141
- expired_datum = ShortLivedData.get_by_data(data: params[:data], reason: Authentication::Constants::VFP_REASON)
94
+ # Upload event to the session as a last step to ensure we capture if it was satiated or not
95
+ add_mfa_event_to_session(event:)
96
+ end
142
97
 
143
- unless expired_datum
144
- redirect_to(RailsBase.url_routes.new_user_password_path, alert: I18n.t('authentication.forgot_password_with_mfa.expired_datum'))
145
- return
146
- end
98
+ # GET auth/password/reset/:data
99
+ def reset_password_input
100
+ return unless validate_mfa_with_event!(mfa_event_name: RailsBase::MfaEvent::FORGOT_PASSWORD)
147
101
 
148
- result = Authentication::MfaValidator.call(params: params, session_mfa_user_id: @token_verifier.user_id, current_user: expired_datum.user)
149
- if result.failure?
150
- redirect_to(RailsBase.url_routes.new_user_password_path, alert: result.message)
151
- return
102
+ if @__rails_base_mfa_event.satiated?
103
+ @data = params[:data]
104
+ @user = User.find(@__rails_base_mfa_event.user_id)
105
+ else
106
+ logger.error("MFA Event was not satiated. Kicking user back to root")
107
+ clear_mfa_event_from_session!(event_name: @__rails_base_mfa_event.event)
108
+ session.clear
109
+ flash[:alert] = "Unauthorized access"
110
+ redirect_to(RailsBase.url_routes.unauthenticated_root_path)
152
111
  end
153
-
154
- @mfa_flow = false
155
- @data = params[:data]
156
- @user = result.user
157
- flash[:notice] = I18n.t('authentication.forgot_password_with_mfa.valid_mfa')
158
- render :forgot_password
159
112
  end
160
113
 
161
114
  # POST auth/email/reset/:data
162
115
  def reset_password
163
- return unless validate_token!(purpose: Authentication::Constants::VFP_PURPOSE)
116
+ return unless validate_mfa_with_event!(mfa_event_name: RailsBase::MfaEvent::FORGOT_PASSWORD)
117
+
118
+ unless @__rails_base_mfa_event.satiated?
119
+ logger.error("MFA Event was not satiated. Kicking user back to root")
120
+ clear_mfa_event_from_session!(event_name: @__rails_base_mfa_event.event)
121
+ session.clear
122
+ flash[:alert] = "Unauthorized access"
123
+ redirect_to(RailsBase.url_routes.unauthenticated_root_path)
124
+ return
125
+ end
164
126
 
165
- result = Authentication::ModifyPassword.call(password: params[:user][:password], password_confirmation: params[:user][:password_confirmation], data: params[:data], user_id: @token_verifier.user_id, flow: :forgot_password)
127
+ result = Authentication::ModifyPassword.call(password: params[:user][:password], password_confirmation: params[:user][:password_confirmation], data: params[:data], user_id: @__rails_base_mfa_event.user_id, flow: :forgot_password)
166
128
  if result.failure?
167
129
  redirect_to RailsBase.url_routes.new_user_password_path, alert: result.message
168
130
  return
@@ -202,23 +164,5 @@ module RailsBase
202
164
  flash[:notice] = I18n.t('authentication.sso_login.valid')
203
165
  redirect_to url
204
166
  end
205
-
206
- private
207
-
208
- def json_validate_current_user!
209
- return if current_user
210
-
211
- render json: { error: "Unauthorized" }.to_json, :status => 401
212
- return false
213
- end
214
-
215
- def validate_token!(purpose: Authentication::Constants::MSET_PURPOSE)
216
- @token_verifier =
217
- Authentication::SessionTokenVerifier.call(purpose: purpose, mfa_randomized_token: session[:mfa_randomized_token])
218
- return true if @token_verifier.success?
219
-
220
- redirect_to RailsBase.url_routes.new_user_session_path, alert: @token_verifier.message
221
- return false
222
- end
223
167
  end
224
168
  end