44

In my app for a certain use case I create a new user (programmatically set the password) and send them a confirmation email.

I would like them to be able to change their password immediately after confirming (without having to enter the system generated one which I don't want to send them)

In effect I would like
1) System creates a new user account with generated password.
2) System sends confirmation email.
3) User clicks confirmation and is redirected to enter in their password (effectively send them to a URL like below)

<a href="http://localhost:3000/users/password/edit?reset_password_token=v5Q3oQGbsyqAUUxyqLtb">Change my password</a>

Any help / pointers would be great.

patrickdavey
  • 1,966
  • 2
  • 18
  • 25

4 Answers4

45

A simple way to have just one step for users to confirm email address and set initial password using the link you proposed...

Send one email your app generates, including a reset_password_token, and consider user's possession of that token confirmation of the validity of that email address.

In system account generation code, assuming User model is set up with :recoverable and :database_authenticatable Devise modules...

acct = User.new
acct.password = User.reset_password_token #won't actually be used...  
acct.reset_password_token = User.reset_password_token 
acct.email = "user@usercompany.com" #assuming users will identify themselves with this field
#set other acct fields you may need
acct.save

Make the devise reset password view a little clearer for users when setting initial password.

views/devise/passwords/edit.html.erb

...
<%= "true" == params[:initial] ? "Set your password" : "Reset your password" %>
...  

Generated Email

Hi <%= @user.name %>
An account has been generated for you.
Please visit www.oursite.com/users/password/edit?initial=true&reset_password_token=<%= @user.reset_password_token %> to set your password.

No need to include :confirmable Devise module in your User model, since accounts created by your app won't get accessed without the reset_password_token in the email.

Devise will handle the submit and clear the reset_password_token field.

See devise_gem_folder/lib/devise/models/recoverable.rb and database_authenticatable.rb for details on reset_password_token method and friends.

If you want to use Devise :confirmable module rather than this approach, see the Devise wiki page.

BryanH
  • 5,826
  • 3
  • 34
  • 47
Anatortoise House
  • 4,941
  • 1
  • 22
  • 18
  • Thanks very much - hadn't seen that wiki as it has just been created. Like the idea of the password reset token automatically being proof of being confirmed. Neat. – patrickdavey May 12 '11 at 04:07
  • yeah, this is a great workflow for many rails apps. I was trying to figure out how my app can initiate the password reset (generate new token). Nicely documented. – jpw Jan 27 '12 at 19:24
  • 9
    BTW I also had to set user.reset_password_sent_at to Time.now or else the password links were 'expired' – jpw Jan 30 '12 at 21:23
  • 18
    It seems that `reset_password_token` [has been removed from the Recoverable model](https://github.com/plataformatec/devise/commit/d75fd56f150f006aee51dcafc7157454190de570). This means `User.reset_password_token` will throw a wobbly in later versions of Devise. I think you can achieve something similar with `Devise.token_generator.generate(User, :reset_password_token)` where `User` is the name of your Devise model. But happier to be told there's a more succinct way. – Chris Jan 19 '14 at 21:43
33

In Rails 4.1, the following modification of Anatortoise House's reply works:

user = User.new
user.password = SecureRandom.hex #some random unguessable string
raw_token, hashed_token = Devise.token_generator.generate(User, :reset_password_token)
user.reset_password_token = hashed_token
user.reset_password_sent_at = Time.now.utc
user.email = 'user@usercompany.com'
user.save!
# Use a mailer you've written, such as:
AccountMailer.set_password_notice(user, raw_token).deliver

The email view has this link:

www.oursite.com/users/password/edit?initial=true&reset_password_token=<%= @raw_token %>
Eliot Sykes
  • 9,616
  • 6
  • 50
  • 64
ClaytonC
  • 475
  • 4
  • 8
  • 1
    Where does the method `set_password_notice` come from? I've configured a custom mailer that inherits from Devise::Mailer but getting a method not found error – PW Kad May 08 '15 at 16:08
  • You would have to define it yourself, just as any other ordinary out-going email, using ActionMailer. – ClaytonC May 09 '15 at 16:47
  • As of Devise version 3.5.1 the reset_password_token will be cleared in any call that also changes the email or password. https://github.com/plataformatec/devise/blob/master/lib/devise/models/recoverable.rb – s01ipsist Jun 09 '15 at 13:36
  • 2
    As of Devise version 3.5.1 the reset_password_token will be cleared in any call that also changes the user email or password https://github.com/plataformatec/devise/blob/master/lib/devise/models/recoverable.rb so you'll need ot set the reset_password_token and reset_password_sent_at values in a separate user.save call – s01ipsist Jun 09 '15 at 13:37
3

Here is my snippet for mailer preview

class Devise::MailerPreview < ActionMailer::Preview
  def reset_password_instructions
    user = User.last
    token = user.send(:set_reset_password_token)
    Devise::Mailer.reset_password_instructions(user, token)
  end
end
woto
  • 2,893
  • 1
  • 29
  • 24
2

You can call

user.send(:set_reset_password_token)

It may not be stable, as it's a protected method but it can work for your case. Just cover it with a test.

(tested on Devise v. 3.4)

Sergii Mostovyi
  • 1,361
  • 1
  • 15
  • 19