rails_base 0.75.5 → 0.80.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 +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/_standardized_collapse.html.erb +28 -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 +84 -1
  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/appearance.rb +0 -2
  75. data/lib/rails_base/configuration/base.rb +1 -0
  76. data/lib/rails_base/configuration/mfa.rb +27 -60
  77. data/lib/rails_base/configuration/totp.rb +82 -0
  78. data/lib/rails_base/configuration/twilio.rb +85 -0
  79. data/lib/rails_base/mfa_event.rb +186 -0
  80. data/lib/rails_base/version.rb +3 -3
  81. data/lib/rails_base.rb +1 -0
  82. data/lib/twilio_helper.rb +3 -3
  83. metadata +129 -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,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
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsBase
4
+ class MfaEvent
5
+ ENABLE_SMS_EVENT = :sms_enable
6
+ DISABLE_SMS_EVENT = :sms_disable
7
+ FORGOT_PASSWORD = :forgot_password
8
+ ADMIN_VERIFY = :admin_verify
9
+
10
+ class InvalidParameter < ArgumentError; end
11
+
12
+ attr_reader :only_mfa, :phone_number, :set_satiated_on_success, :satiated, :access_count, :flash_notice, :sign_in_user,
13
+ :invalid_redirect, :ttl, :user_id, :event, :description, :death_time, :redirect, :params, :access_count_max
14
+
15
+ def self.admin_actions(user:)
16
+ params = {
17
+ user: user,
18
+ event: ADMIN_VERIFY,
19
+ ttl: 30.seconds,
20
+ redirect: "",
21
+ invalid_redirect: "",
22
+ flash_notice: "",
23
+ }
24
+
25
+ new(**params)
26
+ end
27
+
28
+ def self.login_event(user:)
29
+ params = {
30
+ user: user,
31
+ event: :login,
32
+ ttl: 1.minutes,
33
+ redirect: RailsBase.url_routes.authenticated_root_path,
34
+ invalid_redirect: RailsBase.url_routes.unauthenticated_root_path,
35
+ sign_in_user: true,
36
+ flash_notice: "Welcome #{user.full_name}. You have succesfully signed in"
37
+ }
38
+
39
+ new(**params)
40
+ end
41
+
42
+ # This is a JSON event not html; Can leave redirects/notice empty
43
+ def self.sms_enable(user:)
44
+ params = {
45
+ user: user,
46
+ event: ENABLE_SMS_EVENT,
47
+ ttl: 5.minutes,
48
+ invalid_redirect: RailsBase.url_routes.user_settings_path,
49
+ redirect: RailsBase.url_routes.user_settings_path,
50
+ flash_notice: ""
51
+ }
52
+
53
+ new(**params)
54
+ end
55
+
56
+ def self.sms_disable(user:)
57
+ params = {
58
+ user: user,
59
+ event: DISABLE_SMS_EVENT,
60
+ ttl: 5.minutes,
61
+ invalid_redirect: RailsBase.url_routes.user_settings_path,
62
+ redirect: RailsBase.url_routes.user_settings_path,
63
+ flash_notice: "SMS option for MFA is disabled"
64
+ }
65
+
66
+ new(**params)
67
+ end
68
+
69
+ def self.forgot_password(user:, data:)
70
+ params = {
71
+ user: user,
72
+ event: FORGOT_PASSWORD,
73
+ ttl: 2.minutes,
74
+ invalid_redirect: RailsBase.url_routes.unauthenticated_root_path,
75
+ redirect: RailsBase.url_routes.reset_password_input_path(data:),
76
+ flash_notice: "MFA success. You may now reset your forgotten password",
77
+ access_count_max: 1,
78
+ }
79
+
80
+ new(**params)
81
+ end
82
+
83
+ def initialize(event:, flash_notice:, redirect:, only_mfa: nil, phone_number: nil, ttl: nil, death_time: nil, user_id: nil, user: nil, invalid_redirect: nil, sign_in_user: false, access_count: 0, access_count_max: nil, satiated: false, set_satiated_on_success: true)
84
+ @death_time = begin
85
+ raw = (death_time || ttl&.from_now)
86
+ Time.zone.parse(raw.to_s) rescue nil
87
+ end
88
+
89
+ @access_count = access_count
90
+ @access_count_max = access_count_max
91
+ @event = event
92
+ @flash_notice = flash_notice
93
+ @invalid_redirect = invalid_redirect || RailsBase.url_routes.authenticated_root_path
94
+ @only_mfa = only_mfa
95
+ @phone_number = phone_number
96
+ @redirect = redirect
97
+ @satiated = satiated
98
+ @set_satiated_on_success = set_satiated_on_success
99
+ @sign_in_user = sign_in_user
100
+ @user_id = user_id || user.id rescue nil
101
+
102
+ validate_data!
103
+ end
104
+
105
+ def to_hash
106
+ {
107
+ access_count:,
108
+ access_count_max:,
109
+ death_time:,
110
+ event:,
111
+ flash_notice:,
112
+ invalid_redirect:,
113
+ only_mfa:,
114
+ phone_number:,
115
+ redirect:,
116
+ satiated:,
117
+ set_satiated_on_success:,
118
+ sign_in_user:,
119
+ user_id:,
120
+ }
121
+ end
122
+
123
+ def access_count
124
+ @access_count
125
+ end
126
+
127
+ def satiated!
128
+ @satiated = true
129
+ end
130
+
131
+ def satiated?
132
+ @satiated
133
+ end
134
+
135
+ def increase_access_count!
136
+ @access_count += 1
137
+ end
138
+
139
+ def valid?
140
+ valid_by_death_time? && valid_by_access_count?
141
+ end
142
+
143
+ def valid_by_death_time?
144
+ death_time >= Time.now
145
+ end
146
+
147
+ def valid_by_access_count?
148
+ return true if @access_count_max.nil?
149
+
150
+ @access_count_max
151
+ end
152
+
153
+ def invalid_reasons
154
+ arr = []
155
+ arr << "Max Access count reached" unless valid_by_death_time?
156
+ arr << "#{event} has expired" unless valid_by_access_count?
157
+
158
+ arr
159
+ end
160
+
161
+ private
162
+
163
+ def validate_data!
164
+ raise_event!(value: @event, name: :event, klass: [String, Symbol])
165
+ raise_event!(value: @death_time, name: :death_time, klass: [ActiveSupport::TimeWithZone])
166
+ raise_event!(value: @redirect, name: :redirect, klass: [String])
167
+ raise_event!(value: @user_id, name: :user, klass: [Integer])
168
+ raise_event!(value: @flash_notice, name: :flash_notice, klass: [String])
169
+ raise_event!(value: @access_count, name: :access_count, klass: [Integer])
170
+ raise_event!(value: @access_count_max, name: :access_count_max, klass: [Integer, NilClass])
171
+ end
172
+
173
+ def raise_event!(value:, name:, klass:, &blk)
174
+ boolean = klass.include?(value.class)
175
+ raise_message = nil
176
+ if boolean && block_given?
177
+ raise_message = yield(value)
178
+ end
179
+ boolean = false if raise_message
180
+ return if boolean
181
+
182
+ message = raise_message || "@#{name}=#{value}. Value is expected to be in #{klass}"
183
+ raise InvalidParameter, message
184
+ end
185
+ end
186
+ end
@@ -1,7 +1,7 @@
1
1
  module RailsBase
2
- MAJOR = '0'
3
- MINOR = '75'
4
- PATCH = '5'
2
+ MAJOR = "0"
3
+ MINOR = "80"
4
+ PATCH = "0"
5
5
  VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}"
6
6
 
7
7
  def self.print_version
data/lib/rails_base.rb CHANGED
@@ -15,6 +15,7 @@ require 'switch_user'
15
15
 
16
16
  require 'rails_base/admin/action_cache'
17
17
  require 'rails_base/config'
18
+ require 'rails_base/mfa_event'
18
19
 
19
20
  module RailsBase
20
21
 
data/lib/twilio_helper.rb CHANGED
@@ -2,9 +2,9 @@ require 'twilio-ruby'
2
2
 
3
3
  class TwilioHelper
4
4
  class << self
5
- TWILIO_ACCOUNT_SID = RailsBase.config.mfa.twilio_sid
6
- TWILIO_AUTH_TOKEN = RailsBase.config.mfa.twilio_auth_token
7
- TWILIO_FROM_NUMBER = RailsBase.config.mfa.twilio_from_number
5
+ TWILIO_ACCOUNT_SID = RailsBase.config.twilio.twilio_sid
6
+ TWILIO_AUTH_TOKEN = RailsBase.config.twilio.twilio_auth_token
7
+ TWILIO_FROM_NUMBER = RailsBase.config.twilio.twilio_from_number
8
8
 
9
9
  def send_sms(message:, to:)
10
10
  Rails.logger.info "Sending Twilio message:[#{message}] to [#{to}]"