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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac8c98816130bae4d552e2b82ecf75c5a3b8f6441936bb70d9663f07a1ee4c2f
|
4
|
+
data.tar.gz: f9df9901477573b0e827019a141996cec8c2192e940d334d91da624a8c69dc07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f868b2bb553e9e1f4fcd4e14abda131b153e38f6b43f1bfdfef2f34b5a3d0b2d75d31a8eec7720d3a8339db74782706f9e0d39e7284256416130601233bb54d7
|
7
|
+
data.tar.gz: bf26e5f0ae48f7674ee614d7a159d352c990ddced01a7ca28ccb3215dae5a6b6f0b9c3e218b05ec4eb34d917da4c6296bdcd825a511ae8b8a7ab037317f854c8
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
function _railsBase_urlParams(param){
|
3
|
+
urlParams = new URLSearchParams(window.location.search);
|
4
|
+
return urlParams.get(param);
|
5
|
+
}
|
6
|
+
|
7
|
+
function _railsBase_goToStandardizedCollapse(q_param, identifier, function_base_name, function_yield){
|
8
|
+
param = _railsBase_urlParams(q_param)
|
9
|
+
if(param==null){
|
10
|
+
return false
|
11
|
+
}
|
12
|
+
|
13
|
+
// Let callee decide if they want to continue
|
14
|
+
if(typeof(function_yield) === "function") {
|
15
|
+
if (function_yield(param) != true) {
|
16
|
+
// Callee does not want to continue
|
17
|
+
return
|
18
|
+
}
|
19
|
+
} else {
|
20
|
+
// No function provided. Since the param was present, we will continue as expected
|
21
|
+
}
|
22
|
+
|
23
|
+
// Scroll to top of provided class
|
24
|
+
$('html, body').animate({
|
25
|
+
scrollTop: $(`${identifier}`).offset().top
|
26
|
+
}, 'slow');
|
27
|
+
|
28
|
+
|
29
|
+
// function name declared for the collapsable options
|
30
|
+
// Toggle it and open it up
|
31
|
+
console.log(`trying to open ${function_base_name}_collapse_toggle()`)
|
32
|
+
eval(`${function_base_name}_collapse_toggle()`)
|
33
|
+
|
34
|
+
return param
|
35
|
+
}
|
36
|
+
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RailsBase
|
2
4
|
class AdminController < RailsBaseApplicationController
|
3
5
|
before_action :authenticate_user!, except: [:sso_retrieve]
|
@@ -9,9 +11,10 @@ module RailsBase
|
|
9
11
|
|
10
12
|
# GET admin
|
11
13
|
def index
|
14
|
+
add_mfa_event_to_session(event: RailsBase::MfaEvent.admin_actions(user: current_user))
|
12
15
|
end
|
13
16
|
|
14
|
-
# GET admin
|
17
|
+
# GET admin/config
|
15
18
|
def show_config
|
16
19
|
unless RailsBase.config.admin.config_page?(current_user)
|
17
20
|
flash[:alert] = 'You do not have correct permissions to view admin config'
|
@@ -36,7 +39,7 @@ module RailsBase
|
|
36
39
|
uses: Authentication::Constants::SSO_SEND_USES,
|
37
40
|
reason: Authentication::Constants::SSO_REASON,
|
38
41
|
expires_at: Authentication::Constants::SSO_EXPIRES.from_now,
|
39
|
-
url_redirect: RailsBase.url_routes.
|
42
|
+
url_redirect: RailsBase.url_routes.authenticated_root_path
|
40
43
|
}
|
41
44
|
|
42
45
|
status = RailsBase::Authentication::SingleSignOnSend.call(local_params)
|
@@ -50,6 +53,7 @@ module RailsBase
|
|
50
53
|
redirect_to RailsBase.url_routes.admin_base_path
|
51
54
|
end
|
52
55
|
|
56
|
+
# TODO: Move this to a different controller
|
53
57
|
#GET auth/sso/:data
|
54
58
|
def sso_retrieve
|
55
59
|
local_params = {
|
@@ -153,6 +157,14 @@ module RailsBase
|
|
153
157
|
|
154
158
|
# POST admin/update
|
155
159
|
def update_attribute
|
160
|
+
unless RailsBase.config.admin.view_admin_page?(current_user)
|
161
|
+
session.clear
|
162
|
+
sign_out(current_user)
|
163
|
+
logger.warn("Unauthorized user has tried to update attributes. Fail Quickly!")
|
164
|
+
render json: { success: false, message: "Unauthorized action. You have been signed out" }, status: 404
|
165
|
+
return
|
166
|
+
end
|
167
|
+
|
156
168
|
update = RailsBase::AdminUpdateAttribute.call(params: params, admin_user: admin_user)
|
157
169
|
if update.success?
|
158
170
|
@_admin_action_struct = RailsBase::AdminStruct.new(update.original_attribute, update.attribute, update.model)
|
@@ -161,12 +173,17 @@ module RailsBase
|
|
161
173
|
@_admin_action_struct = false
|
162
174
|
render json: { success: false, message: update.message }, status: 404
|
163
175
|
end
|
176
|
+
|
177
|
+
session[:mfa_randomized_token] = nil
|
164
178
|
end
|
165
179
|
|
180
|
+
# POST admin/update/name
|
166
181
|
def update_name
|
167
182
|
unless RailsBase.config.admin.name_tile_users?(admin_user)
|
168
|
-
|
169
|
-
|
183
|
+
session.clear
|
184
|
+
sign_out(current_user)
|
185
|
+
logger.warn("Unauthorized user has tried to update attributes. Fail Quickly!")
|
186
|
+
render json: { success: false, message: "Unauthorized action. You have been signed out" }, status: 404
|
170
187
|
return
|
171
188
|
end
|
172
189
|
user = User.find(params[:id])
|
@@ -186,12 +203,17 @@ module RailsBase
|
|
186
203
|
@_admin_action_struct = false
|
187
204
|
render json: { success: false, message: "Failed to change #{user.id} name" }, status: 404
|
188
205
|
end
|
206
|
+
|
207
|
+
session[:mfa_randomized_token] = nil
|
189
208
|
end
|
190
209
|
|
210
|
+
# POST admin/update/email
|
191
211
|
def update_email
|
192
212
|
unless RailsBase.config.admin.email_tile_users?(admin_user)
|
193
|
-
|
194
|
-
|
213
|
+
session.clear
|
214
|
+
sign_out(current_user)
|
215
|
+
logger.warn("Unauthorized user has tried to update emai. Fail Quickly!")
|
216
|
+
render json: { success: false, message: "Unauthorized action. You have been signed out" }, status: 404
|
195
217
|
return
|
196
218
|
end
|
197
219
|
|
@@ -205,9 +227,20 @@ module RailsBase
|
|
205
227
|
@_admin_action_struct = false
|
206
228
|
render json: { success: false, message: result.message }, status: 404
|
207
229
|
end
|
230
|
+
|
231
|
+
session[:mfa_randomized_token] = nil
|
208
232
|
end
|
209
233
|
|
234
|
+
# POST admin/update/phone
|
210
235
|
def update_phone
|
236
|
+
unless RailsBase.config.admin.view_admin_page?(current_user)
|
237
|
+
session.clear
|
238
|
+
sign_out(current_user)
|
239
|
+
logger.warn("Unauthorized user has tried to update phone. Fail Quickly!")
|
240
|
+
render json: { success: false, message: "Unauthorized action. You have been signed out" }, status: 400
|
241
|
+
return
|
242
|
+
end
|
243
|
+
|
211
244
|
begin
|
212
245
|
params[:value] = params[:phone_number].gsub(/\D/,'')
|
213
246
|
rescue
|
@@ -220,6 +253,14 @@ module RailsBase
|
|
220
253
|
|
221
254
|
# POST admin/validate_intent/send
|
222
255
|
def send_2fa
|
256
|
+
unless RailsBase.config.admin.view_admin_page?(current_user)
|
257
|
+
session.clear
|
258
|
+
sign_out(current_user)
|
259
|
+
logger.warn("Unauthorized user has tried to send 2fa. Fail Quickly!")
|
260
|
+
render json: { success: false, message: "Unauthorized action. You have been signed out" }, status: 404
|
261
|
+
return
|
262
|
+
end
|
263
|
+
|
223
264
|
reason = "#{SESSION_REASON_BASE}-#{SecureRandom.uuid}"
|
224
265
|
result = AdminRiskyMfaSend.call(user: admin_user, reason: reason)
|
225
266
|
if result.success?
|
@@ -232,6 +273,8 @@ module RailsBase
|
|
232
273
|
|
233
274
|
# POST admin/validate_intent/verify
|
234
275
|
def verify_2fa
|
276
|
+
return unless validate_mfa_with_event_json!(mfa_event_name: RailsBase::MfaEvent::ADMIN_VERIFY)
|
277
|
+
|
235
278
|
unless modify_id = params[:modify_id]
|
236
279
|
logger.warn("Failed to find #{modify_id} in payload")
|
237
280
|
render json: { success: false, message: 'Hmm. Something fishy happend. Failed to find text to modify' }, status: 404
|
@@ -251,13 +294,14 @@ module RailsBase
|
|
251
294
|
end
|
252
295
|
|
253
296
|
params = {
|
297
|
+
mfa_event: @__rails_base_mfa_event,
|
254
298
|
session_mfa_user_id: admin_user.id,
|
255
299
|
current_user: admin_user,
|
256
300
|
input_reason: session_reason,
|
257
301
|
params: parse_mfa_to_obj
|
258
302
|
}
|
259
|
-
result = RailsBase::
|
260
|
-
encrypt = RailsBase::
|
303
|
+
result = RailsBase::Mfa::Sms::Validate.call(params)
|
304
|
+
encrypt = RailsBase::Mfa::EncryptToken.call(user: admin_user, purpose: session_reason, expires_at: 1.minute.from_now)
|
261
305
|
|
262
306
|
begin
|
263
307
|
html = render_to_string(partial: render_partial, locals: { user: user, modify_id: modify_id })
|
@@ -266,9 +310,10 @@ module RailsBase
|
|
266
310
|
logger.warn("Failed to render html correctly")
|
267
311
|
html = nil
|
268
312
|
end
|
313
|
+
|
269
314
|
if html.nil?
|
270
315
|
logger.warn("Failed to find render html correctly")
|
271
|
-
render json: { success: false, message: 'Apologies.
|
316
|
+
render json: { success: false, message: 'Apologies. We are struggling to render the page. Please try again later' }, status: 500
|
272
317
|
return
|
273
318
|
end
|
274
319
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBase::Mfa
|
4
|
+
class EvaluationController < RailsBaseApplicationController
|
5
|
+
before_action :authenticate_user!, only: [:mfa_evaluate_authenticated]
|
6
|
+
before_action :validate_mfa_with_event!
|
7
|
+
OTP_TEMPLATE = "rails_base/mfa/validate/totp/totp_event_input"
|
8
|
+
SMS_TEMPLATE = "rails_base/mfa/validate/sms/sms_event_input"
|
9
|
+
|
10
|
+
# GET mfa/:event
|
11
|
+
def mfa_with_event
|
12
|
+
user = User.find(@__rails_base_mfa_event.user_id)
|
13
|
+
decision = RailsBase::Mfa::Decision.(user: user)
|
14
|
+
mfa_type = mfa_decision(provided: params[:type], default: decision.mfa_type, allowed: decision.mfa_options)
|
15
|
+
|
16
|
+
if @__rails_base_mfa_event.phone_number
|
17
|
+
phone_number = @__rails_base_mfa_event.phone_number
|
18
|
+
else
|
19
|
+
phone_number = User.find(@__rails_base_mfa_event.user_id).phone_number
|
20
|
+
end
|
21
|
+
|
22
|
+
@masked_phone = User.masked_number(phone_number)
|
23
|
+
@mfa_options = decision.mfa_options.map do |type|
|
24
|
+
next if type == mfa_type
|
25
|
+
|
26
|
+
{
|
27
|
+
text: "Switch MFA to #{type}",
|
28
|
+
** RailsBase::Mfa.mfa_link(mfa_event: @__rails_base_mfa_event.event, mfa: type)
|
29
|
+
}
|
30
|
+
end.compact
|
31
|
+
|
32
|
+
case mfa_type
|
33
|
+
when RailsBase::Mfa::OTP
|
34
|
+
render OTP_TEMPLATE
|
35
|
+
when RailsBase::Mfa::SMS
|
36
|
+
render SMS_TEMPLATE
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def mfa_decision(provided:, default:, allowed:)
|
43
|
+
if Array === @__rails_base_mfa_event.only_mfa
|
44
|
+
logger.warn("MFA Event is forcing one of #{@__rails_base_mfa_event.only_mfa}")
|
45
|
+
return @__rails_base_mfa_event.only_mfa.sample.to_sym
|
46
|
+
end
|
47
|
+
|
48
|
+
# Nothing was provided by the user
|
49
|
+
return default if provided.nil?
|
50
|
+
|
51
|
+
# Provided input is an allowed type for the current user
|
52
|
+
return provided.to_sym if allowed.include?(provided.to_sym)
|
53
|
+
|
54
|
+
flash[:alert] = "Unknown MFA type #{provided}. Using #{default} instead"
|
55
|
+
|
56
|
+
return default
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBase::Mfa::Register
|
4
|
+
class SmsController < RailsBaseApplicationController
|
5
|
+
before_action :authenticate_user!
|
6
|
+
before_action ->() { validate_mfa_with_event!(mfa_event_name: RailsBase::MfaEvent::ENABLE_SMS_EVENT) }, only: [:sms_confirmation]
|
7
|
+
before_action ->() { validate_mfa_with_event!(mfa_event_name: RailsBase::MfaEvent::DISABLE_SMS_EVENT) }, only: [:sms_removal]
|
8
|
+
before_action :json_validate_current_user!, only: [:sms_registration]
|
9
|
+
|
10
|
+
# POST mfa/register/sms
|
11
|
+
def sms_registration
|
12
|
+
result = RailsBase::Authentication::UpdatePhoneSendVerification.call(user: current_user, phone_number: params[:phone_number])
|
13
|
+
if result.failure?
|
14
|
+
render :json => { error: I18n.t('request_response.teapot.fail'), msg: result.message }.to_json, :status => 418
|
15
|
+
return
|
16
|
+
end
|
17
|
+
session[:mfa_randomized_token] = result.mfa_randomized_token
|
18
|
+
render :json => { status: :success, message: I18n.t('request_response.teapot.valid') }
|
19
|
+
end
|
20
|
+
|
21
|
+
# POST mfa/register/sms/validate
|
22
|
+
def sms_confirmation
|
23
|
+
mfa_validity = RailsBase::Mfa::Sms::Validate.call(mfa_event: @__rails_base_mfa_event, current_user: current_user, params: params, session_mfa_user_id: @__rails_base_mfa_event.user_id)
|
24
|
+
if mfa_validity.failure?
|
25
|
+
redirect_to RailsBase.url_routes.user_settings_path, alert: I18n.t('authentication.confirm_phone_registration.fail', message: mfa_validity.message)
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
current_user.update!(mfa_sms_enabled: true)
|
30
|
+
redirect_to RailsBase.url_routes.user_settings_path, notice: "Successfully added SMS as an MFA option on your account"
|
31
|
+
end
|
32
|
+
|
33
|
+
# DELETE mfa/register/sms
|
34
|
+
def sms_removal
|
35
|
+
result = RailsBase::Mfa::Sms::Remove.(mfa_event: @__rails_base_mfa_event, current_user: current_user, session_mfa_user_id: @__rails_base_mfa_event.user_id, password: params[:password], sms_code: params[:sms_code])
|
36
|
+
if result.success?
|
37
|
+
flash[:notice] = "Successfully removed SMS as an MFA option on your account"
|
38
|
+
else
|
39
|
+
flash[:alert] = result.message
|
40
|
+
end
|
41
|
+
|
42
|
+
redirect_to RailsBase.url_routes.user_settings_path
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBase::Mfa::Register
|
4
|
+
class TotpController < RailsBaseApplicationController
|
5
|
+
before_action :authenticate_user!
|
6
|
+
|
7
|
+
# DELETE mfa/register/totp
|
8
|
+
def totp_remove
|
9
|
+
result = RailsBase::Mfa::Totp::Remove.(password: params[:password], user: current_user, otp_code: params[:totp_code])
|
10
|
+
|
11
|
+
if result.success?
|
12
|
+
flash[:notice] = "Successfully Removed TOTP Authentication to #{RailsBase.app_name}"
|
13
|
+
else
|
14
|
+
flash[:alert] = "Something Went Wrong! #{result.message}"
|
15
|
+
end
|
16
|
+
|
17
|
+
redirect_to RailsBase.url_routes.user_settings_path
|
18
|
+
end
|
19
|
+
|
20
|
+
# POST mfa/register/totp
|
21
|
+
def totp_secret
|
22
|
+
result = RailsBase::Mfa::Totp::OtpMetadata.(user: current_user)
|
23
|
+
if result.success?
|
24
|
+
render json: result.metadata
|
25
|
+
else
|
26
|
+
render json: { status: result.message }, status: 400
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# POST mfa/register/totp/validate
|
31
|
+
def totp_validate
|
32
|
+
result = RailsBase::Mfa::Totp::ValidateTemporaryCode.(user: current_user, otp_code: params[:totp_code])
|
33
|
+
if result.success?
|
34
|
+
flash[:notice] = "Successfully added an Authenticator for TOTP to #{RailsBase.app_name}"
|
35
|
+
else
|
36
|
+
flash[:alert] = "Something Went Wrong! Failed to add an Authenticator for TOTP to #{RailsBase.app_name}. Please try again"
|
37
|
+
end
|
38
|
+
|
39
|
+
redirect_to RailsBase.url_routes.user_settings_path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBase::Mfa::Validate
|
4
|
+
class SmsController < RailsBaseApplicationController
|
5
|
+
before_action :validate_mfa_with_event!, only: [:sms_event_input, :sms_event]
|
6
|
+
|
7
|
+
# POST mfa/validate/sms/:mfa_event/send
|
8
|
+
def sms_event_send
|
9
|
+
if soft_mfa_with_event
|
10
|
+
user = User.find(@__rails_base_mfa_event.user_id)
|
11
|
+
else
|
12
|
+
if request.format.json?
|
13
|
+
render json: { message: @__rails_base_mfa_event_invalid_reason }, status: 400
|
14
|
+
else
|
15
|
+
flash[:alert] = @__rails_base_mfa_event_invalid_reason
|
16
|
+
redirect = @__rails_base_mfa_event&.invalid_redirect || RailsBase.url_routes.new_user_session_path
|
17
|
+
|
18
|
+
redirect_to redirect, email: params.dig(:user,:email)
|
19
|
+
end
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
if request.format.json?
|
24
|
+
# When json, this will always come from an authenticated user
|
25
|
+
# otherwise kick them out now!
|
26
|
+
return unless authenticate_user!
|
27
|
+
|
28
|
+
user = current_user
|
29
|
+
end
|
30
|
+
|
31
|
+
result = RailsBase::Mfa::Sms::Send.call(expires_at: 5.minutes.from_now, phone_number: @__rails_base_mfa_event.phone_number, user: user)
|
32
|
+
|
33
|
+
if result.success?
|
34
|
+
flash[:notice] = msg = "SMS Code succesfully sent. Please check messages"
|
35
|
+
status = 200
|
36
|
+
else
|
37
|
+
flash[:alert] = msg = "Unable to complete Request. #{result.message}"
|
38
|
+
status = 400
|
39
|
+
end
|
40
|
+
|
41
|
+
if request.format.json?
|
42
|
+
render json: { message: msg }, status: status
|
43
|
+
flash.clear
|
44
|
+
else
|
45
|
+
redirect_to RailsBase.url_routes.mfa_with_event_path(mfa_event: @__rails_base_mfa_event.event, type: RailsBase::Mfa::SMS)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# GET mfa/validate/sms/:mfa_event
|
50
|
+
def sms_event_input
|
51
|
+
if @__rails_base_mfa_event.phone_number
|
52
|
+
phone_number = @__rails_base_mfa_event.phone_number
|
53
|
+
else
|
54
|
+
phone_number = User.find(@__rails_base_mfa_event.user_id).phone_number
|
55
|
+
end
|
56
|
+
|
57
|
+
@masked_phone = User.masked_number(phone_number)
|
58
|
+
end
|
59
|
+
|
60
|
+
# POST mfa/validate/sms/:mfa_event
|
61
|
+
def sms_event
|
62
|
+
mfa_validity = RailsBase::Mfa::Sms::Validate.call(mfa_event: @__rails_base_mfa_event, params: params, session_mfa_user_id: @__rails_base_mfa_event.user_id)
|
63
|
+
if mfa_validity.failure?
|
64
|
+
redirect_to(mfa_validity.redirect_url, alert: mfa_validity.message)
|
65
|
+
return
|
66
|
+
end
|
67
|
+
|
68
|
+
mfa_validity.user.set_last_mfa_sms_login!
|
69
|
+
if @__rails_base_mfa_event.sign_in_user
|
70
|
+
logger.info("Logging User in")
|
71
|
+
sign_in(mfa_validity.user)
|
72
|
+
end
|
73
|
+
|
74
|
+
if @__rails_base_mfa_event.set_satiated_on_success
|
75
|
+
logger.info("Satiating MFA Event")
|
76
|
+
@__rails_base_mfa_event.satiated!
|
77
|
+
end
|
78
|
+
|
79
|
+
add_mfa_event_to_session(event: @__rails_base_mfa_event)
|
80
|
+
redirect_to @__rails_base_mfa_event.redirect, notice: @__rails_base_mfa_event.flash_notice
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsBase::Mfa::Validate
|
4
|
+
class TotpController < RailsBaseApplicationController
|
5
|
+
before_action :validate_mfa_with_event!
|
6
|
+
|
7
|
+
# GET mfa/validate/totp/:event
|
8
|
+
def totp_event_input; end
|
9
|
+
|
10
|
+
# POST mfa/validate/totp/:event
|
11
|
+
def totp_event
|
12
|
+
user = User.find(@__rails_base_mfa_event.user_id)
|
13
|
+
mfa_validity = ::RailsBase::Mfa::Totp::ValidateCode.(user: user, otp_code: params[:totp_code])
|
14
|
+
if mfa_validity.failure?
|
15
|
+
redirect_to(RailsBase.url_routes.mfa_with_event_path(mfa_event: @__rails_base_mfa_event.event, type: RailsBase::Mfa::OTP), alert: mfa_validity.message)
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
user.set_last_mfa_otp_login!
|
20
|
+
|
21
|
+
if @__rails_base_mfa_event.sign_in_user
|
22
|
+
logger.info("Logging User in")
|
23
|
+
sign_in(mfa_validity.user)
|
24
|
+
end
|
25
|
+
|
26
|
+
if @__rails_base_mfa_event.set_satiated_on_success
|
27
|
+
logger.info("Satiating MFA Event")
|
28
|
+
@__rails_base_mfa_event.satiated!
|
29
|
+
end
|
30
|
+
|
31
|
+
add_mfa_event_to_session(event: @__rails_base_mfa_event)
|
32
|
+
redirect_to @__rails_base_mfa_event.redirect, notice: @__rails_base_mfa_event.flash_notice
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -2,13 +2,11 @@ module RailsBase
|
|
2
2
|
class SecondaryAuthenticationController < RailsBaseApplicationController
|
3
3
|
before_action :authenticate_user!, only: [:remove_phone_mfa, :confirm_phone_registration]
|
4
4
|
|
5
|
-
before_action :
|
6
|
-
|
7
|
-
before_action :json_validate_current_user!, only: [:phone_registration]
|
5
|
+
before_action :validate_mfa_token!, only: [:resend_email, :wait, :confirm_phone_registration]
|
8
6
|
|
9
7
|
# GET auth/wait
|
10
8
|
def static
|
11
|
-
return unless
|
9
|
+
return unless validate_mfa_token!(purpose: Authentication::Constants::SSOVE_PURPOSE)
|
12
10
|
|
13
11
|
if flash[:notice].nil? && flash[:alert].nil?
|
14
12
|
flash[:notice] = Authentication::Constants::STATIC_WAIT_FLASH
|
@@ -18,11 +16,6 @@ module RailsBase
|
|
18
16
|
def remove_me
|
19
17
|
end
|
20
18
|
|
21
|
-
def testing_route
|
22
|
-
Rails.logger.error("This will cause an error to be thrown")
|
23
|
-
raise ArgumentError, 'Boo'
|
24
|
-
end
|
25
|
-
|
26
19
|
# POST auth/resend_email
|
27
20
|
def resend_email
|
28
21
|
user = User.find @token_verifier.user_id
|
@@ -51,7 +44,7 @@ module RailsBase
|
|
51
44
|
|
52
45
|
# GET auth/login
|
53
46
|
def after_email_login_session_new
|
54
|
-
return unless
|
47
|
+
return unless validate_mfa_token!(purpose: Authentication::Constants::SSOVE_PURPOSE)
|
55
48
|
|
56
49
|
@user = User.new
|
57
50
|
if flash[:alert].nil? && flash[:notice].nil?
|
@@ -61,7 +54,7 @@ module RailsBase
|
|
61
54
|
|
62
55
|
# POST auth/login
|
63
56
|
def after_email_login_session_create
|
64
|
-
return unless
|
57
|
+
return unless validate_mfa_token!(purpose: Authentication::Constants::SSOVE_PURPOSE)
|
65
58
|
|
66
59
|
flash[:notice] = nil
|
67
60
|
flash[:alert] = nil
|
@@ -78,37 +71,6 @@ module RailsBase
|
|
78
71
|
redirect_to RailsBase.url_routes.authenticated_root_path
|
79
72
|
end
|
80
73
|
|
81
|
-
# POST auth/phone
|
82
|
-
def phone_registration
|
83
|
-
result = Authentication::UpdatePhoneSendVerification.call(user: current_user, phone_number: params[:phone_number])
|
84
|
-
if result.failure?
|
85
|
-
render :json => { error: I18n.t('request_response.teapot.fail'), msg: result.message }.to_json, :status => 418
|
86
|
-
return
|
87
|
-
end
|
88
|
-
session[:mfa_randomized_token] = result.mfa_randomized_token
|
89
|
-
|
90
|
-
render :json => { status: :success, message: I18n.t('request_response.teapot.valid') }
|
91
|
-
end
|
92
|
-
|
93
|
-
# POST auth/phone/mfa
|
94
|
-
def confirm_phone_registration
|
95
|
-
mfa_validity = Authentication::MfaValidator.call(current_user: current_user, params: params, session_mfa_user_id: @token_verifier.user_id)
|
96
|
-
if mfa_validity.failure?
|
97
|
-
redirect_to RailsBase.url_routes.authenticated_root_path, alert: I18n.t('authentication.confirm_phone_registration.fail', message: mfa_validity.message)
|
98
|
-
return
|
99
|
-
end
|
100
|
-
|
101
|
-
current_user.update!(mfa_enabled: true)
|
102
|
-
|
103
|
-
redirect_to RailsBase.url_routes.authenticated_root_path, notice: I18n.t('authentication.confirm_phone_registration.valid')
|
104
|
-
end
|
105
|
-
|
106
|
-
# DELETE auth/phone/disable
|
107
|
-
def remove_phone_mfa
|
108
|
-
current_user.update!(mfa_enabled: false, last_mfa_login: nil)
|
109
|
-
redirect_to RailsBase.url_routes.authenticated_root_path, notice: I18n.t('authentication.remove_phone_mfa')
|
110
|
-
end
|
111
|
-
|
112
74
|
# GET auth/email/forgot/:data
|
113
75
|
def forgot_password
|
114
76
|
result = Authentication::VerifyForgotPassword.call(data: params[:data])
|
@@ -117,52 +79,52 @@ module RailsBase
|
|
117
79
|
redirect_to result.redirect_url, alert: result.message
|
118
80
|
return
|
119
81
|
end
|
120
|
-
session[:mfa_randomized_token] = result.encrypted_val
|
121
|
-
flash[:notice] =
|
122
|
-
if @mfa_flow = result.mfa_flow
|
123
|
-
I18n.t('authentication.forgot_password.2fa')
|
124
|
-
else
|
125
|
-
I18n.t('authentication.forgot_password.base')
|
126
|
-
end
|
127
|
-
@user = result.user
|
128
|
-
@data = params[:data]
|
129
|
-
end
|
130
82
|
|
131
|
-
|
132
|
-
|
133
|
-
|
83
|
+
event = RailsBase::MfaEvent.forgot_password(user: result.user, data: params[:data])
|
84
|
+
if result.mfa_flow
|
85
|
+
flash[:notice] = "MFA required to reset password"
|
86
|
+
redirect_to(RailsBase.url_routes.mfa_with_event_path(mfa_event: event.event))
|
87
|
+
else
|
88
|
+
# Requirements to continue were satiatet..we can let the user reset their password
|
89
|
+
event.satiated!
|
90
|
+
flash[:notice] = "Datum valid. Reset your password"
|
91
|
+
redirect_to(RailsBase.url_routes.reset_password_input_path(data: params[:data]))
|
92
|
+
end
|
134
93
|
|
135
|
-
#
|
136
|
-
|
137
|
-
|
138
|
-
# -- validate user by short lived token
|
139
|
-
# -- validate user by mfa_token
|
140
|
-
# -- When all match by user and within the lifetime of the short lived token... we b gucci uber super secure/over engineered
|
141
|
-
expired_datum = ShortLivedData.get_by_data(data: params[:data], reason: Authentication::Constants::VFP_REASON)
|
94
|
+
# Upload event to the session as a last step to ensure we capture if it was satiated or not
|
95
|
+
add_mfa_event_to_session(event:)
|
96
|
+
end
|
142
97
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
end
|
98
|
+
# GET auth/password/reset/:data
|
99
|
+
def reset_password_input
|
100
|
+
return unless validate_mfa_with_event!(mfa_event_name: RailsBase::MfaEvent::FORGOT_PASSWORD)
|
147
101
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
102
|
+
if @__rails_base_mfa_event.satiated?
|
103
|
+
@data = params[:data]
|
104
|
+
@user = User.find(@__rails_base_mfa_event.user_id)
|
105
|
+
else
|
106
|
+
logger.error("MFA Event was not satiated. Kicking user back to root")
|
107
|
+
clear_mfa_event_from_session!(event_name: @__rails_base_mfa_event.event)
|
108
|
+
session.clear
|
109
|
+
flash[:alert] = "Unauthorized access"
|
110
|
+
redirect_to(RailsBase.url_routes.unauthenticated_root_path)
|
152
111
|
end
|
153
|
-
|
154
|
-
@mfa_flow = false
|
155
|
-
@data = params[:data]
|
156
|
-
@user = result.user
|
157
|
-
flash[:notice] = I18n.t('authentication.forgot_password_with_mfa.valid_mfa')
|
158
|
-
render :forgot_password
|
159
112
|
end
|
160
113
|
|
161
114
|
# POST auth/email/reset/:data
|
162
115
|
def reset_password
|
163
|
-
return unless
|
116
|
+
return unless validate_mfa_with_event!(mfa_event_name: RailsBase::MfaEvent::FORGOT_PASSWORD)
|
117
|
+
|
118
|
+
unless @__rails_base_mfa_event.satiated?
|
119
|
+
logger.error("MFA Event was not satiated. Kicking user back to root")
|
120
|
+
clear_mfa_event_from_session!(event_name: @__rails_base_mfa_event.event)
|
121
|
+
session.clear
|
122
|
+
flash[:alert] = "Unauthorized access"
|
123
|
+
redirect_to(RailsBase.url_routes.unauthenticated_root_path)
|
124
|
+
return
|
125
|
+
end
|
164
126
|
|
165
|
-
result = Authentication::ModifyPassword.call(password: params[:user][:password], password_confirmation: params[:user][:password_confirmation], data: params[:data], user_id: @
|
127
|
+
result = Authentication::ModifyPassword.call(password: params[:user][:password], password_confirmation: params[:user][:password_confirmation], data: params[:data], user_id: @__rails_base_mfa_event.user_id, flow: :forgot_password)
|
166
128
|
if result.failure?
|
167
129
|
redirect_to RailsBase.url_routes.new_user_password_path, alert: result.message
|
168
130
|
return
|
@@ -202,23 +164,5 @@ module RailsBase
|
|
202
164
|
flash[:notice] = I18n.t('authentication.sso_login.valid')
|
203
165
|
redirect_to url
|
204
166
|
end
|
205
|
-
|
206
|
-
private
|
207
|
-
|
208
|
-
def json_validate_current_user!
|
209
|
-
return if current_user
|
210
|
-
|
211
|
-
render json: { error: "Unauthorized" }.to_json, :status => 401
|
212
|
-
return false
|
213
|
-
end
|
214
|
-
|
215
|
-
def validate_token!(purpose: Authentication::Constants::MSET_PURPOSE)
|
216
|
-
@token_verifier =
|
217
|
-
Authentication::SessionTokenVerifier.call(purpose: purpose, mfa_randomized_token: session[:mfa_randomized_token])
|
218
|
-
return true if @token_verifier.success?
|
219
|
-
|
220
|
-
redirect_to RailsBase.url_routes.new_user_session_path, alert: @token_verifier.message
|
221
|
-
return false
|
222
|
-
end
|
223
167
|
end
|
224
168
|
end
|