Devise Invitable error: The invitation token provided is not valid - ruby-on-rails

Regardless of what I have tried and I have looked through this Github thread, I keep getting the error The invitation token provided is not valid!
I have the form embedded onto a separate page on my website and the invitation is sending, but the token seems to be wrong.
I do have this line in my mailer code
<p><%= link_to I18n.t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(#resource, :invitation_token => #token) %></p>
I am also using a custom controller, but don't yet have anything in it.

Related

Rails 6 prompt to download on mobile

I have a Rails 6 app where users can submit changes to a model and a confirmation email will be sent to site admins. The changes are submitted through a modal popup and once submitted the popup is replaced with another that confirms that the email has been sent.
The problem I am having is when this is done on a mobile device (I'm using an iPhone, not sure if this happens on Android), once the change is submitted another popup appears prompting me to download a file of size 0 with the name of the model being updated (it happens in both Safari and Chrome). This doesn't happen on the desktop version of the site or in the mobile emulator on the desktop, so I can't think of how I could diagnose this issue.
Here is the code being called just before the download popup appears:
<%= button_tag "Submit", type: 'submit',
id: 'modal-subimt',
class: 'btn btn-primary',
onClick: 'replaceModal()' %>
Here is the create function being called when the form is submitted:
def create
... # Setting the parameters for the model being changed
if #model.save
#model.send_confirmation_email()
else
# Irrelevant because the email gets sent
end
end
Here is the send_confirmation_email function being called in create:
def send_confirmation_email()
UserMailer.model_confirmation(self).deliver_now
end
And here is the model_confirmation function being called by send_confirmation_email:
def model_confirmation(model)
#model = model
recipient = <admin email>
mail to: recipient, subject: "Model Confirmation"
end
Nowhere in this code can I see where I might be prompted to download a file, but alas it is happening. Any help would be appreciated on how to properly diagnose or solve this problem. Thanks!
EDIT:
I changed my code so that the model would be updated without sending a confirmation email Also, the modal is no longer replaced with the new one verifying an email has been sent, and I am still having the same issue. This leads me to believe that the problem is something with the creation of the model.
EDIT 2:
Previously I was creating the model and remaining on the page (with the confirmation that the email was sent). I changed my controller to redirect_to request.referer to reload the page once the change is made, and when that is done I am no longer prompted to download the empty file. Unfortunately, the way I want this to work, reloading the page isn't optimal. Is there any reason that I would be prompted for a download when updating a model without reloading the page?
SOLUTION:
I was able to solve this problem by adding remote: true to the form_for line like the following:
<%= form_for(Model.new, remote: true) do |f| %>
...
<%= button_tag "Submit", type: 'submit',
id: 'modal-subimt',
class: 'btn btn-primary',
onClick: 'replaceModal()' %>
<% end %>
I'm not sure why exactly this solved my problem though so if anyone can provide some insight that would be much appreciated!
The problem is related to how you instantiated the form_for. When you submit the form to a Model.new object, form_for try to infer the controller with the create method for handling it. See more how form_for works in https://apidock.com/rails/ActionView/Helpers/FormHelper/form_for
With that said, the create method is probably outputting something or nothing instead of redirecting, that's why you get a download on a mobile device and in browsers it's just an empty page because they can handle it. If there is no render on the method, Rails will answer with a 204 No Content or an empty text page. You must check your method about this.
When you use remote: true, I will quote from the docs:
:remote - If set to true, will allow the Unobtrusive JavaScript drivers to control the submit behavior. By default this behavior is an ajax submit.
So instead of reloading the page, UJS sends a XMLHttpRequest to the "backend" page, that's why you don't get a redirect. The result of the submit is treated without redirecting. But based on the result of the request the content of the page can come. So it's important that you give a response accordingly. In the links below you will find the right way to do it.
See more in these questions:
How does ':remote => true' works in rails
:remote => true confusion with form_for
Also, in the Rails Guides:
https://guides.rubyonrails.org/form_helpers.html
https://guides.rubyonrails.org/working_with_javascript_in_rails.html
This is also a good reference:
http://www.korenlc.com/remote-true-in-rails-forms/

Creating confirmation link for Devise

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

Rails 4 + Devise: Password Reset is always giving a "Token is invalid" error on the production server, but works fine locally.

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>

Rails devise sending emails to wrong email address

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

Rails - Devise sign in / authentication failure does not populate errors array with message

Using Rails 3.07 and Devise 1.1.5
Everything is working fine and as expected with one exception. When a user tries to login with a bogus password, for instance, devise denies the login attempt, which is correct, but fails to provide an error message.
I have several error display methods set up in app/helpers/devise_helper.rb and I am using one called devise_sign_in_error_messages! for the login view. Therefore I am able to verify that the following line from that function is returning a blank string for errors in this case:
return "" if resource.errors.empty?
If I give a correct username and password, the system logs me in just fine, so all of the devise logic seems fine, it's just this lack of an error message that's a mystery.
What do I need to change to help devise pass me an error message on failed login?
EDIT:
The answer is that:
a) devise is sticking the answer in flash
b) even though it's in flash, it's not using the key you might expect
Using this bit of code, I can see the message:
<% flash.each do |name, msg| %>
<%= content_tag :div, msg, :id => "flash_#{name}" if msg.is_a?(String) %>
<% end %>
Which I came across on a different post as an answer to a different question:
Another stack overflow post
I had tried to output the flash earlier but did not see the message because I followed a bit of code from a different stack overflow post that appears to be insufficient. Namely, I had tried:
<%= flash[:message] if flash[:message]
flash[:warning] if flash[:warning]
flash[:error] if flash[:error] %>
Devise is not using these keys for the login error message so this code will get you nothing.
I find devise's handling of this to be inconsistent. Specifically, if I choose devise's forgot password option and enter a bogus email address for instance, the error gets passed back in the resource.errors array, but here with the bad login it gets passed in flash.
As you have discovered Devise does not use flash[:message], flash[:warning], and flash[:error].
Devise uses flash[:notice] and flash[:alert].
It's not an easy find in the documentation but is just under the third point in Configuring controllers.
Hope this clears things up.

Resources