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 'rake-progressbar', require: false
|
||||||
gem 'react-rails'
|
gem 'react-rails'
|
||||||
gem 'rgeo-geojson'
|
gem 'rgeo-geojson'
|
||||||
|
gem 'rqrcode'
|
||||||
gem 'sanitize-url'
|
gem 'sanitize-url'
|
||||||
gem 'sassc-rails' # Use SCSS for stylesheets
|
gem 'sassc-rails' # Use SCSS for stylesheets
|
||||||
gem 'sentry-raven'
|
gem 'sentry-raven'
|
||||||
|
|
|
@ -571,6 +571,10 @@ GEM
|
||||||
rotp (4.1.0)
|
rotp (4.1.0)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
rouge (3.17.0)
|
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 (3.9.0)
|
||||||
rspec-core (~> 3.9.0)
|
rspec-core (~> 3.9.0)
|
||||||
rspec-expectations (~> 3.9.0)
|
rspec-expectations (~> 3.9.0)
|
||||||
|
@ -849,6 +853,7 @@ DEPENDENCIES
|
||||||
rake-progressbar
|
rake-progressbar
|
||||||
react-rails
|
react-rails
|
||||||
rgeo-geojson
|
rgeo-geojson
|
||||||
|
rqrcode
|
||||||
rspec-rails
|
rspec-rails
|
||||||
rspec_junit_formatter
|
rspec_junit_formatter
|
||||||
rubocop
|
rubocop
|
||||||
|
|
|
@ -1,12 +1,2 @@
|
||||||
class Administrations::SessionsController < ApplicationController
|
class Administrations::SessionsController < Devise::SessionsController
|
||||||
def new
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
if administration_signed_in?
|
|
||||||
sign_out :administration
|
|
||||||
end
|
|
||||||
|
|
||||||
redirect_to root_path
|
|
||||||
end
|
|
||||||
end
|
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 :set_raven_context
|
||||||
before_action :redirect_if_untrusted
|
before_action :redirect_if_untrusted
|
||||||
before_action :reject, if: -> { feature_enabled?(:maintenance_mode) }
|
before_action :reject, if: -> { feature_enabled?(:maintenance_mode) }
|
||||||
|
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||||
|
|
||||||
before_action :staging_authenticate
|
before_action :staging_authenticate
|
||||||
before_action :set_active_storage_host
|
before_action :set_active_storage_host
|
||||||
|
@ -105,6 +106,10 @@ class ApplicationController < ActionController::Base
|
||||||
stored_location_for(:user) || super
|
stored_location_for(:user) || super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def configure_permitted_parameters
|
||||||
|
devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_current_roles
|
def set_current_roles
|
||||||
|
|
|
@ -13,8 +13,10 @@ module Manager
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def authenticate_administration!
|
def authenticate_administration!
|
||||||
if administration_signed_in?
|
if administration_signed_in? && current_administration.otp_required_for_login?
|
||||||
super
|
super
|
||||||
|
elsif administration_signed_in?
|
||||||
|
redirect_to edit_administration_otp_path
|
||||||
else
|
else
|
||||||
redirect_to manager_sign_in_path
|
redirect_to manager_sign_in_path
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,6 +28,24 @@ class Administration < ApplicationRecord
|
||||||
devise :rememberable, :trackable, :validatable, :lockable, :async, :recoverable,
|
devise :rememberable, :trackable, :validatable, :lockable, :async, :recoverable,
|
||||||
:two_factor_authenticatable, :otp_secret_encryption_key => ENV['OTP_SECRET_KEY']
|
: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)
|
def invite_admin(email)
|
||||||
user = User.create_or_promote_to_administrateur(email, SecureRandom.hex)
|
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
|
.super-admin.flex.justify-center
|
||||||
%div
|
%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
|
# Authentication
|
||||||
#
|
#
|
||||||
|
|
||||||
devise_for :administrations,
|
devise_for :administrations, skip: [:registrations]
|
||||||
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: {
|
devise_for :users, controllers: {
|
||||||
sessions: 'users/sessions',
|
sessions: 'users/sessions',
|
||||||
|
|
|
@ -7,13 +7,23 @@ describe Manager::AdministrateursController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#show' do
|
describe '#show' do
|
||||||
render_views
|
let(:subject) { get :show, params: { id: administrateur.id } }
|
||||||
|
|
||||||
before do
|
context 'with 2FA not enabled' do
|
||||||
get :show, params: { id: administrateur.id }
|
let(:administration) { create(:administration, otp_required_for_login: false) }
|
||||||
|
it { expect(subject).to redirect_to(edit_administration_otp_path) }
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(response.body).to include(administrateur.email) }
|
context 'with 2FA enabled' do
|
||||||
|
render_views
|
||||||
|
let(:administration) { create(:administration, otp_required_for_login: true) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(response.body).to include(administrateur.email) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET #new' do
|
describe 'GET #new' do
|
||||||
|
|
|
@ -3,6 +3,6 @@ FactoryBot.define do
|
||||||
factory :administration do
|
factory :administration do
|
||||||
email { generate(:administration_email) }
|
email { generate(:administration_email) }
|
||||||
password { 'my-s3cure-p4ssword' }
|
password { 'my-s3cure-p4ssword' }
|
||||||
otp_required_for_login { false }
|
otp_required_for_login { true }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,4 +34,31 @@ describe Administration, type: :model do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Reference in a new issue