Using Rails 4 and Devise 3.1.0 on my web app. I wrote a Cucumber test to test user sign up; it fails when the "confirm my account" link is clicked from the e-mail.
Scenario: User signs up with valid data # features/users/sign_up.feature:9
When I sign up with valid user data # features/step_definitions/user_steps.rb:87
Then I should receive an email # features/step_definitions/email_steps.rb:51
When I open the email # features/step_definitions/email_steps.rb:76
Then I should see the email delivered from "no-reply#mysite.com" # features/step_definitions/email_steps.rb:116
And I should see "You can confirm your account email through the link below:" in the email body # features/step_definitions/email_steps.rb:108
When I follow "Confirm my account" in the email # features/step_definitions/email_steps.rb:178
Then I should be signed in # features/step_definitions/user_steps.rb:142
expected to find text "Logout" in "...Confirmation token is invalid..." (RSpec::Expectations::ExpectationNotMetError)
./features/step_definitions/user_steps.rb:143:in `/^I should be signed in$
This error is reproducible when I sign up manually through the web server as well, so it doesn't appear to be a Cucumber issue.
I would like:
The user to be able to one-click confirm their account through this e-mail's link
Have the user stay signed in after confirming their account
I have setup:
The latest Devise code, from GitHub (3.1.0, ref 041fcf90807df5efded5fdcd53ced80544e7430f)
A User class that implements confirmable
Using the 'default' confirmation controller (I have not defined my own custom one.)
I have read these posts:
Devise confirmation_token is invalid
Devise 3.1: Now with more secure defaults
GitHub Issue - Devise confirmation_token invalid
And have tried:
Setting config.allow_insecure_tokens_lookup = true in my Devise initializer, which throws an 'unknown method' error on startup. Plus it sounds like this is only supposed to be a temporary fix, so I'd like to avoid using it.
Purged my DB and started from scratch (so no old tokens are present)
Update:
Checking the confirmation token stored on the User after registering. The emails token matches the DBs token. According to the posts above, the new Devise behavior says not supposed to, and that instead it is should generate a second token based on the e-mail's token. This is suspicious. Running User.confirm_by_token('[EMAIL_CONFIRMATION_TOKEN]') returns a User who has errors set "#messages={:confirmation_token=>["is invalid"]}", which appears to be the source of the issue.
Mismatching tokens seems to be the heart of the issue; running the following code in console to manually change the User's confirmation_token causes confirmation to succeed:
new_token = Devise.token_generator.digest(User, :confirmation_token, '[EMAIL_TOKEN]')
u = User.first
u.confirmation_token = new_token
u.save
User.confirm_by_token('[EMAIL_TOKEN]') # Succeeds
So why is it saving the wrong confirmation token to the DB in the first place? I am using a custom registration controller... maybe there's something in it that causes it to be set incorrectly?
routes.rb
devise_for :users,
:path => '',
:path_names => {
:sign_in => 'login',
:sign_out => 'logout',
:sign_up => 'register'
},
:controllers => {
:registrations => "users/registrations",
:sessions => "users/sessions"
}
users/registrations_controller.rb:
class Users::RegistrationsController < Devise::RegistrationsController
def create
# Custom code to fix DateTime issue
Utils::convert_params_date_select params[:user][:profile_attributes], :birthday, nil, true
super
end
def sign_up_params
# TODO: Still need to fix this. Strong parameters with nested attributes not working.
# Permitting all is a security hazard.
params.require(:user).permit!
#params.require(:user).permit(:email, :password, :password_confirmation, :profile_attributes)
end
private :sign_up_params
end
So upgrading to Devise 3.1.0 left some 'cruft' in a view that I hadn't touched in a while.
According to this blog post, you need to change your Devise mailer to use #token instead of the old #resource.confirmation_token.
Find this in app/views/<user>/mailer/confirmation_instructions.html.erb and change it to something like:
<p>Welcome <%= #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 => #token) %></p>
This should fix any token-based confirmation problems you're having. This is likely to fix any unlock or reset password token problems as well.
A friend of mine just found this question and emailed me asking if I had figured this out, which reminded me that I never submitted my own answer, so here goes :)
I ended up resetting the token & using send to get the raw token. It's ugly, but it works in a punch for devise (3.5.1).
26 it "should auto create org" do
27 email = FG.generate :email
28 visit new_user_registration_path
29 fill_in :user_name, with: 'Ryan Angilly'
30 fill_in :user_user_provided_email, with: email
31 fill_in :user_password, with: '1234567890'
32
33 expect do
34 click_button 'Continue'
35 end.to change { Organization.count }.by(1)
36
37 expect(page.current_path).to eq(confirmation_required_path)
38 u = User.where(email: email).first
39 u.send :generate_confirmation_token
40 email_token = u.instance_variable_get(:#raw_confirmation_token)
41 u.save!
42 os = u.organizations
43 expect(os.size).to eq(1)
44 visit user_confirmation_path(confirmation_token: email_token)
45 o = os.first
46
47 u.reload
48 expect(u.confirmed?)
49 expect(page.current_url).to eq(organization_getting_started_url(o))
50 end
As of devise 3.5.2, the confirmation token is no longer digested during the confirmation process. This means that the token in the email will match the token in the database.
I was still having trouble with confirmations after figuring this out, but in my case it turned out to be a bug I introduced when I overrode find_first_by_auth_conditions. By fixing the bug I introduced in that method, I fixed my errors with confirmation.
Related
I'm using Devise for authentication with a Rails 4 app and am having issues with the password reset. Locally, everything works fine, when I paste the reset link in (i.e. localhost:3000/users/password/edit?reset_password_token=e_f3ZpqrE_rTBZmKJk_E) it works as expected.
On Heroku however, Devise seems to not even notice the :reset_password_token param, and automatically redirect to /users/signin with the notice "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
Here's is an example of the link that is being generated: http://mysite.io/users/password/edit?reset_password_token=anzYNreZEcz4-dtZy5Uf
I even overrode the assert_reset_token_passed method in my own controller to check if params[:reset_password_token] was actually blank, and for some reason it is, rails is not pulling this out of the url. Here's my modified method:
def assert_reset_token_passed
logger.info params[:reset_password_token] #This is blank somehow
if params[:reset_password_token].blank?
set_flash_message(:alert, :no_token)
redirect_to new_session_path(resource_name) #This is where the redirect happens
end
end
Any help would be much appreciated.
I was having the exact same issue. The fix for me was to update the config.action_mailer.default_url_options in production.rb to include the full host (in my case 'www.mydomain.com' vs 'mydomain.com').
To clarify, it used to be
config.action_mailer.default_url_options = { :host => 'mydomain.com' }
and now it's
config.action_mailer.default_url_options = { :host => 'www.mydomain.com' }
I have been trying to follow https://github.com/plataformatec/devise/wiki/How-To:-Override-confirmations-so-users-can-pick-their-own-passwords-as-part-of-confirmation-activation in order to allow users on my app to supply only an email address when they sign up, and then be prompted for the remainder of their information after they confirm their email. But the approach seems to be slightly broken or I don't fully understand it.
In the instructions, in the confirmations_controller there is this method:
def with_unconfirmed_confirmable
original_token = params[:confirmation_token]
confirmation_token = Devise.token_generator.digest(User, :confirmation_token, original_token)
#confirmable = User.find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
if !#confirmable.new_record?
#confirmable.only_if_unconfirmed {yield}
end
end
If I leave it as is, I always get a "Confirmation Token is Invalid" error. It appears to be due to the new token being generated and not found. You can see what I mean in the log:
Started GET "/users/confirmation?confirmation_token=9835abdff3d03d0a29e1c5a640c6a22f1ed6289b4cf696ed514ba183aad49caa" for 127.0.0.1 at 2013-11-12 07:39:42 -0700
Processing by ConfirmationsController#show as HTML
Parameters: {"confirmation_token"=>"9835abdff3d03d0a29e1c5a640c6a22f1ed6289b4cf696ed514ba183aad49caa"}
ESC[1mESC[36mUser Load (0.3ms)ESC[0m ESC[1mSELECT "users".* FROM "users" WHERE "users"."confirmation_token" = '98e17d2ea3cc3fcba5cab7d37bd9a865fc2e318372cb293b541b8a05b46f
e4a3' LIMIT 1ESC[0m
But if I change the method to use the original_token instead of the generated confirmation_token, it all works. What am I missing? Should I be worried about not using the derived token?
Turns out the error was because my app was on an older version of devise that inadvertently got upgraded to devise 3.1. Thanks to this post: Upgrading to devise 3.1 => getting Reset password token is invalid
I updated the mailer to send the correct token and now all is working.
Everytime i run the default devise user_step test " sign in" it fails.
Strange thing is the error i get
Scenario: User signs in successfully # features/users/sign_in.feature:12
Given I exist as a user # features/step_definitions/user_steps.rb:58
And I am not logged in # features/step_definitions/user_steps.rb:49
When I sign in with valid credentials # features/step_definitions/user_steps.rb:72
Then show me the page # features/step_definitions/user_steps.rb:152
And I see a successful sign in message # features/step_definitions/user_steps.rb:156
expected to find text "Signed in successfully." in "Mywebsite Follow us How it works More Login × **Invalid email or password**. Login * Email Password Remember me Not a member yet? Sign Up Now!Forgot your password?Didn't receive confirmation instructions? Rails Tutorial " (RSpec::Expectations::ExpectationNotMetError)
./features/step_definitions/user_steps.rb:157:in `/^I see a successful sign in message$/'
features/users/sign_in.feature:17:in `And I see a successful sign in message'
As you see capybara/cucumber tries to connect but gets "Invalid email or password"
So I used a trick seen on SO and added to see what capybara really was getting
Then /^show me the page$/ do
save_and_open_page
end
It tries to use the credentials I put on top of my file user_steps.rb:
def create_visitor
#visitor ||= { :name => "Testy McUserton", :email => "example#example.com",
:password => "please", :password_confirmation => "please" }
end
But it gets me "invalid (i can see example#example.com written in the email field on the capyabra page so it understands that i want this to be the email.
I'm lost. Why doesn't it work ?
You need to create the user in the database first. User.create(name: "Testy McUserton", email: "example#example.com", password: "please", :password_confirmation: "please"). Then you can login with the credentials. You can also use factorygirl link
I've been fighting with this for a couple of days and there doesn't seem to be much help online. I've looked at the Typus wiki, sample app, and tests and I appear to be doing things correctly but I stil get HTTP Status Code 302 (Redirect) where I expect 200 (Success) in my tests.
Below are what should be the appropriate files (with irrelevant stuff removed)
config/initializers/typus.rb (rails g typus:migration has been run as I have an admin_users table):
Typus.setup do |config|
# Application name.
config.admin_title = "Something"
# config.admin_sub_title = ""
# When mailer_sender is set, password recover is enabled. This email
# address will be used in Admin::Mailer.
config.mailer_sender = "noreply#somewhere.com"
# Define paperclip attachment styles.
# config.file_preview = :medium
# config.file_thumbnail = :thumb
# Authentication: +:none+, +:http_basic+
# Run `rails g typus:migration` if you need an advanced authentication system.
config.authentication = :session
# Define user_class_name.
config.user_class_name = "AdminUser"
# Define user_fk.
config.user_fk = "admin_user_id"
# Define master_role.
config.master_role = "admin"
end
config/typus/admin_user.yml
AdminUser:
fields:
default: first_name, last_name, role, email, locale
list: email, role, status
form: first_name, last_name, role, email, password, password_confirmation, locale
options:
selectors: role, locale
booleans:
status: Active, Inactive
filters: status, role
search: first_name, last_name, email
application: Admin
description: Users Administration
test/factories/admin_users.rb:
Factory.define :admin_user do |u|
u.first_name 'Admin'
u.last_name 'User'
u.email 'admin#somewhere.com'
u.role 'admin'
u.password 'password!'
u.token '1A2B3C4D5E6F'
u.status true
u.locale 'en'
end
test/functional/admin/credits_controller_test.rb:
require 'test_helper'
class Admin::CreditsControllerTest < ActionController::TestCase
setup do
#admin_user = Factory(:admin_user)
#request.session[:admin_user_id] = #admin_user.id
#request.env['HTTP_REFERER'] = '/admin/credits/new'
end
context "new" do
should "be successful" do
get :new
assert_response :success
end
end
end
#response.body:
<html>
<body>You are being redirected.
</body>
</html>
As you can see, I've set up the typus to use admin_user and admin_user_id for the session key. But for some reason that test fails getting 302 rather than 200. I'm sure this is because I'm doing something wrong that I just don't see. I've also created all these a gist, just in case someone prefers that.
Edited 2011-05-19 09:58am Central Time: Added Response body text per request.
I figured this out. It was a problem with the config/typus/admin_roles.yml file.
Before:
admin:
Category: create, read, update
Credit: read
...
After:
admin:
Category: create, read, update
Credit: read, create
...
The problem was that admin users didn't have access to the CREATE action on the admin/credits_controller which resulted in the user being sent back to the admin login address.
Giving admin users access to the action and changing the
#session[:admin_user_id]
to
#session[:typus_user_id] #Just like in the Typus docs
solved the problem. I had changed it to :admin_user_id because of the
config.user_fk = "admin_user_id"
in the typus config files, while trying to troubleshoot this issue.
I've been using AR_Mailer for about 6 months without ever running into problems. I recently added DelayedJob for some administrative background jobs. Since DelayedJob also handles emails very well (thanks to DelayedMailer gem) I completely removed AR_Mailer from my application.
Everything works perfectly except this email. The password that is generated automatically is now lost.
#app/models/notifier.rb
def activation_instructions(user)
from default_email
#bcc = BACK_UP
#subject = "Activation instructions"
recipients user.email
sent_on Time.now
body :root_url => root_url, :user => user
end
#app/views/notifier/activation_instructions.erb
Thanks for signing up.
Your password is <%=#user.password-%>. For security reasons please change this on your first connection.
[....]
Any idea on why this bug occurs?
Thanks!
Configuration: Rails 2.3.2 & DelayedJob 2.0.4
I found out where the problem was. I looked in the database at the entry created in the delayed_jobs table:
--- !ruby/struct:Delayed::PerformableMethod
object: LOAD;Notifier
method: :deliver_activation_instructions!
args:
- LOAD;User;589
The user parameter is reloaded from the database by delayed_job before sending the email. In that case, the password is lost because it's not stored in the database.
So I've updated the code in order to pass explicitly the password:
#app/models/notifier.rb
def activation_instructions(user, password)
from default_email
#bcc = BACK_UP
#subject = "Activation instructions"
recipients user.email
sent_on Time.now
body :root_url => root_url, :user => user, :password => password
end
#app/views/notifier/activation_instructions.erb
Thanks for signing up.
Your password is <%=#password-%>. For security reasons please change this on your first connection.
[....]
Hope this helps other too!