Commit 32971b0a authored by Robert Speicher's avatar Robert Speicher

Refactor SessionsController

Also adds test case for providing an invalid 2FA code and then a valid
one without re-entering username and password.
parent 6369d23d
......@@ -29,10 +29,7 @@ class SessionsController < Devise::SessionsController
def create
super do |resource|
# Remove any lingering user data from login
session.delete(:user)
# User has successfully signed in, so clear any unused reset tokens
# User has successfully signed in, so clear any unused reset token
if resource.reset_password_token.present?
resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil)
......@@ -46,34 +43,40 @@ class SessionsController < Devise::SessionsController
params.require(:user).permit(:login, :password, :remember_me, :otp_attempt)
end
def find_user
if user_params[:login]
User.by_login(user_params[:login])
elsif user_params[:otp_attempt] && session[:otp_user_id]
User.find(session[:otp_user_id])
end
end
def authenticate_with_two_factor
@user = User.by_login(user_params[:login])
user = self.resource = find_user
return unless user && user.otp_required_for_login
if user_params[:otp_attempt].present? && session[:user]
if valid_otp_attempt?
# Insert the saved params from the session into the request parameters
# so they're available to Devise::Strategies::DatabaseAuthenticatable
request.params[:user].merge!(session[:user])
if user_params[:otp_attempt].present? && session[:otp_user_id]
if valid_otp_attempt?(user)
# Remove any lingering user data from login
session.delete(:otp_user_id)
sign_in(user)
else
@error = 'Invalid two-factor code'
render :two_factor and return
end
else
if @user && @user.valid_password?(user_params[:password])
self.resource = @user
if resource.otp_required_for_login
# Login is valid, save the values to the session so we can prompt the
# user for a one-time password.
session[:user] = user_params
render :two_factor and return
end
if user && user.valid_password?(user_params[:password])
# Save the user's ID to session so we can ask for a one-time password
session[:otp_user_id] = user.id
render :two_factor and return
end
end
end
def valid_otp_attempt?
@user.valid_otp?(user_params[:otp_attempt]) ||
@user.invalidate_otp_backup_code!(user_params[:otp_attempt])
def valid_otp_attempt?(user)
user.valid_otp?(user_params[:otp_attempt]) ||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end
end
......@@ -7,7 +7,6 @@
- if @error
.alert.alert-danger
= @error
= f.hidden_field :login
= f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor authentication code', required: true, autofocus: true
.prepend-top-20
= f.submit "Verify code", class: "btn btn-save"
......@@ -31,6 +31,14 @@ feature 'Login' do
enter_code('foo')
expect(page).to have_content('Invalid two-factor code')
end
it 'allows login with invalid code, then valid code' do
enter_code('foo')
expect(page).to have_content('Invalid two-factor code')
enter_code(user.current_otp)
expect(current_path).to eq root_path
end
end
context 'using backup code' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment