From 489f9859be38bad3b63913986e618463d1a89818 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 2 Dec 2023 01:50:54 +0200 Subject: [PATCH] add force 2fa toggle --- app/controllers/dashboard_controller.rb | 12 ++++++++++++ app/controllers/mfa_force_controller.rb | 20 ++++++++++++++++++++ app/controllers/mfa_setup_controller.rb | 22 ++++++++++++++++------ app/models/account_config.rb | 1 + app/views/mfa_setup/_form.html.erb | 17 +++++++++++++++++ app/views/mfa_setup/new.html.erb | 18 +----------------- app/views/mfa_setup/show.html.erb | 4 ++++ app/views/users/index.html.erb | 25 ++++++++++++++++++++----- config/routes.rb | 3 ++- 9 files changed, 93 insertions(+), 29 deletions(-) create mode 100644 app/controllers/mfa_force_controller.rb create mode 100644 app/views/mfa_setup/_form.html.erb create mode 100644 app/views/mfa_setup/show.html.erb diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index aa08ae4d..312f71cd 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -5,6 +5,7 @@ class DashboardController < ApplicationController before_action :maybe_redirect_product_url before_action :maybe_render_landing + before_action :maybe_redirect_mfa_setup load_and_authorize_resource :template_folder, parent: false load_and_authorize_resource :template, parent: false @@ -62,6 +63,17 @@ class DashboardController < ApplicationController redirect_to Docuseal::PRODUCT_URL, allow_other_host: true end + def maybe_redirect_mfa_setup + return unless signed_in? + return if current_user.otp_required_for_login + + return if !current_user.otp_required_for_login && !AccountConfig.exists?(value: true, + account_id: current_user.account_id, + key: AccountConfig::FORCE_MFA) + + redirect_to mfa_setup_path, notice: 'Setup 2FA to continue' + end + def maybe_render_landing return if signed_in? diff --git a/app/controllers/mfa_force_controller.rb b/app/controllers/mfa_force_controller.rb new file mode 100644 index 00000000..1be78147 --- /dev/null +++ b/app/controllers/mfa_force_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class MfaForceController < ApplicationController + before_action :load_account_config + authorize_resource :account_config + + def create + @account_config.update!(value: !@account_config.value) + + redirect_back fallback_location: settings_users_path, + notice: "Force 2FA has been #{@account_config.value ? 'enabled' : 'disabled'}." + end + + private + + def load_account_config + @account_config = + AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORCE_MFA) + end +end diff --git a/app/controllers/mfa_setup_controller.rb b/app/controllers/mfa_setup_controller.rb index ce690643..c65b2006 100644 --- a/app/controllers/mfa_setup_controller.rb +++ b/app/controllers/mfa_setup_controller.rb @@ -5,13 +5,11 @@ class MfaSetupController < ApplicationController authorize!(:update, current_user) end - def new - current_user.otp_secret ||= User.generate_otp_secret + before_action :set_provision_url, only: %i[show new] - current_user.save! + def show; end - @provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name) - end + def new; end def edit; end @@ -26,7 +24,7 @@ class MfaSetupController < ApplicationController @error_message = 'Code is invalid' - render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/new'), status: :unprocessable_entity + render turbo_stream: turbo_stream.replace(:mfa_form, partial: 'mfa_setup/form'), status: :unprocessable_entity end end @@ -41,4 +39,16 @@ class MfaSetupController < ApplicationController render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_entity end end + + private + + def set_provision_url + return redirect_to root_path, alert: '2FA has been set up already' if current_user.otp_required_for_login + + current_user.otp_secret ||= User.generate_otp_secret + + current_user.save! + + @provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name) + end end diff --git a/app/models/account_config.rb b/app/models/account_config.rb index b592cbca..56410a89 100644 --- a/app/models/account_config.rb +++ b/app/models/account_config.rb @@ -25,6 +25,7 @@ class AccountConfig < ApplicationRecord SUBMITTER_COMPLETED_EMAIL_KEY = 'submitter_completed_email' SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY = 'submitter_documents_copy_email' BCC_EMAILS = 'bcc_emails' + FORCE_MFA = 'force_mfa' SUBMITTER_REMAILERS = 'submitter_reminders' FORM_COMPLETED_BUTTON_KEY = 'form_completed_button' diff --git a/app/views/mfa_setup/_form.html.erb b/app/views/mfa_setup/_form.html.erb new file mode 100644 index 00000000..c5060c22 --- /dev/null +++ b/app/views/mfa_setup/_form.html.erb @@ -0,0 +1,17 @@ +<%= form_for '', url: mfa_setup_path, data: { turbo_frame: :_top }, html: { id: 'mfa_form'} do |f| %> +

+ Use an authenticator mobile app like Google Authenticator or 1Password to scan the QR code below. +

+
+ <%== RQRCode::QRCode.new(@provision_url).as_svg(viewbox: true, svg_attributes: { class: 'w-80 h-80 my-4 mx-auto' }) %> +
+
+ <%= f.text_field :otp_attempt, required: true, placeholder: 'XXX-XXX', class: 'base-input text-center' %> + + <%= @error_message %> + +
+
+ <%= f.button button_title(title: 'Save'), class: 'base-button' %> +
+<% end %> diff --git a/app/views/mfa_setup/new.html.erb b/app/views/mfa_setup/new.html.erb index 14a20172..cece5f5e 100644 --- a/app/views/mfa_setup/new.html.erb +++ b/app/views/mfa_setup/new.html.erb @@ -1,19 +1,3 @@ <%= render 'shared/turbo_modal', title: 'Setup 2FA' do %> - <%= form_for '', url: mfa_setup_path, data: { turbo_frame: :_top } do |f| %> -

- Use an authenticator mobile app like Google Authenticator or 1Password to scan the QR code below. -

-
- <%== RQRCode::QRCode.new(@provision_url).as_svg(viewbox: true, svg_attributes: { class: 'w-80 h-80 my-4 mx-auto' }) %> -
-
- <%= f.text_field :otp_attempt, required: true, placeholder: 'XXX-XXX', class: 'base-input text-center' %> - - <%= @error_message %> - -
-
- <%= f.button button_title(title: 'Save'), class: 'base-button' %> -
- <% end %> + <%= render 'mfa_setup/form' %> <% end %> diff --git a/app/views/mfa_setup/show.html.erb b/app/views/mfa_setup/show.html.erb new file mode 100644 index 00000000..3aeb85a3 --- /dev/null +++ b/app/views/mfa_setup/show.html.erb @@ -0,0 +1,4 @@ +
+

Setup 2FA

+ <%= render 'mfa_setup/form' %> +
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 4b74f2c4..c38ce8c2 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -3,12 +3,27 @@

Team

- <% if can?(:create, User.new(account: current_account)) %> - <%= link_to new_user_path, class: 'btn btn-primary btn-md gap-2', data: { turbo_frame: 'modal' } do %> - <%= svg_icon('plus', class: 'w-6 h-6') %> - New User +
+ <% if !Docuseal.multitenant? %> + <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORCE_MFA) %> + <% if can?(:manage, account_config) %> + <%= form_for :force_mfa, url: mfa_force_path do |f| %> + + <% end %> + <% end %> + <% end %> + <% if can?(:create, User.new(account: current_account)) %> + <%= link_to new_user_path, class: 'btn btn-primary btn-md gap-2', data: { turbo_frame: 'modal' } do %> + <%= svg_icon('plus', class: 'w-6 h-6') %> + New User + <% end %> <% end %> - <% end %> +
diff --git a/config/routes.rb b/config/routes.rb index cc6fb5c0..830f8e3e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -48,7 +48,8 @@ Rails.application.routes.draw do end resources :verify_pdf_signature, only: %i[create] - resource :mfa_setup, only: %i[new edit create destroy], controller: 'mfa_setup' + resource :mfa_setup, only: %i[show new edit create destroy], controller: 'mfa_setup' + resource :mfa_force, only: %i[create], controller: 'mfa_force' resources :dashboard, only: %i[index] resources :setup, only: %i[index create] resource :newsletter, only: %i[show update]