rails_base 0.75.6 → 0.80.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/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/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
@@ -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">
|
@@ -19,7 +22,7 @@
|
|
19
22
|
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
28
|
<button class="btn btn_primary btn-block show-create-modal" type="button">Modify</button>
|
@@ -39,6 +42,81 @@
|
|
39
42
|
</tbody>
|
40
43
|
</table>
|
41
44
|
</div>
|
45
|
+
<div class='row'>
|
46
|
+
<div class='col'>
|
47
|
+
<div id="advancedSecurity">
|
48
|
+
<div class="row">
|
49
|
+
<div class="col-12">
|
50
|
+
<button id="advancedSecurity-title" class="text-center btn btn-warning btn-block" type="button" id="dropdownMenuButton" aria-haspopup="true" aria-expanded="false">
|
51
|
+
MFA Options
|
52
|
+
</button>
|
53
|
+
</div>
|
54
|
+
</div>
|
55
|
+
<div class="row">
|
56
|
+
<div class="col-12">
|
57
|
+
<div id="advancedSecurity-body" class="collapseable-body">
|
58
|
+
<br>
|
59
|
+
<div class="row">
|
60
|
+
<div class="col-10 offset-1">
|
61
|
+
<% if RailsBase.config.mfa.enable? %>
|
62
|
+
<% if current_user.mfa_sms_enabled %>
|
63
|
+
<button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#modifyMfamodal">
|
64
|
+
Modify 2fa Auth
|
65
|
+
</button>
|
66
|
+
<%= render partial: 'rails_base/shared/modify_mfa_auth_modal'%>
|
67
|
+
<% else %>
|
68
|
+
<button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#enableMfamodal">
|
69
|
+
Enable 2fa Auth
|
70
|
+
</button>
|
71
|
+
<%= render partial: 'rails_base/shared/enable_mfa_auth_modal'%>
|
72
|
+
<% end %>
|
73
|
+
<% end %>
|
74
|
+
</div>
|
75
|
+
</div>
|
76
|
+
<div class="row"><div class="col-6 offset-3">
|
77
|
+
<hr>
|
78
|
+
</div></div>
|
79
|
+
|
80
|
+
<div class="row">
|
81
|
+
<div class="col-10 offset-1">
|
82
|
+
<% if RailsBase.config.totp.enable? %>
|
83
|
+
<% if current_user.mfa_otp_enabled %>
|
84
|
+
<div class="row">
|
85
|
+
<div class="col-12">
|
86
|
+
<button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpDisableModal">
|
87
|
+
Disable One Time Password Auth
|
88
|
+
</button>
|
89
|
+
</div>
|
90
|
+
</div>
|
91
|
+
<%= render partial: 'rails_base/shared/totp/remove_authenticator_modal', locals: { type: @type, endpoint: @endpoint } %>
|
92
|
+
<br>
|
93
|
+
<div class="row">
|
94
|
+
<div class="col-12">
|
95
|
+
<button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpEnableModal">
|
96
|
+
Add One Time Password Auth
|
97
|
+
</button>
|
98
|
+
</div>
|
99
|
+
</div>
|
100
|
+
<% else %>
|
101
|
+
<button type="button" class="btn btn-block btn_info close-me" data-toggle="modal" data-target="#totpEnableModal">
|
102
|
+
Enable One Time Password Auth
|
103
|
+
</button>
|
104
|
+
<% end %>
|
105
|
+
<%= render partial: 'rails_base/shared/totp/add_authenticator_modal', locals: { type: @type, endpoint: @endpoint } %>
|
106
|
+
<% end %>
|
107
|
+
</div>
|
108
|
+
</div>
|
109
|
+
</div>
|
110
|
+
</div>
|
111
|
+
</div>
|
112
|
+
</div>
|
113
|
+
|
114
|
+
<%= 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 } } %>
|
115
|
+
|
116
|
+
</div>
|
117
|
+
</div>
|
118
|
+
</br>
|
119
|
+
<hr>
|
42
120
|
<div class="row" style="margin-top: 50px">
|
43
121
|
<button type="button" class="btn btn_danger btn-block" data-toggle="modal" data-target="#destroyUserModal">
|
44
122
|
Destroy account
|
@@ -52,3 +130,8 @@
|
|
52
130
|
<%= render 'modify_password' %>
|
53
131
|
<%= render 'destroy_user' %>
|
54
132
|
|
133
|
+
<script type="text/javascript">
|
134
|
+
$(document).ready(function(){
|
135
|
+
_railsBase_goToStandardizedCollapse("openmfa", `#advancedSecurity-body`, `<%= function_name %>`)
|
136
|
+
})
|
137
|
+
</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
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'rails_base/configuration/base'
|
2
|
+
|
3
|
+
module RailsBase
|
4
|
+
module Configuration
|
5
|
+
class Twilio < Base
|
6
|
+
|
7
|
+
MFA_TYPE_OPTIONS = [ DEFAULT_TYPE = :totp, :twilio ]
|
8
|
+
MFA_MIN_LENGTH = 4
|
9
|
+
MFA_MAX_LENGTH = 8
|
10
|
+
DEFAULT_VALUES = {
|
11
|
+
enable: {
|
12
|
+
type: :boolean,
|
13
|
+
default: ENV.fetch('MFA_ENABLE', 'true')=='true',
|
14
|
+
description: 'Allow twilio as an MFA option.',
|
15
|
+
},
|
16
|
+
mfa_length: {
|
17
|
+
type: :integer,
|
18
|
+
default: 5,
|
19
|
+
custom: ->(val) { val > MFA_MIN_LENGTH && val < MFA_MAX_LENGTH },
|
20
|
+
msg: "Must be an integer greater than #{MFA_MIN_LENGTH} and less than #{MFA_MAX_LENGTH}",
|
21
|
+
description: 'Length of MFA verification',
|
22
|
+
},
|
23
|
+
twilio_sid: {
|
24
|
+
type: :string,
|
25
|
+
default: ENV.fetch('TWILIO_ACCOUNT_SID',''),
|
26
|
+
secret: true,
|
27
|
+
description: 'Twilio SID',
|
28
|
+
},
|
29
|
+
twilio_auth_token: {
|
30
|
+
type: :string,
|
31
|
+
default: ENV.fetch('TWILIO_AUTH_TOKEN', ''),
|
32
|
+
secret: true,
|
33
|
+
description: 'Twilio Auth Token',
|
34
|
+
},
|
35
|
+
twilio_from_number: {
|
36
|
+
type: :string,
|
37
|
+
default: ENV.fetch('TWILIO_FROM_NUMBER', ''),
|
38
|
+
description: 'Number that we send MFA\'s From',
|
39
|
+
},
|
40
|
+
twilio_velocity_max: {
|
41
|
+
type: :integer,
|
42
|
+
default: ENV.fetch('TWILIO_VELOCITY_MAX', 5).to_i,
|
43
|
+
description: 'Max number of SMS we send to a user in a sliding window',
|
44
|
+
|
45
|
+
},
|
46
|
+
twilio_velocity_max_in_frame: {
|
47
|
+
type: :duration,
|
48
|
+
default: ENV.fetch('TWILIO_VELOCITY_MAX_IN_FRAME', 1).to_i.hours,
|
49
|
+
description: 'Sliding window for twilio_velocity_max',
|
50
|
+
},
|
51
|
+
twilio_velocity_frame: {
|
52
|
+
type: :duration,
|
53
|
+
default: ENV.fetch('TWILIO_VELOCITY_FRAME', 5).to_i.hours,
|
54
|
+
description: 'Debug purposes. How long to keep admin_velocity_max attempts',
|
55
|
+
},
|
56
|
+
active_job_queue: {
|
57
|
+
type: :string,
|
58
|
+
default: 'twilio_sms',
|
59
|
+
description: 'The active job queue to send twilio messages from. Ensure that adapter is bound to the queue',
|
60
|
+
}
|
61
|
+
}
|
62
|
+
attr_accessor *DEFAULT_VALUES.keys
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def custom_validations
|
67
|
+
enforce_twilio!
|
68
|
+
end
|
69
|
+
|
70
|
+
def enforce_twilio!
|
71
|
+
return unless enable == true
|
72
|
+
|
73
|
+
return if twilio_sid.present? &&
|
74
|
+
twilio_auth_token.present? &&
|
75
|
+
twilio_from_number.present?
|
76
|
+
|
77
|
+
raise InvalidConfiguration, "twilio_sid twilio_auth_token twilio_from_number need to be present when `mfa.enabled`"
|
78
|
+
end
|
79
|
+
|
80
|
+
def default_values
|
81
|
+
DEFAULT_VALUES
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|