We have a user whose mail provider seems to be blocking the account from which we send the password reset emails.
I wanted to just get the reset-password-URL running from irb, and mail it by hand. I can't seem to figure out how to run this thing "edit_password_url" or where it lives or in what scope it is defined.
Any tips on how to generate a reset-password url for a user by hand in irb?
You can do it through the console with a little bit of work. Here is how I approached it:
start rails console in your terminal:
$rails c
I looked at the devise mailer view to see what it was calling to create the reset password URL:
<p><%= link_to 'Change my password', edit_password_url(#resource, :reset_password_token => #token) %></p>
The #resource in this code is your user, and the #token is their reset password token
Find your user by id, email or whatever. Then find their reset password token:
u = User.find(1)
token = u.reset_password_token
To get access to the view you will need to create an instance of ActionView::Base
view = ActionView::Base.new
I then tried to access the url helper, but devise complains about
NoMethodError: undefined method `main_app' for #<ActionView::Base>
So I had to type a method into the console to fix that error (see this):
def main_app
Rails.application.class.routes.url_helpers
end
Depending on whether you have your mailer configured correctly in the rails console environment you are in, you may get some errors about the :host param not being set up. To avoid this you could just call _path instead of _url. Now you can call the url helper and pass the variables you set for user and token:
edit_password_path(u, :reset_password_token => token)
=> /users/password/edit?reset_password_token=123
The short answer is that you need to find their reset_password_token and append it to this URL:
http://yourdomain.com/users/password/edit?reset_password_token=<password-token-here>
In Devise 3.3, I did the following:
$ bin/rails c
> include Devise::Controllers::UrlHelpers
I recently found myself in a similar spam-folder situation.
This snippet worked nicely for me with rails 4, devise 4.3.
It also sends the reset password email, but for the sake of simplicity I think that's fine.
user = User.find(123)
token = user.send_reset_password_instructions
Rails
.application
.routes
.url_helpers
.edit_user_password_url(user, reset_password_token: token)
Related
Sometimes our users' confirmation emails get hung up, and I need to generate a confirmation link to send to them manually. I grabbed the code from Devise's mail view, but the link it generates is not the same that gets generated by the auto-generated confirmation email:
Code from Devise's confirmation mailer view:
<p><%= link_to 'Confirm my account', confirmation_url(#resource, :confirmation_token => #token) %></p>
Example confirmation link:
http://myapp.com/confirmation?confirmation_token=dTDYagcDbfJehEJPThRi
Code I'm using in custom confirmation link generator:
<p><%= link_to 'Confirm my account', confirmation_url(#user, :confirmation_token => #user.confirmation_token) %></p>
Example confirmation link (Different from above- doesn't work):
http://myapp.com/confirmation?confirmation_token=162baabc80329f01209297af8c49a42e1fdf9066ffef412322b509bc5967052d
How can I generate a Devise confirmation link?
This is because devise relatively recently (3.1+ I think?) increased security by encrypting tokens (including the confirmation token) before storing them in the database. So the second long token is the encrypted version of the first, shorter token and therefore won't work. The only place the confirmation token exists in unencrypted form is in the original email sent to the user.
This means that a new token needs to be generated each time a confirmation email is sent for a user. Devise can allow users to request another confirmation email (ConfirmationsController) - have you disabled that? It calls the send_confirmation_instructions class method on your user class (which is in devise's Confirmable module and in turn calls ends up calling resend_confirmation_instructions, which calls the send_confirmation_instructions instance method which can generate a new token). You could probably call the send_confirmation_instructions class method on your user class yourself, but it would be easier to allow users to request another confirmation email themselves using the standard devise ConfirmationsController and routes/views.
Alternative if you just want to REDIRECT the user after clicking the confirmation, just
STEP 1
override the after_confirmation_path_for in your confirmations_controller:
Create a new confirmations_controller.rb in app/controllers directory:
class ConfirmationsController < Devise::ConfirmationsController
private
def after_confirmation_path_for(resource_name, resource)
your_new_after_confirmation_path
end
end
STEP 2 In config/routes.rb, add this line so that Devise will use your custom ConfirmationsController. This assumes Devise operates on users table (you may edit to match yours).
devise_for :users, controllers: { confirmations: 'confirmations' }
STEP 3 Restart the web server
I have a Rails 4 application set up to use Devise, and I'm running a problem with password resets. I have the mailer set up, and the password reset email sends fine. The link provided has the correct reset_password_token assigned to it, which I checked with that database. However, when I submit the form with correctly formatted passwords, it gives an error saying that the reset token is invalid.
However, the exact same code works fine locally through rails s. The email sends, and I can actually reset the password. The code I use is just the standard Devise code, I haven't overridden any of it.
Perhaps it's something with Apache? I'm not too familiar with it. Does anyone have any ideas?
Check the code in app/views/devise/mailer/reset_password_instructions.html.erb
The link should be generated with:
edit_password_url(#resource, :reset_password_token => #token)
If your view still uses this code, that will be the cause of the issue:
edit_password_url(#resource, :reset_password_token => #resource.password_reset_token)
Devise started storing hashes of the token, so the email needs to create the link using the real token (#token) rather than the hashed value stored in the database.
This change occurred in Devise in 143794d701
In addition to doctororange's fix, if you're overwriting resource.find_first_by_auth_conditions, you need to account for the case where warden_conditions contains a reset_password_token instead of an email or username.
EDIT: To elaborate:
Devise adds functionality to your model when you say 'devise :registerable, :trackable, ...'.
In your User model (or Admin, etc), you can overwrite the Devise method named find_first_by_auth_conditions. This special method is used by the Devise logic to locate the record that is attempting to be logged in to. Devise passes in some info in a parameter called warden_conditions. This will contain an email, a user-name, or a reset_password_token, or anything else you add to your devise log-in form (such as an account-id).
For example, you might have something that looks like this:
(app/models/user.rb)
class User
...
def self.find_first_by_auth_conditions warden_conditions
conditions = warden_conditions.dup
if (email = conditions.delete(:email)).present?
where(email: email.downcase).first
end
end
end
However, The above code will break the password-reset functionality, because devise is using a token to locate the record. The user doesn't enter an email, they enter the token via a query-string in the URL, which gets passed to this method to try and find the record.
Therefore, when you overwrite this special method you need to make it more robust to account for the password-reset case:
(app/models/user.rb)
class User
...
def self.find_first_by_auth_conditions warden_conditions
conditions = warden_conditions.dup
if (email = conditions.delete(:email)).present?
where(email: email.downcase).first
elsif conditions.has_key?(:reset_password_token)
where(reset_password_token: conditions[:reset_password_token]).first
end
end
end
If you are taking the URL from a log, it can appear like this:
web_1 | <p><a href=3D"http://localhost:3000/admin/password/edit?reset_password_to=
web_1 | ken=3DJ5Z5g6QNVQb3ZXkiKjTx">Change password</a></p>
In this case, using 3DJ5Z5g6QNVQb3ZXkiKjTx as the token will not work because =3D is really an = character encoded.
In this case, you need to use J5Z5g6QNVQb3ZXkiKjTx (with 3D removed)
Although the accepted answer is correct, wanted to explain why this is happening so you can use it in some other cases as well.
If you take a look at the method which is generating the password reset token:
def set_reset_password_token
raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
self.reset_password_token = enc
self.reset_password_sent_at = Time.now.utc
self.save(validate: false)
raw
end
You will see that the raw is being returned, and the enc is being saved in the database. If you are using the value from the database - enc to put into a password_reset_token in a hidden field of your form, then it will always say Token invalid as that is encrypted token. The one which you should use is the raw token.
This was done because in case some admin (or a hacker) can access the database, the admin could easily reset anyone's password by just using encrypted token, which is tried to be avoided.
Some information about this and some other changes in Devise can be found in the devise's change-log blog post or in the devise's issue discussion
It may also be worth noting (in addition to #doctororange's post ablve) the following if you are using a custom confirmation mailer view.
The link in the view has also changed here. This is the NEW link code:
<p><%= link_to 'Confirm my account', confirmation_url(#resource, confirmation_token: #token) %></p>
This is the OLD link code:
<p><%= link_to 'Confirm my account', user_confirmation_url(#resource, :confirmation_token => #resource.confirmation_token) %></p>
I successfully installed devise in my rails app and the user registration works perfect. I've also set it up such that users can confirm their accounts by sending an email. This works fine when the user signs up for the first time (They get the confirmation message with a confirmation link).
However, when the user changes his/her email address from exampleuser#gmail.com to exampleuser#hotmail.com, the mail gets delivered to exampleuser#gmail.com and the confirmation link has no confirmation token it looks like
http://{HOST}/users/confirmation
Instead of the normal
http://{HOST}/users/confirmation?confirmation_token=TOKEN_HERE
When I resave the new email exampleuser#hotmail.com it now gets delivered to this address but the confirmation token is invalid as it does not match the one in the db.
I don't know what went wrong.
confirmation_instructions.html.erb
<p>Welcome <%= #resource.unconfirmed_email? ? #resource.unconfirmed_email : #resource.email %>!</p>
<p>You can confirm your account email through the link below:</p>
<p><%= link_to 'Confirm my account', confirmation_url(#resource, :confirmation_token => #resource.confirmation_token) %></p>
I also have config.reconfirmable = true in devise initializer
Am also using sideqik for delayed jobs. The emails are all processed by sideqik
Any help?
Thanks
I realise it is sometime since you posted, but I have run into this same issue and resolved it.
In my case I had upgraded devise from v2.0.4 to v2.2.6 - which appears to be the newest version that supports Rails 3.
I'd skimmed the change log which mentions this change in v2.2.0:
All mailer methods now expect a second argument with delivery options.
Unfortunately it doesn't say what that second argument is specifically used for; however it turns out is literally what you'd expect... The options hash that is passed to your Mailers mail method.
I'm guessing if you're like me, your previous Devise::Mailer had a line something like the following:
def confirmation_instructions(record, opts)
mail :to => record.email, :template_name => 'confirmation_instructions'
end
The problem is that email is the previous confirmed email address, not the new unconfirmed one. Hence you probably want to call mail with the options hash you were passed, which will now contain the unconfirmed_email in its :to key, e.g.
{:to => "unconfirmed#email.com"}
So simply change your mail calls to something more like:
def confirmation_instructions(record, opts)
mail opts.merge(:template_name => 'confirmation_instructions')
end
I'm creating a password reset functionality for my site in rails and in my mailer password_reset.text.erb file I'm currently sending
http://localhost:3000/password_resets/<%=#user.password_reset_token%>/edit/
in my development environment. This will hit my controller for password reset and redirect the user to edit the password if the token matches with the saved model.
However, I'd like to configure this dynamically so when I deploy to heroku it will know to change that to mywebsite.com/password_resets/...
How would I do this?
EDIT:
def password_reset(user)
#user = user
mail(to: user.email, subject: "Bitelist Password Reset")
end
Typically I configure the host information for the mailer in an appropriate config/environment file.
config.action_mailer.default_url_options = { :host => "example.com" }
You can take a look at the "Generating URLs" section of this page: http://rails.rubyonrails.org/classes/ActionMailer/Base.html
With that configuration set typical routing structures seem to work quite nicely. I am not 100% positive what routes will be available in your situation so you may still need to construct the full URL somewhat manually to include the reset token component.
To generate the actual URLs you can potentially use named routes if your routes are set up in a way that you have a named route that takes a user token. i.e.
<%= edit_password_resets_url(#user.password_reset_token) %>
If you do not have the token integrated into an existing route you may need to manually build the URL:
<%= "#{url_for(:controller => 'password_resets', :action => 'whatever_action_this_is')}/#{#user.password_reset_token}/edit/" %>
or even build it completely manually using default_url_options[:host] and then add the rest that you have above.
If need be you could also set the host at request time although that may be overkill (and will not be thread safe).
def set_mailer_host
ActionMailer::Base.default_url_options[:host] = request.host_with_port
end
After adding this line to the devise/registrations/new.html.haml file (view):
%div
= f.label :account_type
%br/
= f.select(:account_type, [["Usertype1","usertype1"],["Usertype2","usertype2"]], {:selected => nil, :prompt => 'Pick One'})
I get the following error after clicking on the confirmation link in the confirmation e-mail:
ActionController::ActionControllerError in Devise::ConfirmationsController#show
Cannot redirect to nil!
It only happens if I select Usertype2 upon registration. I also made the account_type attr_accessible. The account_type seems to be getting assigned (I checked in the rails console) and the development logs don't have any further information.
I think this is the line in the devise confirmations controller where the error is occurring:
respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) }
Also, the account is being confirmed, but when trying to log in, I get the following:
undefined method `user_url' for #<Devise::SessionsController:0x9d1659c>
which is in the create action of the devise sessions controller.
Any help would be appreciated. Thanks!
John
The two errors you mentioned are one and the same, essentially when you are signing-in successfully Devise is unable to detect where to redirect you. This problem often occurs when you have multiple models or try to setup a custom redirect (post sign in) in the routes file.
Try to define the path in the ApplicationController.
Devise docs say that the after_sign_in_path_for method takes the actual model object (ie: the model being signed-in)
def after_sign_in_path_for(resource)
signed_in_path_for_user
end
Note: You can do the same for several Devise paths / variables (override them). Also for more information on doing this for multiple Devise models in the same app, you can look at this question and it's answer.