I have this code in my controller and model
SessionsController
def create
user = User.from_omniauth(env["omniauth.auth"])
user.skip_password_validation = true
unless user.present?
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
# Log the user in and redirect to the user's show page.
else
# Create an error message.
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
else
log_in user
redirect_to user
end
end
user model
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.email = auth.info.email
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
end
As i have two, different, login methods in my sessions controller I need to skip methods in my User model. I have looked in to a few things such as before filters, before actions and attribute accessors and search the web but cant seem to find out how to skip methods in the model so I can bypass those for each user variable I have assigned in the sessions controller? Ie, one for omniauth facebook and the other for just standard login.
If I understood the question properly, in case of plain auth there would be no env["omniauth.auth"] set, so one might go with:
def create
user = if env["omniauth.auth"]
User.from_omniauth(env["omniauth.auth"]).tap do |u|
u.skip_password_validation = true
end
else
User.find_by(email: params[:session][:email].downcase).tap do |u|
unless u && u.authenticate(params[:session][:password])
# Create an error message.
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
return
end
end
end
log_in user
redirect_to user
end
Related
I've setup a moreless simple social user authentification on top of devise using Google, Linkedin, Dropbox and Github.The Dropbox authentication does not work, instead it gives that error on the callback URL(http://localhost:3000/users/auth/dropbox/callback):
NoMethodError in Users::OmniauthCallbacksController#dropbox
undefined method `first' for nil:NilClass
Issue: User Model (line 8)
My Code:
Callbacks Controller:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
user = User.from_omniauth(env['omniauth.auth'], current_user)
if user.persisted?
sign_in user
flash[:notice] = t('devise.omniauth_callbacks.success', :kind => User::SOCIALS[params[:action].to_sym])
if user.sign_in_count == 1
redirect_to edit_user_registration_path
else
redirect_to root_path
end
else
session['devise.user_attributes'] = user.attributes
redirect_to new_user_registration_url
end
end
User::SOCIALS.each do |k, _|
alias_method k, :all
end
end
User Model:
# omniauth Gem
def self.from_omniauth(auth, current_user)
authorization = Authorization.where(:provider => auth.provider, :uid => auth.uid.to_s,
:token => auth.credentials.token,
:secret => auth.credentials.secret).first_or_initialize
authorization.profile_page = auth.info.urls.first.last unless authorization.persisted?
if authorization.user.blank?
user = current_user.nil? ? User.where('email = ?', auth['info']['email']).first : current_user
if user.blank?
user = User.new
user.skip_confirmation!
user.password = Devise.friendly_token[0, 20]
user.fetch_details(auth)
user.save
end
authorization.user = user
authorization.save
end
authorization.user
end
def fetch_details(auth)
self.email = auth.info.email
self.username = auth.info.name
self.avatar = URI.parse(auth.info.image)
end
I appreciate each help! Thanks in advance.
To answer your question directly:
The undefined method "first" for nil::NilClass is happening because you are attempting to call the method first on an empty, or nil object.
It's probably in your user model where you are attempting to find a User from a current_user.
if authorization.user.blank?
user = current_user.nil? ? User.where('email = ?', auth['info']['email']).first : current_user
#This will cause the error that you are describing if both the current_user is nil and there is no User whose email is auth['info']['email']
Now, There's a few things wrong with this. If they are attempting to log in to your application, then current_user at this stage should be unset.
You could try changing this to
user = User.where(email: auth['info']['email']).first_or_create
Which will create a new instance of User, if one does not exist with the email provided in the Authorization.
Then you can continue with
user.persisted?
which returns true for an existing user, and false for a new instance of User
I am using Hartl's Tutorial for Account Activation
user.rb
attr_accessor :activation_token
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
Once the update action shown below in my UsersController is complete and the user is redirected to the root_url
def update
#user = User.find(params[:id])
...
elsif !params[:user][:email].blank?
if #user.authenticate(params[:user][:current_password])
#user.update_attributes(email_user_params)
if #user.save
#user.create_activation_digest
#user.deactivated
#user.send_activation_email
log_out
flash[:info] = "Please check email dude"
redirect_to root_url
else
flash[:danger] = "Email Update Failed"
redirect_to edit_user_email_path(#user)
end
else
flash[:danger] = "Current Password Is Incorrect"
redirect_to edit_user_email_path(#user)
end
...
def edit
#user = User.find(params[:id])
end
then:
:activation_token = nil.
Is that correct?
I am asking because there are a bunch of topics on the subject of allowing the user to request a second validation email in a separate controller action and in all of those topics the discussion is stuck on routing issues, because in the email that is sent, the :activation_token is used as :id and the error message comes up :id -> nil
Edit:
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.activate
log_in user
flash[:success] = "Account activated!"
redirect_to user
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
end
end
end
user.rb
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
So it appears that since you are using a virtual attribute to store that token at some point in time that object is leaving scope and being reloaded or some such even that is cause you to loose context. The best bet is just to ad a column to your User table to persist it between contexts and just update it where appropriate.
I also met this problem. Even though I removed "redirect_to root_url", after the code runs into a different action (in the same controller), the attr_accessor still becomes nil.
Thus my conclusion is that the lifetime of an attr_accessor only exists within the same action.
I have two facebook buttons in my application. One on users/sign_up and other on /visitors/owner-faq. I want to save the request.referrer in order to know from which page the user has signed up. I had implemented the same for external signup(which uses a form) and it worked. Now I'm unable to implement the same for facebook omniauth.
Code:
#user.rb
def self.from_omniauth(auth)
email = auth.info.email
user = User.find_by_email(email) # first tries to find an existing user who signed up normal way with the same email to sign them in
if user && user.confirmed?
user.provider = auth.provider
user.uid = auth.uid
return user
end
where(provider: auth.provider, uid: auth.uid).first_or_create do |user| # then tries to find the user who authenticated through FB, and if
user.email = auth.info.email # not present, creates that user
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.social_photo_url = auth.info.image
user.skip_confirmation!
end
end
#users/omniauth_callbacks_controller
def facebook
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
Rails.logger.info(#user.errors.inspect)
redirect_to new_user_registration_url
end
end
#application_controller.rb
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 :store_referrer_url, :only => [:new]
private
def store_referrer_url
session[:referrer] = URI(request.referer).path
end
end
Attempt #1:
I managed to save the request.referrer like this in users/omniauth_callbacks_controller
def facebook
#user = User.from_omniauth(request.env["omniauth.auth"])
#user.referrer_url = session[:referrer] #here
#user.save!
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
Rails.logger.info(#user.errors.inspect)
redirect_to new_user_registration_url
end
end
But the problem here is the value of referrer_url is overwritten when the existing user logs in from another page. I don't want to get the referrer_url to be overwritten.
Attempt #2:
I tried to save the request.referrer in the from_omniauth(auth) method User model like this
def self.from_omniauth(auth)
email = auth.info.email
user = User.find_by_email(email) # first tries to find an existing user who signed up normal way with the same email to sign them in
if user && user.confirmed?
user.provider = auth.provider
user.uid = auth.uid
return user
end
where(provider: auth.provider, uid: auth.uid).first_or_create do |user| # then tries to find the user who authenticated through FB, and if
user.email = auth.info.email # not present, creates that user
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.social_photo_url = auth.info.image
user.referrer_url = session[:referrer]
user.skip_confirmation!
end
end
But it gave me this error
undefined local variable or method `session' for #<Class:0x0000000e1c46f8>
Any suggestions would be greatly helpful.
If you want the referrer_url property to stay the same after it's first set, you should use the OR Equal operator, that will only set the value if it's currently nil or false:
#user.referrer_url ||= session[:referrer]
If I understand correctly, your facebook callback is called both on sign up and login, and of course the value would be overwritten. OR Equals will prevent the value from being overwritten once it's set.
Regarding Attempt #2, the session hash is only available in the controller and the view, so you can't use it in the model.
Another possibility:
You are setting session[:referrer] in your store_referrer_url method which is called in a before_filter callback on the new method.
Most likely your login is also made with a new method (for example, with Devise it would be in SessionsController), so the callback is called again and the value is overwritten (all Devise controllers inherit from your ApplicationController). In this case, you could take out the before_filter callback out of the ApplicationController to the controller where you are handling signing up (would be RegistrationsController with Devise).
Creating a Rails app that interacts heavily with Facebook. I want to users to be able to look at profiles of other users and see information on those users that has been pulled in from Facebook.
I'm using the omniauth-facebook and koala gems. I followed Ryan Bates' Railscasts on Facebook authentication and Facebook Graph API to get where I'm at.
I would prefer not to have to store a user's Facebook info in my database, but that seems to be the only way of doing it, because otherwise, when I try to view the profile page of another user who may have logged out, I get: "Koala::Facebook::AuthenticationError in Profiles#show"
type: OAuthException, code: 190, error_subcode: 467, message: Error validating access token: The session is invalid because the user logged out. [HTTP 400].
Is there a way to bring in this data from Facebook, even if that user has logged out?
Here's the necessary code:
user.rb:
class User < ActiveRecord::Base
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.image = auth.info.image
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
def facebook
#facebook ||= Koala::Facebook::API.new(oauth_token)
block_given? ? yield(#facebook) : #facebook
rescue Koala::Facebook::APIError
logger.info e.to_s
nil
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to feed_path
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
rescue ActiveRecord::RecordNotFound
end
helper_method :current_user
end
profiles_controller.rb
class ProfilesController < ApplicationController
def show
#user = User.find(params[:id])
end
end
profiles_helper.rb:
module ProfilesHelper
def facebook_profile_info
#user.facebook.get_object("#{#user.uid}",fields:'gender, locale, bio, birthday,
picture, relationship_status')
end
end
Also, if you have suggestions on better ways to accomplish these tasks than the trajectory I'm headed in, I welcome your suggestions.
I am having a little trouble with Twitter authentication. I keep getting the email can't be blank, and redirect to sign up, but after inserting email and clicking sign up, I still get the same error. I tried making it unneeded, but I get the error saying that someone with "" email exists already.
Thanks.
Devise Routes.rb
def has_role?(role)
return true;
end
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.username = auth.info.nickname
end
end
def self.new_with_session(params, session)
if session["devise.user_attributes"]
new(session["devise.user_attributes"]) do |user|
user.attributes = params
user.valid?
end
else
super
end
end
def password_required?
super && provider.blank?
end
def update_with_password(params, *options)
if encrypted_password.blank?
update_attributes(params, *options)
else
super
end
end
end
Call Back Controller
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash.notice = "Signed in!"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
alias_method :twitter, :all
end
You can define username to be your "authentication key" instead of email by
uncommenting this line
config.authentication_keys = [ :email ]
in config/initializers/devise.rb
and changing it to
config.authentication_keys = [ :username ]
i also had the same problem.
this error arises due the fact that in devise User model Email field is set to NotNull.
Solutions:-
1.Set email field in devise to allow null values.
2.so i had saved the email from twitter in my devise email field this is the code for twitter
def self.find_for_twitter_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
temp = Tempfile.new([auth["uid"], ".jpg"])
temp.binmode
temp.write(open(auth["info"]["image"]).read)
user = User.create(name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20],
image:temp
)
user.build_profile(name:user.name,image:temp)
user.profile.save
end
user
end