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

Dont allow reusing password reset token, use normal rate limit #4719

Merged
merged 3 commits into from
May 21, 2024

Conversation

Nutomic
Copy link
Member

@Nutomic Nutomic commented May 14, 2024

With the current code it is possible to reuse the same reset token multiple times.

let conn = &mut get_conn(pool).await?;
password_reset_request
update(password_reset_request)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use delete instead of update

Copy link
Member Author

Choose a reason for hiding this comment

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

We need to keep the column so that reset count works. That one is used to limit users to 3 password resets per day. However we could get rid of that and use a normal rate limit instead, eg auth rate limit which applies to login, register and password reset.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Per-user rate limits are better

@dessalines
Copy link
Member

dessalines commented May 15, 2024

This seems kind of extreme to me. As the code exists, the reset token is only valid for a day anyway. Why not just decrease that time to like an hour, or 15 minutes?

These single-use tokens are annoying even when I get them as texts, and they're too quickly invalidated.

@Nutomic
Copy link
Member Author

Nutomic commented May 15, 2024

This is not about valid time, but about using the same reset token multiple times. Right now you can do "forgot password" once, then use the reset token to set a new password, and afterwards use the same reset token to change your password again. In fact you can do that an unlimited amount of times which seems completely wrong.

@dessalines
Copy link
Member

This is basically the difference between "one-time-use" tokens, HOTP, vs time-based tokens, TOTP. TOTP is much more forgiving and reliable IMO.

IMO HOTP are way too pointlessly complicated for our use here, it should be enough to shorten our TOTP time to like 10-15 minutes.

@Nothing4You
Copy link
Contributor

OTP is literally only to be used once.

Quoting Wikipedia:

A one-time password (OTP), also known as a one-time PIN, one-time authorization code (OTAC) or dynamic password, is a password that is valid for only one login session or transaction, on a computer system or other digital device.

A TOTP server side implementation that allows token reuse is vulnerable to reply attacks, the very thing that OTPs are designed to prevent.

I'm assuming you only brought up TOTP/HOTP for the user experience part, as this is not using that technology, but I don't agree with your stance on that regardless.
In a TOTP implementation you have a new code every e.g. 30 seconds. Depending on when you start reading it, it might be rotating before you're done entering it, or your time window may have passed. For the time window part (after rotation), and to account for clock drift, many providers are already tolerating multiple tokens from that time frame, such as the codes directly before and after the current time. The user experience is degraded as they may need to wait for a rotation to have enough time to read and type it.
In an HOTP implementation however, the token does not rotate on its own, but instead it rotates following user interaction. This has a few drawbacks, such as synchronization issues when generating a lot of new tokens without using them, or if someone can intercept a token, then have the user submit another one and replace it with the first one, allowing the attacker to still have a valid token. This is probably mostly theoretical, as the attacker could then probably just capture a session cookie. The general user experience is improved by not putting time pressure on them.
There is probably no harm in allowing a password reset token to be valid for e.g. a day.

Both of these implementations however are only secure if token reuse is prevented.

The first potential attack that comes to my mind for reusable reset tokens is shared computers, when someone resets their lemmy password, then logs out of all of their sessions, but doesn't clear their browsing history. The next person using that computer will be able to find the reset url in the browser history and compromise the account.

let conn = &mut get_conn(pool).await?;
password_reset_request
update(password_reset_request)
.filter(valid.eq(true))
.filter(token.eq(token_))
.filter(published.gt(now.into_sql::<Timestamptz>() - 1.days()))
Copy link
Member

Choose a reason for hiding this comment

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

This is the only line you need to change. Change this to a shorter time span, and none of this is necessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thats not it, having the reset token valid for a day is fine in case you dont receive it immediately. But it should be possible to reuse the same one multiple times.

Copy link
Member

Choose a reason for hiding this comment

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

Alright, seems a bit overkill to me still, but its fine.

@Nutomic
Copy link
Member Author

Nutomic commented May 20, 2024

This is not about TOTP (2 facor auth), but about the password reset at https://lemmy.ml/login_reset. After you reset the password, it should not be possible to reuse the same reset link again.

@Nutomic Nutomic mentioned this pull request May 20, 2024
4 tasks
@Nutomic Nutomic marked this pull request as draft May 20, 2024 15:19
@Nutomic Nutomic changed the title Dont allow reusing password reset token Dont allow reusing password reset token, use normal rate limit May 20, 2024
@Nutomic
Copy link
Member Author

Nutomic commented May 20, 2024

Ive changed it now to use the normal rate limit, so get_recent_password_resets_count() and valid field arent necessary anymore.

@Nutomic Nutomic marked this pull request as ready for review May 21, 2024 00:34
let conn = &mut get_conn(pool).await?;
password_reset_request
.filter(local_user_id.eq(user_id))
delete(password_reset_request)
Copy link
Member

Choose a reason for hiding this comment

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

Surprised that delete works with .get_result, but you verified it below in the tests anyway.

@dessalines dessalines merged commit 4ffaa93 into main May 21, 2024
2 checks passed
@SleeplessOne1917 SleeplessOne1917 deleted the password-reset-no-reuse branch May 21, 2024 19:09
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.

None yet

4 participants