I'm trying to set up Omniauth login for Twitter/FB. I created my own authentication system that validates for password and email upon creation of a user. However, I do not want to validate for password or email when my users log in through Twitter/Fb.
I created a user attribute called omniauth_login. I am using it as a boolean to test whether validation is required or not in my should_validate_password? method.
User.rb
attr_accessor :password, :updating_password, :omniauth_login
validates_presence_of :password, :if => :should_validate_password?
validates_confirmation_of :password, :if => :should_validate_password?
def should_validate_password?
(updating_password || new_record?) && !(self.omniauth_login == 'true')
end
def self.create_with_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["name"]
end
end
Here is my controller used to create the user:
sessions_controller.rb
def omniauth_create
auth = request.env["omniauth.auth"]
user = User.new
user.omniauth_login = 'true'
user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) ||
User.create_with_omniauth(auth)
session[:user_id] = user.id
redirect_to user
end
When I try to create a new user by logging in via twitter, I still get the validation error:
Validation failed: Password can't be blank, Email can't be blank, Email is not valid.
How do I skip validation of password and email if my object is being created in the create_with_omniauth method?
Thanks.
I think the problem here is that you have two separate instances of User, one that has omniauth_login set to 'true', and another that does not. The first is gotten from
user = User.new
user.omniauth_login = 'true'
The second is
User.create_with_omniauth(auth)
. The second instance created here doesn't have omniauth_login set to 'true', so it still runs the validations. Try this instead
user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) ||
User.create_with_omniauth(auth)
and in create_with_omniauth, add user.omniauth_login = 'true'
.
Related
I'm trying to Redirect a user after successful Facebook SIGN UP (not sign in).
I want to redirect to /getstarted/welcome after a user Registers for the first time.
My omniauth callback is :
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user ||=
User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
if #user.persisted?
# This will throw if #user is not activated
sign_in_and_redirect #user, event: :authentication
if is_navigational_format?
set_flash_message(:notice, :success, kind: "Facebook")
end
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
For devise i use
def after_sign_up_path_for(source)
'/getstarted/welcome'
end
My User Model:
Facebook Settings
def self.find_for_facebook_oauth(auth, signed_in_resource = nil)
user = User.where(provider: auth.provider, uid: auth.uid).first
if user.present?
user
else
user = User.create(first_name:auth.extra.raw_info.first_name,
last_name:auth.extra.raw_info.last_name,
facebook_link:auth.extra.raw_info.link,
user_name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20])
end
end
Can someone help me set this up ?
I solved it by adding to my User model
attr_accessor `just_signed_up`
and setting it in User.find_for_facebook_oauth in this part of the block where I create a new user (first_or_create block).
EDIT: more explanation
So in Ruby (not Rails) there is a class method/macro called attr_accessor (actually theres also attr_reader and attr_writer, attr_accessor is a shorthand for calling the other two)
If you, in your User model write
class User
attr_accessor :some_attribute
then you're able to perform
u = User.first
u.some_attribute = 'asdf'
u.some_attribute # => 'asdf'
but this attribute is not going to be saved to DB, so it may be used as a temporary storage of some value in Rails model.
Another thing to know is that there are only two "values" that are false in Ruby, those are false and nil.
Using those two tricks you may create a new user and temporarily set this flag on the object
u = User.create
u.just_signed_up = true
u.just_signed_up # => true
u.reload! # fetches record from DB
u.just_signed_up # => nil
and since nil is false, this check will fail for every user except for ones you just created!
I was following this tutorial on Omniauth: http://railscasts.com/episodes/235-omniauth-part-1?view=asciicast
I keep getting this error:
no such column: authentication.provider:
Now the main thing I want to know is why "provider" isn't being accepted. It exists in the class... the authentications database exists... so why is it saying it isn't there?
Here's my authentications controller:
class AuthenticationsController < InheritedResources::Base
def index
#authentications = current_user.authentications if current_user
end
def create
#user = User.where(authentication: auth).first_or_create(:provider => auth['provider'], :uid => auth['uid'])
self.current_user = #user
# auth = request.env["omniauth.auth"] current_user.authentications.create(:provider => auth['provider'], :uid => auth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
end
def auth
request.env['omniauth.auth']
end
def destroy
#authentication = current_user.authentications.find(params[:id])
#authentication.destroy
flash[:notice] = "Successfully destroyed authentication."
redirect_to authentications_url
end
end
I can assure you I have a model called authentication and that this model has a provider and uid field. I've also tried where(authentications: auth) and where(auth: auth)
each with no luck.
Any ideas would be appreciated.
UPDATE
authentication.rb (model)
class Authentication < ActiveRecord::Base
attr_accessible :create, :destroy, :index, :provider, :uid, :user_id
belongs_to :user
end
UPDATE 2
I'm basically attempting to adapt this tutorial to rails 3.2.
The original line from the tutorial is commented out above.
UPDATE 3
Here is the entire first line of error:
SQLite3::SQLException: no such column: authentication.provider: SELECT "users".* FROM "users" WHERE "authentication"."provider" = 'facebook' AND "authentication"."uid" = '2222222' AND "authentication"."info" = '--- !ruby/hash:OmniAuth::AuthHash::InfoHash
Hate to be a burden... but the clock's really ticking, my ass is on the line, and I'm about to go completely insane trying to figure this out. If you can tell me just why provider isn't being accepted I'm sure I can figure out the rest.
your create action has not sense
User.where(authentication: auth) converts to SELECT * FROM users WHERE authentication = a_hash
You shoul do something like
auth1 = Authentication.where(provider: auth['provider'], uid: auth['uid']).first
if !auth1.nil?
current_user = auth.user
else
user = User.new
user.authentications.build(provider: auth['provider'], uid: auth['uid'])
user.save!
current_user = user
end
Since you are just adding a record in the authentications table, I am unable to understand why you are reassigning this.current_user. Also is current_user a helper method or a member, if it's a member where is it declared?
Don't you just want to create an authentication for the current user as such?:
def create
current_user.authentications.first_or_create(:provider => auth['provider'], :uid => auth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
end
This finds the first authentication record by provider and uid, if not found then creates that authentication record.
Also by that error, I hope you have figured out the answer to this question:
Now the main thing I want to know is why "provider" isn't being
accepted. It exists in the class... the authentications database
exists... so why is it saying it isn't there?
It is because you are calling first_or_create() on User object, not Authentication.
I also faced this issue recently. At first I thought I had forgotten to add a provider column to users table, but that wasn't it.
This is how I eventually solved it:
def self.from_omniauth(auth)
where(provider: auth["provider"], uid: auth["uid"]).first_or_create do |user|
user.email = auth["info"]["email"]
user.password = Devise.friendly_token[0, 20]
user.logo = auth["info"]["image"]
# if you use confirmable, since facebook validates emails
# skip confirmation emails
user.skip_confirmation!
end
end
auth is a hash like the one below, so instead of auth.provider, I used auth["provider"] etc:
omniauth.auth: {"provider"=>"facebook", "uid"=>"11111111111111", "info"=>{"email"=>"some#email.com", "image"=>"http://graph.facebook.com/v2.6/11111111111111/picture"}, "credentials"=>{"token"=>"sometoken", "expires_at"=>1506680013, "expires"=>true}, "extra"=>{"raw_info"=>{"email"=>"some#email.com", "id"=>"11111111111111"}}}
I'm trying to implement a method to allow password to be changed from another service outside devise anyhow.
# Profile password change
def change_password(oldpass, newpass)
pepper = nil
cost = 10
# Encrypt plain text passwords
encrypt_old = ::BCrypt::Password.create("#{oldpass}#{pepper}", :cost => cost).to_s
# Validate old
if self.encrypted_password == encrypt_old
encrypt_new = ::BCrypt::Password.create("#{newpass}#{pepper}", :cost => cost).to_s
self.encrypted_password = encrypt_new
self.save
else
Logger.new("Wrong old password!")
end
end
It seems i got the password encryption wrong oldpass contains a plain text of old password i need to hash it see if it matches the current password then allow new password to be stored. However all that i'm getting is wrong password.
Reworked:
def change_password(oldpass, newpass)
if valid_password?(oldpass)
password = newpass
save
return true
else
return false
end
end
You don't need to encrypt the password yourself, if you are in the application or in Rails console.
Just update the user following way and Devise will take care of it itself.
user.password = new_password
user.save
Devise will then encrypt the password and store it. You just need to ensure that user.password_confirmation is nil. If password_confirmation is anything else, it will be matched against password.
EDIT
You can check the existing password with: user.valid_password?(old_password)
With Devise, you don't need to handle using bcrypt yourself. By default, it handles this and the change password method. You can look at source here or just look in config/intitalizers/devise.rb in your Rails app.
Also, if you use the #update_with_password method provided by Devise, then you can pass it a hash like this:
{ :current_password => 'pass', :password => 'pass_new', :password_confirmation => 'pass_new' }
Or you can omit :password_confirmation if you don't want the user to have to provide a confirmation.
EDIT: I used the wrong field; it should have been 'current_password' instead of 'old_password'.
Here's the method in question in the source for Devise:
# Update record attributes when :current_password matches, otherwise returns
# error on :current_password. It also automatically rejects :password and
# :password_confirmation if they are blank.
def update_with_password(params, *options)
current_password = params.delete(:current_password)
if params[:password].blank?
params.delete(:password)
params.delete(:password_confirmation) if params[:password_confirmation].blank?
end
result = if valid_password?(current_password)
update_attributes(params, *options)
else
self.assign_attributes(params, *options)
self.valid?
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
false
end
clean_up_passwords
result
end
View on GitHub
I'm trying to pull out profile information from my user model. Currently I'm using Omniauth (twitter & facebook) to create users and just writing all the information to the user table... this works great, however I was told that proper Rails convention is to separate the user & profile.
After a couple of days of trial and error, I'm having trouble getting the has_one relationship working while creating a profile with the users info from the Omniauth hash.
I've tried to user a before_filter to automagically create a profile before the user is create, which properly creates the user_id foreign key in the profiles table, however I can't seem to get any of the omniauth hash data to write.
my sessions controller:
def create
auth = request.env["omniauth.auth"]
if user = User.find_by_provider_and_uid(auth["provider"], auth["uid"])
redirect_to root_url, :notice => 'Signed In'
else
user = User.create_with_omniauth(auth)
redirect_to profile_path(user), :notice => 'Please verify your profile'
end
in my user model:
before_create :create_profile
def self.create_profile(auth)
create! do |profile|
profile.user_id = session[:user_id]
profile.name = auth["info"]["name"]
profile.email = auth["info"]["email"]
profile.nickname = auth["info"]["nickname"]
profile.location = auth["info"]["location"]
profile.image = auth["info"]["image"]
end
end
...
The other option I tried was to write the profile data with the profile model... which works for the Omniauth hash data, but wont write the correct user_id (so no foreign key)
profile.rb
def self.create_profile_with_omniauth(auth)
create! do |profile|
profile.user_id = session[:user_id]
profile.name = auth["info"]["name"]
profile.email = auth["info"]["email"]
profile.nickname = auth["info"]["nickname"]
profile.location = auth["info"]["location"]
profile.image = auth["info"]["image"]
end
end
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.