rails_base 0.75.6 → 0.80.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) 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 +16 -13
  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 +61 -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 +22 -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/mfa/sms/_login_input.html.erb +13 -0
  57. data/app/views/rails_base/shared/mfa/totp/_login_input.html.erb +22 -0
  58. data/app/views/rails_base/shared/totp/_add_authenticator.html.erb +76 -0
  59. data/app/views/rails_base/shared/totp/_add_authenticator_modal.html.erb +25 -0
  60. data/app/views/rails_base/shared/totp/_confirm_code.html.erb +31 -0
  61. data/app/views/rails_base/shared/totp/_confirm_code_ajax.html.erb +3 -0
  62. data/app/views/rails_base/shared/totp/_confirm_code_rest.html.erb +5 -0
  63. data/app/views/rails_base/shared/totp/_remove_authenticator_modal.html.erb +50 -0
  64. data/app/views/rails_base/user_settings/index.html.erb +84 -1
  65. data/config/initializers/admin_action_helper.rb +44 -8
  66. data/config/routes.rb +42 -7
  67. data/db/migrate/20240808013706_add_totp_to_users.rb +9 -0
  68. data/db/migrate/20240825012724_reconfigure_mfa_variable_names.rb +10 -0
  69. data/lib/rails_base/admin/action_helper.rb +0 -1
  70. data/lib/rails_base/admin/default_index_tile.rb +3 -3
  71. data/lib/rails_base/config.rb +26 -22
  72. data/lib/rails_base/configuration/admin.rb +5 -5
  73. data/lib/rails_base/configuration/base.rb +1 -0
  74. data/lib/rails_base/configuration/mfa.rb +27 -60
  75. data/lib/rails_base/configuration/totp.rb +82 -0
  76. data/lib/rails_base/configuration/twilio.rb +85 -0
  77. data/lib/rails_base/mfa_event.rb +186 -0
  78. data/lib/rails_base/version.rb +3 -3
  79. data/lib/rails_base.rb +1 -0
  80. data/lib/twilio_helper.rb +3 -3
  81. metadata +129 -64
  82. data/app/controllers/rails_base/mfa_auth_controller.rb +0 -50
  83. data/app/services/rails_base/authentication/mfa_set_encrypt_token.rb +0 -32
  84. data/app/services/rails_base/authentication/mfa_validator.rb +0 -88
  85. data/app/views/rails_base/mfa_auth/mfa_code.html.erb +0 -11
  86. data/app/views/rails_base/secondary_authentication/forgot_password.html.erb +0 -9
@@ -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">
@@ -19,7 +22,7 @@
19
22
  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
28
  <button class="btn btn_primary btn-block show-create-modal" type="button">Modify</button>
@@ -39,6 +42,81 @@
39
42
  </tbody>
40
43
  </table>
41
44
  </div>
45
+ <div class='row'>
46
+ <div class='col'>
47
+ <div id="advancedSecurity">
48
+ <div class="row">
49
+ <div class="col-12">
50
+ <button id="advancedSecurity-title" class="text-center btn btn-warning btn-block" type="button" id="dropdownMenuButton" aria-haspopup="true" aria-expanded="false">
51
+ MFA Options
52
+ </button>
53
+ </div>
54
+ </div>
55
+ <div class="row">
56
+ <div class="col-12">
57
+ <div id="advancedSecurity-body" class="collapseable-body">
58
+ <br>
59
+ <div class="row">
60
+ <div class="col-10 offset-1">
61
+ <% if RailsBase.config.mfa.enable? %>
62
+ <% if current_user.mfa_sms_enabled %>
63
+ <button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#modifyMfamodal">
64
+ Modify 2fa Auth
65
+ </button>
66
+ <%= render partial: 'rails_base/shared/modify_mfa_auth_modal'%>
67
+ <% else %>
68
+ <button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#enableMfamodal">
69
+ Enable 2fa Auth
70
+ </button>
71
+ <%= render partial: 'rails_base/shared/enable_mfa_auth_modal'%>
72
+ <% end %>
73
+ <% end %>
74
+ </div>
75
+ </div>
76
+ <div class="row"><div class="col-6 offset-3">
77
+ <hr>
78
+ </div></div>
79
+
80
+ <div class="row">
81
+ <div class="col-10 offset-1">
82
+ <% if RailsBase.config.totp.enable? %>
83
+ <% if current_user.mfa_otp_enabled %>
84
+ <div class="row">
85
+ <div class="col-12">
86
+ <button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpDisableModal">
87
+ Disable One Time Password Auth
88
+ </button>
89
+ </div>
90
+ </div>
91
+ <%= render partial: 'rails_base/shared/totp/remove_authenticator_modal', locals: { type: @type, endpoint: @endpoint } %>
92
+ <br>
93
+ <div class="row">
94
+ <div class="col-12">
95
+ <button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpEnableModal">
96
+ Add One Time Password Auth
97
+ </button>
98
+ </div>
99
+ </div>
100
+ <% else %>
101
+ <button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpEnableModal">
102
+ Enable One Time Password Auth
103
+ </button>
104
+ <% end %>
105
+ <%= render partial: 'rails_base/shared/totp/add_authenticator_modal', locals: { type: @type, endpoint: @endpoint } %>
106
+ <% end %>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ <%= 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 } } %>
115
+
116
+ </div>
117
+ </div>
118
+ </br>
119
+ <hr>
42
120
  <div class="row" style="margin-top: 50px">
43
121
  <button type="button" class="btn btn_danger btn-block" data-toggle="modal" data-target="#destroyUserModal">
44
122
  Destroy account
@@ -52,3 +130,8 @@
52
130
  <%= render 'modify_password' %>
53
131
  <%= render 'destroy_user' %>
54
132
 
133
+ <script type="text/javascript">
134
+ $(document).ready(function(){
135
+ _railsBase_goToStandardizedCollapse("openmfa", `#advancedSecurity-body`, `<%= function_name %>`)
136
+ })
137
+ </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
@@ -0,0 +1,85 @@
1
+ require 'rails_base/configuration/base'
2
+
3
+ module RailsBase
4
+ module Configuration
5
+ class Twilio < Base
6
+
7
+ MFA_TYPE_OPTIONS = [ DEFAULT_TYPE = :totp, :twilio ]
8
+ MFA_MIN_LENGTH = 4
9
+ MFA_MAX_LENGTH = 8
10
+ DEFAULT_VALUES = {
11
+ enable: {
12
+ type: :boolean,
13
+ default: ENV.fetch('MFA_ENABLE', 'true')=='true',
14
+ description: 'Allow twilio as an MFA option.',
15
+ },
16
+ mfa_length: {
17
+ type: :integer,
18
+ default: 5,
19
+ custom: ->(val) { val > MFA_MIN_LENGTH && val < MFA_MAX_LENGTH },
20
+ msg: "Must be an integer greater than #{MFA_MIN_LENGTH} and less than #{MFA_MAX_LENGTH}",
21
+ description: 'Length of MFA verification',
22
+ },
23
+ twilio_sid: {
24
+ type: :string,
25
+ default: ENV.fetch('TWILIO_ACCOUNT_SID',''),
26
+ secret: true,
27
+ description: 'Twilio SID',
28
+ },
29
+ twilio_auth_token: {
30
+ type: :string,
31
+ default: ENV.fetch('TWILIO_AUTH_TOKEN', ''),
32
+ secret: true,
33
+ description: 'Twilio Auth Token',
34
+ },
35
+ twilio_from_number: {
36
+ type: :string,
37
+ default: ENV.fetch('TWILIO_FROM_NUMBER', ''),
38
+ description: 'Number that we send MFA\'s From',
39
+ },
40
+ twilio_velocity_max: {
41
+ type: :integer,
42
+ default: ENV.fetch('TWILIO_VELOCITY_MAX', 5).to_i,
43
+ description: 'Max number of SMS we send to a user in a sliding window',
44
+
45
+ },
46
+ twilio_velocity_max_in_frame: {
47
+ type: :duration,
48
+ default: ENV.fetch('TWILIO_VELOCITY_MAX_IN_FRAME', 1).to_i.hours,
49
+ description: 'Sliding window for twilio_velocity_max',
50
+ },
51
+ twilio_velocity_frame: {
52
+ type: :duration,
53
+ default: ENV.fetch('TWILIO_VELOCITY_FRAME', 5).to_i.hours,
54
+ description: 'Debug purposes. How long to keep admin_velocity_max attempts',
55
+ },
56
+ active_job_queue: {
57
+ type: :string,
58
+ default: 'twilio_sms',
59
+ description: 'The active job queue to send twilio messages from. Ensure that adapter is bound to the queue',
60
+ }
61
+ }
62
+ attr_accessor *DEFAULT_VALUES.keys
63
+
64
+ private
65
+
66
+ def custom_validations
67
+ enforce_twilio!
68
+ end
69
+
70
+ def enforce_twilio!
71
+ return unless enable == true
72
+
73
+ return if twilio_sid.present? &&
74
+ twilio_auth_token.present? &&
75
+ twilio_from_number.present?
76
+
77
+ raise InvalidConfiguration, "twilio_sid twilio_auth_token twilio_from_number need to be present when `mfa.enabled`"
78
+ end
79
+
80
+ def default_values
81
+ DEFAULT_VALUES
82
+ end
83
+ end
84
+ end
85
+ end