How can I automatically assign extra params to my User with omniauth? - ruby-on-rails-3

I have omniauth and devise successfully set-up for LinkedIn and Twitter.
When authenticating with LinkedIn, I am able to get the users name and email passed into the sign-up form automatically - but then it still gives an error and ask's the user to 'check the errors' which is bad UX given there technically are no errors.
How can I get them automatically assigned to the user during the create process, but only when they are present? Here's the relevant code:
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
flash.notice = "Please confirm your name and email"
redirect_to sign_up_path
end
end
alias_method :linkedin, :all
alias_method :twitter, :all
end
From the User model:
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.email = auth.info.email
end
end

Related

Using flash to notify user of previous registration

I'm trying to use my Users controller to notify the user when their email has already been used in a registration, but even when the email already exists, I still get the error "Plase validate your input and try again," rather than "You've already registered! Thanks for being enthusiastic!" Is using the controller not the create way of achieving this behavior?
In the rails console (assuming "foo#bar.com" is in the database"), when I use user = User.new(name:"Example", email:"foo#bar.com") then User.find_by_email(user.email) it does return the proper User entry, so I'm not sure if I'm on the right track and just executing it incorrectly or what. Any ideas?
users_controller.rb:
class UsersController < ApplicationController
def new
#user = User.new(params[:user])
end
def create
#user = User.new(params[:user])
if #user.save
flash[:success] = "Thanks for supporting cofind! We'll be in touch!"
redirect_to root_path
UserMailer.welcome_email(#user).deliver
else
if #user.email == User.find_by_email(#user.email)
flash[:error] = "You've already registered! Thanks for being enthusiastic!"
redirect_to root_path
else
flash[:error] = "Plase validate your input and try again."
redirect_to signup_path
end
end
end
end
user.rb:
class User < ActiveRecord::Base
attr_accessible :email, :name
before_save { |user| user.email = email.downcase }
validates :name, presence: true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
end
this line
if #user.email == User.find_by_email(#user.email)
checks the user's email (a string) against a user record (an ActiveRecord object) which will always be false. You should change that to
if User.where(email: #user.email).exists?

Omniauth NoMethodError in SessionsController

Hi I'm trying to run Railscast's #241 - using Omniauth for twitter - eventually with facebook.The app I'm trying to make will require Facebook's login, but since there is a video on how to use twitter to login, might as well follow it first but I'm bumping into problems.
I keep getting this error after I signed into Twitter. "NoMethodError in SessionController#create" - undefined method `[]' for nil:NilClass. Here is the related code.
class SessionsController < ApplicationController
def create
auth = request.env["omniauth.auth"]
user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth)
session[:user_id] = user.id
redirect_to root_url, :notice => "Signed in!"
end
end
I have also set the website and callback link via dev.twitter.com as follows:
website: "http://127.0.0.1:3000"
Callback: "http://127.0.0.1:3000/auth/twitter/callback"
Omniauth.rb in config/initializers:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, 'CONSUMER_KEY', 'CONSUMER_SECRET'
end
(I've already provided the consumer key and secret)
Application.html.erb
<div id="user_nav">
<%= link_to "Sign in with Twitter", "/auth/twitter" %>
</div>
Added this line in routes.rb
match "/auth/:provider/callback" => "sessions#create"
User.rb
class User < ActiveRecord::Base
attr_accessible :name, :provider, :uid
def self.create_with_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["user_info"]["name"]
end
end
I don't know where I went wrong
Ok I found the problem. Took me a while.
Twitter changed their "user_info" element into "info"
So instead of
user.name = auth["user_info"]["name"]
it is
user.name = auth["info"]["name"]
Next thing to change was in sessions controller from
session[:user_id] = user.id
to
session[:user_id] = user.uid

How to allow a user to enter a password when deleting an authorization in devise/omniauth

I have a rais 3 app that uses devise and omniauth to allow users to register/login via their twitter account and/or with local login credentials. Everything works fine for registering and logging in. My problem occurs when a user chooses to destroy their twitter authorization without first establishing a local password. If a user destroys their authorizations, then I would like to route them to new_password_path so that they can choose a password for future log-ins.
Here is the controller code:
class AuthenticationsController < ApplicationController
before_filter :authenticate_user!, :except => [:create, :failure]
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication #existing user is logging-in with existing authentication service
flash[:notice] = "Signed in successfully."
set_home_location_cookies(authentication.user, authentication.user.home_lat, authentication.user.home_lng)
sign_in(:user, authentication.user)
redirect_to root_path
elsif current_user #existing user who is already logged-in is creating a new authentication service for future use
current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'], :token => omniauth['credentials']['token'])
current_user.update_posting_preferences(omniauth['provider'])
flash[:notice] = "Successfully linked to your #{omniauth['provider'].titleize} account."
redirect_to root_path
else #new user is creating a new authentication service and logging in
user = User.new
user.apply_omniauth(omniauth)
if user.save
flash[:notice] = "Signed in successfully."
sign_in(:user, user)
redirect_to root_path
else
session[:omniauth] = omniauth.except('extra')
session[:user_message] = {:success => false, :message => "userSaveError"}
redirect_to new_user_registration_url
end
end
end
def failure
flash[:alert] = "Could not authorize you from your social service."
redirect_to root_path
end
def destroy
#authentication = current_user.authentications.find(params[:id])
current_user.update_posting_preferences(#authentication.provider)
#authentication.destroy
flash[:notice] = "You have successfully destroyed your link to your #{#authentication.provider.titleize} account."
if current_user.authentications.empty? && current_user.encrypted_password.empty?
sign_out
flash[:alert] = "Alert: Your account does not currently have a password for account authorization. You are in danger of losing your account unless you create a new password by using this form."
redirect_to new_password_path(current_user) and return
else
redirect_back_or(root_path)
end
end
The code results in a "could not find valid mapping for nil" error triggered by my redirect_to new_password_path(current_user) and return command
I would greatly appreciate some help figuring out this problem.
Thanks!
OK. I'll admit it. I implemented the authentications controller from a tutorial without studying devise routing to learn what was going on behind the scenes. Last night I reviewed the docs and figured out my problem. What is funny is that the above routine did work on an older version of devise but does not work on devise 1.5.3.
In the destroy action I sign-out the current_user then I try to route to the new_password_path sending in "current_user" as a parameter. Not surprisingly, at that point "current_user" has been nulled out. So, I get the, "could not find a valid mapping for nil" error. Here is my easy fix:
def destroy
#authentication = current_user.authentications.find(params[:id])
user = current_user
current_user.update_posting_preferences(#authentication.provider)
#authentication.destroy
flash[:notice] = "You have successfully destroyed your link to your #{#authentication.provider.titleize} account."
if current_user.authentications.empty? && current_user.encrypted_password.empty?
sign_out
flash[:alert] = "Alert: Your account does not currently have a password for account authorization. You are in danger of losing your account unless you create a new password by using this form."
redirect_to new_password_path(user) and return
else
redirect_back_or(root_path)
end
end

Rails 3: updating user attributes when authentications are created

I followed Railscasts #235 and #236 to setup creating user authentications with omniauth.
http://railscasts.com/episodes/235-omniauth-part-1
http://railscasts.com/episodes/236-omniauth-part-2
I have a 2 boolean attributes on the user model called :facebok_share and :twitter_share that I want to set to true when a new authentication is created.
I have this working for me when I create a new user, but if an existing user adds an authentication I cannot get the boolean to update to true.
When apply_omniauth(omniauth) is called it sets self.facebook_share = true or self.twitter_share = true in my user model.
I've tried to add a new method called apply_share which changes the booleans depending on provider, and I'm trying to call current_user.apply_share(omniauth) but nothing is happening in the database.
What am I doing wrong? Thanks!
## authentications controller
class AuthenticationsController < ApplicationController
def index
#title = "Authentications"
#authentications = current_user.authentications if current_user
end
def create
# creates omniauth hash and looks for an previously established authentication
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
# if previous authentication found, sign in user
if authentication
flash[:notice] = "Signed in successfully"
sign_in_and_redirect(:user, authentication.user)
# for users already signed in (current_user), create a new authentication for the user
elsif current_user
current_user.apply_share(omniauth)
current_user.authentications.create(:provider => omniauth['provider'], :uid => omniauth['uid'], :token => (omniauth['credentials']['token'] rescue nil),
:secret => (omniauth['credentials']['secret'] rescue nil))
flash[:notice] = "authentications successful"
redirect_to authentications_url
# new user is created and authentications are built through apply_omniauth(omniauth)
else
user = User.new
user.apply_omniauth(omniauth)
if user.save
flash[:notice] = "Signed in successfully"
sign_in_and_redirect(:user, user)
# if validations fail to save user, redirects to new user registration page
# new twitter authentications redirect so user can enter their password
else
session[:omniauth] = omniauth
redirect_to new_user_registration_url
end
end
end
def destroy
#authentication = current_user.authentications.find(params[:id])
#authentication.destroy
flash[:notice] = "Successfully destroyed authentication."
redirect_to authentications_url
end
end
## user model
# set share booleans to true depending on 'provider' type
def apply_share(omniauth)
case omniauth['provider']
when 'facebook'
self.facebook_share = true
when 'twitter'
self.twitter_share = true
end
end
# from authentications controller, new user split into type of provider
def apply_omniauth(omniauth)
case omniauth['provider']
when 'facebook'
self.apply_facebook(omniauth)
when 'twitter'
self.apply_twitter(omniauth)
end
# builds authentication with provider, uid, token, and secret
authentications.build(hash_from_omniauth(omniauth))
end
protected
# sets new user attributes from facebook
def apply_facebook(omniauth)
self.name = omniauth['user_info']['name']
self.email = omniauth['user_info']['email'] if email.blank?
self.facebook_share = true
end
# sets new user attributes from twitter
def apply_twitter(omniauth)
if (extra = omniauth['extra']['user_hash'] rescue false)
# Example fetching extra data. Needs migration to User model:
# self.firstname = (extra['name'] rescue '')
self.name = (extra['name'] rescue '')
self.bio = (extra['description'] rescue '')
end
self.twitter_share = true
end
# set authentication attributes to those from 'omniauth' hash
def hash_from_omniauth(omniauth)
{
:provider => omniauth['provider'],
:uid => omniauth['uid'],
:token => (omniauth['credentials']['token'] rescue nil),
:secret => (omniauth['credentials']['secret'] rescue nil)
}
end
end
## new methid with :before add => :apply_share
def apply_share(authentication)
case authentication['provider']
when 'facebook'
self.facebook_share = true
when 'twitter'
self.twitter_share = true
end
self.save
end
I believe your never actually saving current_user. So your setting your attributes to true, and then redirecting. The association is stored in the authentication model, so Rails, trying to be helpful, doesn't update current_user, just the new instance of authentication
try:
current_user.apply_share(omniauth)
current_user.save
and see if that fixes it. Now if it does, I would strongly recommend using a callback instead. Take a look here:
http://guides.rubyonrails.org/association_basics.html
Section 4.5 about association callbacks. You can do a before_add callback on your has_many authentications assocation to remove that code from your controller as its getting pretty bloated as is.
class User < ActiveRecord::Base
has_many :authentications, :before_add => :apply_share
def apply_share(authentication)
#update attributes
#save model
end
end
You need to call #save on the User object after setting the *_share attributes.
Adding new items to a has_many collection automatically saves the collection item, but does not trigger a save operation on the parent (belongs_to).

updating user record when associating providers with omniauth

When I create a new account through google, the email gets stored in my user record. When I create a user through twitter, the email column is blank. I'd like to update that entry if a user associates their current twitter account with google.
in my User model:
def self.create_from_hash!(hash)
create! do |user|
user.name = hash['user_info']['name']
user.email = hash['user_info']['email']
end
end
In sessions controller:
def create
auth = request.env['rack.auth']
unless #auth = Authorization.find_from_hash(auth)
#auth = Authorization.create_from_hash(auth, current_user)
end
self.current_user = #auth.user
flash[:notice] = "Welcome, #{current_user.name}."
redirect_to '/'
end
and in the Authorization model:
def self.create_from_hash(hash, user = nil)
user ||= User.create_from_hash!(hash)
Authorization.create(:user => user, :uid => hash['uid'], :provider => hash['provider'])
end
How can I update that column when I am adding an authorization method?
I added the following line into my sessionscontroller create action, which seems to solve the issue:
if #user && (#user.email.blank? || #user.email.nil?)
#user.update_attribute(:email, request.env['rack.auth']['user_info']['email']) unless request.env['rack.auth']['user_info']['email'].nil? || #user.nil?
end