rails_base 0.51.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +32 -0
- data/Rakefile +32 -0
- data/app/assets/config/rails_base/manifest.js +3 -0
- data/app/assets/images/rails_base/favicon.ico +0 -0
- data/app/assets/javascripts/rails_base/admin.js +2 -0
- data/app/assets/javascripts/rails_base/application.js +22 -0
- data/app/assets/javascripts/rails_base/cable.js +13 -0
- data/app/assets/javascripts/rails_base/mfa_auth.coffee +3 -0
- data/app/assets/javascripts/rails_base/secondary_authentication.coffee +3 -0
- data/app/assets/javascripts/rails_base/sessions.js +152 -0
- data/app/assets/javascripts/rails_base/user_settings.coffee +3 -0
- data/app/assets/stylesheets/rails_base/admin.css +4 -0
- data/app/assets/stylesheets/rails_base/application.scss +15 -0
- data/app/assets/stylesheets/rails_base/mfa_auth.scss +3 -0
- data/app/assets/stylesheets/rails_base/scaffolds.scss +84 -0
- data/app/assets/stylesheets/rails_base/secondary_authentication.scss +3 -0
- data/app/assets/stylesheets/rails_base/user_settings.scss +3 -0
- data/app/controllers/rails_base/admin_controller.rb +315 -0
- data/app/controllers/rails_base/application_controller.rb +153 -0
- data/app/controllers/rails_base/errors_controller.rb +29 -0
- data/app/controllers/rails_base/mfa_auth_controller.rb +50 -0
- data/app/controllers/rails_base/secondary_authentication_controller.rb +224 -0
- data/app/controllers/rails_base/switch_user_controller.rb +29 -0
- data/app/controllers/rails_base/user_settings_controller.rb +81 -0
- data/app/controllers/rails_base/users/passwords_controller.rb +19 -0
- data/app/controllers/rails_base/users/registrations_controller.rb +80 -0
- data/app/controllers/rails_base/users/sessions_controller.rb +108 -0
- data/app/helpers/rails_base/admin_helper.rb +107 -0
- data/app/helpers/rails_base/appearance_helper.rb +58 -0
- data/app/helpers/rails_base/application_helper.rb +26 -0
- data/app/helpers/rails_base/capture_reference_helper.rb +57 -0
- data/app/helpers/rails_base/mfa_auth_helper.rb +2 -0
- data/app/helpers/rails_base/secondary_authentication_helper.rb +2 -0
- data/app/helpers/rails_base/user_field_validators.rb +108 -0
- data/app/helpers/rails_base/user_settings_helper.rb +22 -0
- data/app/jobs/rails_base/application_job.rb +10 -0
- data/app/jobs/twilio_job.rb +9 -0
- data/app/mailers/rails_base/application_mailer.rb +9 -0
- data/app/mailers/rails_base/email_verification_mailer.rb +22 -0
- data/app/mailers/rails_base/event_mailer.rb +16 -0
- data/app/models/admin_action.rb +119 -0
- data/app/models/rails_base/application_record.rb +22 -0
- data/app/models/rails_base/user_constants.rb +28 -0
- data/app/models/secret.rb +37 -0
- data/app/models/short_lived_data.rb +132 -0
- data/app/models/user.rb +143 -0
- data/app/services/rails_base/admin_risky_mfa_send.rb +80 -0
- data/app/services/rails_base/admin_update_attribute.rb +100 -0
- data/app/services/rails_base/authentication/authenticate_user.rb +28 -0
- data/app/services/rails_base/authentication/constants.rb +60 -0
- data/app/services/rails_base/authentication/decision_twofa_type.rb +76 -0
- data/app/services/rails_base/authentication/destroy_user.rb +45 -0
- data/app/services/rails_base/authentication/mfa_set_encrypt_token.rb +32 -0
- data/app/services/rails_base/authentication/mfa_validator.rb +88 -0
- data/app/services/rails_base/authentication/modify_password.rb +67 -0
- data/app/services/rails_base/authentication/send_forgot_password.rb +26 -0
- data/app/services/rails_base/authentication/send_login_mfa_to_user.rb +77 -0
- data/app/services/rails_base/authentication/send_verification_email.rb +103 -0
- data/app/services/rails_base/authentication/session_token_verifier.rb +31 -0
- data/app/services/rails_base/authentication/single_sign_on_create.rb +44 -0
- data/app/services/rails_base/authentication/single_sign_on_send.rb +101 -0
- data/app/services/rails_base/authentication/single_sign_on_verify.rb +42 -0
- data/app/services/rails_base/authentication/sso_verify_email.rb +43 -0
- data/app/services/rails_base/authentication/update_phone_send_verification.rb +46 -0
- data/app/services/rails_base/authentication/verify_forgot_password.rb +46 -0
- data/app/services/rails_base/email_change.rb +20 -0
- data/app/services/rails_base/encryption.rb +87 -0
- data/app/services/rails_base/name_change.rb +71 -0
- data/app/services/rails_base/service_base.rb +65 -0
- data/app/services/rails_base/service_logging.rb +23 -0
- data/app/views/layouts/rails_base/application.html.erb +185 -0
- data/app/views/layouts/rails_base/mailer.html.erb +13 -0
- data/app/views/layouts/rails_base/mailer.text.erb +1 -0
- data/app/views/new.html.erb +4 -0
- data/app/views/rails_base/admin/history.html.erb +26 -0
- data/app/views/rails_base/admin/index.html.erb +149 -0
- data/app/views/rails_base/admin/show_config.html.erb +18 -0
- data/app/views/rails_base/devise/confirmations/new.html.erb +16 -0
- data/app/views/rails_base/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/rails_base/devise/mailer/email_changed.html.erb +7 -0
- data/app/views/rails_base/devise/mailer/password_change.html.erb +3 -0
- data/app/views/rails_base/devise/mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/rails_base/devise/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/rails_base/devise/passwords/edit.html.erb +25 -0
- data/app/views/rails_base/devise/passwords/new.html.erb +27 -0
- data/app/views/rails_base/devise/registrations/edit.html.erb +43 -0
- data/app/views/rails_base/devise/registrations/new.html.erb +123 -0
- data/app/views/rails_base/devise/sessions/new.html.erb +4 -0
- data/app/views/rails_base/devise/shared/_error_messages.html.erb +15 -0
- data/app/views/rails_base/devise/shared/_links.html.erb +25 -0
- data/app/views/rails_base/devise/unlocks/new.html.erb +16 -0
- data/app/views/rails_base/email_verification_mailer/email_verification.html.erb +25 -0
- data/app/views/rails_base/email_verification_mailer/event.html.erb +20 -0
- data/app/views/rails_base/email_verification_mailer/forgot_password.html.erb +22 -0
- data/app/views/rails_base/errors/internal_error.html.erb +1 -0
- data/app/views/rails_base/errors/not_found.html.erb +1 -0
- data/app/views/rails_base/errors/unacceptable.html.erb +1 -0
- data/app/views/rails_base/event_mailer/event.html.erb +10 -0
- data/app/views/rails_base/mfa_auth/mfa_code.html.erb +10 -0
- data/app/views/rails_base/secondary_authentication/after_email_login_session_new.html.erb +3 -0
- data/app/views/rails_base/secondary_authentication/forgot_password.html.erb +9 -0
- data/app/views/rails_base/secondary_authentication/remove_me.html.erb +1 -0
- data/app/views/rails_base/secondary_authentication/static.html.erb +5 -0
- data/app/views/rails_base/shared/_admin_actions_modal.html.erb +65 -0
- data/app/views/rails_base/shared/_admin_config_class.html.erb +52 -0
- data/app/views/rails_base/shared/_admin_history.html.erb +86 -0
- data/app/views/rails_base/shared/_admin_modify_email.html.erb +78 -0
- data/app/views/rails_base/shared/_admin_modify_name.html.erb +107 -0
- data/app/views/rails_base/shared/_admin_modify_phone.html.erb +87 -0
- data/app/views/rails_base/shared/_admin_modify_text.html.erb +35 -0
- data/app/views/rails_base/shared/_admin_risky_change.html.erb +57 -0
- data/app/views/rails_base/shared/_admin_risky_mfa.html.erb +74 -0
- data/app/views/rails_base/shared/_admin_selector_dropdown.html.erb +70 -0
- data/app/views/rails_base/shared/_admin_toggle_button.html.erb +72 -0
- data/app/views/rails_base/shared/_admin_warning_alert.html.erb +7 -0
- data/app/views/rails_base/shared/_appearance_mode_selector.html.erb +183 -0
- data/app/views/rails_base/shared/_custom_form_validation_javascript.html.erb +129 -0
- data/app/views/rails_base/shared/_enable_mfa_auth_modal.html.erb +105 -0
- data/app/views/rails_base/shared/_error_pages.html.erb +123 -0
- data/app/views/rails_base/shared/_logged_in_header.html.erb +123 -0
- data/app/views/rails_base/shared/_logged_out_header.html.erb +14 -0
- data/app/views/rails_base/shared/_mfa_input_layout.html.erb +5 -0
- data/app/views/rails_base/shared/_mfa_input_layout_default.html.erb +97 -0
- data/app/views/rails_base/shared/_mfa_input_layout_fallback.html.erb +55 -0
- data/app/views/rails_base/shared/_modify_mfa_auth_modal.html.erb +20 -0
- data/app/views/rails_base/shared/_password_confirm_javascript.html.erb +71 -0
- data/app/views/rails_base/shared/_reset_password_form.html.erb +111 -0
- data/app/views/rails_base/shared/_session_create_form.html.erb +32 -0
- data/app/views/rails_base/shared/_session_timeout_modal.html.erb +76 -0
- data/app/views/rails_base/switch_user/_widget.html.erb +5 -0
- data/app/views/rails_base/user_settings/_confirm_destroy_user.html.erb +42 -0
- data/app/views/rails_base/user_settings/_destroy_user.html.erb +106 -0
- data/app/views/rails_base/user_settings/_modify_name.html.erb +71 -0
- data/app/views/rails_base/user_settings/_modify_password.html.erb +101 -0
- data/app/views/rails_base/user_settings/_modify_password_update_password.html.erb +2 -0
- data/app/views/rails_base/user_settings/index.html.erb +54 -0
- data/config/initializers/01_rails_config.rb +19 -0
- data/config/initializers/admin_action_helper.rb +88 -0
- data/config/initializers/browser.rb +4 -0
- data/config/initializers/default_logged_in_headers.rb +23 -0
- data/config/initializers/devise.rb +314 -0
- data/config/initializers/encryption.rb +2 -0
- data/config/initializers/switch_user.rb +58 -0
- data/config/initializers/switch_user_helper.rb +29 -0
- data/config/locales/devise.en.yml +65 -0
- data/config/locales/en.yml +58 -0
- data/config/routes.rb +114 -0
- data/db/migrate/20210212175453_devise_create_rails_base_users.rb +56 -0
- data/db/migrate/20210212190537_create_rails_base_short_lived_data.rb +19 -0
- data/db/migrate/20210212192645_create_rails_base_secrets.rb +11 -0
- data/db/migrate/20210406015744_create_rails_base_admin_actions.rb +17 -0
- data/db/seeds.rb +23 -0
- data/lib/link_decision_helper.rb +71 -0
- data/lib/rails_base.rb +50 -0
- data/lib/rails_base/admin/action_cache.rb +99 -0
- data/lib/rails_base/admin/action_helper.rb +134 -0
- data/lib/rails_base/admin/default_index_tile.rb +176 -0
- data/lib/rails_base/admin/index_tile.rb +186 -0
- data/lib/rails_base/config.rb +52 -0
- data/lib/rails_base/configuration/active_job.rb +38 -0
- data/lib/rails_base/configuration/admin.rb +231 -0
- data/lib/rails_base/configuration/app.rb +52 -0
- data/lib/rails_base/configuration/appearance.rb +131 -0
- data/lib/rails_base/configuration/authentication.rb +37 -0
- data/lib/rails_base/configuration/base.rb +209 -0
- data/lib/rails_base/configuration/display/background_color.rb +25 -0
- data/lib/rails_base/configuration/display/btn_danger.rb +25 -0
- data/lib/rails_base/configuration/display/btn_dark.rb +25 -0
- data/lib/rails_base/configuration/display/btn_info.rb +25 -0
- data/lib/rails_base/configuration/display/btn_light.rb +25 -0
- data/lib/rails_base/configuration/display/btn_primary.rb +25 -0
- data/lib/rails_base/configuration/display/btn_secondary.rb +25 -0
- data/lib/rails_base/configuration/display/btn_success.rb +25 -0
- data/lib/rails_base/configuration/display/btn_warning.rb +25 -0
- data/lib/rails_base/configuration/display/footer.rb +54 -0
- data/lib/rails_base/configuration/display/navbar.rb +25 -0
- data/lib/rails_base/configuration/display/table_body.rb +25 -0
- data/lib/rails_base/configuration/display/table_header.rb +25 -0
- data/lib/rails_base/configuration/display/text.rb +26 -0
- data/lib/rails_base/configuration/exceptions_app.rb +25 -0
- data/lib/rails_base/configuration/login_behavior.rb +17 -0
- data/lib/rails_base/configuration/mailer.rb +116 -0
- data/lib/rails_base/configuration/mfa.rb +84 -0
- data/lib/rails_base/configuration/owner.rb +17 -0
- data/lib/rails_base/configuration/redis.rb +29 -0
- data/lib/rails_base/configuration/user.rb +43 -0
- data/lib/rails_base/engine.rb +51 -0
- data/lib/rails_base/version.rb +10 -0
- data/lib/tasks/rails_base_tasks.rake +4 -0
- data/lib/twilio_helper.rb +26 -0
- data/lib/velocity_limiter.rb +91 -0
- metadata +619 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'velocity_limiter'
|
2
|
+
|
3
|
+
module RailsBase::Authentication
|
4
|
+
class SendVerificationEmail < RailsBase::ServiceBase
|
5
|
+
include ActionView::Helpers::DateHelper
|
6
|
+
include VelocityLimiter
|
7
|
+
|
8
|
+
delegate :user, to: :context
|
9
|
+
delegate :reason, to: :context
|
10
|
+
|
11
|
+
MAX_USE_COUNT = 1.freeze
|
12
|
+
DATA_USE = :alphanumeric
|
13
|
+
VELOCITY_MAX = 5
|
14
|
+
VELOCITY_MAX_IN_FRAME = 10.minutes
|
15
|
+
VELOCITY_FRAME = 1.hour
|
16
|
+
|
17
|
+
REASON_MAPPER = {
|
18
|
+
Constants::SVE_LOGIN_REASON => { method: :email_verification, url_method: :email_verification_url },
|
19
|
+
Constants::SVE_FORGOT_REASON => { method: :forgot_password, url_method: :forgot_password_auth_url }
|
20
|
+
}
|
21
|
+
|
22
|
+
def call
|
23
|
+
velocity = velocity_limit_reached?
|
24
|
+
if velocity[:reached]
|
25
|
+
context.fail!(message: velocity[:msg])
|
26
|
+
end
|
27
|
+
|
28
|
+
data_point = create_short_lived_data
|
29
|
+
begin
|
30
|
+
url = assign_url(data_point.data)
|
31
|
+
rescue StandardError => e
|
32
|
+
log(level: :error, msg: "Error: #{e.class}")
|
33
|
+
log(level: :error, msg: "Error: #{e.message}")
|
34
|
+
log(level: :error, msg: "Failed to get url for #{url_method}")
|
35
|
+
log(level: :error, msg: "Swallowing Error. Returning to user")
|
36
|
+
context.fail!(message: "Unknown error occurred. Please log in with credentials to restart process")
|
37
|
+
return
|
38
|
+
end
|
39
|
+
log(level: :info, msg: "SSO url for user #{user.id}: #{url}")
|
40
|
+
|
41
|
+
begin
|
42
|
+
RailsBase::EmailVerificationMailer.public_send(method, user: user, url: url).deliver_me
|
43
|
+
rescue StandardError => e
|
44
|
+
log(level: :error, msg: "Unkown error occured when sending EmailVerificationMailer.#{method}")
|
45
|
+
log(level: :error, msg: "Params: #{method}, #{reason}, user: #{user.id}")
|
46
|
+
context.fail!(message: "Unknown error occurred. Please log in with credentials to restart process")
|
47
|
+
return
|
48
|
+
end
|
49
|
+
log(level: :info, msg: "Succesfully sent EmailVerificationMailer.#{method} to #{user.id} @ #{user.email}")
|
50
|
+
end
|
51
|
+
|
52
|
+
def assign_url(data)
|
53
|
+
params = {
|
54
|
+
data: data,
|
55
|
+
host: Constants::BASE_URL,
|
56
|
+
}
|
57
|
+
params[:port] = Constants::BASE_URL_PORT if Constants::BASE_URL_PORT
|
58
|
+
Constants::URL_HELPER.public_send(url_method, params)
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_short_lived_data
|
62
|
+
params = {
|
63
|
+
user: user,
|
64
|
+
max_use: MAX_USE_COUNT,
|
65
|
+
reason: reason,
|
66
|
+
data_use: DATA_USE,
|
67
|
+
ttl: Constants::SVE_TTL,
|
68
|
+
length: Constants::EMAIL_LENGTH,
|
69
|
+
}
|
70
|
+
ShortLivedData.create_data_key(params)
|
71
|
+
end
|
72
|
+
|
73
|
+
def velocity_max_in_frame
|
74
|
+
VELOCITY_MAX_IN_FRAME
|
75
|
+
end
|
76
|
+
|
77
|
+
def velocity_max
|
78
|
+
VELOCITY_MAX
|
79
|
+
end
|
80
|
+
|
81
|
+
def velocity_frame
|
82
|
+
VELOCITY_FRAME
|
83
|
+
end
|
84
|
+
|
85
|
+
def cache_key
|
86
|
+
"#{self.class.name.downcase}.#{user.id}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def method
|
90
|
+
REASON_MAPPER[reason][:method]
|
91
|
+
end
|
92
|
+
|
93
|
+
def url_method
|
94
|
+
REASON_MAPPER[reason][:url_method]
|
95
|
+
end
|
96
|
+
|
97
|
+
def validate!
|
98
|
+
raise "Expected user to be a User. Received #{user.class}" unless user.is_a? User
|
99
|
+
raise "Expected reason to be a symbol. Received #{reason.class}" unless reason.is_a? Symbol
|
100
|
+
raise "Expected #{reason} to be in #{REASON_MAPPER.keys}" unless REASON_MAPPER.keys.include?(reason)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module RailsBase::Authentication
|
4
|
+
class SessionTokenVerifier < RailsBase::ServiceBase
|
5
|
+
delegate :mfa_randomized_token, to: :context
|
6
|
+
delegate :purpose, to: :context
|
7
|
+
|
8
|
+
def call
|
9
|
+
if mfa_randomized_token.nil?
|
10
|
+
context.fail!(message: "Authorization token not present. Please Log in")
|
11
|
+
end
|
12
|
+
|
13
|
+
decoded = RailsBase::Encryption.decode(value: mfa_randomized_token, purpose: purpose || Constants::MSET_PURPOSE)
|
14
|
+
if decoded.nil?
|
15
|
+
context.fail!(message: "Authorization token has expired. Please Log in")
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
json_decoded = JSON.parse(decoded)
|
20
|
+
rescue StandardError => e
|
21
|
+
log(level: :fatal, msg: "Json parse error. [#{decoded}] could not be parsed.")
|
22
|
+
context.fail!(message: "Authorization token has failed. Please Log in")
|
23
|
+
end
|
24
|
+
|
25
|
+
log(level: :info, msg: "Decoded message: #{json_decoded}")
|
26
|
+
|
27
|
+
context.user_id = json_decoded['user_id']
|
28
|
+
context.expires_at = json_decoded['expires_at']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RailsBase::Authentication
|
2
|
+
class SingleSignOnCreate < RailsBase::ServiceBase
|
3
|
+
delegate :user, to: :context
|
4
|
+
delegate :token_length, to: :context
|
5
|
+
delegate :uses, to: :context
|
6
|
+
delegate :expires_at, to: :context
|
7
|
+
delegate :reason, to: :context
|
8
|
+
delegate :token_type, to: :context
|
9
|
+
delegate :url_redirect, to: :context
|
10
|
+
|
11
|
+
def call
|
12
|
+
msg = "Creating SSO token [#{reason}]: user_id:#{user.id}; "\
|
13
|
+
"uses:#{uses.nil? ? 'unlimited' : uses}; expires_at:#{expires_at}"
|
14
|
+
log(level: :info, msg: msg)
|
15
|
+
context.data = create_sso_token
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_sso_token
|
19
|
+
params = {
|
20
|
+
user: user,
|
21
|
+
max_use: uses,
|
22
|
+
data_use: data_type,
|
23
|
+
expires_at: expires_at,
|
24
|
+
reason: reason,
|
25
|
+
length: token_length,
|
26
|
+
extra: url_redirect,
|
27
|
+
}.compact
|
28
|
+
ShortLivedData.create_data_key(params)
|
29
|
+
end
|
30
|
+
|
31
|
+
def data_type
|
32
|
+
ShortLivedData::VALID_DATA_USE_LENGTH.include?(token_type&.to_sym) ? token_type.to_sym : nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate!
|
36
|
+
raise "Expected user to be a User. Received #{user.class}" unless user.is_a? User
|
37
|
+
raise "Expected token_length to be a Int. Received #{token_length.class}" unless token_length.is_a? Integer
|
38
|
+
raise "Expected reason to be present." if reason.nil?
|
39
|
+
|
40
|
+
time_class = ActiveSupport::TimeWithZone
|
41
|
+
raise "Expected expires_at to be a Received #{time_class}. Received #{expires_at.class}" unless expires_at.is_a? time_class
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'twilio_helper'
|
2
|
+
|
3
|
+
module RailsBase::Authentication
|
4
|
+
class SingleSignOnSend < RailsBase::ServiceBase
|
5
|
+
delegate :user, to: :context
|
6
|
+
delegate :token_length, to: :context
|
7
|
+
delegate :uses, to: :context
|
8
|
+
delegate :expires_at, to: :context
|
9
|
+
delegate :reason, to: :context
|
10
|
+
delegate :token_type, to: :context
|
11
|
+
delegate :url_redirect, to: :context
|
12
|
+
|
13
|
+
SSO_DECISION_TWILIO = :twilio
|
14
|
+
SSO_DECISION_EMAIL = :email
|
15
|
+
VALID_SSO_DECISIONS = [SSO_DECISION_TWILIO, SSO_DECISION_EMAIL].freeze
|
16
|
+
|
17
|
+
def call
|
18
|
+
# :nocov:
|
19
|
+
# redundant check, unless user overwrites `sso_decision_type`
|
20
|
+
unless VALID_SSO_DECISIONS.include?(sso_decision_type)
|
21
|
+
context.fail!(message: "Invalid sso decision. Given [#{sso_decision_type}]. Expected [#{VALID_SSO_DECISIONS}]")
|
22
|
+
end
|
23
|
+
# :nocov:
|
24
|
+
|
25
|
+
params = {
|
26
|
+
user: user,
|
27
|
+
token_length: token_length,
|
28
|
+
uses: uses,
|
29
|
+
expires_at: expires_at,
|
30
|
+
reason: reason || RailsBase::Authentication::Constants::SSO_LOGIN_REASON,
|
31
|
+
token_type: token_type,
|
32
|
+
url_redirect: url_redirect
|
33
|
+
}
|
34
|
+
datum = SingleSignOnCreate.call(params)
|
35
|
+
context.fail!(message: 'Failed to create SSO token. Try again') if datum.failure?
|
36
|
+
|
37
|
+
url = sso_url(data: datum.data.data)
|
38
|
+
case sso_decision_type
|
39
|
+
when SSO_DECISION_TWILIO
|
40
|
+
context.sso_destination = :sms
|
41
|
+
send_to_twilio!(message: message(url: url))
|
42
|
+
when SSO_DECISION_EMAIL
|
43
|
+
context.sso_destination = :email
|
44
|
+
send_to_email!(message: message(url: url))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# This method is expected to be overridden by the main app
|
49
|
+
# This is the default message
|
50
|
+
# Might consider shipping this to a locales that can be easily overridden in downstream app
|
51
|
+
def message(url:)
|
52
|
+
"Hello #{user.full_name}. This is your SSO link to your favorite site.\n#{url}"
|
53
|
+
end
|
54
|
+
|
55
|
+
# This method is expected to be overridden by the main app
|
56
|
+
# This is expected default behavior if SSO is available
|
57
|
+
def sso_decision_type
|
58
|
+
if user.phone_number.present?
|
59
|
+
SSO_DECISION_TWILIO
|
60
|
+
elsif user.email_validated
|
61
|
+
SSO_DECISION_EMAIL
|
62
|
+
else
|
63
|
+
log(level: :error, msg: "No SSO will be sent for user #{user.id}. No email/phone available to send to at this time")
|
64
|
+
context.fail!(message: "User does not have a validated email nor phone number. Unable to do SSO")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def send_to_twilio!(message:)
|
69
|
+
TwilioJob.perform_later(message: message, to: user.phone_number)
|
70
|
+
log(level: :info, msg: "Sent twilio message to #{user.phone_number}")
|
71
|
+
rescue StandardError => e
|
72
|
+
log(level: :error, msg: "Error caught #{e.class.name}")
|
73
|
+
log(level: :error, msg: "Error caught #{e.message}")
|
74
|
+
log(level: :error, msg: "Failed to send sms to #{user.phone_number}")
|
75
|
+
context.fail!(message: "Failed to send sms to user. Try again.")
|
76
|
+
end
|
77
|
+
|
78
|
+
def send_to_email!(message:)
|
79
|
+
RailsBase::EventMailer.send_sso(user: user, message: message).deliver_me
|
80
|
+
rescue StandardError => e
|
81
|
+
log(level: :error, msg: "Error caught #{e.class.name}")
|
82
|
+
log(level: :error, msg: "Failed to send email to #{user.email}")
|
83
|
+
context.fail!(message: "Failed to send email to user. Try again.")
|
84
|
+
end
|
85
|
+
|
86
|
+
def sso_url(data:)
|
87
|
+
params = {
|
88
|
+
data: data,
|
89
|
+
host: Constants::BASE_URL,
|
90
|
+
}
|
91
|
+
params[:port] = Constants::BASE_URL_PORT if Constants::BASE_URL_PORT
|
92
|
+
Constants::URL_HELPER.sso_retrieve_url(params)
|
93
|
+
end
|
94
|
+
|
95
|
+
def validate!
|
96
|
+
raise "Expected user to be a User. Received #{user.class}" unless user.is_a? User
|
97
|
+
|
98
|
+
# Other validations take place in SingleSignOnCreate class
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RailsBase::Authentication
|
2
|
+
class SingleSignOnVerify < RailsBase::ServiceBase
|
3
|
+
delegate :data, to: :context
|
4
|
+
delegate :reason, to: :context
|
5
|
+
delegate :bypass, to: :context
|
6
|
+
|
7
|
+
def call
|
8
|
+
datum = find_data_point
|
9
|
+
context.data = datum
|
10
|
+
if bypass
|
11
|
+
context.should_fail = !datum[:valid]
|
12
|
+
context.url_redirect = datum[:extra] || RailsBase.url_routes.authenticated_root_path
|
13
|
+
log(level: :info, msg: "sending full data point. bypass set to true")
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
context.sign_in = false
|
18
|
+
if datum[:valid]
|
19
|
+
context.sign_in = true
|
20
|
+
context.url_redirect = datum[:extra] || RailsBase.url_routes.authenticated_root_path
|
21
|
+
context.user = datum[:user]
|
22
|
+
else
|
23
|
+
context.url_redirect = datum[:extra] || RailsBase.url_routes.unauthenticated_root_path
|
24
|
+
context.fail!(message: "Authorization token error: #{datum[:invalid_reason].join(',')}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_data_point
|
29
|
+
params = {
|
30
|
+
data: data,
|
31
|
+
reason: reason,
|
32
|
+
access_count: !(bypass || false)
|
33
|
+
}
|
34
|
+
ShortLivedData.find_datum(params)
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate!
|
38
|
+
raise "Expected data to be a String. Received #{data.class}" unless data.is_a? String
|
39
|
+
raise "Expected reason to be a String. Received #{reason.class}" unless reason.is_a? String
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module RailsBase::Authentication
|
2
|
+
class SsoVerifyEmail < RailsBase::ServiceBase
|
3
|
+
delegate :verification, to: :context
|
4
|
+
|
5
|
+
def call
|
6
|
+
datum = get_short_lived_datum(verification)
|
7
|
+
validate_datum?(datum)
|
8
|
+
|
9
|
+
user = datum[:user]
|
10
|
+
user.update(email_validated: true)
|
11
|
+
|
12
|
+
context.user = datum[:user]
|
13
|
+
params = {
|
14
|
+
expires_at: Time.zone.now + Constants::SVE_TTL,
|
15
|
+
user: user,
|
16
|
+
purpose: Constants::SSOVE_PURPOSE
|
17
|
+
}
|
18
|
+
context.encrypted_val = MfaSetEncryptToken.call(params).encrypted_val
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate_datum?(datum)
|
22
|
+
return true if datum[:valid]
|
23
|
+
|
24
|
+
if datum[:found]
|
25
|
+
msg = "Errors with Email Verification: #{datum[:invalid_reason].join(", ")}. Please login again"
|
26
|
+
log(level: :warn, msg: msg)
|
27
|
+
context.fail!(message: msg, redirect_url: Constants::URL_HELPER.new_user_session_path, level: :warn)
|
28
|
+
end
|
29
|
+
|
30
|
+
log(level: :warn, msg: "Could not find MFA code. Incorrect Email verification code. User may be doing Fishyyyyy things")
|
31
|
+
|
32
|
+
context.fail!(message: "Invalid Email Verification Code. Log in again.", redirect_url: Constants::URL_HELPER.new_user_session_path, level: :warn)
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_short_lived_datum(mfa_code)
|
36
|
+
ShortLivedData.find_datum(data: mfa_code, reason: Constants::SVE_LOGIN_REASON)
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate!
|
40
|
+
raise "verification is expected" if verification.nil?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module RailsBase::Authentication
|
2
|
+
class UpdatePhoneSendVerification < RailsBase::ServiceBase
|
3
|
+
delegate :user, to: :context
|
4
|
+
delegate :phone_number, to: :context
|
5
|
+
|
6
|
+
EXPECTED_LENGTH = 10
|
7
|
+
|
8
|
+
def call
|
9
|
+
if sanitized_phone_number.nil?
|
10
|
+
context.fail!(message: "Unexpected params passed")
|
11
|
+
end
|
12
|
+
|
13
|
+
# should be after successful twilio MFA send
|
14
|
+
# requires a bit of a restructure that I dont have time for
|
15
|
+
update_user_number!
|
16
|
+
|
17
|
+
twilio_sms = SendLoginMfaToUser.call(user: user.reload)
|
18
|
+
|
19
|
+
if twilio_sms.failure?
|
20
|
+
log(level: :error, msg: "Failed with #{twilio_sms.message}")
|
21
|
+
context.fail!(message: twilio_sms.message)
|
22
|
+
end
|
23
|
+
context.expires_at = twilio_sms.short_lived_data.death_time
|
24
|
+
context.mfa_randomized_token =
|
25
|
+
MfaSetEncryptToken.call(user: user, expires_at: context.expires_at, purpose: Constants::MSET_PURPOSE).encrypted_val
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_user_number!
|
29
|
+
log(level: :info, msg: "Received: #{phone_number}. Sanitized to #{sanitized_phone_number}")
|
30
|
+
user.update!(phone_number: sanitized_phone_number)
|
31
|
+
end
|
32
|
+
|
33
|
+
def sanitized_phone_number
|
34
|
+
@sanitized_phone_number ||= begin
|
35
|
+
sanitized = phone_number.tr('^0-9', '')
|
36
|
+
log(level: :debug, msg: "Sanitized phone number to: #{sanitized}. Given: #{sanitized.length} Expected? #{EXPECTED_LENGTH} ")
|
37
|
+
sanitized.length == EXPECTED_LENGTH ? sanitized : nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate!
|
42
|
+
raise "Expected user to be a User. Received #{user.class}" unless user.is_a? User
|
43
|
+
raise "Expected phone_number to be a String. Received #{phone_number.class}" unless phone_number.is_a? String
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module RailsBase::Authentication
|
2
|
+
class VerifyForgotPassword < RailsBase::ServiceBase
|
3
|
+
delegate :data, to: :context
|
4
|
+
|
5
|
+
def call
|
6
|
+
mfa_flow = false
|
7
|
+
data_point = short_lived_data
|
8
|
+
validate_datum?(data_point)
|
9
|
+
|
10
|
+
log(level: :info, msg: "Validated user 2fa email #{data_point[:user].full_name}")
|
11
|
+
context.user = data_point[:user]
|
12
|
+
context.encrypted_val =
|
13
|
+
MfaSetEncryptToken.call(user: data_point[:user], expires_at: Time.zone.now + 10.minutes, purpose: Constants::VFP_PURPOSE).encrypted_val
|
14
|
+
return unless data_point[:user].mfa_enabled
|
15
|
+
|
16
|
+
result = SendLoginMfaToUser.call(user: data_point[:user], expires_at: Time.zone.now + 10.minutes)
|
17
|
+
if result.failure?
|
18
|
+
log(level: :warn, msg: "Attempted to send MFA to user from #{self.class.name}: Exiting with #{result.message}")
|
19
|
+
context.fail!(message: result.message, redirect_url: Constants::URL_HELPER.new_user_password_path, level: :warn)
|
20
|
+
end
|
21
|
+
context.mfa_flow = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_datum?(datum)
|
25
|
+
return true if datum[:valid]
|
26
|
+
|
27
|
+
if datum[:found]
|
28
|
+
msg = "Errors with email validation: #{datum[:invalid_reason].join(", ")}. Please go through forget password flow again."
|
29
|
+
log(level: :warn, msg: msg)
|
30
|
+
context.fail!(message: msg, redirect_url: Constants::URL_HELPER.new_user_password_path, level: :warn)
|
31
|
+
end
|
32
|
+
|
33
|
+
log(level: :warn, msg: "Could not find MFA code. Incorrect MFA code. User is doing something fishy.")
|
34
|
+
|
35
|
+
context.fail!(message: Constants::MV_FISHY, redirect_url: Constants::URL_HELPER.authenticated_root_path, level: :warn)
|
36
|
+
end
|
37
|
+
|
38
|
+
def short_lived_data
|
39
|
+
ShortLivedData.find_datum(data: data, reason: Constants::VFP_REASON)
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate!
|
43
|
+
raise "Expected data to be a String. Received #{data.class}" unless data.is_a? String
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|