From ba192f12e381842c90df0fab2fcb1a23cae97fc4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 27 Jan 2017 20:28:46 +0100 Subject: Added optional two-factor authentication --- app/assets/stylesheets/forms.scss | 32 ++++++++++++++++++++-- app/controllers/auth/sessions_controller.rb | 6 ++++ .../settings/two_factor_auths_controller.rb | 28 +++++++++++++++++++ app/models/user.rb | 4 ++- app/views/auth/sessions/new.html.haml | 1 + app/views/settings/shared/_links.html.haml | 4 ++- app/views/settings/two_factor_auths/show.html.haml | 17 ++++++++++++ 7 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 app/controllers/settings/two_factor_auths_controller.rb create mode 100644 app/views/settings/two_factor_auths/show.html.haml (limited to 'app') diff --git a/app/assets/stylesheets/forms.scss b/app/assets/stylesheets/forms.scss index 365396511..560388f8f 100644 --- a/app/assets/stylesheets/forms.scss +++ b/app/assets/stylesheets/forms.scss @@ -7,6 +7,18 @@ code { max-width: 400px; padding: 20px; margin: 0 auto; + + p { + font-size: 14px; + line-height: 18px; + color: $color2; + margin-bottom: 20px; + + strong { + color: $color5; + font-weight: 500; + } + } } .simple_form { @@ -118,7 +130,7 @@ code { margin-top: 30px; } - button { + button, .block-button { display: block; width: 100%; border: 0; @@ -128,6 +140,9 @@ code { font-size: 18px; padding: 10px; text-transform: uppercase; + text-decoration: none; + text-align: center; + box-sizing: border-box; cursor: pointer; font-weight: 500; outline: 0; @@ -176,7 +191,7 @@ code { text-align: center; a { - color: white; + color: $color5; text-decoration: none; &:hover { @@ -200,3 +215,16 @@ code { font-weight: 500; } } + +.qr-code { + background: #fff; + padding: 4px; + margin-bottom: 20px; + box-shadow: 0 0 15px rgba($color8, 0.2); + display: inline-block; + + svg { + display: block; + margin: 0; + } +} diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index c8350f9a1..889b20e11 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -5,6 +5,8 @@ class Auth::SessionsController < Devise::SessionsController layout 'auth' + before_action :configure_sign_in_params, only: [:create] + def create super do |resource| remember_me(resource) @@ -13,6 +15,10 @@ class Auth::SessionsController < Devise::SessionsController protected + def configure_sign_in_params + devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt]) + end + def after_sign_in_path_for(_resource) last_url = stored_location_for(:user) diff --git a/app/controllers/settings/two_factor_auths_controller.rb b/app/controllers/settings/two_factor_auths_controller.rb new file mode 100644 index 000000000..66a82aab7 --- /dev/null +++ b/app/controllers/settings/two_factor_auths_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class Settings::TwoFactorAuthsController < ApplicationController + layout 'auth' + + before_action :authenticate_user! + + def show + return unless current_user.otp_required_for_login + + @qrcode = RQRCode::QRCode.new(current_user.otp_provisioning_uri(current_user.email, issuer: Rails.configuration.x.local_domain)) + end + + def enable + current_user.otp_required_for_login = true + current_user.otp_secret = User.generate_otp_secret + current_user.save! + + redirect_to settings_two_factor_auth_path + end + + def disable + current_user.otp_required_for_login = false + current_user.save! + + redirect_to settings_two_factor_auth_path + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 71d3ee0b8..b34144f2c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,7 +3,9 @@ class User < ApplicationRecord include Settings::Extend - devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable + devise :registerable, :recoverable, + :rememberable, :trackable, :validatable, :confirmable, + :two_factor_authenticatable, otp_secret_encryption_key: ENV['OTP_SECRET'] belongs_to :account, inverse_of: :user accepts_nested_attributes_for :account diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml index 93b9629f1..192a54bc6 100644 --- a/app/views/auth/sessions/new.html.haml +++ b/app/views/auth/sessions/new.html.haml @@ -4,6 +4,7 @@ = simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| = f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') } = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password') } + = f.input :otp_attempt, placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt') } .actions = f.button :button, t('auth.login'), type: :submit diff --git a/app/views/settings/shared/_links.html.haml b/app/views/settings/shared/_links.html.haml index a6e90f457..6490ffdd8 100644 --- a/app/views/settings/shared/_links.html.haml +++ b/app/views/settings/shared/_links.html.haml @@ -5,4 +5,6 @@ %li= link_to t('settings.preferences'), settings_preferences_path - if controller_name != 'registrations' %li= link_to t('auth.change_password'), edit_user_registration_path - %li= link_to t('settings.back'), root_path \ No newline at end of file + - if controller_name != 'two_factor_auths' + %li= link_to t('settings.two_factor_auth'), settings_two_factor_auth_path + %li= link_to t('settings.back'), root_path diff --git a/app/views/settings/two_factor_auths/show.html.haml b/app/views/settings/two_factor_auths/show.html.haml new file mode 100644 index 000000000..5070bb9d4 --- /dev/null +++ b/app/views/settings/two_factor_auths/show.html.haml @@ -0,0 +1,17 @@ +- content_for :page_title do + = t('settings.two_factor_auth') + +- if current_user.otp_required_for_login + %p= t('two_factor_auth.instructions_html') + + .qr-code= raw @qrcode.as_svg(padding: 0, module_size: 5) + + .simple_form + = link_to t('two_factor_auth.disable'), disable_settings_two_factor_auth_path, data: { method: 'POST' }, class: 'block-button' +- else + %p= t('two_factor_auth.description_html') + + .simple_form + = link_to t('two_factor_auth.enable'), enable_settings_two_factor_auth_path, data: { method: 'POST' }, class: 'block-button' + +.form-footer= render "settings/shared/links" -- cgit