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
@@ -0,0 +1,50 @@
1
+ <div class="modal fade" id="totpDisableModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
2
+ <div class="modal-dialog modal-lg" role="document">
3
+ <div class="modal-content">
4
+ <div class="modal-header">
5
+ <h3 class="modal-title" id="exampleModalLabel">Remove TOTP as Multi Factor Authenticator</h3>
6
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
7
+ <span aria-hidden="true">&times;</span>
8
+ </button>
9
+ </div>
10
+ <div class="modal-body">
11
+ <div class="text-center">
12
+ Time based One time Password is one of the the most Secure MFA option that <%= RailsBase.app_name %> provides. If you would like to remove this MFA option, please enter your credentials below and confirm. Upon Successful request, TOTP is no longer active on your account
13
+ </div>
14
+ <hr>
15
+ <%= form_with(url: RailsBase.url_routes.totp_register_delete_path, method: :delete) do |form| %>
16
+ <div id="confirmTotpAuthenticationCode">
17
+ <div class="totpInput">
18
+ <div class="input-group input-group-lg">
19
+ <div class="input-group-prepend">
20
+ <span class="input-group-text" id="totpInput-Prepend">Password</span>
21
+ </div>
22
+ <%= form.password_field :password, id: "totpRemoval-password", class:"form-control" %>
23
+ </div>
24
+ </div>
25
+ <br>
26
+ <div class="totpInput">
27
+ <div class="input-group input-group-lg">
28
+ <div class="input-group-prepend">
29
+ <span class="input-group-text" id="totpInput-Prepend">TOTP</span>
30
+ </div>
31
+ <%= form.telephone_field :totp_code, id: "totpRemoval-code", class:"form-control", placeholder: "One Time Password Value" %>
32
+ </div>
33
+ </div>
34
+ <br>
35
+ <div class="totpSubmit">
36
+ <div class="row">
37
+ <div class="col-md-6 offset-md-3">
38
+ <%= form.submit "Confirm TOTP Removal", class: " btn btn_danger btn-block" %>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ <% end %>
44
+ </div>
45
+ <div class="modal-footer">
46
+ <button type="button" class="mr-auto btn btn_secondary" data-dismiss="modal">Close</button>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
@@ -1,3 +1,6 @@
1
+ <%
2
+ function_name = "advancedSecurityCollapse"
3
+ %>
1
4
  <div class="row">
2
5
  <div class="col-md-10 offset-md-1">
3
6
  <div class="row">
@@ -16,13 +19,24 @@
16
19
  </tr>
17
20
  <tr>
18
21
  <th scope="col" class='text-right' style="width: 40%">
19
- MFA enabled?
22
+ SMS MFA enabled?
20
23
  </th>
21
24
  <td style="width: 40%">
22
- <%= current_user.mfa_enabled %>
25
+ <%= current_user.mfa_sms_enabled %>
23
26
  </td>
24
27
  <td style="width: 20%">
25
- <button class="btn btn_primary btn-block show-create-modal" type="button">Modify</button>
28
+ <button onclick="advancedSecurityCollapse_collapse_open()" class="btn btn_primary btn-block" type="button">Modify</button>
29
+ </td>
30
+ </tr>
31
+ <tr>
32
+ <th scope="col" class='text-right' style="width: 40%">
33
+ TOTP MFA enabled?
34
+ </th>
35
+ <td style="width: 40%">
36
+ <%= current_user.mfa_otp_enabled %>
37
+ </td>
38
+ <td style="width: 20%">
39
+ <button onclick="advancedSecurityCollapse_collapse_open()" class="btn btn_primary btn-block" type="button">Modify</button>
26
40
  </td>
27
41
  </tr>
28
42
  <tr>
@@ -39,6 +53,86 @@
39
53
  </tbody>
40
54
  </table>
41
55
  </div>
56
+ <div class='row'>
57
+ <div class='col'>
58
+ <div id="advancedSecurity">
59
+ <div class="row">
60
+ <div class="col-12">
61
+ <button id="advancedSecurity-title" class="text-center btn btn-warning btn-block" type="button" id="dropdownMenuButton" aria-haspopup="true" aria-expanded="false">
62
+ MFA Options
63
+ </button>
64
+ </div>
65
+ </div>
66
+ <div class="row">
67
+ <div class="col-12">
68
+ <div id="advancedSecurity-body" class="collapseable-body">
69
+ <br>
70
+ <div class="row">
71
+ <div class="col-10 offset-1">
72
+ <% if RailsBase.config.mfa.enable? %>
73
+ <% if current_user.mfa_sms_enabled %>
74
+ <button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#modifyMfamodal">
75
+ Modify 2fa Auth
76
+ </button>
77
+ <%= render partial: 'rails_base/shared/modify_mfa_auth_modal'%>
78
+ <% else %>
79
+ <button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#enableMfamodal">
80
+ Enable 2fa Auth
81
+ </button>
82
+ <%= render partial: 'rails_base/shared/enable_mfa_auth_modal'%>
83
+ <% end %>
84
+ <% end %>
85
+ </div>
86
+ </div>
87
+ <div class="row"><div class="col-6 offset-3">
88
+ <hr>
89
+ </div></div>
90
+
91
+ <div class="row">
92
+ <div class="col-10 offset-1">
93
+ <% if RailsBase.config.totp.enable? %>
94
+ <% if current_user.mfa_otp_enabled %>
95
+ <div class="row">
96
+ <div class="col-12">
97
+ <button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpDisableModal">
98
+ Disable One Time Password Auth
99
+ </button>
100
+ </div>
101
+ </div>
102
+ <%= render partial: 'rails_base/shared/totp/remove_authenticator_modal', locals: { type: @type, endpoint: @endpoint } %>
103
+ <br>
104
+ <div class="row">
105
+ <div class="col-12">
106
+ <button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpEnableModal" style="display: none;">
107
+ <!--
108
+ This is currently disabled.
109
+ Steps to re-enabld
110
+ - Enforce TOTP code is entered before showing totp secret
111
+ -->
112
+ Add One Time Password Auth
113
+ </button>
114
+ </div>
115
+ </div>
116
+ <% else %>
117
+ <button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpEnableModal">
118
+ Enable One Time Password Auth
119
+ </button>
120
+ <%= render partial: 'rails_base/shared/totp/add_authenticator_modal', locals: { type: @type, endpoint: @endpoint } %>
121
+ <% end %>
122
+ <% end %>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+
130
+ <%= render partial: 'rails_base/shared/standardized_collapse', locals: { options: { id: "advancedSecurity", title_id: "advancedSecurity-title", body_id: "advancedSecurity-body", default_closed: true, function_name: function_name } } %>
131
+
132
+ </div>
133
+ </div>
134
+ </br>
135
+ <hr>
42
136
  <div class="row" style="margin-top: 50px">
43
137
  <button type="button" class="btn btn_danger btn-block" data-toggle="modal" data-target="#destroyUserModal">
44
138
  Destroy account
@@ -52,3 +146,8 @@
52
146
  <%= render 'modify_password' %>
53
147
  <%= render 'destroy_user' %>
54
148
 
149
+ <script type="text/javascript">
150
+ $(document).ready(function(){
151
+ _railsBase_goToStandardizedCollapse("openmfa", `#advancedSecurity-body`, `<%= function_name %>`)
152
+ })
153
+ </script>
@@ -59,21 +59,57 @@ params = {
59
59
  }
60
60
  RailsBase::Admin::ActionHelper.new(**params).add!
61
61
 
62
+ proc = Proc.new do |req, params, admin_user, user, title, struct|
63
+ actions_mapping = {
64
+ sms_registration: 'MFA Attempt to Register new SMS number',
65
+ sms_confirmation: 'MFA Confirm new SMS number',
66
+ sms_removal: 'MFA SMS removed',
67
+ }
62
68
 
69
+ if actions_mapping.keys.include?(params[:action].to_sym)
70
+ {
71
+ admin_user: admin_user,
72
+ user: user,
73
+ action: actions_mapping[params[:action].to_sym] ,
74
+ original_attribute: struct&.original_attribute,
75
+ new_attribute: struct&.new_attribute
76
+ }
77
+ else
78
+ nil
79
+ end
80
+ end
81
+ # All SMS register actions
63
82
  params = {
64
- proc: nil,
65
- title: 'Removed Phone from account',
66
- controller: RailsBase::SecondaryAuthenticationController,
67
- action: 'remove_phone_mfa',
83
+ proc: proc,
84
+ controller: RailsBase::Mfa::Register::SmsController,
68
85
  default: true
69
86
  }
70
87
  RailsBase::Admin::ActionHelper.new(**params).add!
71
88
 
89
+
90
+ proc = Proc.new do |req, params, admin_user, user, title, struct|
91
+ actions_mapping = {
92
+ totp_remove: 'MFA TOTP remove',
93
+ totp_secret: 'MFA TOTP Attempt to add new Authenticator',
94
+ totp_validate: 'MFA TOTP Authenticator added',
95
+ }
96
+
97
+ if actions_mapping.keys.include?(params[:action].to_sym)
98
+ {
99
+ admin_user: admin_user,
100
+ user: user,
101
+ action: actions_mapping[params[:action].to_sym] ,
102
+ original_attribute: struct&.original_attribute,
103
+ new_attribute: struct&.new_attribute
104
+ }
105
+ else
106
+ nil
107
+ end
108
+ end
109
+ # All TOTP register actions
72
110
  params = {
73
- proc: nil,
74
- title: 'Setting Phone on account',
75
- controller: RailsBase::SecondaryAuthenticationController,
76
- action: 'phone_registration',
111
+ proc: proc,
112
+ controller: RailsBase::Mfa::Register::TotpController,
77
113
  default: true
78
114
  }
79
115
  RailsBase::Admin::ActionHelper.new(**params).add!
data/config/routes.rb CHANGED
@@ -64,18 +64,53 @@ Rails.application.routes.draw do
64
64
  get 'auth/login', to: 'rails_base/secondary_authentication#after_email_login_session_new', as: :login_after_email
65
65
  post 'auth/login', to: 'rails_base/secondary_authentication#after_email_login_session_create', as: :login_after_email_session_create
66
66
  post 'auth/resend_email', to: 'rails_base/secondary_authentication#resend_email', as: :resend_email_verification
67
- delete 'auth/phone/mfa', to: 'rails_base/secondary_authentication#remove_phone_mfa', as: :remove_phone_registration_mfa
68
67
  get 'auth/password/forgot/:data', to: 'rails_base/secondary_authentication#forgot_password', as: :forgot_password_auth
69
- post 'auth/password/forgot/:data', to: 'rails_base/secondary_authentication#forgot_password_with_mfa', as: :forgot_password_with_mfa_auth
68
+ get 'auth/password/reset/:data', to: 'rails_base/secondary_authentication#reset_password_input', as: :reset_password_input
70
69
  post 'auth/password/reset/:data', to: 'rails_base/secondary_authentication#reset_password', as: :reset_password_auth
71
70
 
72
71
  constraints(->(_req) { RailsBase.config.mfa.enable? }) do
73
- get 'mfa_verify', to: 'rails_base/mfa_auth#mfa_code', as: :mfa_code
74
- post 'mfa_verify', to: 'rails_base/mfa_auth#mfa_code_verify', as: :mfa_code_verify
75
- post 'resend_mfa', to: 'rails_base/mfa_auth#resend_mfa', as: :resend_mfa
72
+ scope "mfa/register" do
73
+ mfa_base_register = "rails_base/mfa/register"
74
+ constraints(->(_req) { RailsBase.config.totp.enable? }) do
75
+ scope :totp do
76
+ delete "/", to: "#{mfa_base_register}/totp#totp_remove", as: :totp_register_delete
77
+ post "/", to: "#{mfa_base_register}/totp#totp_secret", as: :totp_register_secret
78
+ post "validate", to: "#{mfa_base_register}/totp#totp_validate", as: :totp_register_validate
79
+ end
80
+ end
81
+
82
+ constraints(->(_req) { RailsBase.config.twilio.enable? }) do
83
+ scope :sms do
84
+ delete "/", to: "#{mfa_base_register}/sms#sms_removal", as: :remove_phone_registration_mfa
85
+ post "/", to: "#{mfa_base_register}/sms#sms_registration", as: :phone_registration
86
+ post "validate", to: "#{mfa_base_register}/sms#sms_confirmation", as: :phone_registration_mfa_code
87
+ end
88
+ end
89
+ end
90
+
91
+ scope "mfa/validate" do
92
+ mfa_base_validate = "rails_base/mfa/validate"
93
+ constraints(->(_req) { RailsBase.config.totp.enable? }) do
94
+ scope :totp do
95
+ get ":mfa_event", to: "#{mfa_base_validate}/totp#totp_event_input", as: :totp_validate_event_input
96
+ post ":mfa_event", to: "#{mfa_base_validate}/totp#totp_event", as: :totp_validate_event
97
+ end
98
+ end
99
+
100
+ constraints(->(_req) { RailsBase.config.twilio.enable? }) do
101
+ scope :sms do
102
+ post ":mfa_event/send", to: "#{mfa_base_validate}/sms#sms_event_send", as: :sms_validate_send_event
103
+ post ":mfa_event", to: "#{mfa_base_validate}/sms#sms_event", as: :sms_validate_event
104
+ get ":mfa_event", to: "#{mfa_base_validate}/sms#sms_event_input", as: :sms_validate_event_input
105
+ end
106
+ end
107
+ end
108
+ end
76
109
 
77
- post 'auth/phone', to: 'rails_base/secondary_authentication#phone_registration', as: :phone_registration
78
- post 'auth/phone/mfa', to: 'rails_base/secondary_authentication#confirm_phone_registration', as: :phone_registration_mfa_code
110
+ # These routes need to be outside of the scope so that they can be called independently
111
+ # The code itself will evaluate if it should run or not
112
+ scope "mfa/evaluation" do
113
+ get ':mfa_event', to: 'rails_base/mfa/evaluation#mfa_with_event', as: :mfa_with_event
79
114
  end
80
115
 
81
116
  ################################
@@ -0,0 +1,9 @@
1
+ class AddTotpToUsers < ActiveRecord::Migration[6.1]
2
+ def change
3
+ add_column :users, :otp_secret, :string
4
+ add_column :users, :temp_otp_secret, :string
5
+ add_column :users, :consumed_timestep, :integer
6
+ add_column :users, :mfa_otp_enabled, :boolean, default: false
7
+ add_column :users, :otp_backup_codes, :text
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class ReconfigureMfaVariableNames < ActiveRecord::Migration[6.1]
2
+ def change
3
+ ## Rename original MFA columns to SMS specific columns
4
+ rename_column :users, :mfa_enabled, :mfa_sms_enabled
5
+ rename_column :users, :last_mfa_login, :last_mfa_sms_login
6
+
7
+ # Add new Column for Last time logged in via OTP generated code
8
+ add_column :users, :last_mfa_otp_login, :datetime
9
+ end
10
+ end
@@ -85,7 +85,6 @@ module RailsBase::Admin
85
85
  end
86
86
 
87
87
  def call(req:, params:, admin_user:, user:, struct: nil)
88
- # byebug
89
88
  if proc
90
89
  action_params = proc.call(req, params, admin_user, user, title, struct)
91
90
  return if action_params.nil?
@@ -86,11 +86,11 @@ RailsBase::Admin::IndexTile.add(instance)
86
86
  # Mfa Enabled Validated Tile
87
87
  params = {
88
88
  type: :toggle,
89
- value: ->(user) { user.mfa_enabled },
89
+ value: ->(user) { user.mfa_sms_enabled },
90
90
  on: 'Enabled',
91
91
  off: 'Disabled',
92
- name: 'mfa_enabled', # name to be amended to html id
93
- col_name: 'MFA Enabled?', # Expected to be the column header name
92
+ name: 'mfa_sms_enabled', # name to be amended to html id
93
+ col_name: 'MFA SMS Enabled?', # Expected to be the column header name
94
94
  disabled: -> (user, admin_user) { !RailsBase.config.admin.mfa_tile_users.call(admin_user) },
95
95
  disabled_msg: -> (user, admin_user) { 'Your admin user does not have permissions' },
96
96
  min_width: 220,
@@ -1,36 +1,40 @@
1
- require 'singleton'
2
- require 'rails_base/configuration/active_job'
3
- require 'rails_base/configuration/admin'
4
- require 'rails_base/configuration/app'
5
- require 'rails_base/configuration/appearance'
6
- require 'rails_base/configuration/authentication'
7
- require 'rails_base/configuration/exceptions_app'
8
- require 'rails_base/configuration/login_behavior'
9
- require 'rails_base/configuration/mailer'
10
- require 'rails_base/configuration/mfa'
11
- require 'rails_base/configuration/owner'
12
- require 'rails_base/configuration/redis'
13
- require 'rails_base/configuration/templates'
14
- require 'rails_base/configuration/user'
1
+ require "singleton"
2
+ require "rails_base/configuration/active_job"
3
+ require "rails_base/configuration/admin"
4
+ require "rails_base/configuration/app"
5
+ require "rails_base/configuration/appearance"
6
+ require "rails_base/configuration/authentication"
7
+ require "rails_base/configuration/exceptions_app"
8
+ require "rails_base/configuration/login_behavior"
9
+ require "rails_base/configuration/mailer"
10
+ require "rails_base/configuration/mfa"
11
+ require "rails_base/configuration/owner"
12
+ require "rails_base/configuration/redis"
13
+ require "rails_base/configuration/templates"
14
+ require "rails_base/configuration/totp"
15
+ require "rails_base/configuration/twilio"
16
+ require "rails_base/configuration/user"
15
17
 
16
18
  module RailsBase
17
19
  class Config
18
20
  include Singleton
19
21
 
20
22
  VARIABLES = {
23
+ active_job: nil,
21
24
  admin: nil,
22
- mfa: nil,
23
- auth: :authentication,
24
- redis: nil,
25
- owner: nil,
26
- mailer: nil,
27
- exceptions_app: nil,
28
25
  app: nil,
29
26
  appearance: nil,
30
- user: nil,
31
- active_job: nil,
27
+ auth: :authentication,
28
+ exceptions_app: nil,
32
29
  login_behavior: nil,
30
+ mailer: nil,
31
+ mfa: nil,
32
+ owner: nil,
33
+ redis: nil,
33
34
  templates: nil,
35
+ totp: nil,
36
+ twilio: nil,
37
+ user: nil,
34
38
  }
35
39
  attr_reader *VARIABLES.keys
36
40
 
@@ -44,13 +44,13 @@ module RailsBase
44
44
  proc: ->(user, admin_user) { user.email_validated? }
45
45
  }
46
46
 
47
- DEFAULT_MFA_ENABLED = {
48
- filter: 'MFA Enabled',
49
- id: 'mfa_enabled',
50
- proc: ->(user, admin_user) { user.mfa_enabled? }
47
+ DEFAULT_MFA_SMS_ENABLED = {
48
+ filter: 'MFA SMS Enabled',
49
+ id: 'mfa_sms_enabled',
50
+ proc: ->(user, admin_user) { user.mfa_sms_enabled? }
51
51
  }
52
52
 
53
- DEFAULT_PAGE_FILTER = [DEFAULT_ADMIN_TYPE, DEFAULT_ADMIN_SELF, DEFAULT_ADMIN_ACTIVE, DEFAULT_EMAIL_VALIDATED, DEFAULT_MFA_ENABLED].flatten
53
+ DEFAULT_PAGE_FILTER = [DEFAULT_ADMIN_TYPE, DEFAULT_ADMIN_SELF, DEFAULT_ADMIN_ACTIVE, DEFAULT_EMAIL_VALIDATED, DEFAULT_MFA_SMS_ENABLED].flatten
54
54
  DEFAULT_VALUES = {
55
55
  enable: {
56
56
  type: :boolean,
@@ -22,6 +22,7 @@ module RailsBase
22
22
  duration: -> (val) { [ActiveSupport::Duration].include?(val.class) },
23
23
  hash: -> (val) { [Hash].include?(val.class) },
24
24
  integer: -> (val) { [Integer].include?(val.class) },
25
+ integer_nil: -> (val) { [Integer, NilClass].include?(val.class) },
25
26
  klass: -> (_val) { true },
26
27
  path: -> (val) { [Pathname].include?(val.class) },
27
28
  proc: -> (val) { [Proc].include?(val.class) },
@@ -3,82 +3,49 @@ require 'rails_base/configuration/base'
3
3
  module RailsBase
4
4
  module Configuration
5
5
  class Mfa < Base
6
- MFA_MIN_LENGTH = 4
7
- MFA_MAX_LENGTH = 8
6
+ MFA_TYPE_OPTIONS = [ DEFAULT_TYPE = :totp, :twilio ]
8
7
  DEFAULT_VALUES = {
9
8
  enable: {
10
9
  type: :boolean,
11
10
  default: ENV.fetch('MFA_ENABLE', 'true')=='true',
12
- description: 'Enable MFA and SMS verification. When not enabled, there are some interesting consequences',
11
+ description: 'Enable MFA with SMS or TOTP verification. When not enabled, there are some interesting consequences',
13
12
  },
14
- mfa_length: {
15
- type: :integer,
16
- default: 5,
17
- custom: ->(val) { val > MFA_MIN_LENGTH && val < MFA_MAX_LENGTH },
18
- msg: "Must be an integer greater than #{MFA_MIN_LENGTH} and less than #{MFA_MAX_LENGTH}",
19
- description: 'Length of MFA verification',
20
- },
21
- twilio_sid: {
22
- type: :string,
23
- default: ENV.fetch('TWILIO_ACCOUNT_SID',''),
24
- secret: true,
25
- description: 'Twilio SID',
13
+ enable_twilio: {
14
+ type: :boolean,
15
+ default: true,
16
+ description: 'Add Twilio as an MFA option.',
26
17
  },
27
- twilio_auth_token: {
28
- type: :string,
29
- default: ENV.fetch('TWILIO_AUTH_TOKEN', ''),
30
- secret: true,
31
- description: 'Twilio Auth Token',
18
+ enable_totp: {
19
+ type: :boolean,
20
+ default: true,
21
+ description: 'Add TOTP as an MFA option.',
32
22
  },
33
- twilio_from_number: {
34
- type: :string,
35
- default: ENV.fetch('TWILIO_FROM_NUMBER', ''),
36
- description: 'Number that we send MFA\'s From',
23
+ max_attempts_before_password_expire: {
24
+ type: :integer,
25
+ default: 5,
26
+ description: 'Max MFA attempts before password expires and password must get re-entered',
37
27
  },
38
- twilio_velocity_max: {
28
+ max_password_expires_before_account_locked: {
39
29
  type: :integer,
40
- default: ENV.fetch('TWILIO_VELOCITY_MAX', 5).to_i,
41
- description: 'Max number of SMS we send to a user in a sliding window',
42
-
30
+ default: 5,
31
+ description: 'Max number of password expires before account is locked',
43
32
  },
44
- twilio_velocity_max_in_frame: {
45
- type: :duration,
46
- default: ENV.fetch('TWILIO_VELOCITY_MAX_IN_FRAME', 1).to_i.hours,
47
- description: 'Sliding window for twilio_velocity_max',
33
+ reauth_strategy: {
34
+ type: :klass,
35
+ default: -> (_val) { RailsBase::Mfa::Strategy::EveryRequest },
36
+ custom: ->(val) { (Proc === val ? val.call(nil) : val).ancestors.include?(RailsBase::Mfa::Strategy::Base) },
37
+ msg: "Invalid ReAuth Strategy. Provided class must be descendent of RailsBase::Mfa::Strategy::Base",
38
+ description: "Value is expected to be a descendent of RailsBase::Mfa::Strategy::Base. It can be lazily loaded via a proc",
39
+ on_assignment: ->(val, instance) { instance.reauth_strategy = (Proc === val ? val.call(nil) : val) },
48
40
  },
49
- twilio_velocity_frame: {
41
+ reauth_duration: {
50
42
  type: :duration,
51
- default: ENV.fetch('TWILIO_VELOCITY_FRAME', 5).to_i.hours,
52
- description: 'Debug purposes. How long to keep admin_velocity_max attempts',
53
- },
54
- active_job_queue: {
55
- type: :string,
56
- default: 'twilio_sms',
57
- description: 'The active job queue to send twilio messages from. Ensure that adapter is bound to the queue',
43
+ default: 2.days,
44
+ description: "When `reauth_strategy` is `time_based`, this value is the max time before MFA is required",
58
45
  }
59
46
  }
60
47
 
61
48
  attr_accessor *DEFAULT_VALUES.keys
62
-
63
- private
64
-
65
- def custom_validations
66
- enforce_twilio!
67
- end
68
-
69
- def enforce_twilio!
70
- return unless enable == true
71
-
72
- return if twilio_sid.present? &&
73
- twilio_auth_token.present? &&
74
- twilio_from_number.present?
75
-
76
- raise InvalidConfiguration, "twilio_sid twilio_auth_token twilio_from_number need to be present when `mfa.enabled`"
77
- end
78
-
79
- def default_values
80
- DEFAULT_VALUES
81
- end
82
49
  end
83
50
  end
84
51
  end
@@ -0,0 +1,82 @@
1
+ require 'rails_base/configuration/base'
2
+
3
+ module RailsBase
4
+ module Configuration
5
+ class Totp < Base
6
+
7
+ DEFAULT_VALUES = {
8
+ enable: {
9
+ type: :boolean,
10
+ default: true,
11
+ description: "TOTP must be explicitly enabled per application"
12
+ },
13
+ secret_code_length: {
14
+ type: :integer,
15
+ default: 32,
16
+ description: "The length of the secret to generate an OTP"
17
+ },
18
+ backup_code_length: {
19
+ type: :integer,
20
+ default: 64,
21
+ description: "The length of each backup code provided"
22
+ },
23
+ backup_code_count: {
24
+ type: :integer,
25
+ default: 10,
26
+ description: "The number of Backup codes generated if TOTP is cannot be solved.",
27
+ },
28
+ allowed_drift: {
29
+ type: :integer,
30
+ default: 30,
31
+ description: "The allowed drift around the current timestamp.",
32
+ },
33
+ allowed_drift_behind:{
34
+ type: :integer_nil,
35
+ default: nil,
36
+ description: "Allowed drift behind current timestamp. Takes precendence over allowed_drift",
37
+ },
38
+ allowed_drift_ahead:{
39
+ type: :integer_nil,
40
+ default: nil,
41
+ description: "Allowed drift ahead current timestamp. Takes precendence over allowed_drift",
42
+ },
43
+ velocity_max: {
44
+ type: :integer,
45
+ default: 3,
46
+ description: 'Max number of TOTP we allow a user to attempt in a sliding window',
47
+ },
48
+ velocity_max_in_frame: {
49
+ type: :duration,
50
+ default: 60.seconds,
51
+ description: 'Sliding window for velocity_max',
52
+ },
53
+ velocity_frame: {
54
+ type: :duration,
55
+ default: 300.seconds,
56
+ description: 'Debug purposes. How long to keep velocity_max attempts',
57
+ },
58
+ }
59
+ attr_accessor *DEFAULT_VALUES.keys
60
+
61
+ private
62
+
63
+ def custom_validations
64
+ if velocity_max_in_frame < max_behind || velocity_max_in_frame < max_ahead
65
+ raise ArgumentError, "totp.velocity_max_in_frame must be greater than the allowed_drift"
66
+ end
67
+
68
+ if velocity_frame < velocity_max_in_frame
69
+ raise ArgumentError, "totp.velocity_frame must be greater than totp.velocity_max_in_frame"
70
+ end
71
+ end
72
+
73
+ def max_behind
74
+ allowed_drift_ahead || allowed_drift
75
+ end
76
+
77
+ def max_ahead
78
+ allowed_drift_behind || allowed_drift
79
+ end
80
+ end
81
+ end
82
+ end