I have been following Ryan Boland's excellent Rails multitenancy tutorial, but have run into a snag with devise_invitable. I am using...
Rails 4.1.5
devise 3.3.0
devise_invitable 1.3.6
Postgresql
I create a new account and user/account owner on a chosen subdomain (mysubdomain.lvh.me:3000), from which I can send a user invitation just fine. I open the invitation link in an incognito Chrome session to ensure I am not logged in or have any current session. Upon clicking on the invitation link, I am redirected to the sign in page (mysubdomain.lvh.me:3000/users/sign_in) and see a flash notice: "The invitation token provided is not valid!"
I am using a very simple mailer view (app/views/devise/mailer/invitation_instructions.html.erb)...
<%= link_to 'Accept invitation', accept_invitation_url(#resource, :invitation_token => #token) %>
As you can see, I ensured the use of #token, as described here.
Upon creating the invitation, I have confirmed the invitation token is saved to the database (in this case for hey#test.com - d1801fd8df78bd8cd125d5d8091fdc6a72c8f8faf4136cb282d497ec612195e9). I have confirmed this matches the token on invitation lookup upon acceptance request (see below traces). Still, it redirects to user sign in page rather than completing sign up, and also displays in the trace log "Filter chain halted as :resource_from_invitation_token rendered or redirected". The user remains uncomfirmed in the end after this transaction.
Any ideas on what might be going wrong for me here? I am including logs, my application controller, and my devise config below...
Here is the trace log for the invitation creation:
Started POST "/users/invitation" for 127.0.0.1 at 2014-09-07 01:28:33 +0800
Processing by Devise::InvitationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"BiIQ95wwdQz3CJ0+OoLOE9xHHvxhloHsRHrxsqf1D2Q=", "user"=>{"email"=>"hey#test.com"}, "commit"=>"Invite User"}
User Load (4.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1
Account Load (0.4ms) SELECT "public"."accounts".* FROM "public"."accounts" WHERE "public"."accounts"."subdomain" = 'mysubdomain' LIMIT 1
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'hey#test.com' ORDER BY "users"."id" ASC LIMIT 1
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."invitation_token" = 'd1801fd8df78bd8cd125d5d8091fdc6a72c8f8faf4136cb282d497ec612195e9' ORDER BY "users"."id" ASC LIMIT 1
(0.1ms) BEGIN
SQL (0.5ms) INSERT INTO "users" ("created_at", "email", "invitation_created_at", "invitation_sent_at", "invitation_token", "invited_by_id", "invited_by_type", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING "id" [["created_at", "2014-09-06 17:28:34.296123"], ["email", "hey#test.com"], ["invitation_created_at", "2014-09-06 17:28:34.294987"], ["invitation_sent_at", "2014-09-06 17:28:34.294987"], ["invitation_token", "d1801fd8df78bd8cd125d5d8091fdc6a72c8f8faf4136cb282d497ec612195e9"], ["invited_by_id", 1], ["invited_by_type", "User"], ["updated_at", "2014-09-06 17:28:34.296123"]]
(2.2ms) COMMIT
Rendered devise/mailer/invitation_instructions.html.erb (1.3ms)
Devise::Mailer#invitation_instructions: processed outbound mail in 23.5ms
Sent mail to hey#test.com (26.0ms)
Date: Sun, 07 Sep 2014 01:28:34 +0800
From: please-change-me-at-config-initializers-devise#example.com
Reply-To: please-change-me-at-config-initializers-devise#example.com
To: hey#test.com
Message-ID: <...>
Subject: Invitation instructions
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Accept invitation
Redirected to http://mysubdomain.lvh.me:3000/users
Completed 302 Found in 888ms (ActiveRecord: 10.0ms)
Here is the trace upon following the invitation link...
Started GET "/users/invitation/accept?invitation_token=3GXDmi7NntDRdhvo57q5" for 127.0.0.1 at 2014-09-07 01:28:38 +0800
Processing by Devise::InvitationsController#edit as HTML
Parameters: {"invitation_token"=>"3GXDmi7NntDRdhvo57q5"}
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."invitation_token" = 'd1801fd8df78bd8cd125d5d8091fdc6a72c8f8faf4136cb282d497ec612195e9' ORDER BY "users"."id" ASC LIMIT 1
Redirected to http://mysubdomain.lvh.me:3000/users/sign_in
Filter chain halted as :resource_from_invitation_token rendered or redirected
Completed 302 Found in 5ms (ActiveRecord: 0.6ms)
Started GET "/users/sign_in" for 127.0.0.1 at 2014-09-07 01:28:38 +0800
Processing by Devise::SessionsController#new as HTML
Account Load (0.4ms) SELECT "public"."accounts".* FROM "public"."accounts" WHERE "public"."accounts"."subdomain" = 'mysubdomain' LIMIT 1
Rendered devise/shared/_links.erb (0.7ms)
Rendered devise/sessions/new.html.erb within layouts/application (4.4ms)
Completed 200 OK in 21ms (Views: 16.6ms | ActiveRecord: 1.3ms)
Here is my application_controller for good measure...
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :load_schema, :authenticate_user!, :set_mailer_host
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:first_name, :last_name, :company, :email, :password, :password_confirmation, :remember_me, :image, :image_cache)}
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:first_name, :last_name, :company, :email, :password_confirmation, :current_password, :image, :image_cache)}
end
private
def load_schema
Apartment::Database.switch('public')
return unless request.subdomain.present?
if current_account
Apartment::Database.switch(current_account.subdomain)
else
redirect_to root_url(subdomain: false)
end
end
def current_account
#current_account ||= Account.find_by(subdomain: request.subdomain)
end
helper_method :current_account
def set_mailer_host
subdomain = current_account ? "#{current_account.subdomain}." : ""
ActionMailer::Base.default_url_options[:host] = "#{subdomain}lvh.me:3000"
end
def after_sign_out_path_for(resource_or_scope)
new_user_session_path
end
def after_invite_path_for(resource)
users_path
end
end
Here is my Devise initializer (config/initializers/devise.rb), I have added the line "config.allow_insecure_token_lookup = true" to see if this helps, but to no avail...
Devise.setup do |config|
config.mailer_sender = 'please-change-me-at-config-initializers-devise#example.com'
require 'devise/orm/active_record'
config.case_insensitive_keys = [ :email ]
config.strip_whitespace_keys = [ :email ]
config.skip_session_storage = [:http_auth]
config.stretches = Rails.env.test? ? 1 : 10
config.reconfirmable = true
config.expire_all_remember_me_on_sign_out = true
config.password_length = 8..128
config.sign_out_via = :delete
config.allow_insecure_token_lookup = true
end
I'd prefer to comment but I have only 36 points and am not allowed so here's an incomplete answer:
this is the code from devise_invitable InvitationsController which is redirecting your request
def resource_from_invitation_token
unless params[:invitation_token] && self.resource = resource_class.find_by_invitation_token(params[:invitation_token], true)
set_flash_message(:alert, :invitation_token_invalid)
redirect_to after_sign_out_path_for(resource_name)
end
end
in your rails console try running:
token = '3GXDmi7NntDRdhvo57q5' #the token sent in the invitation email
User.find_by_invitation_token(token, true)
and see if that returns your User. It probably won't but maybe this will bring you closer to an answer. I hope so.
Related
I initially wanted to send my user to a different page after sign_up. This works now, as I created a registrations controller. The problem now is that the user is prompted to login right after sign_up. Of course I want him to be automatically signed_in after sign_up. Any idea how I can fix that? I found this explanation but both answers do not work for me: Rails: Devise login after sign up.
Here is my registrations controller:
class RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters, if: :devise_controller?
def start_date
#user =current_user
end
protected
def configure_permitted_parameters
update_attrs = [:password, :password_confirmation, :current_password, :start_date]
devise_parameter_sanitizer.permit(:sign_up, keys: [:applications, :job_category, :job_status])
end
def after_sign_up_path_for(resource)
'/profiles/new' # Or :prefix_to_your_route
end
end
here are my application Logs after signup:
Started GET "/users/sign_up?job_category=IT+%26+Development&job_status=Other+Category&vacancy_id=general+application" for ::1 at 2019-08-08 15:32:17 +0200
Processing by RegistrationsController#new as HTML
Parameters: {"job_category"=>"IT & Development", "job_status"=>"Other Category", "vacancy_id"=>"general application"}
Rendering devise/registrations/new.html.erb within layouts/application
Rendered devise/registrations/new.html.erb within layouts/application (1.1ms)
Rendered shared/_navbar.html.erb (1.0ms)
Rendered shared/_flashes.html.erb (0.4ms)
Completed 200 OK in 174ms (Views: 171.7ms | ActiveRecord: 0.0ms)
Started POST "/users" for ::1 at 2019-08-08 15:32:32 +0200
Processing by RegistrationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Rb06/S6dB019mw8I46x0tJytVG7HNeVV23ZbuX/9Ykb9amYwI3bCLlk8AqNjTEGAR0qTy6rlCNZE1U6w8skslA==", "user"=>{"email"=>"testtesttest#test.de", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "applications"=>"\#{general application}", "job_category"=>"IT & Development", "job_status"=>"Other Category", "terms"=>"1"}, "commit"=>"Sign up"}
Can't verify CSRF token authenticity.
Unpermitted parameter: :terms
(0.5ms) BEGIN
User Exists (0.5ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "testtesttest#test.de"], ["LIMIT", 1]]
SQL (6.9ms) INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at", "applications", "job_category", "job_status") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["email", "testtesttest#test.de"], ["encrypted_password", "$2a$11$MlDweUU00bKQsNyA81/Cuu/o9HQSYJHM8FCInvVSaYVspvcs36oMS"], ["created_at", "2019-08-08 13:32:32.764097"], ["updated_at", "2019-08-08 13:32:32.764097"], ["applications", "{{\"general application\"}}"], ["job_category", "IT & Development"], ["job_status", "Other Category"]]
UserMailer#welcome: processed outbound mail in 0.2ms
(8.4ms) COMMIT
Redirected to http://localhost:3000/profiles/new
Completed 302 Found in 316ms (ActiveRecord: 16.2ms)
Started GET "/profiles/new" for ::1 at 2019-08-08 15:32:32 +0200
Processing by ProfilesController#new as HTML
Completed 401 Unauthorized in 3ms (ActiveRecord: 0.0ms)
Started GET "/users/sign_in" for ::1 at 2019-08-08 15:32:32 +0200
Processing by Devise::SessionsController#new as HTML
Rendering devise/sessions/new.html.erb within layouts/application
Rendered devise/shared/_links.html.erb (1.3ms)
Rendered devise/sessions/new.html.erb within layouts/application (11.0ms)
Rendered shared/_navbar.html.erb (1.4ms)
Rendered shared/_flashes.html.erb (0.5ms)
Completed 200 OK in 198ms (Views: 196.5ms | ActiveRecord: 0.0ms)
Change permitted params from:
def configure_permitted_parameters
update_attrs = [:password, :password_confirmation, :current_password, :start_date]
devise_parameter_sanitizer.permit(:sign_up, keys: [:applications, :job_category, :job_status])
end
to:
def configure_permitted_parameters
update_attrs = [:password, :password_confirmation, :current_password, :start_date]
devise_parameter_sanitizer.permit(:sign_up, keys: [ update_attrs, :applications, :job_category, :job_status])
end
And I think in profiles controller has a problem with before_action :authenticate_user! .
In terminal redirect has work.
You have a problem with terms, in your terminal:
Can't verify CSRF token authenticity.
Unpermitted parameter: :terms
Try changing the protect_from_forgery with: :exception to protect_from_forgery with: :exception, prepend: true
Ref: https://github.com/plataformatec/devise#controller-filters-and-helpers
Devise by default signout the session if the request is unverified.
Ref: from devise code(Devise::Controllers::Helpers),
# Overwrite Rails' handle unverified request to sign out all scopes,
# clear run strategies and remove cached variables.
def handle_unverified_request
super # call the default behaviour which resets/nullifies/raises
request.env["devise.skip_storage"] = true
sign_out_all_scopes(false)
end
Try this out if the first one solves the issue, https://github.com/plataformatec/devise/issues/2734 for solving the issue,
Change this:
devise_parameter_sanitizer.permit(:sign_up, keys: [:applications, :job_category, :job_status]
To this:
devise_parameter_sanitizer.permit(:sign_up, keys: [:applications, :job_category, :job_status, :terms]
My method is executing, but Devise is not using the return value at all. On the sign in page, it just reloads the page with a 'Signed in successfully' notice. It doesn't redirect to the value returned from the method.
Log
Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 22:19:50 -0500
Processing by Users::SessionsController#create as HTML
Parameters: {"utf8"=>"√", "authenticity_token"=>"tQd5a43StP85oyyCpEmFU8cAkFXdJL2OLpuAK1+sqQC6/rIqcd+fB2iE4RT0RoPKPCqreNBYlv2bxjl9gZFrWg==", "user"=>{"email"=>"test11#example.com", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Log in"}
User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["email", "test11#example.com"], ["LIMIT", 1]]
(5.0ms) BEGIN
User Exists (3.0ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 AND ("users"."id" != $2) LIMIT $3 [["email", "test11#example.com"], ["id", 23], ["LIMIT", 1]]
Sector Load (0.0ms) SELECT "sectors".* FROM "sectors" INNER JOIN "sectors_users" ON "sectors"."id" = "sectors_users"."sector_id" WHERE "sectors_users"."user_id" = $1 [["user_id", 23]]
Region Load (0.0ms) SELECT "regions".* FROM "regions" INNER JOIN "regions_users" ON "regions"."id" = "regions_users"."region_id" WHERE "regions_users"."user_id" = $1 [["user_id", 23]]
Criterium Load (0.0ms) SELECT "criteria".* FROM "criteria" INNER JOIN "criteria_users" ON "criteria"."id" = "criteria_users"."criterium_id" WHERE "criteria_users"."user_id" = $1 [["user_id", 23]]
AssetType Load (0.0ms) SELECT "asset_types".* FROM "asset_types" INNER JOIN "asset_types_users" ON "asset_types"."id" = "asset_types_users"."asset_type_id" WHERE "asset_types_users"."user_id" = $1 [["user_id", 23]]
Company Load (1.0ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = $1 LIMIT $2 [["id", 42], ["LIMIT", 1]]
(5.0ms) ROLLBACK
############### /users/23/edit
Rendering users/sessions/new.haml within layouts/application
Rendered users/shared/_links.html.erb (3.0ms)
Rendered users/sessions/new.haml within layouts/application (251.2ms)
Rendered layouts/_footer.haml (15.0ms)
Completed 200 OK in 6554ms (Views: 3364.9ms | ActiveRecord: 86.1ms)
Notice it is rendering users/sessions/new.haml instead of the edit page?
Code
class ApplicationController < ActionController::Base
...
def after_sign_in_path_for(resource)
logger.debug '############### ' + edit_user_path(resource) if resource.is_a?(User) && resource.signature.blank?
return edit_user_path resource if resource.is_a?(User) && resource.signature.blank?
stored_location_for(resource) ||
if resource.is_a?(User)
dashboard_path
elsif resource.is_a?(Facilitator) && resource.name.nil?
edit_facilitator_path resource
elsif resource.is_a?(Facilitator)
facilitator_path resource
else
super
end
end
I completely commented out the method and it still reloaded the login page.
Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 22:25:21 -0500
...
Rendering users/sessions/new.haml within layouts/application
Devise 4.4.0
Documentation:
https://github.com/plataformatec/devise/wiki/How-To%3A-Redirect-to-a-specific-page-on-successful-sign-in-and-sign-out
http://www.rubydoc.info/github/plataformatec/devise/master/Devise/Controllers/Helpers:after_sign_in_path_for
I added
def after_sign_in_path_for(resource)
logger.debug '############# ' + resource.errors.full_messages.join(', ')
And did discover validation errors like
############# Title can't be blank, Country can't be blank, Signature can't be blank, ...
But it does show the notice
Signed in successfully.
And I do have a session and can navigate elsewhere. My validations are on: :update.
validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true, on: :update
This should not cause log in behavior errors.
I commented all validations on the model and it does work, but this is highly unusual! Validations should not affect login behavior. There has to be a workaround.
Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 23:11:43 -0500
SQL (15.0ms) UPDATE "users" SET "current_sign_in_at" = $1, "last_sign_in_at" = $2, "current_sign_in_ip" = $3, "sign_in_count" = $4, "updated_at" = $5 WHERE "users"."id" = $6 [["current_sign_in_at", "2018-03-06 04:11:44.225501"], ["last_sign_in_at", "2017-11-09 01:22:28.245231"], ["current_sign_in_ip", "127.0.0.1/32"], ["sign_in_count", 6], ["updated_at", "2018-03-06 04:11:44.230506"], ["id", 23]]
Redirected to http://localhost:3000/users/23/edit
Completed 302 Found in 2183ms (ActiveRecord: 48.0ms)
As you only want your validations on update, I guess that you only need them for a specific form, since your users are still valid even without this validations.
In that case I would use a so called form object, that does the on update validations for you and remove the on update validations on your user model. In that case your validations don't affect other parts of your app.
Here is a good guide on how to do that with just using ActiveModel.
Example:
app/models/user.rb
class User < ApplicationRecord
# remove the validations here
end
app/forms/user_edit_form.rb
class UserEditForm
include ActiveModel::Model
ATTRIBUTES = :email, :name, :title, :phone,
:address1, :city, :state, :zip,
:country, :type, :signature
attr_accessor *ATTRIBUTES
validates *ATTRIBUTES, presence: true
def update(user)
if valid?
user.update(self.attributes)
end
end
def self.for_user(user)
new(user.slice(*ATTRIBUTES)
end
end
users_controller.rb
class UsersController
def edit
#user = User.find(params[:id])
#user_edit_form = UserEditForm.for_user(#user)
end
def update
#user = User.find(params[:id])
#user_edit_form = UserEditForm.new(user_update_params).update(#user)
if #user_edit_form.errors?
render :edit
else
redirect_to user_path(#user)
end
end
def user_update_params
# ...
end
end
edit.html.erb
<%= form_for #user_edit_form, url: user_path(#user), method: :patch do |f| %>
# ...
<%= f.submit %>
<% end %>
Alternative
An alternative could be to add a virtual attribute to the model and run your validations conditionally in the user controller.
class User < ApplicationRecord
attr_accessor :profile_complete
with_options if: -> { profile_complete } do
validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true
end
end
users_controller.rb
class UsersController < ApplicationController
def update
#user = User.find(params[:id])
#user.profile_complete = true
if #user.update(user_update_params)
redirect_to #user
else
render :edit
end
# ...
end
end
Note: Instead of using a virtual attribute (attr_accessor) you could also use a real DB attribute, so you can also actually know which users filled out their profile completely.
Alternative 2
In some other projects I also used state machine gems (there are a couple e.g. aasm or statemachines-activerecord) to do somehing similar. Some of the state machine gems even support having validations only for certain states or transisions.
Check this documentation https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in. They have clearly mentioned when you will go in loop and solution for it. Check Preventing redirect loops section in above doc.
You might need conditional validation on your model. Something like this:
validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true, on: :update, unless: Proc.new {|user| user.current_sign_in_at.present? }
Devise will update sign_in_at whenever sign_in happens. Which will trigger update action and related validations.
Also Documentation said the allow_nil: true instruct the model to validate the fields ONLY if it exists on the submitted form.
Trying to signup in my RoR webapp give me the Devise Message "Auth token has already been taken"
Also, the webapp have an API and works fine, doesn't give any message, this only happen when I'm trying to use the HTML view.
user_controller.rb
before_action :set_user, only: [:show, :edit, :update, :destroy]
# DELETE /users/:id.:format
def destroy
# authorize! :delete, #user
#user.destroy
respond_to do |format|
format.html { redirect_to root_url }
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
accessible = [ :name, :email ]
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
params.require(:user).permit(accessible)
end
User.rb
validates :auth_token, uniqueness: true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
before_create :generate_authentication_token!
def generate_authentication_token!
begin
self.auth_token = Devise.friendly_token
end while self.class.exists?(auth_token: auth_token)
end
logs
Started GET "/users/sign_up" for 127.0.0.1 at 2015-06-30 09:31:46 -0500
Processing by Devise::RegistrationsController#new as HTML
Rendered devise/registrations/new.html.haml within layouts/application (12.9ms)
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."auth_token" IS NULL LIMIT 1
Rendered layouts/_navigation_links.html.haml (2.1ms)
Rendered layouts/_navigation.html.haml (3.4ms)
Rendered layouts/_messages.html.haml (0.2ms)
Completed 200 OK in 132ms (Views: 117.0ms | ActiveRecord: 1.5ms)
Started POST "/users" for 127.0.0.1 at 2015-06-30 09:32:00 -0500
Processing by Devise::RegistrationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"20w9AXmACwggvPocKfLBdrxQRasT5OiaC7niuzooBBm3BAp8xkN6VLWyxZLRoLIpFPEIIdkxZRd9CCwsJxkeUA==", "user"=>{"email"=>"hola#x.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Sign up"}
(0.1ms) BEGIN
User Exists (0.4ms) SELECT 1 AS one FROM "users" WHERE "users"."auth_token" = '' LIMIT 1
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'hola#x.com' LIMIT 1
(0.1ms) ROLLBACK
Rendered devise/registrations/new.html.haml within layouts/application (3.2ms)
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."auth_token" IS NULL LIMIT 1
Rendered layouts/_navigation_links.html.haml (1.5ms)
Rendered layouts/_navigation.html.haml (2.1ms)
Rendered layouts/_messages.html.haml (0.2ms)
Completed 200 OK in 232ms (Views: 134.4ms | ActiveRecord: 1.2ms)
Started GET "/assets/jquery/jquery-bb5529929fa5581a780a38ecb7470f2c.js?body=1" for 127.0.0.1 at 2015-06-30 09:32:00 -0500
Follow the following
1) Open Rails console
rails console
2) Get the total count of users
user = User.all
user.count
this should be 1
3) Get the user and check the auth token
user = User.last
user.auth_token
auth token would be an empty string which is the reason your command is failing as the user doesn't have valid auth token
4) Create a valid auth token for the user
user.auth_token = Devise.friendly_token
user.save
It would create a valid auth token for the user and save it
5) Now you can run your commands and it would work perfectly
Cheers! :)
It's probably because you already have users in your db without auth_token,
use Devise.friendly_token to update those users with a token
In my app, I give access to a customer to HIS own deals thanks to Cancan.
It works when I try it "manually" with the browser but I fail at implementing the rspec tests. A customer can't access other customer's deals but only his own (the administrator give him access through Active Admin interface).
It's like I am not managing to make rspec understand that the customer(through FactoryGirl) I create for tests should be allowed/associated with the deals I create for the tests (again through FactoryGirl).
THE TEST: /spec/controllers/deals_controller_spec.rb
require 'spec_helper'
require "cancan/matchers"
describe DealsController do
context "As signed-in CUSTOMER" do
before do
#customer = FactoryGirl.create(:customer) #the factory builds a basic customer i.e with 'prospect role' attributed by default
#deal = FactoryGirl.create(:deal, :customers => [#customer]) # in array as a deal has_many customers
sign_in_customer #customer
end
describe "the customer can read=view the page of a Deal HE OWNS " do
it "can access the page" do
get :deal_page, { :id => #deal.id }
expect(current_path).to eq(deal_page_path(#deal))
page.should have_content('Here is one of your deals, dear customer')
end
end
end
Here is the error I get:
DealsController As signed-in CUSTOMER with access to the deal page
Failure/Error: expect(current_path).to eq(deal_page_path(#deal))
expected: "/deals_page/2"
got: "/customer_interface_homepage"
(compared using ==)
Here is the detailed test log
Deal Exists (0.8ms) SELECT 1 AS one FROM "deals" WHERE LOWER("deals"."deal_code") = LOWER('CHA1FR001') LIMIT 1
SQL (2.1ms) INSERT INTO "deals" ("admin_user_id", "client_contact_point_name", blabla") VALUES ($1, $2, blabla...) RETURNING "id" [["admin_user_id", 1], ["client_contact_point_name", "henri Cool"], ["client_contact_point_profile_url", "http://example.com"], ....blabla...]
(...blabla)
Customer Exists (0.6ms) SELECT 1 AS one FROM "customers" WHERE (LOWER("customers"."email") = LOWER('person_1#example.com') AND "customers"."id" != 1) LIMIT 1
(...blabla)
Started GET "/customers/signin" for 127.0.0.1 at 2014-05-28 18:37:05 +0200
Processing by Customers::SessionsController#new as HTML
Rendered customers/sessions/new.html.erb within layouts/lightbox (40.0ms)
Rendered layouts/_metas.html.erb (0.4ms)
Rendered layouts/_messages.html.erb (0.7ms)
Rendered layouts/_footer.html.erb (1.2ms)
Completed 200 OK in 77ms (Views: 51.5ms | ActiveRecord: 0.0ms)
Started POST "/customers/signin" for 127.0.0.1 at 2014-05-28 18:37:05 +0200
Processing by Customers::SessionsController#create as HTML
Parameters: {"utf8"=>"✓", "customer"=>{"email"=>"person_1#example.com", "password"=>"[FILTERED]"}, "commit"=>"Log In"}
Customer Load (4.0ms) SELECT "customers".* FROM "customers" WHERE "customers"."email" = 'person_1#example.com' ORDER BY "customers"."id" ASC LIMIT 1
SQL (1.0ms) UPDATE "customers" SET "remember_created_at" = $1, "updated_at" = $2 WHERE "customers"."id" = 1 [["remember_created_at", 2014-05-28 16:37:05 UTC], ["updated_at", 2014-05-28 18:37:05 +0200]]
SQL (1.2ms) UPDATE "customers" SET "last_sign_in_at" = $1, "current_sign_in_at" = $2, "last_sign_in_ip" = $3, "current_sign_in_ip" = $4, "sign_in_count" = $5, "updated_at" = $6 WHERE "customers"."id" = 1 [["last_sign_in_at", 2014-05-28 16:37:05 UTC], ["current_sign_in_at", 2014-05-28 16:37:05 UTC], ["last_sign_in_ip", "127.0.0.1"], ["current_sign_in_ip", "127.0.0.1"], ["sign_in_count", 1], ["updated_at", 2014-05-28 18:37:05 +0200]]
**Redirected to http://www.example.com/customer_interface_homepage**
Completed 302 Found in 33ms (ActiveRecord: 6.2ms)
Started GET "/customer_interface_homepage" for 127.0.0.1 at 2014-05-28 18:37:05 +0200
Processing by ClientreportingPagesController#index as HTML
Customer Load (0.5ms) SELECT "customers".* FROM "customers" WHERE "customers"."id" = 1 ORDER BY "customers"."id" ASC LIMIT 1
(1.2ms) SELECT COUNT(*) FROM "roles" INNER JOIN "customers_roles" ON "roles"."id" = "customers_roles"."role_id" WHERE "customers_roles"."customer_id" = $1 AND (((roles.name = 'prospect') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["customer_id", 1]]
Rendered layouts/_metas.html.erb (0.2ms)
(0.8ms) SELECT COUNT(*) FROM "roles" INNER JOIN "customers_roles" ON "roles"."id" = "customers_roles"."role_id" WHERE "customers_roles"."customer_id" = $1 AND (((roles.name = 'superadmin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["customer_id", 1]]
Rendered layouts/client_interface_partials
Completed 200 OK in 34ms (Views: 27.7ms | ActiveRecord: 2.4ms)
Processing by DealsController#deal_page as HTML
Parameters: {"id"=>"2"}
**Completed 401 Unauthorized in 1ms**
Rendered text template (0.1ms)
(0.5ms) ROLLBACK TO SAVEPOINT active_record_2
(0.3ms) ROLLBACK TO SAVEPOINT active_record_1
(0.3ms) ROLLBACK
I'm not sure it's the root cause of the issue but 2 things seem strange to me in this log:
why does rspec send to example.com/customer_interface_homepage (i have in my spec_helper file told rspec that i test locally: Capybara.asset_host = 'http:// localhost:3000') ?
why does rspec experience a "Completed 401 Unauthorized in 1ms at the end ?
Some files that might be useful to solve the issue:
/app/models/customer_ability.rb
class CustomerAbility
include CanCan::Ability
def initialize(customer)
alias_action :show, :to => :read #this will have no change on the alias :read!
customer ||= Customer.new # guest customer (not logged in)
if customer.has_role? :superadmin
Log.info "Ability: customer is superadmin"
can :manage, :all
else
can :read, Deal do |deal|
# Only customers who have been granted access in Active Admin to a deal can read
deal.customers.include? customer
end
end
end
end
controllers/deals_controller.rb
class DealsController < ApplicationController
before_filter :authenticate_customer!,
:only => [ :deal_page ]
def deal_page
#deal = Deal.find(params[:id])
authorize! :read, #deal # only allow customers with authorized access in AA; sends to customer_ability
respond_to do |format|
format.html
format.json { render json: #deal }
end
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
# handle Cancan authorization exception
rescue_from CanCan::AccessDenied do |exception|
exception.default_message = t("errors.application_controller_exception_messages.only_open_to_admin")
if current_user # if it's user redirect to main HP
redirect_to root_path, :alert => exception.message
else # if it's a Customer redirect him to client interface HP
redirect_to customer_interface_homepage_path, :alert=> exception.message
end
end
def current_ability #inspired by http://mikepackdev.com/blog_posts/12-managing-devise-s-current-user-current-admin-and-current-troll-with-cancan
#current_ability ||= case
when current_user
UserAbility.new(current_user)
when current_customer
CustomerAbility.new(current_customer)
end
end
/spec/support/utilities.rb
include ApplicationHelper
def sign_in_customer(customer)
customer.confirm!
visit new_customer_session_path
fill_in "Email", with: customer.email
fill_in "Password", with: customer.password
click_on "Log In"
#populate cookie when not using capybara
cookies[:authentication_token] = customer.authentication_token
end
/spec/factories/deals.rb
FactoryGirl.define do
factory :deal do
# id i don't here any id
sequence(:deal_campaign_code) { |n| "CHA#{n}FR001" }
featured true
admin_user_id 1
end
end
/spec/factories/customers.rb
FactoryGirl.define do
factory :customer do # we use prospect as by definition a visitor signing in gets 'prospect status'
sequence(:email) { |n| "person_#{n}#example.com"}
password "bet(8a3#"
password_confirmation "bet(8a3#"
# required if the Devise Confirmable module is used
confirmed_at Time.now
confirmation_token nil
# create deals connected to the Customer
after(:create) do |customer|
customer.deals << FactoryGirl.create(:deal)
end
end
end
/app/models/customer.rb
class Customer < ActiveRecord::Base
rolify
# -- Relationships --------------------------------------------------------
has_many :customer_deals, dependent: :destroy
has_many :deals, through: :customer_deals
/app/models/deal.rb
class Deal < ActiveRecord::Base
# -- Relationships --------------------------------------------------------
belongs_to :admin_user, :foreign_key => 'admin_user_id'
has_many :customer_deals, dependent: :destroy
has_many :customers, through: :customer_deals
/app/models/customer_deal.rb
class CustomerDeal < ActiveRecord::Base
# -- Relationships --------------------------------------------------------
belongs_to :customer, :foreign_key => 'customer_id'
belongs_to :deal, :foreign_key => 'deal_id'
I think the session information is not being passed into the request you make in the spec. See this guide for how to use devise with controller tests.
As an alternative approach, I would recommend making this a feature spec instead of a controller spec. Note to make capybara play nice with devise follow this guide.
To answer your log questions:
Not sure why it says example.com/customer_interface_homepage but you can see on the next line that it is actually sending the GET request to 127.0.0.1/customer_interface_homepage. So that part is working. Perhaps your hosts file has example.com pointing to 127.0.0.1 and the first message is resolving the IP with DNS?
The 401 unauthorized looks to be due to your deal controller's authorize! call.
The spec is almost certainly failing due to current_path not being updated because of the 401 return from the deals controller. I believe if you fix that issue the current_path issue will also be fixed.
I have had problems in the past with FactoryGirl not actually writing models to the database on creation. This will cause anything relying on associations (which authorize! looks like it probably relies on due to CustomerAbility) to fail.
My suggestions moving forward are to debug authorize! (likely using log/puts statements) and figuring out what #deal actually looks like at that point as well as what deal.customers looks like. If you post the code to authorize! we may be able to help more.
I have Devise running on my Rails 3.2 application. Google Oauth is used to sign in.
New Users attempt to sign in with Google and are redirected to the sign-in page without being signed in. I check the DB and the User's accounts are created with the correct credentials (everything is correct except IP).
Returning Users have no problem signing in, Log out also works perfectly.
I'm not sure what I am doing wrong, I followed this tutorial: http://blogs.burnsidedigital.com/2013/03/rails-3-devise-omniauth-and-google/
The difference is that I do not have any need for additional user information, I just want to authenticate users through their google accounts ensuring they belong to a certain domain foobar.com
I have an OmniAuth controller which looks like this:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
auth = request.env["omniauth.auth"]
proceeder = !!(auth.info.email =~ /^[a-zA-Z0-9.]+#foobar\.com$/)
if proceeder
user = User.from_omniauth(auth)
flash.notice = "Signed in!"
sign_in_and_redirect user
redirect_to :root
else
flash[:notice] = "You must use an email ending in #foobar.com"
redirect_to signup_path
end
and my User model is as follows:
class User < ActiveRecord::Base
has_many :posts
has_many :comments
has_many :votes
has_many :reports
devise :database_authenticatable, :registerable, :omniauthable,
:recoverable, :rememberable, :trackable, :validatable#, :omniauth_providers => [:google_oauth2]
attr_accessible :email, :password, :password_confirmation, :remember_me, :provider, :uid
def self.from_omniauth(auth)
if user = User.find_by_email(auth.info.email)
user.provider = auth.provider
user.uid = auth.uid
user
else
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user
end
end
end
end
For visitors who are not signed in I call posts#login
class PostsController < ApplicationController
...
def login
Rails.logger.debug(current_user)
if current_user
redirect_to :root
end
end
...
and the root_path contains a current_user check which redirects to posts#login if no user is logged in.
I thought the problem was within the method sign_in_and_redirect so I added the following to my ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery
serialization_scope :view_context
def after_sign_in_path_for(resource)
root_url
end
def after_sign_up_path_for(resource)
root_url
end
end
Why aren't new accounts signed in? Why would their accounts be created but not logged in?
Any help is greatly appreciated, I am completely stumped (Devise documentation didn't lead me to a solution).
EDIT
Here are the development logs of a new user signing in/up:
>> Listening on 0.0.0.0:3000, CTRL+C to stop
Started GET "/signup" for 127.0.0.1 at 2013-11-16 16:58:54 -0500
Processing by PostsController#login as HTML
Rendered posts/login.html.erb within layouts/application (24.0ms)
Completed 200 OK in 31ms (Views: 30.6ms | ActiveRecord: 0.0ms)
(google_oauth2) Request phase initiated.
Started GET "/users/auth/google_oauth2" for 127.0.0.1 at 2013-11-16 16:58:56 -0500
(google_oauth2) Callback phase initiated.
Started GET "/users/auth/google_oauth2/callback?state=e5c02458190b79758da474baa623717a29078427ad6049f7&code=4/yimrxaCXMOY_FTZyAgd_-CpZxMrF.EhatByjkrMkYshQV0ieZDAoQx9zFhAI" for 127.0.0.1 at 2013-11-16 16:59:00 -0500
Processing by OmniauthCallbacksController#google_oauth2 as HTML
Parameters: {"state"=>"e5c02458190b79758da474baa623717a29078427ad6049f7", "code"=>"4/yimrxaCXMOY_FTZyAgd_-CpZxMrF.EhatByjkrMkYshQV0ieZDAoQx9zFhAI"}
User Load (54.0ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'jquadr01#foobar.com' LIMIT 1
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."provider" = 'google_oauth2' AND "users"."uid" = '105565017494971239846' LIMIT 1
(0.1ms) BEGIN
User Exists (0.4ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'jquadr01#foobar.com' LIMIT 1
(0.1ms) ROLLBACK
(0.1ms) BEGIN
SQL (76.2ms) INSERT INTO "users" ("created_at", "current_sign_in_at", "current_sign_in_ip", "email", "encrypted_password", "last_sign_in_at", "last_sign_in_ip", "provider", "remember_created_at", "reset_password_sent_at", "reset_password_token", "sign_in_count", "uid", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING "id" [["created_at", Sat, 16 Nov 2013 21:59:01 UTC +00:00], ["current_sign_in_at", Sat, 16 Nov 2013 21:59:01 UTC +00:00], ["current_sign_in_ip", "127.0.0.1"], ["email", "jquadr01#foobar.com"], ["encrypted_password", ""], ["last_sign_in_at", Sat, 16 Nov 2013 21:59:01 UTC +00:00], ["last_sign_in_ip", "127.0.0.1"], ["provider", "google_oauth2"], ["remember_created_at", nil], ["reset_password_sent_at", nil], ["reset_password_token", nil], ["sign_in_count", 1], ["uid", "1055650112345678911846"], ["updated_at", Sat, 16 Nov 2013 21:59:01 UTC +00:00]]
(16.9ms) COMMIT
#<User:0x000000020d7a08>
Redirected to http://localhost:3000/
Completed 302 Found in 338ms (ActiveRecord: 162.0ms)
Started GET "/" for 127.0.0.1 at 2013-11-16 16:59:01 -0500
Processing by MainController#index as HTML
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
Redirected to http://localhost:3000/signup
Completed 302 Found in 5ms (ActiveRecord: 0.8ms)
Started GET "/signup" for 127.0.0.1 at 2013-11-16 16:59:01 -0500
Processing by PostsController#login as HTML
Rendered posts/login.html.erb within layouts/application (0.5ms)
Completed 200 OK in 4ms (Views: 2.7ms | ActiveRecord: 0.0m
You say “New Users attempt to sign in with Google and are redirected to the sign-in page without being signed in” and I’m not 100% sure what that means. Every time you use Google OAuth to let someone into your app, they’re going to see a one-time approval screen so they get a chance to say whether or not they’re OK with their identity being sent to your app. They really shouldn’t be going to the sign-in page if they’re already signed in. Which page do they go to?
I don't have a sure solution, but I have an idea.
Consider this code:
if proceeder
user = User.from_omniauth(auth)
flash.notice = "Signed in!"
sign_in_and_redirect user
redirect_to :root
Notice that you're triggering two redirects. First you call the devise redirect, and then you call redirect_to :root. Normally, if you issue two redirects in the same action, it results in an AbstractController::DoubleRenderError. The fact that you're not getting a DoubleRenderError is a clue that something is not right with sign_in_and_redirect.
Now, I don't know what's wrong with sign_in_and_redirect, so let's examine the source:
# File 'lib/devise/controllers/helpers.rb', line 159
def sign_in_and_redirect(resource_or_scope, *args)
options = args.extract_options!
scope = Devise::Mapping.find_scope!(resource_or_scope)
resource = args.last || resource_or_scope
sign_in(scope, resource, options)
redirect_to after_sign_in_path_for(resource)
end
I'm guessing it has something to do with finding the user scope, since the rest of it looks okay, and perhaps your mappings are not configured correctly.
In order to avoid any ambiguity (as well as the DoubleRenderError that I would expect to be happening), try replacing the above code with this:
if proceeder
user = User.from_omniauth(auth)
flash.notice = "Signed in!"
sign_in :user, user
redirect_to :root
Let me know if that works, and if not, I'll take another crack at it.
P.S. do you see the flash message "Signed in!" after you authenticate with Google?
As described here, add the following code to your OmniauthCallbacksController:
skip_before_filter :verify_authenticity_token
The user isn't signed in because of CSRF authentication failure. In Rails 4, if the CSRF authentication fails, the default behaviour is :null_session, which is why the user isn't signed in.
EDIT :
The issue seems to be with the code that creates a new user in the User.from_omniauth method. You're creating a new user, if one doesn't exist with a given provider and uid, without setting the email attribute, which can't be blank! Consequently, validation fails and the user object is not persisted, and hence there's no valid session.