Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update PasswordsControllerTest to use modern Rails IntegrationTest #5291

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

martinemde
Copy link
Member

@martinemde martinemde commented Dec 1, 2024

I was working on a vulnerability report that ended up being nothing. Just to be sure, I went through and increased the coverage on the password controller and converted it to use newer rails recommendations for functional tests instead of controller tests.

Copy link

codecov bot commented Dec 1, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.14%. Comparing base (de200d3) to head (c47f971).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #5291   +/-   ##
=======================================
  Coverage   97.14%   97.14%           
=======================================
  Files         457      457           
  Lines        9561     9562    +1     
=======================================
+ Hits         9288     9289    +1     
  Misses        273      273           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member Author

@martinemde martinemde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a sensitive controller, here's a summary of changes outside of the tests.

@@ -38,10 +38,11 @@ def update
@user.reset_api_key! if reset_params[:reset_api_key] == "true" # singular
@user.api_keys.expire_all! if reset_params[:reset_api_keys] == "true" # plural
delete_password_reset_session
flash[:notice] = t(".success")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a notice on success (though I have observed that it doesn't always display)

redirect_to signed_in? ? dashboard_path : sign_in_path
else
flash.now[:alert] = t(".failure")
render :edit
render :edit, status: :unprocessable_entity
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Send a code when rendering after post/put.

@@ -65,6 +66,7 @@ def ensure_email_present

def validate_confirmation_token
confirmation_token = params.permit(:token).fetch(:token, "").to_s
return login_failure(t("passwords.edit.token_failure")) if confirmation_token.blank?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being extra cautious about blank tokens just in case there's ever a blank token in the database.

@@ -98,8 +100,7 @@ def reset_params
end

def mfa_failure(message)
flash.now.alert = message
render template: "multifactor_auths/prompt", status: :unauthorized
prompt_mfa(alert: message, status: :unauthorized)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes a bug that I found where the otp form/webauthn form would lose the token in its url after a failed OTP submit.

@@ -212,12 +212,12 @@ def total_rubygems_count

def confirm_email!
return false if unconfirmed_email && !update_email
update!(email_confirmed: true, confirmation_token: nil)
update!(email_confirmed: true, confirmation_token: nil, token_expires_at: Time.now)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrects an inconsistency where the expiration was still in the future after the token was cleared.

def valid_confirmation_token?
token_expires_at > Time.zone.now
confirmation_token.present? && Time.zone.now.before?(token_expires_at)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed really strange that valid_confirmation_token? would return true after clearing the token. This is functionally the same with how it's used in the controllers, but helps the method behave as I would expect.

@@ -19,7 +19,7 @@
<div class="text_field">
<% if @user.totp_enabled? %>
<%= label_tag :otp, t(".otp_or_recovery"), class: 'form__label' %>
<%= text_field_tag :otp, '', class: 'form__input', autofocus: true, autocomplete: :off %>
<%= text_field_tag :otp, '', class: 'form__input', autofocus: true, autocomplete: "one-time-code" %>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following 1Password's guidelines.

Comment on lines +16 to +18
after :create do |user, evaluator|
user.confirm_email! if evaluator.email_confirmed != false && evaluator.unconfirmed_email.blank?
end
Copy link
Member Author

@martinemde martinemde Dec 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed the user factory was making users with "confirmed emails" that still had valid unexpired confirmation_tokens. This allowed some of the password tests to pass because the factory user was already in "password reset" state.

Change the factory to use the standard process, making the factory user more like an live user.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this for every test? Can we set the correct state and then specifically for the password/confirmation token tests, we set the state required there?

Copy link
Member Author

@martinemde martinemde Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that creating a user without a token doesn't work. If you pass in nil you always get a token due to user hooks.

This confirmed state is the correct state for every other test. In the password tests we must run forgot_password! in many of them (something that was previously skipped because the factory state was wrong). It used to be that all users were in a sort of mixed email partially confirmed password reset state.

We could generate a factory user with email confirmed and no token (the standard user state for most other tests), but it would require preventing the "generate token" hook from running on factory created users. Instead, this seemed like the cleanest and most canonical solution.

@martinemde martinemde force-pushed the martinemde/modernize-password-controller-tests branch 7 times, most recently from 2308701 to 0d7f8e1 Compare December 4, 2024 18:39
@martinemde martinemde force-pushed the martinemde/modernize-password-controller-tests branch from 0d7f8e1 to c47f971 Compare December 5, 2024 21:31
@martinemde martinemde requested a review from simi December 10, 2024 01:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging this pull request may close these issues.

2 participants