Refactor SessionsController
Also adds test case for providing an invalid 2FA code and then a valid one without re-entering username and password.
This commit is contained in:
		
							parent
							
								
									6369d23d58
								
							
						
					
					
						commit
						32971b0af4
					
				|  | @ -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 authenticate_with_two_factor | ||||
|     @user = User.by_login(user_params[:login]) | ||||
|   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 | ||||
| 
 | ||||
|     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]) | ||||
|   def authenticate_with_two_factor | ||||
|     user = self.resource = find_user | ||||
| 
 | ||||
|     return unless user && user.otp_required_for_login | ||||
| 
 | ||||
|     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 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue