Devise throws:
"NoMethodError (undefined method `login' for #<ActionDispatch::Request:0x00000004e42d80>):
"
every time I try to log in.
In this application "login" field is used as authentication key:
/config/initializers/devise.rb:
config.authentication_keys = [ :login ]
In session_controller.rb I used before_filter:
def configure_permitted_parameters
devise_parameter_sanitizer.for(:user) { |u| u.permit(:login, :password) }
end
And my routes.rb:
devise_for :users, :controllers => { :sessions => 'sessions', :registrations => 'registrations', :invitations => 'users/invitations'}
This problem appeared after upgrade from Rails 3 to Rails 4.
Can someone explain to me, what I'm doing wrong?
UPDATE
My bad. Found wrong parameter in devise initializer, set by my co-worker.
Anyway i have error message:
NameError (undefined local variable or method `invitation_token' for #<User:0x0000000286c750>):
app/controllers/sessions_controller.rb:6:in `create'
sessions#create:
def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
render :json => { :user_id => resource.id }, :status => :created
end
UPDATE
Crap. My co-worker also changed database.yml to another DB. So this DB was not migrated to last state =. After rake db:migrate all works fine. Thanks to all.
The underlying issue here is generally that devise's invitable code is generated by an second step in your devise work flow, a generator that makes a second migration:
$ rails g devise_invitable:install
$ rails g devise_invitable User (where User is my Model)
$ rake db:migrate
What you need to check for is if both migrations are in sync (in my case I reran the user migration but NOT the invitable migration and thus my user table was incorrect).
According to this link, you should create a login virtual attribute in the User model.
#Virtual attribute for authenticating by either username or email
#This is in addition to a real persisted field like 'username'
attr_accessor :login
Also add login to attr_accessible for rails 3
attr_accessible :login
You may also need to overwrite Devise's find_for_database_authentication method in User model
(assuming it is activerecord)
# app/models/user.rb
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else
where(conditions).first
end
end
You may need to modify config/initializers/devise.rb to have
config.reset_password_keys = [ :login ]
config.confirmation_keys = [ :login ]
Related
I am using Apartment and Devise gem for Muti-tenancy and authentication.
I have a sign_up page in root domain URL(example.com) where I get the subdomain details from user.
I need to sign_in the user after successfully saving the record and redirect to the new subdomain(sub.example.com).
Apartment Schemas:
Account => Common for all schemas(Public)
User => Created seperately for each schemas(Private)
RegistrationController:
class Users::RegistrationsController < Devise::RegistrationsController
...
...
def create
ActiveRecord::Base.transaction do
#account = Account.new(account_params)
if #account.valid?
# Create & switch to the new tenant
Apartment::Tenant.create(#account.subdomain)
Apartment::Tenant.switch!(#account.subdomain)
#account.save
sign_in(:user, #account.user)
redirect_to root_url(subdomain: "#{Apartment::Tenant.current}")
else
render :action => 'new'
end
end
end
...
...
protected
def account_params
params.require(:account).permit(:name, :subdomain, user_attributes: [:first_name, :last_name, :email, :password, :password_confirmation])
end
end
The above code successfully redirects to the new subdomain but, It is not signing_in the user although I am signing_in the user before redirect.
Anyone please help me to redirect the user as signed_in to the new subdomain.
Thank you..
Finally, solved this issue by adding :domain => :all or the rootdomain :domain => 'example.com' option in session_store which I found in this answer.
config/initializers/session_store.rb
config.session_store :cookie_store, :key => '_domain_session', :domain => :all
(or)
config.session_store :cookie_store, :key => '_domain_session', :domain => 'example.com'
This question is related to one that I asked a couple years ago:
Instantiating Devise user models manually using contents of params hash
I am not sure if this is a rails 4 issue, but I am finding that I cannot manually instantiate devise user in my controller code. This used to work in rails 3.
class RegistrationsController < Devise::RegistrationsController
...
def schema_test
#user = User.new(:email => 'jhw#ausd.k12.edu', :password => 'asdf123', :password_confirmation => 'asdf123')
#user.save
end
...
end
This is the devise-specific part of my routes.rb:
devise_for :users, :controllers => {:registrations => "registrations"}
devise_scope :user do
get '/schema_test', to: 'registrations#schema_test'
end
When I call schema_test, I am finding that the user object is not getting saved to the database. Does anyone have any suggestions?
Best practice is to check the behavior in the rails console before you integrate it into your rails application.
The simplest way to figure out your issue is to read the error messages that devise returns. This could be caused by not meeting the password requirements and/or that the email already exists in your database.
From the root of your application, run rails c.
#user = User.new(:email => 'jhw#ausd.k12.edu', :password => 'asdf123', :password_confirmation => 'asdf123')
# Check if the user object is valid
#user.valid?
# If it comes back false, read the error messages
#user.errors.messages
=> {:password=>["is too short (minimum is 8 characters)"]}
In your example, the user is not being saved because the password is too short.
I have a custom mailer (UserMailer.rb) and a few methods to override the default Devise methods for the welcome email and forgot password emails. The mailer uses a custom template to style the emails--and it works great.
In config/initializers, I have a file with
module Devise::Models::Confirmable
# Override Devise's own method. This one is called only on user creation, not on subsequent address modifications.
def send_on_create_confirmation_instructions
UserMailer.welcome_email(self).deliver
end
...
end
(Again, UserMailer is setup and works great for the welcome email and reset password email.)
But what's not working is the option to "Resend confirmation instructions." It sends with the default Devise styling and I want it to use the styling of my mailer layout. I know I can manually add the layout to the default Devise layout, but I'd like to keep DRY in effect and not have to do that.
I've tried overriding the send_confirmation_instructions method found here, but I'm getting a wrong number of arguments (1 for 0) error in create(gem) devise-2.2.3/app/controllers/devise/confirmations_controller.rb at
7 # POST /resource/confirmation
8 def create
9 self.resource = resource_class.send_confirmation_instructions(resource_params)
In my initializer file, I'm able to get to this error by adding a new override for Devise, but I'm probably not doing this correctly:
module Devise::Models::Confirmable::ClassMethods
def send_confirmation_instructions
UserMailer.send_confirmation_instructions(self).deliver
end
end
Any ideas?
You don't have to go through that initializer to do that. I've done this by overriding the confirmations controller. My routes for devise look like:
devise_for :user, :path => '', :path_names => { :sign_in => 'login', :sign_out => 'logout', :sign_up => 'signup'},
:controllers => {
:sessions => "sessions",
:registrations => "registrations",
:confirmations => "confirmations"
}
Then, create the confirmations_controller and extend the Devise::ConfirmationsController to override:
class ConfirmationsController < Devise::ConfirmationsController
In that controller, I have a create method to override the default:
def create
#user = User.where(:email => params[:user][:email]).first
if #user && #user.confirmed_at.nil?
UserMailer.confirmation_instructions(#user).deliver
flash[:notice] = "Set a notice if you want"
redirect_to root_url
else
# ... error messaging or actions here
end
end
Obviously, in UserMailer you can specify the html/text templates that will be used to display the confirmation message. confirmation_token should be a part of the #user model, you can use that to create the URL with the correct token:
<%= link_to 'Confirm your account', confirmation_url(#user, :confirmation_token => #user.confirmation_token) %>
I followed the steps that are described in https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview and have a method in user model like this:
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
if user = self.find_by_email(data.email)
user
else # Create a user with a stub password.
self.create!(:email => data.email, :password => Devise.friendly_token[0,20])
end
end
I intermittently get errors like
A NoMethodError occurred in omniauth_callbacks#facebook:
undefined method email' for "false":String
app/models/user.rb:138:infind_for_facebook_oauth'
that I haven't been able to reproduce. What is the source of this problem?
I am not sure what causes this either. Here's a work-around that simply creates a new object:
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
if data == "false"
self.new
elsif user = self.find_by_email(data.email)
user
else # Create a user with a stub password.
self.create!(:email => data.email, :password => Devise.friendly_token[0,20])
end
end
The controller code shown in the example will then work - it will redirect the user to sign up.
In a recent project, facebook Users can login using their Facebook UID to upload picture submissions based on file uploads or uploads from their personal albums etc.
Everything works quite nice on my local system in the development environment. Login via Facebook, Logout, Upload - all great.
In production though I'm facing a unknown and hard to debug problem. It seems that every once in a while (actually reproducable when uploading a new Submission to the system) the session is lost, the picture is NOT uploaded and the facebook user is logged out (!).
I'm using devise and omniauth. Omniauth is integrated into Devise.
Following is all the code that touches Devise/Omniauth or the User.
app/models/user.rb
class User < ActiveRecord::Base
devise :omniauthable, :rememberable, :omniauth_providers => [:facebook]
def self.create_with_omniauth(auth)
u = User.find_by_uid(auth["uid"])
return u unless u.nil?
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["user_info"]["name"]
user.email = auth['user_info']['email']
end
end
def after_signin_path
'/competition'
end
end
Database contains all needed fields for :rememberable, I hope.
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model
#user = User.create_with_omniauth(env["omniauth.auth"])
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
#user.update_attributes!(:current_auth_token => env["omniauth.auth"]['credentials']['token'], :last_language => I18n.locale.to_s, :updated_at => Time.now, :remember_created_at => Time.now)
sign_in_and_redirect(:user, #user)
else
redirect_to '/competition'
end
end
protected
def after_omniauth_failure_path_for resource
'/competition'
end
end
config/initializers/devise.rb
OmniAuth.config.full_host = "http://#{APP_CONFIG[:domain]}"
Devise.setup do |config|
config.mailer_sender = "devise#myapp.host.com"
require 'devise/orm/active_record'
config.stretches = 10
config.encryptor = :bcrypt
config.timeout_in = 3.days
config.pepper = "2a4b8b2ed9e12e553a7a542176f2ace1af62c062f3ba203a590b8b6307f33042b394922807a840004a3dcdf1c4e97ae085fe2c29654ddaeab7c60f431a8078abb"
config.omniauth :facebook, APP_CONFIG[:facebook_app_id], APP_CONFIG[:facebook_app_secret], {
:scope => "email,user_photos,user_photos,publish_stream,offline_access",
:client_options => {
:ssl => {
:ca_file => "/etc/pki/tls/certs/ca-bundle.crt"
}
}
}
end
There are no auth-related methods in application_controller.rb.
routes.rb:
The interesting part below:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
match '/logout_fb' => 'start#logoutfb'
authenticate :user do
get '/users/connect/:network', :to => redirect("/users/auth/%{network}")
end
Somehow I cannot get to understand the authenticate block, which according to another post should be helpful.. ideas on this too?
So many theories:
One is that the facebook function in the omniauth_callbacks_controller runs aside of the users' session, and hence sign_in_and_redirect won't work. So I had the idea of redirecting to another page like '/auth?uid=xxx' but this sounds both wrong, insecure and not stable.
Any help or hints are appreciated!
A bit of a long shot but try turning off protect_from_forgery - I had some issues with sessions disappearing and it turned out to be the issue discussed here https://github.com/intridea/omniauth/issues/203
In my config/initializers/omniauth.rb, I had to add the following:
OmniAuth.config.full_host = "http://yourdomain.com" # Or have an environment specific URL.
You are using devise but you are not using it's own helpers. For instance, you've defined your own current_user method. To be honest, I can't see any obvious mistakes you've made, so it's just a desperate tip.
what kind of a session store do you use locally and what in production?
When you say "facebook user is logged out", this user is still logged in to facebook, but lost his session at yourapp.com ?
Are you sure that user.id is never nil or that you anywhere else than in .destroy set session[:user_id]= some_nil_variable ?