Commit 2c4c0ad
Changed files (7)
app
controllers
models
spec
requests
support
app/controllers/application_controller.rb
@@ -4,6 +4,7 @@ class ApplicationController < ActionController::Base
include SamlRespondable
protect_from_forgery with: :exception
before_action :authenticate!
+ before_action :authenticate_mfa!
helper_method :current_user, :current_user?
add_flash_types :error, :warning
@@ -29,4 +30,9 @@ class ApplicationController < ActionController::Base
def authenticate!
redirect_to new_session_path unless current_user?
end
+
+ def authenticate_mfa!
+ return unless current_user?
+ redirect_to mfa_path unless current_user.tfa.valid_session?(session[:mfa])
+ end
end
app/controllers/mfas_controller.rb
@@ -1,10 +1,13 @@
# frozen_string_literal: true
class MfasController < ApplicationController
+ skip_before_action :authenticate_mfa!
+
def new; end
def create
if current_user.tfa.authenticate(secure_params[:code])
+ session[:mfa] = { issued_at: Time.now.utc.to_i }
redirect_to response_path
else
redirect_to mfa_path, error: "Invalid code"
app/models/tfa.rb
@@ -35,6 +35,11 @@ class Tfa
totp.verify(entered_code)
end
+ def valid_session?(session)
+ return true unless setup?
+ session && session[:issued_at].present?
+ end
+
private
def totp
spec/requests/my/mfas_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe '/my/mfa' do
describe "GET /my/mfa" do
context "when MFA is set up" do
let(:current_user) { create(:user, :mfa_configured) }
+
before { get '/my/mfa' }
specify { expect(response).to redirect_to(edit_my_mfa_path) }
end
spec/requests/mfas_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe "/mfa" do
before { post '/mfa', params: { mfa: { code: correct_code } } }
specify { expect(response).to redirect_to(response_path) }
+ specify { expect(session[:mfa]).to be_present }
end
context "when the code is incorrect" do
spec/requests/response_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe "/response" do
context "when the user has completed password authentication" do
let(:current_user) { create(:user) }
- before { http_login(current_user) }
+ before { http_login(current_user, skip_mfa: true) }
context "when a saml request was present in session" do
let(:registry) { Saml::Kit::DefaultRegistry.new }
@@ -54,6 +54,14 @@ RSpec.describe "/response" do
specify { expect(response).to redirect_to(my_dashboard_path) }
end
+
+ context "when MFA authentication has not been completed" do
+ let(:current_user) { create(:user, :mfa_configured) }
+
+ before { get '/response' }
+
+ specify { expect(response).to redirect_to(mfa_path) }
+ end
end
end
end
spec/support/request.rb
@@ -1,7 +1,13 @@
RSpec.configure do |config|
config.include(Module.new do
- def http_login(user)
+ def http_login(user, skip_mfa: false)
post '/session', params: { user: { email: user.email, password: user.password } }
+ return if skip_mfa
+ mfa_login(user) if user.tfa.setup?
+ end
+
+ def mfa_login(user)
+ post '/mfa', params: { mfa: { code: user.tfa.current_totp } }
end
end)
end