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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/rails_base/rails_base_query_checker.js +36 -0
- data/app/controllers/rails_base/admin_controller.rb +54 -9
- data/app/controllers/rails_base/mfa/evaluation_controller.rb +59 -0
- data/app/controllers/rails_base/mfa/register/sms_controller.rb +45 -0
- data/app/controllers/rails_base/mfa/register/totp_controller.rb +42 -0
- data/app/controllers/rails_base/mfa/validate/sms_controller.rb +83 -0
- data/app/controllers/rails_base/mfa/validate/totp_controller.rb +35 -0
- data/app/controllers/rails_base/secondary_authentication_controller.rb +40 -96
- data/app/controllers/rails_base/user_settings_controller.rb +11 -1
- data/app/controllers/rails_base/users/registrations_controller.rb +1 -1
- data/app/controllers/rails_base/users/sessions_controller.rb +16 -13
- data/app/controllers/rails_base_application_controller.rb +96 -1
- data/app/jobs/twilio_job.rb +1 -1
- data/app/mailers/rails_base/email_verification_mailer.rb +6 -4
- data/app/mailers/rails_base/event_mailer.rb +4 -2
- data/app/mailers/rails_base/mailer_kwarg_inject.rb +31 -0
- data/app/models/rails_base/user_constants.rb +6 -3
- data/app/models/rails_base/user_helper/totp/backup_method_options.rb +33 -0
- data/app/models/rails_base/user_helper/totp/class_options.rb +35 -0
- data/app/models/rails_base/user_helper/totp/consume_method_options.rb +60 -0
- data/app/models/rails_base/user_helper/totp.rb +41 -0
- data/app/models/user.rb +28 -13
- data/app/services/rails_base/authentication/constants.rb +1 -1
- data/app/services/rails_base/authentication/decision_twofa_type.rb +61 -30
- data/app/services/rails_base/authentication/send_forgot_password.rb +0 -1
- data/app/services/rails_base/authentication/single_sign_on_send.rb +1 -1
- data/app/services/rails_base/authentication/sso_verify_email.rb +3 -1
- data/app/services/rails_base/authentication/update_phone_send_verification.rb +2 -2
- data/app/services/rails_base/authentication/verify_forgot_password.rb +8 -11
- data/app/services/rails_base/mfa/decision.rb +70 -0
- data/app/services/rails_base/mfa/encrypt_token.rb +34 -0
- data/app/services/rails_base/mfa/sms/remove.rb +35 -0
- data/app/services/rails_base/{authentication/send_login_mfa_to_user.rb → mfa/sms/send.rb} +19 -13
- data/app/services/rails_base/mfa/sms/validate.rb +105 -0
- data/app/services/rails_base/mfa/strategy/base.rb +44 -0
- data/app/services/rails_base/mfa/strategy/every_request.rb +14 -0
- data/app/services/rails_base/mfa/strategy/skip_every_request.rb +14 -0
- data/app/services/rails_base/mfa/strategy/time_based.rb +24 -0
- data/app/services/rails_base/mfa/totp/helper.rb +21 -0
- data/app/services/rails_base/mfa/totp/otp_metadata.rb +19 -0
- data/app/services/rails_base/mfa/totp/remove.rb +40 -0
- data/app/services/rails_base/mfa/totp/validate_code.rb +52 -0
- data/app/services/rails_base/mfa/totp/validate_temporary_code.rb +37 -0
- data/app/services/rails_base/mfa.rb +18 -0
- data/app/services/rails_base/name_change.rb +3 -3
- data/app/views/layouts/rails_base/application.html.erb +22 -6
- data/app/views/rails_base/devise/passwords/new.html.erb +1 -1
- data/app/views/rails_base/mfa/_switch_mfa_type.html.erb +17 -0
- data/app/views/rails_base/mfa/validate/sms/sms_event_input.html.erb +2 -0
- data/app/views/rails_base/mfa/validate/totp/totp_event_input.html.erb +1 -0
- data/app/views/rails_base/secondary_authentication/reset_password_input.html.erb +4 -0
- data/app/views/rails_base/shared/_enable_mfa_auth_modal.html.erb +1 -1
- data/app/views/rails_base/shared/_logged_in_header.html.erb +1 -25
- data/app/views/rails_base/shared/_modify_mfa_auth_modal.html.erb +102 -3
- data/app/views/rails_base/shared/_standardized_collapse.html.erb +28 -0
- data/app/views/rails_base/shared/mfa/sms/_login_input.html.erb +13 -0
- data/app/views/rails_base/shared/mfa/totp/_login_input.html.erb +22 -0
- data/app/views/rails_base/shared/totp/_add_authenticator.html.erb +76 -0
- data/app/views/rails_base/shared/totp/_add_authenticator_modal.html.erb +25 -0
- data/app/views/rails_base/shared/totp/_confirm_code.html.erb +31 -0
- data/app/views/rails_base/shared/totp/_confirm_code_ajax.html.erb +3 -0
- data/app/views/rails_base/shared/totp/_confirm_code_rest.html.erb +5 -0
- data/app/views/rails_base/shared/totp/_remove_authenticator_modal.html.erb +50 -0
- data/app/views/rails_base/user_settings/index.html.erb +84 -1
- data/config/initializers/admin_action_helper.rb +44 -8
- data/config/routes.rb +42 -7
- data/db/migrate/20240808013706_add_totp_to_users.rb +9 -0
- data/db/migrate/20240825012724_reconfigure_mfa_variable_names.rb +10 -0
- data/lib/rails_base/admin/action_helper.rb +0 -1
- data/lib/rails_base/admin/default_index_tile.rb +3 -3
- data/lib/rails_base/config.rb +26 -22
- data/lib/rails_base/configuration/admin.rb +5 -5
- data/lib/rails_base/configuration/appearance.rb +0 -2
- data/lib/rails_base/configuration/base.rb +1 -0
- data/lib/rails_base/configuration/mfa.rb +27 -60
- data/lib/rails_base/configuration/totp.rb +82 -0
- data/lib/rails_base/configuration/twilio.rb +85 -0
- data/lib/rails_base/mfa_event.rb +186 -0
- data/lib/rails_base/version.rb +3 -3
- data/lib/rails_base.rb +1 -0
- data/lib/twilio_helper.rb +3 -3
- metadata +129 -64
- data/app/controllers/rails_base/mfa_auth_controller.rb +0 -50
- data/app/services/rails_base/authentication/mfa_set_encrypt_token.rb +0 -32
- data/app/services/rails_base/authentication/mfa_validator.rb +0 -88
- data/app/views/rails_base/mfa_auth/mfa_code.html.erb +0 -11
- 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
|
data/lib/rails_base/version.rb
CHANGED
data/lib/rails_base.rb
CHANGED
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.
|
6
|
-
TWILIO_AUTH_TOKEN = RailsBase.config.
|
7
|
-
TWILIO_FROM_NUMBER = RailsBase.config.
|
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}]"
|