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.
Files changed (194) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +32 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/rails_base/manifest.js +3 -0
  6. data/app/assets/images/rails_base/favicon.ico +0 -0
  7. data/app/assets/javascripts/rails_base/admin.js +2 -0
  8. data/app/assets/javascripts/rails_base/application.js +22 -0
  9. data/app/assets/javascripts/rails_base/cable.js +13 -0
  10. data/app/assets/javascripts/rails_base/mfa_auth.coffee +3 -0
  11. data/app/assets/javascripts/rails_base/secondary_authentication.coffee +3 -0
  12. data/app/assets/javascripts/rails_base/sessions.js +152 -0
  13. data/app/assets/javascripts/rails_base/user_settings.coffee +3 -0
  14. data/app/assets/stylesheets/rails_base/admin.css +4 -0
  15. data/app/assets/stylesheets/rails_base/application.scss +15 -0
  16. data/app/assets/stylesheets/rails_base/mfa_auth.scss +3 -0
  17. data/app/assets/stylesheets/rails_base/scaffolds.scss +84 -0
  18. data/app/assets/stylesheets/rails_base/secondary_authentication.scss +3 -0
  19. data/app/assets/stylesheets/rails_base/user_settings.scss +3 -0
  20. data/app/controllers/rails_base/admin_controller.rb +315 -0
  21. data/app/controllers/rails_base/application_controller.rb +153 -0
  22. data/app/controllers/rails_base/errors_controller.rb +29 -0
  23. data/app/controllers/rails_base/mfa_auth_controller.rb +50 -0
  24. data/app/controllers/rails_base/secondary_authentication_controller.rb +224 -0
  25. data/app/controllers/rails_base/switch_user_controller.rb +29 -0
  26. data/app/controllers/rails_base/user_settings_controller.rb +81 -0
  27. data/app/controllers/rails_base/users/passwords_controller.rb +19 -0
  28. data/app/controllers/rails_base/users/registrations_controller.rb +80 -0
  29. data/app/controllers/rails_base/users/sessions_controller.rb +108 -0
  30. data/app/helpers/rails_base/admin_helper.rb +107 -0
  31. data/app/helpers/rails_base/appearance_helper.rb +58 -0
  32. data/app/helpers/rails_base/application_helper.rb +26 -0
  33. data/app/helpers/rails_base/capture_reference_helper.rb +57 -0
  34. data/app/helpers/rails_base/mfa_auth_helper.rb +2 -0
  35. data/app/helpers/rails_base/secondary_authentication_helper.rb +2 -0
  36. data/app/helpers/rails_base/user_field_validators.rb +108 -0
  37. data/app/helpers/rails_base/user_settings_helper.rb +22 -0
  38. data/app/jobs/rails_base/application_job.rb +10 -0
  39. data/app/jobs/twilio_job.rb +9 -0
  40. data/app/mailers/rails_base/application_mailer.rb +9 -0
  41. data/app/mailers/rails_base/email_verification_mailer.rb +22 -0
  42. data/app/mailers/rails_base/event_mailer.rb +16 -0
  43. data/app/models/admin_action.rb +119 -0
  44. data/app/models/rails_base/application_record.rb +22 -0
  45. data/app/models/rails_base/user_constants.rb +28 -0
  46. data/app/models/secret.rb +37 -0
  47. data/app/models/short_lived_data.rb +132 -0
  48. data/app/models/user.rb +143 -0
  49. data/app/services/rails_base/admin_risky_mfa_send.rb +80 -0
  50. data/app/services/rails_base/admin_update_attribute.rb +100 -0
  51. data/app/services/rails_base/authentication/authenticate_user.rb +28 -0
  52. data/app/services/rails_base/authentication/constants.rb +60 -0
  53. data/app/services/rails_base/authentication/decision_twofa_type.rb +76 -0
  54. data/app/services/rails_base/authentication/destroy_user.rb +45 -0
  55. data/app/services/rails_base/authentication/mfa_set_encrypt_token.rb +32 -0
  56. data/app/services/rails_base/authentication/mfa_validator.rb +88 -0
  57. data/app/services/rails_base/authentication/modify_password.rb +67 -0
  58. data/app/services/rails_base/authentication/send_forgot_password.rb +26 -0
  59. data/app/services/rails_base/authentication/send_login_mfa_to_user.rb +77 -0
  60. data/app/services/rails_base/authentication/send_verification_email.rb +103 -0
  61. data/app/services/rails_base/authentication/session_token_verifier.rb +31 -0
  62. data/app/services/rails_base/authentication/single_sign_on_create.rb +44 -0
  63. data/app/services/rails_base/authentication/single_sign_on_send.rb +101 -0
  64. data/app/services/rails_base/authentication/single_sign_on_verify.rb +42 -0
  65. data/app/services/rails_base/authentication/sso_verify_email.rb +43 -0
  66. data/app/services/rails_base/authentication/update_phone_send_verification.rb +46 -0
  67. data/app/services/rails_base/authentication/verify_forgot_password.rb +46 -0
  68. data/app/services/rails_base/email_change.rb +20 -0
  69. data/app/services/rails_base/encryption.rb +87 -0
  70. data/app/services/rails_base/name_change.rb +71 -0
  71. data/app/services/rails_base/service_base.rb +65 -0
  72. data/app/services/rails_base/service_logging.rb +23 -0
  73. data/app/views/layouts/rails_base/application.html.erb +185 -0
  74. data/app/views/layouts/rails_base/mailer.html.erb +13 -0
  75. data/app/views/layouts/rails_base/mailer.text.erb +1 -0
  76. data/app/views/new.html.erb +4 -0
  77. data/app/views/rails_base/admin/history.html.erb +26 -0
  78. data/app/views/rails_base/admin/index.html.erb +149 -0
  79. data/app/views/rails_base/admin/show_config.html.erb +18 -0
  80. data/app/views/rails_base/devise/confirmations/new.html.erb +16 -0
  81. data/app/views/rails_base/devise/mailer/confirmation_instructions.html.erb +5 -0
  82. data/app/views/rails_base/devise/mailer/email_changed.html.erb +7 -0
  83. data/app/views/rails_base/devise/mailer/password_change.html.erb +3 -0
  84. data/app/views/rails_base/devise/mailer/reset_password_instructions.html.erb +8 -0
  85. data/app/views/rails_base/devise/mailer/unlock_instructions.html.erb +7 -0
  86. data/app/views/rails_base/devise/passwords/edit.html.erb +25 -0
  87. data/app/views/rails_base/devise/passwords/new.html.erb +27 -0
  88. data/app/views/rails_base/devise/registrations/edit.html.erb +43 -0
  89. data/app/views/rails_base/devise/registrations/new.html.erb +123 -0
  90. data/app/views/rails_base/devise/sessions/new.html.erb +4 -0
  91. data/app/views/rails_base/devise/shared/_error_messages.html.erb +15 -0
  92. data/app/views/rails_base/devise/shared/_links.html.erb +25 -0
  93. data/app/views/rails_base/devise/unlocks/new.html.erb +16 -0
  94. data/app/views/rails_base/email_verification_mailer/email_verification.html.erb +25 -0
  95. data/app/views/rails_base/email_verification_mailer/event.html.erb +20 -0
  96. data/app/views/rails_base/email_verification_mailer/forgot_password.html.erb +22 -0
  97. data/app/views/rails_base/errors/internal_error.html.erb +1 -0
  98. data/app/views/rails_base/errors/not_found.html.erb +1 -0
  99. data/app/views/rails_base/errors/unacceptable.html.erb +1 -0
  100. data/app/views/rails_base/event_mailer/event.html.erb +10 -0
  101. data/app/views/rails_base/mfa_auth/mfa_code.html.erb +10 -0
  102. data/app/views/rails_base/secondary_authentication/after_email_login_session_new.html.erb +3 -0
  103. data/app/views/rails_base/secondary_authentication/forgot_password.html.erb +9 -0
  104. data/app/views/rails_base/secondary_authentication/remove_me.html.erb +1 -0
  105. data/app/views/rails_base/secondary_authentication/static.html.erb +5 -0
  106. data/app/views/rails_base/shared/_admin_actions_modal.html.erb +65 -0
  107. data/app/views/rails_base/shared/_admin_config_class.html.erb +52 -0
  108. data/app/views/rails_base/shared/_admin_history.html.erb +86 -0
  109. data/app/views/rails_base/shared/_admin_modify_email.html.erb +78 -0
  110. data/app/views/rails_base/shared/_admin_modify_name.html.erb +107 -0
  111. data/app/views/rails_base/shared/_admin_modify_phone.html.erb +87 -0
  112. data/app/views/rails_base/shared/_admin_modify_text.html.erb +35 -0
  113. data/app/views/rails_base/shared/_admin_risky_change.html.erb +57 -0
  114. data/app/views/rails_base/shared/_admin_risky_mfa.html.erb +74 -0
  115. data/app/views/rails_base/shared/_admin_selector_dropdown.html.erb +70 -0
  116. data/app/views/rails_base/shared/_admin_toggle_button.html.erb +72 -0
  117. data/app/views/rails_base/shared/_admin_warning_alert.html.erb +7 -0
  118. data/app/views/rails_base/shared/_appearance_mode_selector.html.erb +183 -0
  119. data/app/views/rails_base/shared/_custom_form_validation_javascript.html.erb +129 -0
  120. data/app/views/rails_base/shared/_enable_mfa_auth_modal.html.erb +105 -0
  121. data/app/views/rails_base/shared/_error_pages.html.erb +123 -0
  122. data/app/views/rails_base/shared/_logged_in_header.html.erb +123 -0
  123. data/app/views/rails_base/shared/_logged_out_header.html.erb +14 -0
  124. data/app/views/rails_base/shared/_mfa_input_layout.html.erb +5 -0
  125. data/app/views/rails_base/shared/_mfa_input_layout_default.html.erb +97 -0
  126. data/app/views/rails_base/shared/_mfa_input_layout_fallback.html.erb +55 -0
  127. data/app/views/rails_base/shared/_modify_mfa_auth_modal.html.erb +20 -0
  128. data/app/views/rails_base/shared/_password_confirm_javascript.html.erb +71 -0
  129. data/app/views/rails_base/shared/_reset_password_form.html.erb +111 -0
  130. data/app/views/rails_base/shared/_session_create_form.html.erb +32 -0
  131. data/app/views/rails_base/shared/_session_timeout_modal.html.erb +76 -0
  132. data/app/views/rails_base/switch_user/_widget.html.erb +5 -0
  133. data/app/views/rails_base/user_settings/_confirm_destroy_user.html.erb +42 -0
  134. data/app/views/rails_base/user_settings/_destroy_user.html.erb +106 -0
  135. data/app/views/rails_base/user_settings/_modify_name.html.erb +71 -0
  136. data/app/views/rails_base/user_settings/_modify_password.html.erb +101 -0
  137. data/app/views/rails_base/user_settings/_modify_password_update_password.html.erb +2 -0
  138. data/app/views/rails_base/user_settings/index.html.erb +54 -0
  139. data/config/initializers/01_rails_config.rb +19 -0
  140. data/config/initializers/admin_action_helper.rb +88 -0
  141. data/config/initializers/browser.rb +4 -0
  142. data/config/initializers/default_logged_in_headers.rb +23 -0
  143. data/config/initializers/devise.rb +314 -0
  144. data/config/initializers/encryption.rb +2 -0
  145. data/config/initializers/switch_user.rb +58 -0
  146. data/config/initializers/switch_user_helper.rb +29 -0
  147. data/config/locales/devise.en.yml +65 -0
  148. data/config/locales/en.yml +58 -0
  149. data/config/routes.rb +114 -0
  150. data/db/migrate/20210212175453_devise_create_rails_base_users.rb +56 -0
  151. data/db/migrate/20210212190537_create_rails_base_short_lived_data.rb +19 -0
  152. data/db/migrate/20210212192645_create_rails_base_secrets.rb +11 -0
  153. data/db/migrate/20210406015744_create_rails_base_admin_actions.rb +17 -0
  154. data/db/seeds.rb +23 -0
  155. data/lib/link_decision_helper.rb +71 -0
  156. data/lib/rails_base.rb +50 -0
  157. data/lib/rails_base/admin/action_cache.rb +99 -0
  158. data/lib/rails_base/admin/action_helper.rb +134 -0
  159. data/lib/rails_base/admin/default_index_tile.rb +176 -0
  160. data/lib/rails_base/admin/index_tile.rb +186 -0
  161. data/lib/rails_base/config.rb +52 -0
  162. data/lib/rails_base/configuration/active_job.rb +38 -0
  163. data/lib/rails_base/configuration/admin.rb +231 -0
  164. data/lib/rails_base/configuration/app.rb +52 -0
  165. data/lib/rails_base/configuration/appearance.rb +131 -0
  166. data/lib/rails_base/configuration/authentication.rb +37 -0
  167. data/lib/rails_base/configuration/base.rb +209 -0
  168. data/lib/rails_base/configuration/display/background_color.rb +25 -0
  169. data/lib/rails_base/configuration/display/btn_danger.rb +25 -0
  170. data/lib/rails_base/configuration/display/btn_dark.rb +25 -0
  171. data/lib/rails_base/configuration/display/btn_info.rb +25 -0
  172. data/lib/rails_base/configuration/display/btn_light.rb +25 -0
  173. data/lib/rails_base/configuration/display/btn_primary.rb +25 -0
  174. data/lib/rails_base/configuration/display/btn_secondary.rb +25 -0
  175. data/lib/rails_base/configuration/display/btn_success.rb +25 -0
  176. data/lib/rails_base/configuration/display/btn_warning.rb +25 -0
  177. data/lib/rails_base/configuration/display/footer.rb +54 -0
  178. data/lib/rails_base/configuration/display/navbar.rb +25 -0
  179. data/lib/rails_base/configuration/display/table_body.rb +25 -0
  180. data/lib/rails_base/configuration/display/table_header.rb +25 -0
  181. data/lib/rails_base/configuration/display/text.rb +26 -0
  182. data/lib/rails_base/configuration/exceptions_app.rb +25 -0
  183. data/lib/rails_base/configuration/login_behavior.rb +17 -0
  184. data/lib/rails_base/configuration/mailer.rb +116 -0
  185. data/lib/rails_base/configuration/mfa.rb +84 -0
  186. data/lib/rails_base/configuration/owner.rb +17 -0
  187. data/lib/rails_base/configuration/redis.rb +29 -0
  188. data/lib/rails_base/configuration/user.rb +43 -0
  189. data/lib/rails_base/engine.rb +51 -0
  190. data/lib/rails_base/version.rb +10 -0
  191. data/lib/tasks/rails_base_tasks.rake +4 -0
  192. data/lib/twilio_helper.rb +26 -0
  193. data/lib/velocity_limiter.rb +91 -0
  194. 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