enable 2FA for manager
when trying to access manager, if superadmin did'nt enable otp, he/she is redirected to a page to enable 2FA. When superadmin is enabling 2FA, he has to to scan a qrcode with the 2FA application client. And afterwards, the superadmin has to log in with email, password and OTP code.
This commit is contained in:
parent
3fdb045356
commit
2a0ebd062a
14 changed files with 141 additions and 20 deletions
1
Gemfile
1
Gemfile
|
@ -64,6 +64,7 @@ gem 'rails-i18n' # Locales par défaut
|
|||
gem 'rake-progressbar', require: false
|
||||
gem 'react-rails'
|
||||
gem 'rgeo-geojson'
|
||||
gem 'rqrcode'
|
||||
gem 'sanitize-url'
|
||||
gem 'sassc-rails' # Use SCSS for stylesheets
|
||||
gem 'sentry-raven'
|
||||
|
|
|
@ -571,6 +571,10 @@ GEM
|
|||
rotp (4.1.0)
|
||||
addressable (~> 2.5)
|
||||
rouge (3.17.0)
|
||||
rqrcode (1.1.2)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 0.1)
|
||||
rqrcode_core (0.1.2)
|
||||
rspec (3.9.0)
|
||||
rspec-core (~> 3.9.0)
|
||||
rspec-expectations (~> 3.9.0)
|
||||
|
@ -849,6 +853,7 @@ DEPENDENCIES
|
|||
rake-progressbar
|
||||
react-rails
|
||||
rgeo-geojson
|
||||
rqrcode
|
||||
rspec-rails
|
||||
rspec_junit_formatter
|
||||
rubocop
|
||||
|
|
|
@ -1,12 +1,2 @@
|
|||
class Administrations::SessionsController < ApplicationController
|
||||
def new
|
||||
end
|
||||
|
||||
def destroy
|
||||
if administration_signed_in?
|
||||
sign_out :administration
|
||||
end
|
||||
|
||||
redirect_to root_path
|
||||
end
|
||||
class Administrations::SessionsController < Devise::SessionsController
|
||||
end
|
||||
|
|
28
app/controllers/administrations_controller.rb
Normal file
28
app/controllers/administrations_controller.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
class AdministrationsController < ApplicationController
|
||||
before_action :authenticate_administration!
|
||||
|
||||
def edit_otp
|
||||
end
|
||||
|
||||
def enable_otp
|
||||
current_administration.enable_otp!
|
||||
@qrcode = generate_qr_code
|
||||
sign_out :administration
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def authenticate_administration!
|
||||
if !administration_signed_in?
|
||||
redirect_to root_path
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_qr_code
|
||||
issuer = 'DSManager'
|
||||
label = "#{issuer}:#{current_administration.email}"
|
||||
RQRCode::QRCode.new(current_administration.otp_provisioning_uri(label, issuer: issuer))
|
||||
end
|
||||
end
|
|
@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base
|
|||
before_action :set_raven_context
|
||||
before_action :redirect_if_untrusted
|
||||
before_action :reject, if: -> { feature_enabled?(:maintenance_mode) }
|
||||
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||
|
||||
before_action :staging_authenticate
|
||||
before_action :set_active_storage_host
|
||||
|
@ -105,6 +106,10 @@ class ApplicationController < ActionController::Base
|
|||
stored_location_for(:user) || super
|
||||
end
|
||||
|
||||
def configure_permitted_parameters
|
||||
devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_current_roles
|
||||
|
|
|
@ -13,8 +13,10 @@ module Manager
|
|||
protected
|
||||
|
||||
def authenticate_administration!
|
||||
if administration_signed_in?
|
||||
if administration_signed_in? && current_administration.otp_required_for_login?
|
||||
super
|
||||
elsif administration_signed_in?
|
||||
redirect_to edit_administration_otp_path
|
||||
else
|
||||
redirect_to manager_sign_in_path
|
||||
end
|
||||
|
|
|
@ -28,6 +28,24 @@ class Administration < ApplicationRecord
|
|||
devise :rememberable, :trackable, :validatable, :lockable, :async, :recoverable,
|
||||
:two_factor_authenticatable, :otp_secret_encryption_key => ENV['OTP_SECRET_KEY']
|
||||
|
||||
def enable_otp!
|
||||
self.otp_secret = Administration.generate_otp_secret
|
||||
self.otp_required_for_login = true
|
||||
save!
|
||||
end
|
||||
|
||||
def disable_otp!
|
||||
self.assign_attributes(
|
||||
{
|
||||
encrypted_otp_secret: nil,
|
||||
encrypted_otp_secret_iv: nil,
|
||||
encrypted_otp_secret_salt: nil,
|
||||
consumed_timestep: nil,
|
||||
otp_required_for_login: false
|
||||
}
|
||||
)
|
||||
save!
|
||||
end
|
||||
|
||||
def invite_admin(email)
|
||||
user = User.create_or_promote_to_administrateur(email, SecureRandom.hex)
|
||||
|
|
6
app/views/administrations/edit_otp.html.haml
Normal file
6
app/views/administrations/edit_otp.html.haml
Normal file
|
@ -0,0 +1,6 @@
|
|||
.super-admin.flex.justify-center
|
||||
%div
|
||||
%h2.huge-title Espace Manager
|
||||
%p Munissez-vous de votre téléphone sur lequel vous avez installé une application cliente 2FA (Google Authenticator, Authy, AndOTP, ...)
|
||||
%br
|
||||
%p= link_to "Activer l'authentification double-facteur", enable_administration_otp_path, method: :put, class: 'button primary'
|
12
app/views/administrations/enable_otp.html.haml
Normal file
12
app/views/administrations/enable_otp.html.haml
Normal file
|
@ -0,0 +1,12 @@
|
|||
.container
|
||||
%p
|
||||
%strong Si vous n'effectuez pas cette étape maintenant, vous ne pourrez plus vous connecter au manager !
|
||||
%p Depuis votre téléphone, lancez votre application cliente 2FA et scannez ce QRCode afin d'ajouter votre compte DSManager. Votre application vous fournira ensuite à chaque connexion au manager le code otp à saisir.
|
||||
%br
|
||||
= raw @qrcode.as_svg(module_size: 6)
|
||||
|
||||
%br
|
||||
|
||||
%p
|
||||
Après avoir scanné le QRCode ci-dessus, nous vous invitons à
|
||||
= link_to 'accéder au Manager' , manager_root_path
|
|
@ -1,3 +1,18 @@
|
|||
.super-admin.flex.justify-center
|
||||
%div
|
||||
%h2 Espace Admin
|
||||
%h2.huge-title Espace Manager
|
||||
.auth-form.sign-in-form
|
||||
|
||||
= form_for Administration.new, url: administration_session_path, html: { class: "form" } do |f|
|
||||
%h1 Connectez-vous
|
||||
|
||||
= f.label :email, "Email (nom@site.com)"
|
||||
= f.text_field :email, type: :email, autocomplete: 'username', autofocus: true
|
||||
|
||||
= f.label :password, "Mot de passe (#{PASSWORD_MIN_LENGTH} caractères minimum)"
|
||||
= f.password_field :password, autocomplete: 'current-password'
|
||||
|
||||
= f.label :otp_attempt, 'Code OTP (uniquement si vous avez déjà activé 2FA)'
|
||||
= f.text_field :otp_attempt
|
||||
|
||||
= f.submit "Se connecter", class: "button large primary expand"
|
||||
|
|
|
@ -77,8 +77,10 @@ Rails.application.routes.draw do
|
|||
# Authentication
|
||||
#
|
||||
|
||||
devise_for :administrations,
|
||||
skip: [:registrations]
|
||||
devise_for :administrations, skip: [:registrations]
|
||||
|
||||
get 'administrations/edit_otp', to: 'administrations#edit_otp', as: 'edit_administration_otp'
|
||||
put 'administrations/enable_otp', to: 'administrations#enable_otp', as: 'enable_administration_otp'
|
||||
|
||||
devise_for :users, controllers: {
|
||||
sessions: 'users/sessions',
|
||||
|
|
|
@ -7,14 +7,24 @@ describe Manager::AdministrateursController, type: :controller do
|
|||
end
|
||||
|
||||
describe '#show' do
|
||||
let(:subject) { get :show, params: { id: administrateur.id } }
|
||||
|
||||
context 'with 2FA not enabled' do
|
||||
let(:administration) { create(:administration, otp_required_for_login: false) }
|
||||
it { expect(subject).to redirect_to(edit_administration_otp_path) }
|
||||
end
|
||||
|
||||
context 'with 2FA enabled' do
|
||||
render_views
|
||||
let(:administration) { create(:administration, otp_required_for_login: true) }
|
||||
|
||||
before do
|
||||
get :show, params: { id: administrateur.id }
|
||||
subject
|
||||
end
|
||||
|
||||
it { expect(response.body).to include(administrateur.email) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #new' do
|
||||
render_views
|
||||
|
|
|
@ -3,6 +3,6 @@ FactoryBot.define do
|
|||
factory :administration do
|
||||
email { generate(:administration_email) }
|
||||
password { 'my-s3cure-p4ssword' }
|
||||
otp_required_for_login { false }
|
||||
otp_required_for_login { true }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,4 +34,31 @@ describe Administration, type: :model do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'enable_otp!' do
|
||||
let(:administration) { create(:administration, otp_required_for_login: false) }
|
||||
let(:subject) { administration.enable_otp! }
|
||||
|
||||
it 'updates otp_required_for_login' do
|
||||
expect { subject }.to change { administration.otp_required_for_login? }.from(false).to(true)
|
||||
end
|
||||
|
||||
it 'updates otp_secret' do
|
||||
expect { subject }.to change { administration.otp_secret }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'disable_otp!' do
|
||||
let(:administration) { create(:administration, otp_required_for_login: true) }
|
||||
let(:subject) { administration.disable_otp! }
|
||||
|
||||
it 'updates otp_required_for_login' do
|
||||
expect { subject }.to change { administration.otp_required_for_login? }.from(true).to(false)
|
||||
end
|
||||
|
||||
it 'nullifies otp_secret' do
|
||||
administration.enable_otp!
|
||||
expect { subject }.to change { administration.reload.otp_secret }.to(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue