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,28 @@
1
+ module RailsBase
2
+ module UserConstants
3
+ ADMIN_ENUMS = [
4
+ ADMIN_ROLE_NONE = :none,
5
+ ADMIN_ROLE_VIEW_ONLY = :view_only,
6
+ ADMIN_ROLE_SUPER = :super,
7
+ ADMIN_ROLE_OWNER = :owner,
8
+ ]
9
+
10
+ SOFT_DESTROY_PARAMS = {
11
+ mfa_enabled: false,
12
+ email_validated: false,
13
+ last_mfa_login: nil,
14
+ encrypted_password: '',
15
+ phone_number: nil,
16
+ }
17
+
18
+ SAFE_AUTOMAGIC_UPGRADE_COLS = {
19
+ active: ->(user) { RailsBase.config.admin.active_tile_users?(user) } ,
20
+ admin: ->(user) { RailsBase.config.admin.admin_type_tile_users?(user) } ,
21
+ email: ->(user) { RailsBase.config.admin.email_tile_users?(user) } ,
22
+ email_validated: ->(user) { RailsBase.config.admin.email_validate_tile_users?(user) } ,
23
+ mfa_enabled: ->(user) { RailsBase.config.admin.mfa_tile_users?(user) } ,
24
+ phone_number: ->(user) { RailsBase.config.admin.phone_tile_users?(user) } ,
25
+ last_known_timezone: ->(user) { RailsBase.config.admin.modify_timezone_tile_users?(user) }
26
+ }
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: secrets
4
+ #
5
+ # id :bigint not null, primary key
6
+ # version :integer
7
+ # secret :text(65535)
8
+ # name :string(255)
9
+ # created_at :datetime not null
10
+ # updated_at :datetime not null
11
+ #
12
+
13
+ class Secret < RailsBase::ApplicationRecord
14
+ class << self
15
+ def update(name:, secret:)
16
+ next_version = get_secrets(name: name).select(:version).last&.version || 0
17
+ next_version += 1 # always increase the version
18
+
19
+ instance = new(version: next_version, name: name, secret: secret)
20
+ instance.save!
21
+
22
+ instance
23
+ end
24
+
25
+ def get_current_secret(name:)
26
+ get_secrets(name: name).last
27
+ end
28
+
29
+ def get_secret_range(name:, range: [-2..-1])
30
+ where(name: name)[*range]
31
+ end
32
+
33
+ def get_secrets(name:)
34
+ where(name: name)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,132 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: short_lived_data
4
+ #
5
+ # id :bigint not null, primary key
6
+ # user_id :integer not null
7
+ # data :string(255) not null
8
+ # reason :string(255)
9
+ # death_time :datetime not null
10
+ # extra :string(255)
11
+ # created_at :datetime not null
12
+ # updated_at :datetime not null
13
+ # exclusive_use_count :integer default(0)
14
+ # exclusive_use_count_max :integer
15
+ #
16
+
17
+ class ShortLivedData < RailsBase::ApplicationRecord
18
+ self.table_name = 'short_lived_data'
19
+
20
+ DEFAULT_TIME_TO_LIVE = 1.hour.freeze
21
+ LENGTH_OF_HEX = 64.freeze
22
+ MAX_ATTEMPTS = 10.freeze
23
+ VALID_DATA_USE_LENGTH = [:numeric, :alphanumeric, :hex].freeze
24
+ VALID_DATA_NON_LENGTH = [:uuid]
25
+ VALID_DATA_USE = [VALID_DATA_NON_LENGTH, VALID_DATA_USE_LENGTH].flatten.freeze
26
+
27
+ class << self
28
+ # ShortLivedData.create_data_key(user: User.first, ttl: 5.hours)
29
+ def create_data_key(user:, max_use: nil, data: nil, data_use: :alphanumeric, expires_at: nil, ttl: DEFAULT_TIME_TO_LIVE, reason: 'default', length: LENGTH_OF_HEX, extra: nil)
30
+ raise ":ttl is expected to be an ActiveSupport::Duration" unless ttl.is_a?(ActiveSupport::Duration)
31
+
32
+ if data.nil?
33
+ data = generate_secure_datum(data_use, length: length)
34
+ attempt = 0
35
+ while get_by_data(data: data, reason: reason)
36
+ Rails.logger.warn "Data key already in use for #{data_use}. Attempt #{attempt} for reason #{reason} for user_id #{user.id}"
37
+ data = generate_secure_hex(data_use, length: length)
38
+ attempt +=1
39
+ raise "Failed to generate unique id. Attempted #{attempt} times" if attempt >= MAX_ATTEMPTS
40
+ end
41
+ end
42
+
43
+ # expires at takes precedence since this is a dangerous oeration and should only be done with caution
44
+ # dangerous because of time zone checks --- we dont do that
45
+ death_time = Time.now + ttl
46
+ death_time = expires_at if expires_at
47
+
48
+ create(user_id: user.id, data: data, death_time: death_time, reason: reason, extra: extra, exclusive_use_count_max: max_use)
49
+ end
50
+
51
+ def get_by_data(data:, reason: nil)
52
+ # data is indexed and uniq
53
+ params = { data: data, reason: reason }.compact
54
+ where(params).first
55
+ end
56
+
57
+ def generate_secure_datum(data_use, length: LENGTH_OF_HEX)
58
+ case data_use.to_sym
59
+ when :numeric
60
+ return rand.to_s[2..(2+(length-1))]
61
+ when *VALID_DATA_USE_LENGTH
62
+ return SecureRandom.public_send(data_use, length)
63
+ when *VALID_DATA_NON_LENGTH
64
+ return SecureRandom.public_send(data_use)
65
+ else
66
+ raise ArgumentError, "Unexpected data_use: Expected #{VALID_DATA_USE}. given [#{data_use}]"
67
+ end
68
+ end
69
+
70
+ def find_datum(data:, reason: nil, access_count: true)
71
+ datum = get_by_data(data: data, reason: reason)
72
+
73
+ params = {
74
+ user: datum&.user,
75
+ use_count: datum&.exclusive_use_count,
76
+ max_use_count: datum&.exclusive_use_count_max,
77
+ valid: datum&.is_valid? || false,
78
+ invalid_reason: datum&.invalid_reason || ['Forbidden. Invalid usecase'],
79
+ found: !datum.nil?,
80
+ extra: datum&.extra,
81
+ access_count_proc: -> { datum&.add_access_count! }
82
+ }
83
+ datum&.add_access_count! if access_count
84
+
85
+ return params unless params[:valid]
86
+
87
+ if reason && (datum&.reason.to_sym != reason.to_sym)
88
+ params[:valid] = false
89
+ params[:invalid_reason] = ['Unknown reason for datum field']
90
+ end
91
+
92
+ params
93
+ end
94
+ end
95
+
96
+ def add_access_count!
97
+ # only update if count is valid and we can add things -- save db call
98
+ return false unless used_count_valid?
99
+
100
+ update_attributes(exclusive_use_count: exclusive_use_count + 1)
101
+ end
102
+
103
+ def invalid_reason
104
+ arr = []
105
+ arr << 'too many uses' unless used_count_valid?
106
+ arr << 'expired' unless still_alive?
107
+ arr
108
+ end
109
+
110
+ def is_valid?
111
+ used_count_valid? && still_alive?
112
+ end
113
+
114
+ def still_alive?
115
+ (death_time).to_f > Time.now.to_f
116
+ end
117
+
118
+ def used_count_valid?
119
+ return true if exclusive_use_count_max.nil?
120
+
121
+ return exclusive_use_count < exclusive_use_count_max
122
+ end
123
+
124
+ def user
125
+ @user ||= User.find(user_id)
126
+ end
127
+
128
+ def user=(u)
129
+ update_attributes(user_id: u.id)
130
+ u.id
131
+ end
132
+ end
@@ -0,0 +1,143 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: users
4
+ #
5
+ # id :bigint not null, primary key
6
+ # first_name :string(255) default(""), not null
7
+ # last_name :string(255) default(""), not null
8
+ # phone_number :string(255)
9
+ # last_mfa_login :datetime
10
+ # email_validated :boolean default(FALSE)
11
+ # mfa_enabled :boolean default(FALSE), not null
12
+ # active :boolean default(TRUE), not null
13
+ # admin :string(255)
14
+ # last_known_timezone :string(255)
15
+ # last_known_timezone_update :datetime
16
+ # email :string(255) default(""), not null
17
+ # encrypted_password :string(255) default(""), not null
18
+ # reset_password_token :string(255)
19
+ # reset_password_sent_at :datetime
20
+ # remember_created_at :datetime
21
+ # sign_in_count :integer default(0), not null
22
+ # current_sign_in_at :datetime
23
+ # last_sign_in_at :datetime
24
+ # current_sign_in_ip :string(255)
25
+ # last_sign_in_ip :string(255)
26
+ # created_at :datetime not null
27
+ # updated_at :datetime not null
28
+ #
29
+ class User < RailsBase::ApplicationRecord
30
+ # Include default devise modules. Others available are:
31
+ # :confirmable, :lockable, :trackable and :omniauthable
32
+ devise :database_authenticatable, :registerable,
33
+ :recoverable, :rememberable, :validatable, :timeoutable, :trackable
34
+
35
+ include RailsBase::UserConstants
36
+
37
+ validate :enforce_owner, if: :will_save_change_to_admin?
38
+ validate :enforce_admin_type, if: :will_save_change_to_admin?
39
+
40
+ def self._def_admin_convenience_method!(admin_method:)
41
+ types = RailsBase.config.admin.admin_types
42
+ #### metods on the instance
43
+ define_method("at_least_#{admin_method}?") do
44
+ i = types.find_index(admin.to_sym)
45
+ i >= types.find_index(admin_method.to_sym)
46
+ end
47
+
48
+ define_method("admin_#{admin_method}?") do
49
+ admin.to_sym == admin_method
50
+ end
51
+
52
+ define_method("admin_#{admin_method}!") do
53
+ update_attributes!(admin: admin_method)
54
+ end
55
+
56
+ #### metods on the class
57
+ define_singleton_method("admin_#{admin_method}s") do
58
+ where(admin: admin_method)
59
+ end
60
+
61
+ define_singleton_method("admin_#{admin_method}") do
62
+ arr = [admin_method]
63
+ arr = [admin_method, '', nil] if ADMIN_ROLE_NONE == admin_method
64
+ where(admin: arr)
65
+ end
66
+ end
67
+
68
+ def self.time_bound
69
+ Time.zone.now - RailsBase.config.auth.mfa_time_duration
70
+ end
71
+
72
+ def admin
73
+ (self[:admin].presence || ADMIN_ROLE_NONE).to_sym
74
+ end
75
+
76
+ def full_name
77
+ "#{first_name} #{last_name}"
78
+ end
79
+
80
+ def past_mfa_time_duration?
81
+ return true if last_mfa_login.nil?
82
+
83
+ last_mfa_login < self.class.time_bound
84
+ end
85
+
86
+ def set_last_mfa_login!(time: Time.zone.now)
87
+ update(last_mfa_login: time)
88
+ end
89
+
90
+ def masked_phone
91
+ return nil unless phone_number
92
+
93
+ "(#{phone_number[0]}**) ****-**#{phone_number[-2..-1]}"
94
+ end
95
+
96
+ def soft_destroy_user!
97
+ update(SOFT_DESTROY_PARAMS)
98
+ end
99
+
100
+ def destroy_user!
101
+ self.delete
102
+ end
103
+
104
+ def inspect_name
105
+ "[#{id}]: #{full_name}"
106
+ end
107
+
108
+ def update_tz(tz_name:)
109
+ return if last_known_timezone == tz_name
110
+
111
+ Rails.logger.info { "#{id}: Setting tz_name: #{tz_name}" }
112
+ update_attributes(last_known_timezone: tz_name, last_known_timezone_update: Time.now )
113
+ end
114
+
115
+ def timezone
116
+ RailsBase.config.user.user_timezone(self)
117
+ end
118
+
119
+ def convert_time(time:)
120
+ time.in_time_zone(timezone)
121
+ end
122
+
123
+ private
124
+
125
+ def enforce_admin_type
126
+ from, to = admin_change_to_be_saved
127
+ return if RailsBase.config.admin.admin_types.include?(to.to_sym)
128
+
129
+ errors.add(:admin, "Undefined admin type. Expected #{RailsBase.config.admin.admin_types}. Given #{to}")
130
+ end
131
+
132
+ def enforce_owner
133
+ from, to = admin_change_to_be_saved
134
+ # skip validation event if we are not updating to owner role
135
+ return if to.to_sym != ADMIN_ROLE_OWNER
136
+
137
+ # add 1 because we are trying to change the current user to ADMIN_ROLE_OWNER
138
+ count = User.where(admin: ADMIN_ROLE_OWNER).count + 1
139
+ return if count <= RailsBase.config.owner.max
140
+
141
+ errors.add(:admin, "unable to have more than #{RailsBase.config.owner.max} owner(s).")
142
+ end
143
+ end
@@ -0,0 +1,80 @@
1
+ require 'twilio_helper'
2
+ require 'velocity_limiter'
3
+
4
+ module RailsBase
5
+ class AdminRiskyMfaSend < RailsBase::ServiceBase
6
+ include ActionView::Helpers::DateHelper
7
+ include VelocityLimiter
8
+
9
+ class NoPhoneNumber < StandardError; end
10
+
11
+ MAX_USE_COUNT = 1.freeze
12
+ DATA_USE = :numeric
13
+ EXPIRES_AT = 1.minutes
14
+
15
+ delegate :user, to: :context
16
+ delegate :reason, to: :context
17
+
18
+ def call
19
+ validate_phone!
20
+
21
+ velocity = velocity_limit_reached?
22
+ context.fail!(message: velocity[:msg]) if velocity[:reached]
23
+
24
+ data_point = create_short_lived_data
25
+ send_twilio!(data_point.data)
26
+ context.short_lived_data = data_point
27
+ context.message = "MFA code has been succesfully sent. you have #{EXPIRES_AT}"
28
+ end
29
+
30
+ def send_twilio!(code)
31
+ TwilioJob.perform_later(message: message(code), to: user.phone_number)
32
+ log(level: :info, msg: "Sent twilio message to #{user.phone_number}")
33
+ rescue StandardError => e
34
+ log(level: :error, msg: "Error caught #{e.class.name}")
35
+ log(level: :error, msg: "Failed to send sms to #{user.phone_number}")
36
+ context.fail!(message: "Failed to send sms. Please retry logging in.")
37
+ end
38
+
39
+ def message(code)
40
+ "Hello #{user.full_name}. Here is your admin verification code #{code}."
41
+ end
42
+
43
+ def create_short_lived_data
44
+ params = {
45
+ user: user,
46
+ max_use: MAX_USE_COUNT,
47
+ reason: reason,
48
+ data_use: DATA_USE,
49
+ expires_at: EXPIRES_AT.from_now,
50
+ length: Authentication::Constants::MFA_LENGTH,
51
+ }
52
+ ShortLivedData.create_data_key(params)
53
+ end
54
+
55
+ def velocity_max_in_frame
56
+ RailsBase.config.admin.admin_velocity_max_in_frame
57
+ end
58
+
59
+ def velocity_max
60
+ RailsBase.config.admin.admin_velocity_max
61
+ end
62
+
63
+ def velocity_frame
64
+ RailsBase.config.admin.admin_velocity_frame
65
+ end
66
+
67
+ def cache_key
68
+ "#{self.class.name.downcase}.#{user.id}"
69
+ end
70
+
71
+ def validate_phone!
72
+ context.fail!(message: "No phone for user [#{user.id}] [#{user.phone_number}]") if user.phone_number.nil?
73
+ end
74
+
75
+ def validate!
76
+ raise "Expected user to be a User. Received #{user.class}" unless user.is_a? User
77
+ raise "Expected reason to be a present." if reason.nil?
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,100 @@
1
+ module RailsBase
2
+ class AdminUpdateAttribute < RailsBase::ServiceBase
3
+ delegate :params, to: :context
4
+ delegate :klass_string, to: :context
5
+ delegate :admin_user, to: :context
6
+
7
+ def call
8
+ validate_model!
9
+ validate_model_row!
10
+
11
+ attribute = validate_attribute!
12
+ validate_permission!(attribute: attribute)
13
+ fail_attribute!(attribute: attribute)
14
+
15
+ original_value = model_row.public_send(attribute)
16
+ begin
17
+ model_row.update_attributes!(attribute => sanitized_value)
18
+ rescue ActiveRecord::RecordInvalid => e
19
+ context.fail!(message: "Failed to update [#{attribute}] with #{sanitized_value} on #{model}##{model_row.id}. #{e.message}")
20
+ rescue StandardError
21
+ context.fail!(message: "Failed to update [#{attribute}] with #{sanitized_value} on #{model}##{model_row.id}")
22
+ end
23
+ context.attribute = sanitized_value
24
+ context.original_attribute = original_value
25
+ context.model = model_row
26
+ context.message = "#{model}##{params[:id]} has changed attribute [#{attribute}] from [#{original_value}]=>#{sanitized_value}"
27
+ end
28
+
29
+ def fail_attribute!(attribute:)
30
+ if params[:_fail_].present?
31
+ log(level: :warn, msg: "FAIL param passed in. Automagic failure for admin update")
32
+ context.fail!(message: "Failed to update [#{attribute}] with #{sanitized_value} on #{model}##{model_row.id}")
33
+ end
34
+ end
35
+
36
+ def validate_permission!(attribute:)
37
+ proc = model::SAFE_AUTOMAGIC_UPGRADE_COLS[attribute]
38
+ return if proc.call(admin_user)
39
+
40
+ context.fail!(message: "User does not have permissions to update #{attribute}")
41
+ end
42
+
43
+ def validate_attribute!
44
+ attribute = params[:attribute].to_sym
45
+ unless model::SAFE_AUTOMAGIC_UPGRADE_COLS.keys.include?(attribute)
46
+ context.fail!(message: "#{attribute} is not part of allowed updatable columns")
47
+ end
48
+ attribute
49
+ end
50
+
51
+ def sanitized_value
52
+ case params[:value]
53
+ when 'true'
54
+ true
55
+ when 'false'
56
+ false
57
+ else
58
+ params[:value]
59
+ end
60
+ end
61
+
62
+ def model
63
+ @model ||= klass_string ? klass_string.constantize : User
64
+ end
65
+
66
+ def model_row
67
+ @model_row ||= begin
68
+ model.find(params[:id])
69
+ rescue ActiveRecord::RecordNotFound
70
+ nil
71
+ end
72
+ end
73
+
74
+ def validate_model_row!
75
+ return if model_row
76
+
77
+ context.fail!(message: "Failed to find id:#{params[:id]} on model: #{model}")
78
+ end
79
+
80
+ def validate_model!
81
+ begin
82
+ model
83
+ rescue StandardError
84
+ context.fail!(message: "Failed to find model")
85
+ end
86
+
87
+ begin
88
+ model::SAFE_AUTOMAGIC_UPGRADE_COLS
89
+ rescue StandardError
90
+ context.fail!(message: "#{model}::SAFE_AUTOMAGIC_UPGRADE_COLS array does not exist")
91
+ end
92
+ end
93
+
94
+ def validate!
95
+ raise "Expected params to be a Hash. Received #{params.class}" unless [ActionController::Parameters, Hash].include?(params.class)
96
+ raise "Expected params to have a id. Received #{params[:id]}" if params[:id].nil?
97
+ raise "Expected admin_user." if admin_user.nil?
98
+ end
99
+ end
100
+ end