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

Set an expiration for password reset tokens #532

Closed
wants to merge 70 commits into from
Closed

Conversation

mxie
Copy link
Contributor

@mxie mxie commented Feb 6, 2015

Overview:
Password reset tokens will now have an expiration date/time. By default, it
will be in 15 minutes. This is configurable via the password_reset_time_limit
option.

Users can request a reset as many times as they wish. Once they "claim" a reset
with one of their tokens, all others will be invalidated (set as expired).

Details:

  • User-token data in a separate table called password_resets.
  • Password reset logic has been extracted from the User model. It's now
    handled at the controller level, referring to the PasswordReset and
    PasswordResetDeactivator classes as needed.
  • Routes and views have stayed the same for transparency.
    • To consider: renaming PasswordsController to PasswordResetsController
      later.
  • Introduced Clearance::Generators::Migration to abstract common migration-related methods

This has been tested on a repo that's currently using Clearance, and it seems to work fine. I plan on adding some info on the Wiki for anyone who's adding the expiration feature to their existing Clearance'd project.

Also, this is a huge PR. Suggestions on how to break this up would be great appreciated.

Fixes #465

unless find_user_by_id_and_confirmation_token
unless find_user_from_password_reset
flash_failure_when_forbidden
render template: 'passwords/new'

Choose a reason for hiding this comment

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

Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

@mxie mxie force-pushed the mx-expiring-resets branch from 840d69b to c686c74 Compare February 6, 2015 19:03
def time_limit
Clearance.configuration.password_reset_time_limit
end

Choose a reason for hiding this comment

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

Extra empty line detected at body end.

@mxie mxie force-pushed the mx-expiring-resets branch from b011ae8 to 5690e51 Compare February 6, 2015 19:31
deliver_email(user)
password_reset = PasswordReset.new
password_reset.user = user
password_reset.save
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's been a while since I've Rails'ed, but there's a better way to do this, right? It's a one-way association, i.e. there's only a belongs_to association in PasswordReset, no has_many from User.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe:

password_reset = PasswordReset.create!(user: user)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I had something like that originally, but Rails 3.2 doesn't seem to like it because of mass-assignments. =\ I guess I could at least extract the reset-building as a private method in here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did something like this instead: f2ee26c.

@mike-burns
Copy link
Contributor

Since this changes the interface for documented template methods (the documentation is about as broad as possible: "See app/controllers/clearance for the default behavior"), this means that this will be part of the major version bump. Right?

@mxie
Copy link
Contributor Author

mxie commented Feb 6, 2015

@mike-burns Yes, you are correct.

t.timestamps null: false
end

add_index :password_resets, :user_id
Copy link
Contributor

Choose a reason for hiding this comment

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

should we index the expires_at column as well? Maybe change this to a compound index on [:user_id, :expires_at]? Might be too much...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good to me. Done in 8f0f9a8.

@derekprior
Copy link
Contributor

Took a quick spin through everything. This is great, thanks so much.

I do think we can do this in a manner that lets us ship this in opt-in fashion for 1.x. Either via a separate controller and/or via service objects like monban. We can look at it Friday.

@@ -0,0 +1,36 @@
class PasswordReset < ActiveRecord::Base
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

No. Clearance models are internal to clearance and not generate. User is a template because we expect you already have a user class of some sort.

@mxie mxie force-pushed the mx-expiring-resets branch from 832f668 to b919253 Compare March 2, 2015 22:08
def up
expiration_timestamp = Clearance.configuration.
password_reset_time_limit.
from_now

Choose a reason for hiding this comment

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

Align the operands of an expression in an assignment spanning multiple lines.

@mxie mxie force-pushed the mx-expiring-resets branch from 0aa95df to e8358da Compare July 17, 2015 19:46
provide_existing_application_controller
allow(password_reset_migration_generator).to receive(:new).
and_return(
instance_double(password_reset_migration_generator).as_null_object

Choose a reason for hiding this comment

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

Put a comma after the last parameter of a multiline method call.

where(
"#{Clearance.configuration.user_id_parameter} = ? AND expires_at > ?",
user_id,
Time.zone.now

Choose a reason for hiding this comment

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

Put a comma after the last parameter of a multiline method call.

@mxie
Copy link
Contributor Author

mxie commented Jul 24, 2015

@derekprior Whenever you get a chance, could you take a look at this again? I think it's pretty much all set to go. The only thing I might add is an "Upgrade Guide" of sorts. That could be done separately though.

@derekprior
Copy link
Contributor

With Clearance 2.0 progressing, I was finally able to return to this. I made some major changes that are afforded by dropping support for older versions of Rails. We now no longer need the database for password reset tokens, so the resulting PR is simpler, I think.

See #682.

@derekprior derekprior closed this May 15, 2016
@mjankowski mjankowski deleted the mx-expiring-resets branch February 25, 2019 19:19
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.

Expire password reset token after a period of time
5 participants