rails_base 0.75.6 → 0.81.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 +17 -11
- 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 +63 -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 +10 -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/_request_link_alert.html.erb +48 -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 +102 -3
- 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/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/request_link.rb +27 -0
- data/lib/rails_base/version.rb +3 -3
- data/lib/rails_base.rb +2 -0
- data/lib/twilio_helper.rb +3 -3
- metadata +131 -64
- data/app/controllers/rails_base/mfa_auth_controller.rb +0 -50
- data/app/services/rails_base/authentication/mfa_set_encrypt_token.rb +0 -32
- data/app/services/rails_base/authentication/mfa_validator.rb +0 -88
- data/app/views/rails_base/mfa_auth/mfa_code.html.erb +0 -11
- data/app/views/rails_base/secondary_authentication/forgot_password.html.erb +0 -9
@@ -0,0 +1,50 @@
|
|
1
|
+
<div class="modal fade" id="totpDisableModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
2
|
+
<div class="modal-dialog modal-lg" role="document">
|
3
|
+
<div class="modal-content">
|
4
|
+
<div class="modal-header">
|
5
|
+
<h3 class="modal-title" id="exampleModalLabel">Remove TOTP as Multi Factor Authenticator</h3>
|
6
|
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
7
|
+
<span aria-hidden="true">×</span>
|
8
|
+
</button>
|
9
|
+
</div>
|
10
|
+
<div class="modal-body">
|
11
|
+
<div class="text-center">
|
12
|
+
Time based One time Password is one of the the most Secure MFA option that <%= RailsBase.app_name %> provides. If you would like to remove this MFA option, please enter your credentials below and confirm. Upon Successful request, TOTP is no longer active on your account
|
13
|
+
</div>
|
14
|
+
<hr>
|
15
|
+
<%= form_with(url: RailsBase.url_routes.totp_register_delete_path, method: :delete) do |form| %>
|
16
|
+
<div id="confirmTotpAuthenticationCode">
|
17
|
+
<div class="totpInput">
|
18
|
+
<div class="input-group input-group-lg">
|
19
|
+
<div class="input-group-prepend">
|
20
|
+
<span class="input-group-text" id="totpInput-Prepend">Password</span>
|
21
|
+
</div>
|
22
|
+
<%= form.password_field :password, id: "totpRemoval-password", class:"form-control" %>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
<br>
|
26
|
+
<div class="totpInput">
|
27
|
+
<div class="input-group input-group-lg">
|
28
|
+
<div class="input-group-prepend">
|
29
|
+
<span class="input-group-text" id="totpInput-Prepend">TOTP</span>
|
30
|
+
</div>
|
31
|
+
<%= form.telephone_field :totp_code, id: "totpRemoval-code", class:"form-control", placeholder: "One Time Password Value" %>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
<br>
|
35
|
+
<div class="totpSubmit">
|
36
|
+
<div class="row">
|
37
|
+
<div class="col-md-6 offset-md-3">
|
38
|
+
<%= form.submit "Confirm TOTP Removal", class: " btn btn_danger btn-block" %>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
</div>
|
43
|
+
<% end %>
|
44
|
+
</div>
|
45
|
+
<div class="modal-footer">
|
46
|
+
<button type="button" class="mr-auto btn btn_secondary" data-dismiss="modal">Close</button>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
</div>
|
@@ -1,3 +1,6 @@
|
|
1
|
+
<%
|
2
|
+
function_name = "advancedSecurityCollapse"
|
3
|
+
%>
|
1
4
|
<div class="row">
|
2
5
|
<div class="col-md-10 offset-md-1">
|
3
6
|
<div class="row">
|
@@ -16,13 +19,24 @@
|
|
16
19
|
</tr>
|
17
20
|
<tr>
|
18
21
|
<th scope="col" class='text-right' style="width: 40%">
|
19
|
-
MFA enabled?
|
22
|
+
SMS MFA enabled?
|
20
23
|
</th>
|
21
24
|
<td style="width: 40%">
|
22
|
-
<%= current_user.
|
25
|
+
<%= current_user.mfa_sms_enabled %>
|
23
26
|
</td>
|
24
27
|
<td style="width: 20%">
|
25
|
-
<button class="btn btn_primary btn-block
|
28
|
+
<button onclick="advancedSecurityCollapse_collapse_open()" class="btn btn_primary btn-block" type="button">Modify</button>
|
29
|
+
</td>
|
30
|
+
</tr>
|
31
|
+
<tr>
|
32
|
+
<th scope="col" class='text-right' style="width: 40%">
|
33
|
+
TOTP MFA enabled?
|
34
|
+
</th>
|
35
|
+
<td style="width: 40%">
|
36
|
+
<%= current_user.mfa_otp_enabled %>
|
37
|
+
</td>
|
38
|
+
<td style="width: 20%">
|
39
|
+
<button onclick="advancedSecurityCollapse_collapse_open()" class="btn btn_primary btn-block" type="button">Modify</button>
|
26
40
|
</td>
|
27
41
|
</tr>
|
28
42
|
<tr>
|
@@ -39,6 +53,86 @@
|
|
39
53
|
</tbody>
|
40
54
|
</table>
|
41
55
|
</div>
|
56
|
+
<div class='row'>
|
57
|
+
<div class='col'>
|
58
|
+
<div id="advancedSecurity">
|
59
|
+
<div class="row">
|
60
|
+
<div class="col-12">
|
61
|
+
<button id="advancedSecurity-title" class="text-center btn btn-warning btn-block" type="button" id="dropdownMenuButton" aria-haspopup="true" aria-expanded="false">
|
62
|
+
MFA Options
|
63
|
+
</button>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
<div class="row">
|
67
|
+
<div class="col-12">
|
68
|
+
<div id="advancedSecurity-body" class="collapseable-body">
|
69
|
+
<br>
|
70
|
+
<div class="row">
|
71
|
+
<div class="col-10 offset-1">
|
72
|
+
<% if RailsBase.config.mfa.enable? %>
|
73
|
+
<% if current_user.mfa_sms_enabled %>
|
74
|
+
<button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#modifyMfamodal">
|
75
|
+
Modify 2fa Auth
|
76
|
+
</button>
|
77
|
+
<%= render partial: 'rails_base/shared/modify_mfa_auth_modal'%>
|
78
|
+
<% else %>
|
79
|
+
<button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#enableMfamodal">
|
80
|
+
Enable 2fa Auth
|
81
|
+
</button>
|
82
|
+
<%= render partial: 'rails_base/shared/enable_mfa_auth_modal'%>
|
83
|
+
<% end %>
|
84
|
+
<% end %>
|
85
|
+
</div>
|
86
|
+
</div>
|
87
|
+
<div class="row"><div class="col-6 offset-3">
|
88
|
+
<hr>
|
89
|
+
</div></div>
|
90
|
+
|
91
|
+
<div class="row">
|
92
|
+
<div class="col-10 offset-1">
|
93
|
+
<% if RailsBase.config.totp.enable? %>
|
94
|
+
<% if current_user.mfa_otp_enabled %>
|
95
|
+
<div class="row">
|
96
|
+
<div class="col-12">
|
97
|
+
<button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpDisableModal">
|
98
|
+
Disable One Time Password Auth
|
99
|
+
</button>
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
<%= render partial: 'rails_base/shared/totp/remove_authenticator_modal', locals: { type: @type, endpoint: @endpoint } %>
|
103
|
+
<br>
|
104
|
+
<div class="row">
|
105
|
+
<div class="col-12">
|
106
|
+
<button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpEnableModal" style="display: none;">
|
107
|
+
<!--
|
108
|
+
This is currently disabled.
|
109
|
+
Steps to re-enabld
|
110
|
+
- Enforce TOTP code is entered before showing totp secret
|
111
|
+
-->
|
112
|
+
Add One Time Password Auth
|
113
|
+
</button>
|
114
|
+
</div>
|
115
|
+
</div>
|
116
|
+
<% else %>
|
117
|
+
<button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpEnableModal">
|
118
|
+
Enable One Time Password Auth
|
119
|
+
</button>
|
120
|
+
<%= render partial: 'rails_base/shared/totp/add_authenticator_modal', locals: { type: @type, endpoint: @endpoint } %>
|
121
|
+
<% end %>
|
122
|
+
<% end %>
|
123
|
+
</div>
|
124
|
+
</div>
|
125
|
+
</div>
|
126
|
+
</div>
|
127
|
+
</div>
|
128
|
+
</div>
|
129
|
+
|
130
|
+
<%= render partial: 'rails_base/shared/standardized_collapse', locals: { options: { id: "advancedSecurity", title_id: "advancedSecurity-title", body_id: "advancedSecurity-body", default_closed: true, function_name: function_name } } %>
|
131
|
+
|
132
|
+
</div>
|
133
|
+
</div>
|
134
|
+
</br>
|
135
|
+
<hr>
|
42
136
|
<div class="row" style="margin-top: 50px">
|
43
137
|
<button type="button" class="btn btn_danger btn-block" data-toggle="modal" data-target="#destroyUserModal">
|
44
138
|
Destroy account
|
@@ -52,3 +146,8 @@
|
|
52
146
|
<%= render 'modify_password' %>
|
53
147
|
<%= render 'destroy_user' %>
|
54
148
|
|
149
|
+
<script type="text/javascript">
|
150
|
+
$(document).ready(function(){
|
151
|
+
_railsBase_goToStandardizedCollapse("openmfa", `#advancedSecurity-body`, `<%= function_name %>`)
|
152
|
+
})
|
153
|
+
</script>
|
@@ -59,21 +59,57 @@ params = {
|
|
59
59
|
}
|
60
60
|
RailsBase::Admin::ActionHelper.new(**params).add!
|
61
61
|
|
62
|
+
proc = Proc.new do |req, params, admin_user, user, title, struct|
|
63
|
+
actions_mapping = {
|
64
|
+
sms_registration: 'MFA Attempt to Register new SMS number',
|
65
|
+
sms_confirmation: 'MFA Confirm new SMS number',
|
66
|
+
sms_removal: 'MFA SMS removed',
|
67
|
+
}
|
62
68
|
|
69
|
+
if actions_mapping.keys.include?(params[:action].to_sym)
|
70
|
+
{
|
71
|
+
admin_user: admin_user,
|
72
|
+
user: user,
|
73
|
+
action: actions_mapping[params[:action].to_sym] ,
|
74
|
+
original_attribute: struct&.original_attribute,
|
75
|
+
new_attribute: struct&.new_attribute
|
76
|
+
}
|
77
|
+
else
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
# All SMS register actions
|
63
82
|
params = {
|
64
|
-
proc:
|
65
|
-
|
66
|
-
controller: RailsBase::SecondaryAuthenticationController,
|
67
|
-
action: 'remove_phone_mfa',
|
83
|
+
proc: proc,
|
84
|
+
controller: RailsBase::Mfa::Register::SmsController,
|
68
85
|
default: true
|
69
86
|
}
|
70
87
|
RailsBase::Admin::ActionHelper.new(**params).add!
|
71
88
|
|
89
|
+
|
90
|
+
proc = Proc.new do |req, params, admin_user, user, title, struct|
|
91
|
+
actions_mapping = {
|
92
|
+
totp_remove: 'MFA TOTP remove',
|
93
|
+
totp_secret: 'MFA TOTP Attempt to add new Authenticator',
|
94
|
+
totp_validate: 'MFA TOTP Authenticator added',
|
95
|
+
}
|
96
|
+
|
97
|
+
if actions_mapping.keys.include?(params[:action].to_sym)
|
98
|
+
{
|
99
|
+
admin_user: admin_user,
|
100
|
+
user: user,
|
101
|
+
action: actions_mapping[params[:action].to_sym] ,
|
102
|
+
original_attribute: struct&.original_attribute,
|
103
|
+
new_attribute: struct&.new_attribute
|
104
|
+
}
|
105
|
+
else
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
# All TOTP register actions
|
72
110
|
params = {
|
73
|
-
proc:
|
74
|
-
|
75
|
-
controller: RailsBase::SecondaryAuthenticationController,
|
76
|
-
action: 'phone_registration',
|
111
|
+
proc: proc,
|
112
|
+
controller: RailsBase::Mfa::Register::TotpController,
|
77
113
|
default: true
|
78
114
|
}
|
79
115
|
RailsBase::Admin::ActionHelper.new(**params).add!
|
data/config/routes.rb
CHANGED
@@ -64,18 +64,53 @@ Rails.application.routes.draw do
|
|
64
64
|
get 'auth/login', to: 'rails_base/secondary_authentication#after_email_login_session_new', as: :login_after_email
|
65
65
|
post 'auth/login', to: 'rails_base/secondary_authentication#after_email_login_session_create', as: :login_after_email_session_create
|
66
66
|
post 'auth/resend_email', to: 'rails_base/secondary_authentication#resend_email', as: :resend_email_verification
|
67
|
-
delete 'auth/phone/mfa', to: 'rails_base/secondary_authentication#remove_phone_mfa', as: :remove_phone_registration_mfa
|
68
67
|
get 'auth/password/forgot/:data', to: 'rails_base/secondary_authentication#forgot_password', as: :forgot_password_auth
|
69
|
-
|
68
|
+
get 'auth/password/reset/:data', to: 'rails_base/secondary_authentication#reset_password_input', as: :reset_password_input
|
70
69
|
post 'auth/password/reset/:data', to: 'rails_base/secondary_authentication#reset_password', as: :reset_password_auth
|
71
70
|
|
72
71
|
constraints(->(_req) { RailsBase.config.mfa.enable? }) do
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
scope "mfa/register" do
|
73
|
+
mfa_base_register = "rails_base/mfa/register"
|
74
|
+
constraints(->(_req) { RailsBase.config.totp.enable? }) do
|
75
|
+
scope :totp do
|
76
|
+
delete "/", to: "#{mfa_base_register}/totp#totp_remove", as: :totp_register_delete
|
77
|
+
post "/", to: "#{mfa_base_register}/totp#totp_secret", as: :totp_register_secret
|
78
|
+
post "validate", to: "#{mfa_base_register}/totp#totp_validate", as: :totp_register_validate
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
constraints(->(_req) { RailsBase.config.twilio.enable? }) do
|
83
|
+
scope :sms do
|
84
|
+
delete "/", to: "#{mfa_base_register}/sms#sms_removal", as: :remove_phone_registration_mfa
|
85
|
+
post "/", to: "#{mfa_base_register}/sms#sms_registration", as: :phone_registration
|
86
|
+
post "validate", to: "#{mfa_base_register}/sms#sms_confirmation", as: :phone_registration_mfa_code
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
scope "mfa/validate" do
|
92
|
+
mfa_base_validate = "rails_base/mfa/validate"
|
93
|
+
constraints(->(_req) { RailsBase.config.totp.enable? }) do
|
94
|
+
scope :totp do
|
95
|
+
get ":mfa_event", to: "#{mfa_base_validate}/totp#totp_event_input", as: :totp_validate_event_input
|
96
|
+
post ":mfa_event", to: "#{mfa_base_validate}/totp#totp_event", as: :totp_validate_event
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
constraints(->(_req) { RailsBase.config.twilio.enable? }) do
|
101
|
+
scope :sms do
|
102
|
+
post ":mfa_event/send", to: "#{mfa_base_validate}/sms#sms_event_send", as: :sms_validate_send_event
|
103
|
+
post ":mfa_event", to: "#{mfa_base_validate}/sms#sms_event", as: :sms_validate_event
|
104
|
+
get ":mfa_event", to: "#{mfa_base_validate}/sms#sms_event_input", as: :sms_validate_event_input
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
76
109
|
|
77
|
-
|
78
|
-
|
110
|
+
# These routes need to be outside of the scope so that they can be called independently
|
111
|
+
# The code itself will evaluate if it should run or not
|
112
|
+
scope "mfa/evaluation" do
|
113
|
+
get ':mfa_event', to: 'rails_base/mfa/evaluation#mfa_with_event', as: :mfa_with_event
|
79
114
|
end
|
80
115
|
|
81
116
|
################################
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class AddTotpToUsers < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
add_column :users, :otp_secret, :string
|
4
|
+
add_column :users, :temp_otp_secret, :string
|
5
|
+
add_column :users, :consumed_timestep, :integer
|
6
|
+
add_column :users, :mfa_otp_enabled, :boolean, default: false
|
7
|
+
add_column :users, :otp_backup_codes, :text
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class ReconfigureMfaVariableNames < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
## Rename original MFA columns to SMS specific columns
|
4
|
+
rename_column :users, :mfa_enabled, :mfa_sms_enabled
|
5
|
+
rename_column :users, :last_mfa_login, :last_mfa_sms_login
|
6
|
+
|
7
|
+
# Add new Column for Last time logged in via OTP generated code
|
8
|
+
add_column :users, :last_mfa_otp_login, :datetime
|
9
|
+
end
|
10
|
+
end
|
@@ -86,11 +86,11 @@ RailsBase::Admin::IndexTile.add(instance)
|
|
86
86
|
# Mfa Enabled Validated Tile
|
87
87
|
params = {
|
88
88
|
type: :toggle,
|
89
|
-
value: ->(user) { user.
|
89
|
+
value: ->(user) { user.mfa_sms_enabled },
|
90
90
|
on: 'Enabled',
|
91
91
|
off: 'Disabled',
|
92
|
-
name: '
|
93
|
-
col_name: 'MFA Enabled?', # Expected to be the column header name
|
92
|
+
name: 'mfa_sms_enabled', # name to be amended to html id
|
93
|
+
col_name: 'MFA SMS Enabled?', # Expected to be the column header name
|
94
94
|
disabled: -> (user, admin_user) { !RailsBase.config.admin.mfa_tile_users.call(admin_user) },
|
95
95
|
disabled_msg: -> (user, admin_user) { 'Your admin user does not have permissions' },
|
96
96
|
min_width: 220,
|
data/lib/rails_base/config.rb
CHANGED
@@ -1,36 +1,40 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
1
|
+
require "singleton"
|
2
|
+
require "rails_base/configuration/active_job"
|
3
|
+
require "rails_base/configuration/admin"
|
4
|
+
require "rails_base/configuration/app"
|
5
|
+
require "rails_base/configuration/appearance"
|
6
|
+
require "rails_base/configuration/authentication"
|
7
|
+
require "rails_base/configuration/exceptions_app"
|
8
|
+
require "rails_base/configuration/login_behavior"
|
9
|
+
require "rails_base/configuration/mailer"
|
10
|
+
require "rails_base/configuration/mfa"
|
11
|
+
require "rails_base/configuration/owner"
|
12
|
+
require "rails_base/configuration/redis"
|
13
|
+
require "rails_base/configuration/templates"
|
14
|
+
require "rails_base/configuration/totp"
|
15
|
+
require "rails_base/configuration/twilio"
|
16
|
+
require "rails_base/configuration/user"
|
15
17
|
|
16
18
|
module RailsBase
|
17
19
|
class Config
|
18
20
|
include Singleton
|
19
21
|
|
20
22
|
VARIABLES = {
|
23
|
+
active_job: nil,
|
21
24
|
admin: nil,
|
22
|
-
mfa: nil,
|
23
|
-
auth: :authentication,
|
24
|
-
redis: nil,
|
25
|
-
owner: nil,
|
26
|
-
mailer: nil,
|
27
|
-
exceptions_app: nil,
|
28
25
|
app: nil,
|
29
26
|
appearance: nil,
|
30
|
-
|
31
|
-
|
27
|
+
auth: :authentication,
|
28
|
+
exceptions_app: nil,
|
32
29
|
login_behavior: nil,
|
30
|
+
mailer: nil,
|
31
|
+
mfa: nil,
|
32
|
+
owner: nil,
|
33
|
+
redis: nil,
|
33
34
|
templates: nil,
|
35
|
+
totp: nil,
|
36
|
+
twilio: nil,
|
37
|
+
user: nil,
|
34
38
|
}
|
35
39
|
attr_reader *VARIABLES.keys
|
36
40
|
|
@@ -44,13 +44,13 @@ module RailsBase
|
|
44
44
|
proc: ->(user, admin_user) { user.email_validated? }
|
45
45
|
}
|
46
46
|
|
47
|
-
|
48
|
-
filter: 'MFA Enabled',
|
49
|
-
id: '
|
50
|
-
proc: ->(user, admin_user) { user.
|
47
|
+
DEFAULT_MFA_SMS_ENABLED = {
|
48
|
+
filter: 'MFA SMS Enabled',
|
49
|
+
id: 'mfa_sms_enabled',
|
50
|
+
proc: ->(user, admin_user) { user.mfa_sms_enabled? }
|
51
51
|
}
|
52
52
|
|
53
|
-
DEFAULT_PAGE_FILTER = [DEFAULT_ADMIN_TYPE, DEFAULT_ADMIN_SELF, DEFAULT_ADMIN_ACTIVE, DEFAULT_EMAIL_VALIDATED,
|
53
|
+
DEFAULT_PAGE_FILTER = [DEFAULT_ADMIN_TYPE, DEFAULT_ADMIN_SELF, DEFAULT_ADMIN_ACTIVE, DEFAULT_EMAIL_VALIDATED, DEFAULT_MFA_SMS_ENABLED].flatten
|
54
54
|
DEFAULT_VALUES = {
|
55
55
|
enable: {
|
56
56
|
type: :boolean,
|
@@ -22,6 +22,7 @@ module RailsBase
|
|
22
22
|
duration: -> (val) { [ActiveSupport::Duration].include?(val.class) },
|
23
23
|
hash: -> (val) { [Hash].include?(val.class) },
|
24
24
|
integer: -> (val) { [Integer].include?(val.class) },
|
25
|
+
integer_nil: -> (val) { [Integer, NilClass].include?(val.class) },
|
25
26
|
klass: -> (_val) { true },
|
26
27
|
path: -> (val) { [Pathname].include?(val.class) },
|
27
28
|
proc: -> (val) { [Proc].include?(val.class) },
|
@@ -3,82 +3,49 @@ require 'rails_base/configuration/base'
|
|
3
3
|
module RailsBase
|
4
4
|
module Configuration
|
5
5
|
class Mfa < Base
|
6
|
-
|
7
|
-
MFA_MAX_LENGTH = 8
|
6
|
+
MFA_TYPE_OPTIONS = [ DEFAULT_TYPE = :totp, :twilio ]
|
8
7
|
DEFAULT_VALUES = {
|
9
8
|
enable: {
|
10
9
|
type: :boolean,
|
11
10
|
default: ENV.fetch('MFA_ENABLE', 'true')=='true',
|
12
|
-
description: 'Enable MFA
|
11
|
+
description: 'Enable MFA with SMS or TOTP verification. When not enabled, there are some interesting consequences',
|
13
12
|
},
|
14
|
-
|
15
|
-
type: :
|
16
|
-
default:
|
17
|
-
|
18
|
-
msg: "Must be an integer greater than #{MFA_MIN_LENGTH} and less than #{MFA_MAX_LENGTH}",
|
19
|
-
description: 'Length of MFA verification',
|
20
|
-
},
|
21
|
-
twilio_sid: {
|
22
|
-
type: :string,
|
23
|
-
default: ENV.fetch('TWILIO_ACCOUNT_SID',''),
|
24
|
-
secret: true,
|
25
|
-
description: 'Twilio SID',
|
13
|
+
enable_twilio: {
|
14
|
+
type: :boolean,
|
15
|
+
default: true,
|
16
|
+
description: 'Add Twilio as an MFA option.',
|
26
17
|
},
|
27
|
-
|
28
|
-
type: :
|
29
|
-
default:
|
30
|
-
|
31
|
-
description: 'Twilio Auth Token',
|
18
|
+
enable_totp: {
|
19
|
+
type: :boolean,
|
20
|
+
default: true,
|
21
|
+
description: 'Add TOTP as an MFA option.',
|
32
22
|
},
|
33
|
-
|
34
|
-
type: :
|
35
|
-
default:
|
36
|
-
description: '
|
23
|
+
max_attempts_before_password_expire: {
|
24
|
+
type: :integer,
|
25
|
+
default: 5,
|
26
|
+
description: 'Max MFA attempts before password expires and password must get re-entered',
|
37
27
|
},
|
38
|
-
|
28
|
+
max_password_expires_before_account_locked: {
|
39
29
|
type: :integer,
|
40
|
-
default:
|
41
|
-
description: 'Max number of
|
42
|
-
|
30
|
+
default: 5,
|
31
|
+
description: 'Max number of password expires before account is locked',
|
43
32
|
},
|
44
|
-
|
45
|
-
type: :
|
46
|
-
default:
|
47
|
-
|
33
|
+
reauth_strategy: {
|
34
|
+
type: :klass,
|
35
|
+
default: -> (_val) { RailsBase::Mfa::Strategy::EveryRequest },
|
36
|
+
custom: ->(val) { (Proc === val ? val.call(nil) : val).ancestors.include?(RailsBase::Mfa::Strategy::Base) },
|
37
|
+
msg: "Invalid ReAuth Strategy. Provided class must be descendent of RailsBase::Mfa::Strategy::Base",
|
38
|
+
description: "Value is expected to be a descendent of RailsBase::Mfa::Strategy::Base. It can be lazily loaded via a proc",
|
39
|
+
on_assignment: ->(val, instance) { instance.reauth_strategy = (Proc === val ? val.call(nil) : val) },
|
48
40
|
},
|
49
|
-
|
41
|
+
reauth_duration: {
|
50
42
|
type: :duration,
|
51
|
-
default:
|
52
|
-
description:
|
53
|
-
},
|
54
|
-
active_job_queue: {
|
55
|
-
type: :string,
|
56
|
-
default: 'twilio_sms',
|
57
|
-
description: 'The active job queue to send twilio messages from. Ensure that adapter is bound to the queue',
|
43
|
+
default: 2.days,
|
44
|
+
description: "When `reauth_strategy` is `time_based`, this value is the max time before MFA is required",
|
58
45
|
}
|
59
46
|
}
|
60
47
|
|
61
48
|
attr_accessor *DEFAULT_VALUES.keys
|
62
|
-
|
63
|
-
private
|
64
|
-
|
65
|
-
def custom_validations
|
66
|
-
enforce_twilio!
|
67
|
-
end
|
68
|
-
|
69
|
-
def enforce_twilio!
|
70
|
-
return unless enable == true
|
71
|
-
|
72
|
-
return if twilio_sid.present? &&
|
73
|
-
twilio_auth_token.present? &&
|
74
|
-
twilio_from_number.present?
|
75
|
-
|
76
|
-
raise InvalidConfiguration, "twilio_sid twilio_auth_token twilio_from_number need to be present when `mfa.enabled`"
|
77
|
-
end
|
78
|
-
|
79
|
-
def default_values
|
80
|
-
DEFAULT_VALUES
|
81
|
-
end
|
82
49
|
end
|
83
50
|
end
|
84
51
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rails_base/configuration/base'
|
2
|
+
|
3
|
+
module RailsBase
|
4
|
+
module Configuration
|
5
|
+
class Totp < Base
|
6
|
+
|
7
|
+
DEFAULT_VALUES = {
|
8
|
+
enable: {
|
9
|
+
type: :boolean,
|
10
|
+
default: true,
|
11
|
+
description: "TOTP must be explicitly enabled per application"
|
12
|
+
},
|
13
|
+
secret_code_length: {
|
14
|
+
type: :integer,
|
15
|
+
default: 32,
|
16
|
+
description: "The length of the secret to generate an OTP"
|
17
|
+
},
|
18
|
+
backup_code_length: {
|
19
|
+
type: :integer,
|
20
|
+
default: 64,
|
21
|
+
description: "The length of each backup code provided"
|
22
|
+
},
|
23
|
+
backup_code_count: {
|
24
|
+
type: :integer,
|
25
|
+
default: 10,
|
26
|
+
description: "The number of Backup codes generated if TOTP is cannot be solved.",
|
27
|
+
},
|
28
|
+
allowed_drift: {
|
29
|
+
type: :integer,
|
30
|
+
default: 30,
|
31
|
+
description: "The allowed drift around the current timestamp.",
|
32
|
+
},
|
33
|
+
allowed_drift_behind:{
|
34
|
+
type: :integer_nil,
|
35
|
+
default: nil,
|
36
|
+
description: "Allowed drift behind current timestamp. Takes precendence over allowed_drift",
|
37
|
+
},
|
38
|
+
allowed_drift_ahead:{
|
39
|
+
type: :integer_nil,
|
40
|
+
default: nil,
|
41
|
+
description: "Allowed drift ahead current timestamp. Takes precendence over allowed_drift",
|
42
|
+
},
|
43
|
+
velocity_max: {
|
44
|
+
type: :integer,
|
45
|
+
default: 3,
|
46
|
+
description: 'Max number of TOTP we allow a user to attempt in a sliding window',
|
47
|
+
},
|
48
|
+
velocity_max_in_frame: {
|
49
|
+
type: :duration,
|
50
|
+
default: 60.seconds,
|
51
|
+
description: 'Sliding window for velocity_max',
|
52
|
+
},
|
53
|
+
velocity_frame: {
|
54
|
+
type: :duration,
|
55
|
+
default: 300.seconds,
|
56
|
+
description: 'Debug purposes. How long to keep velocity_max attempts',
|
57
|
+
},
|
58
|
+
}
|
59
|
+
attr_accessor *DEFAULT_VALUES.keys
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def custom_validations
|
64
|
+
if velocity_max_in_frame < max_behind || velocity_max_in_frame < max_ahead
|
65
|
+
raise ArgumentError, "totp.velocity_max_in_frame must be greater than the allowed_drift"
|
66
|
+
end
|
67
|
+
|
68
|
+
if velocity_frame < velocity_max_in_frame
|
69
|
+
raise ArgumentError, "totp.velocity_frame must be greater than totp.velocity_max_in_frame"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def max_behind
|
74
|
+
allowed_drift_ahead || allowed_drift
|
75
|
+
end
|
76
|
+
|
77
|
+
def max_ahead
|
78
|
+
allowed_drift_behind || allowed_drift
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|