Skip to content

Use SQL for password reset token queries#3546

Merged
dnephin merged 4 commits intomainfrom
dnephin/sql-for-passwordreset
Nov 7, 2022
Merged

Use SQL for password reset token queries#3546
dnephin merged 4 commits intomainfrom
dnephin/sql-for-passwordreset

Conversation

@dnephin
Copy link
Contributor

@dnephin dnephin commented Nov 2, 2022

Summary

This PR converts the two data queries used for password reset tokens to use SQL.

I also made the following changes:

  1. when fetching the token, delete it immediately. Previously we were waiting until later to delete it. Since we run all of these operations in the same transaction there is no need to do this in separate queries. We can delete the token and use RETURNING to return the fields we care about at the same time. This allows us to remove one of the data functions.
  2. remove models.PasswordResetToken. With a couple small changes to the return values this type became an implementation detail of the data package. The caller only needs the user.ID and the token, which we can return without a type. The model was replaced with an unexported struct in the data package.

@dnephin dnephin requested review from ssoroka and removed request for BruceMacD, jmorganca and pdevine November 2, 2022 23:15
Copy link
Contributor Author

@dnephin dnephin left a comment

Choose a reason for hiding this comment

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

Edit: added some tests to this PR

Comment on lines -33 to -34
// TODO: must use errors.As for error types
if tries <= 3 && errors.Is(err, UniqueConstraintError{}) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also resolved this TODO at the same time. Previously the errors.Is would never return true because the err would have the Table and Column field set.

@dnephin dnephin force-pushed the dnephin/sql-for-passwordreset branch from 1325cb5 to 234975b Compare November 3, 2022 20:15
@dnephin
Copy link
Contributor Author

dnephin commented Nov 3, 2022

I added some direct test coverage of these two functions and fixed the conflicts.

@dnephin dnephin requested a review from BruceMacD November 4, 2022 18:07
)

func PasswordResetRequest(c *gin.Context, email string, ttl time.Duration) (token string, user *models.Identity, err error) {
func PasswordResetRequest(c *gin.Context, email string, ttl time.Duration) (string, *models.Identity, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

unnamed string return arguments are less clear than named ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ya, especially true when there are multiple return values with the same type. Usually when they have different types it's pretty clear.

It's unfortunate that naming them also extends the scope of the variable (to any place in the function, instead of only after declaration). I can add these back

return tx.Commit()
}

func getDefaultSortFromType(t interface{}) string {
Copy link
Contributor

Choose a reason for hiding this comment

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

did we lose default sorts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Every list function has a sort, but there's no central function that uses reflection to try and guess at which field to use.

_, err = GetUserIDForPasswordResetToken(tx, token)
assert.ErrorIs(t, err, internal.ErrExpired)
})
})
Copy link
Contributor

Choose a reason for hiding this comment

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

might be worth adding a case where the record is gone because the background job trimmed it already.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe that case is covered by the second call in deletes token. It's calling the function with a token that was already deleted.

@dnephin dnephin force-pushed the dnephin/sql-for-passwordreset branch from eda75b6 to 82b82f5 Compare November 4, 2022 20:20
Also remove the model. No other packages care about this model, so we can use a unexported
type in the data package.
Now that this function does a delete, rename it from Get to Claim to indicate
that it's more than just returning data.
@dnephin dnephin force-pushed the dnephin/sql-for-passwordreset branch from 82b82f5 to 05fc867 Compare November 7, 2022 16:03
// ClaimPasswordResetToken deletes the password reset token, and returns the
// user ID that was associated with the token. Returns an error if the token
// does not exist or has expired.
func ClaimPasswordResetToken(tx WriteTxn, token string) (uid.ID, error) {
Copy link
Contributor Author

@dnephin dnephin Nov 7, 2022

Choose a reason for hiding this comment

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

I pushed one more commit which renames this function from Get to Claim. Now that it does a delete, I think calling it Get could easily be misleading.

@dnephin dnephin merged commit 779ffc5 into main Nov 7, 2022
@dnephin dnephin deleted the dnephin/sql-for-passwordreset branch November 7, 2022 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants