Rails, Omniauth for Slack: How to access Auth Hash Information - ruby-on-rails

I am allowing Users to Sign Up to my Service using their Slack Account.
I integrated the Sign In with Slack via the omniauth-slack gem.
Now I've got problems accessing the email and other information.
This is how I am currently doing it:
def self.find_for_slack_oauth(auth)
user = UserProvider.where(:provider => auth.provider, :uid => auth.uid).first
unless user.nil?
user.user
else
registered_user = User.where(:email => auth.info.email).first
unless registered_user.nil?
user_provider = UserProvider.new
user_provider.provider = auth.provider
user_provider.uid = auth.uid
user_provider.user_id = registered_user.id
user_provider.save!
registered_user.name = auth.info.name if registered_user.name == nil
registered_user.avatar = auth.info.image if registered_user.avatar == nil
registered_user.slack_id = auth.extra.raw_info.user_identity if registered_user.slack_id == nil
registered_user.slack_team_id = auth.extra.raw_info.team_identity if registered_user.slack_team_id == nil
registered_user.slack_team_name = auth.info.team_name if registered_user.slack_team_name == nil
registered_user.skip_confirmation!
registered_user.save!
registered_user
else
user = User.new
user.name = auth.info.name
user.email = auth.info.email
user.avatar = auth.info.image
user.slack_id = auth.extra.raw_info.user_identity
user.slack_team_id = auth.extra.raw_info.team_identity
user.slack_team_name = auth.info.team_name
user.password = Devise.friendly_token[0,20]
user.skip_confirmation!
user.save!
user_provider = UserProvider.new
user_provider.provider = auth.provider
user_provider.uid = auth.uid
user_provider.user_id = user.id
user_provider.save!
user
end
end
end
But I get no information out of it. Devise complaints that I cannot create a User without an email which is not blank.
What is the auth hash, which contains the information?
EDIT:
I don't get any values via the Auth Hash.
Here is my initialization (devise.rb):
config.omniauth :slack, Settings.slack.id, Settings.slack.secret, scope: 'identity.basic,identity.email,identity.team,identity.avatar'
Why do I get nothing?

I noticed that I was just using the wrong scope.
Instead of:
config.omniauth :slack, Settings.slack.id, Settings.slack.secret, scope: 'identity.basic,identity.email,identity.team,identity.avatar'
I should use:
config.omniauth :slack, Settings.slack.id, Settings.slack.secret, scope: 'team:read,users:read,identify'
I hope this helps someone!
The Slack documentation isn't really helpful on this.

Related

Refactor the code to reduce Assignment Branch Condition size

I am developing a Rails web application. But when I run rubocop to check the code. It said that the ABC (Assignment Branch Condition) size of the method below is too high. While I'm a newbie in Ruby on Rails, can someone give me some advice to refactor this block of code? For more details, I am implementing the third party authentication which allows user to sign in by facebook or google, etc.
Thank you
def self.from_omniauth auth, current_user
identity = Identity.find_by(provider: auth.provider, uid: auth.id)
.first_or_initialize
if identity.user.blank?
user = current_user || User.find_by("email = ?",
auth["info"]["email"])
if user.blank?
user = User.new
user.password = Devise.friendly_token[0, 10]
user.name = auth.info.name
user.email = auth.info.email
user.picture = auth.info.image
return user.save(validate: false) if auth.provider == "twitter"
user.save
end
identity.user_id = user.id
identity.save
end
identity.user
end
def self.from_omniauth auth, current_user
identity = Identity.find_by(provider: auth.provider, uid: auth.id)
.first_or_initialize
if identity.user.blank?
user = current_user || User.find_by("email = ?",
auth["info"]["email"])
create_user(auth) if user.blank?
identity.user_id = user.id
identity.save
end
identity.user
end
def self.create_user(auth)
user = User.new
user.password = Devise.friendly_token[0, 10]
user.name = auth.info.name
user.email = auth.info.email
user.picture = auth.info.image
return user.save(validate: false) if auth.provider == "twitter"
user.save
end
Is something you can try. But if the complexity is actually needed you can set a comment to ignore that cop # rubocop:disable ABC (Assignment Branch Condition), or whatever the actual name of the cop is. Also you can configure the ABC size if you feel the size set is too low
I don't get any error like you are saying, so probably you should try # rubocop:disable ABC
when I saved this it added bracket in parameters
def self.from_omniauth(auth, current_user)
identity = Identity.find_by(provider: auth.provider, uid: auth.id)
.first_or_initialize
if identity.user.blank?
user = current_user || User.find_by("email = ?",
auth["info"]["email"])
if user.blank?
user = User.new
user.password = Devise.friendly_token[0, 10]
user.name = auth.info.name
user.email = auth.info.email
user.picture = auth.info.image
return user.save(validate: false) if auth.provider == "twitter"
user.save
end
identity.user_id = user.id
identity.save
end
identity.user
end

Facebook migration from api v2.3 to v2.4 - Rails omniauth

I have some old app in Rails 4.0.0. Until 8th of Jul 2017 facebook registering works ok, but now v2.3 is deprecated and users can't register on my page.
Here is user.rb code:
def self.from_omniauth(auth)
oauth = Koala::Facebook::OAuth.new('xxx', 'xxxx')
new_access_info = oauth.exchange_access_token_info auth.credentials.token
new_access_token = new_access_info["access_token"]
new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds
user = where(auth.slice(:provider, :uid)).first
if !user.present?
user = where(email: auth.info.email).first
if user.present?
user.provider = auth.provider
user.uid = auth.uid
user.oauth_token = new_access_token
user.oauth_expires_at = new_access_expires_at
user.save!
else
user = User.new
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.profile.gender = 1
user.confirmed_at = Time.now
user.first_name = auth.info.first_name
user.fb_image_url = auth.info.image
user.oauth_token = new_access_token
user.oauth_expires_at = new_access_expires_at
#str = user.fb_image_url.to_s
#index = #str.index('type')
#sliced = #str.slice(0, #index)
#slices = #sliced + 'width=800&height=800'
user.fb_image_url = #slices
if auth.info.gender == 'female'
user.fb_gender = 1
elsif auth.fb_gender == 'male'
user.fb_gender = 2
end
end
end
user
end
And here is /initializers/devise.rb :
require "omniauth-facebook"
config.omniauth :facebook, 'xxxxxxx', 'xxxxxxxxx',
scope: 'email,public_profile',
info_fields: 'email',
client_options: {
site: 'https://graph.facebook.com/v2.4', authorize_url: "https://www.facebook.com/v2.4/dialog/oauth"},
token_params: { parse: :json }
Gemfile - gem 'omniauth-facebook', '1.4.0'
What I need to change to make it work ? Right now after click "register by facebook" user is redirected to facebook, he login, accept permission and after redirect to my site - he is redirect to /users/sign_in - not like earlier to /users/sign_up to fill few more fields.
I stop making apps in Rails so do not know how to make it. Thanks for help
EDIT:
Ok, now it works but facebook do not return email so I need to add email field after redirect to my site. Why email is blank ?
Have you tried adding this in devise.rb:
config.omniauth :facebook, "KEY", "SECRET", scope: 'email', info_fields: 'email, name'
Also check https://github.com/mkdynamic/omniauth-facebook for more info.

How to find user by email and if not, redirect him to register page

I use Omniauth and Omniauth-facebook gems and I want to click on button, if user is found, then sign him in. If not, redirect him to register page.
So far I use just classic login/register:
user.rb:
def self.from_omniauth(auth, role)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
if user
return user
else
registered_user = User.where(:email => auth.info.email).first
if registered_user
registered_user.provider = auth.provider
return registered_user
else
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
if (role == "1")
user.add_role :sportsman
elsif (role == "2")
user.add_role :donor
end
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
end
end
end
end
omniauth_callbacks_controller.rb:
def facebook
role = cookies[:role]
# signin = cookies[:signin]
user = User.from_omniauth(request.env["omniauth.auth"], role)
if user.persisted?
flash.notice = "Signed by Facebooku"
sign_in_and_redirect user
else
flash.notice = "Error, try again."
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
This code works, but if user is not registered, it will register him. But I sign roles when user register himself.
Thanks for help.
Because you are already saving the user attributes into the session with
session["devise.user_attributes"] = user.attributes
Then the following should already work:
def self.from_omniauth(auth, role)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
if user
return user
else
# NOTE: because you're searching "email" and not both "email" + "provider", then I assume
# that you want users to have only one shared account between all providers (i.e. if they also sign-in in Google with the exact email as his email in Facebook)
registered_user = User.where(:email => auth.info.email).first
if registered_user
# NOTE: this line doesn't do anything because registered_user is not being "saved"
registered_user.provider = auth.provider
return registered_user
else
# build a new User object (don't save yet!)
return User.new.tap do |u|
u.provider = auth.provider
u.email = auth.info.email
u.uid = uid: auth.uid
u.password = Devise.friendly_token[0,20]
# because I assume you're using "rolify" gem, don't add the roles yet here, because I don't think you can store the "roles" into the session
# add the roles logic in whatever controller you have for user registration
end
end
end
end
Then override new_with_session in your user model. new_with_session is automatically called by Devise in registrations#new. We need to set the
user attributes that we previously stored in session inside omniauth_callbacks#facebook
class User < ApplicationRecord
def self.new_with_session(params, session)
super.tap do |user|
if user_attributes = session['devise.user_attributes']
user.assign(user_attributes)
end
end
end
end
So I used simple params to get where the user come from:
<script type="text/javascript">
document.cookie = "login=0"
</script>
This code is in views/devise/registrations/new.html.erb (and in views/devise/sessions/new.html.erb) and it is a JavaScript. It tells me if user went from login page (sessions folder - login=1) or from registration page (registrations folder - login=0). Then I use this code to determinate if user is from login page AND if he is not registered yet. If both conditions are true, then he is redirected to register page. It is so simple that I am embarrassed...
def facebook
hash = request.env["omniauth.auth"]
info = hash.info
email = info["email"]
user = User.find_by_email(email)
login = cookies[:login]
if (user == nil && login == "1")
redirect_to new_user_registration_path
else
role = cookies[:role]
user = User.from_omniauth(request.env["omniauth.auth"], role)
if user.persisted?
flash.notice = "Logged in by Facebook"
sign_in_and_redirect user
else
flash.notice = "Error, try again."
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
end

NoMethodError undefined method `persisted?' for nil:NilClass when signing in with Facebook thru Omniauth/Devise?

I am putting the finishing touches on a Rails app, and everything was working fine just yesterday, (most of it still is). But now, when I try to sign in with Facebook I get the error NoMethodError undefined method persisted? for nil:NilClass I haven't changed any code in the omniauth_callbacks_controller, or in the User Model, so I do not understand why this is happening all of the sudden.
Here's the code in omniauth_callbacks_controller
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
#user = User.from_omniauth(env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
[:twitter, :facebook].each do |provider|
provides_callback_for provider
end
And in User.rb
def self.from_omniauth(auth)
identity = Identity.where(provider: auth.provider, uid: auth.uid).first_or_create do |identity|
if identity.user == nil
user = User.new
user.email = auth.info.email || "#{auth.uid}##{auth.provider}.generated"
user.password = Devise.friendly_token[0,20]
user.provider = auth.provider
if auth.provider == "facebook"
user.name = auth.info.name
user.username = "FacebookUser" + (1 + rand(1000)).to_s
elsif auth.provider == "twitter"
user.name = auth.info.name
user.username = auth.info.nickname
end
end
identity.user = user
end
identity.access_token = auth['credentials']['token']
identity.refresh_token = auth['credentials']['refresh_token']
identity.expires_at = auth['credentials']['expires_at']
identity.timezone = auth['info']['timezone']
identity.save
identity.user
end
I cannot figure out why it WAS working yesterday, but now it's not. Nothing has changed! I'm still accessing it from the same URL, I haven't messed with the API settings on Facebook, or in devise.rb.
the problem is that identity.user is nill. It could happen, because user not valid(not saved in db), in user model did not pass validations.
I think problem can be in user.email = auth.info.email || "#{auth.uid}##{auth.provider}.generated" - wrong email type. You can add something like puts user.errors in block wehe you created user.
Also i don't understand why you need a model - identity. Much easier add field to user model
Here is my code(it's work):
def self.from_omniauth(auth)
email = auth.info.email
user = User.find_by(email: email) if email
user ||= User.create do |user|
user.provider= auth.provider
user.uid= auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.name = auth.info.name
user.email ||= "#{auth.uid}_#{auth.uid}#email.com"
end
end

connect with facebook for existing users

I am using omniauth-facebook gem with devise to authenticate with Facebook in my rails application in my user model
def self.from_omniauth(auth)
# immediately get 60 day auth token
oauth = Koala::Facebook::OAuth.new("App Key", "App secrets" )
new_access_info = oauth.exchange_access_token_info auth.credentials.token
new_access_token = new_access_info["access_token"]
new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds
begin
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.username = auth.info.first_name
user.lastname =auth.info.last_name
user.email =auth.info.email
user.authentication_token = new_access_token
user.oauth_expires_at = new_access_expires_at
user.save!
end
rescue ActiveRecord::RecordInvalid
end
end
#interact with facebook
def facebook
#facebook ||= Koala::Facebook::API.new(authentication_token)
block_given? ? yield(#facebook) : #facebook
rescue Koala::Facebook::APIError => e
logger.info e.to_s
nil # or consider a custom null object
end
def self.new_with_session(params, session)
if session["devise.user_attributes"]
new(session["devise.user_attributes"], :without_protection=> true) do |user|
user.attributes = params
user.valid?
end
else
super
end
end
and on my omniauth_callbacks controller I have this method:
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 :facebook, :all
These methods are used to authenticate user from scratch via Facebook. I need a way to connect existing users with their Facebook accounts not new ones if they registered via normal devise registration method
When an existing user is trying to sign in via Facebook the following error occurs:
A `NoMethodError` occurred in `omniauth_callbacks#facebook`:
undefined method `persisted?' for nil:NilClass
app/controllers/omniauth_callbacks_controller.rb:4:in `all'
You can find user by email first and just update provider and uid fields in case he is already exists.
So your User.from_omniauth may looks like that:
def self.from_omniauth(auth)
# immediately get 60 day auth token
oauth = Koala::Facebook::OAuth.new("", "" )
new_access_info = oauth.exchange_access_token_info auth.credentials.token
new_access_token = new_access_info["access_token"]
new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds
user = where(provider: auth.provider, uid: auth.uid).first
unless user
# in that case you will find existing user doesn't connected to facebook
# or create new one by email
user = where(email: auth.info.email).first_or_initialize
user.provider = auth.provider # and connect him to facebook here
user.uid = auth.uid # and here
user.username = auth.info.first_name
user.lastname = auth.info.last_name
user.authentication_token = new_access_token
user.oauth_expires_at = new_access_expires_at
# other user's data you want to update
user.save!
end
user
end
upd:
In case you faced password validation error you can override User#password_required? method to skip validation for user's signed in via Facebook.
That behavior described in following episode of RailsCasts
Just for add an updated version of the answer for rails 4 and devise 3.4.x
Like this is how an updated omniauth would look like
def self.from_omniauth(auth)
if !where(email: auth.info.email).empty?
user = where(email: auth.info.email).first
user.provider = auth.provider # and connect him to facebook here
user.uid = auth.uid # and here
user.save!
user
else
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.first_name = auth.info.name # assuming the user model has a name
user.avatar = process_uri(auth.info.image) # assuming the user model has an image
end
end
end
Just look for user by email, if there is one, just add the provider and uid, if there is not one, just create it as suggested in the documentation
You can have your from_omniauth be something like this:
def self.from_omniauth(auth)
where(auth.info.slice(:email)).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.username = auth.info.name
user.description = auth.extra.raw_info.bio
end
end
In this way, if there's an existing user with email same as the facebook account, then the first_or_create will return it and then the user can sign in (it won't be update).

Resources