overriding Devise update action inside omniauth_callbacks_controller - ruby-on-rails

References:
Rails 5.0.1
omniauth 1.8.1
omniauth-facebook 5.0.0
I have users logged in with email + password and others with facebook account.
The main issue is to make possible to facebook's users edit their own account without needing to use the password, because they just don't have. The solution in this post so I can add this class:
def update
if current_user.provider == "facebook"
params.delete("current_password")
resource.update_without_password(user_params)
else
resource.update_with_password(user_params)
end
redirect_to root_path
end
So I have this:
routes.br
devise_for :users, controllers: { omniauth_callbacks: "omniauth_callbacks" }
and inside the omniauth_callbacks_controller.rb I add this update function:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def update
if current_user.provider == "facebook"
params.delete("current_password")
resource.update_without_password(user_params)
else
resource.update_with_password(user_params)
end
redirect_to root_path
end
def facebook
user = User.find_for_facebook_oauth(request.env['omniauth.auth'])
if user.persisted?
sign_in user
redirect_to root_path
set_flash_message(:notice, :success, kind: 'Facebook') if is_navigational_format?
else
session['devise.facebook_data'] = request.env['omniauth.auth']
redirect_to new_user_registration_url
end
end
def user_params
params.require(:user).permit(:first_name, :last_name, :phone, :picture, :facebook_picture_url)
end
end
The problem is that the devise doesn't read this function. So if we add a byebug inside it will not stop there.
The only way to override this update function is to add this configurations below.
routes.br
devise_for :users, controllers: { registrations: 'registrations' }
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def update
if current_user.provider == "facebook"
params.delete("current_password")
resource.update_without_password(user_params)
else
resource.update_with_password(user_params)
end
redirect_to root_path
end
def facebook
user = User.find_for_facebook_oauth(request.env['omniauth.auth'])
if user.persisted?
sign_in user
redirect_to root_path
set_flash_message(:notice, :success, kind: 'Facebook') if is_navigational_format?
else
session['devise.facebook_data'] = request.env['omniauth.auth']
redirect_to new_user_registration_url
end
end
def user_params
params.require(:user).permit(:first_name, :last_name, :phone, :picture, :facebook_picture_url)
end
end
The only problem with this second approach is that if I try to login with facebook I receive this error message:
The action 'facebook' could not be found for
Devise::OmniauthCallbacksController
My devise configuration:
devise.rb
Devise.setup do |config|
config.omniauth :facebook, ENV["FB_ID"], ENV["FB_SECRET"],
scope: 'email',
info_fields: 'email, first_name, last_name',
image_size: 'large', # 50x50, guaranteed ratio
secure_image_url: true
...
end

Just in case somebody else has a similar problem. The thing was that the routes.rb needed to specify both of the controllers being overwritten, like:
devise_for :users, controllers: { registrations: 'registrations' , omniauth_callbacks: 'omniauth_callbacks' }

Related

Omniauth2 Google Record Invalid Error using Devise

I'm using Devise with Google Omniauth, but I keep getting User Record Invalid error:
I only have this validation in my User model:
validates :email, presence: true, uniqueness: true
User model
devise :omniauthable, omniauth_providers: [:google_oauth2]
def self.from_google(email:, full_name:, uid:, avatar_url:)
create_with(uid: uid, full_name: full_name, avatar_url: avatar_url).find_or_create_by!(email: email)
end
My routes
devise_for :users, :controllers => { omniauth_callbacks: 'users/omniauth_callbacks', :registrations => "registrations"}
omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
user = User.from_google(from_google_params)
if user.present?
sign_out_all_scopes
flash[:success] = t 'devise.omniauth_callbacks.success', kind: 'Google'
sign_in_and_redirect user, event: :authentication
else
flash[:alert] = t 'devise.omniauth_callbacks.failure', kind: 'Google', reason: "#{auth.info.email} is not authorized."
redirect_to new_user_session_path
end
end
protected
def after_omniauth_failure_path_for(_scope)
new_user_session_path
end
def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || root_path
end
private
def from_google_params
#from_google_params ||= {
uid: auth.uid,
email: auth.info.email,
full_name: auth.info.name,
avatar_url: auth.info.image
}
end
def auth
#auth ||= request.env['omniauth.auth']
end
end
Sessions controller
class Users::SessionsController < Devise::SessionsController
def after_sign_out_path_for(_resource_or_scope)
new_user_session_path
end
def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || root_path
end
end
Any clue on how to trace or fix this? It's driving me crazy! Thanks
Devise tries to display a relevant error message for the Spanish-translated app of your site. You are missing a translation for the Spanish error message.
Go to config/locales/es.yml and add in:
es:
activerecord:
errors:
messages:
record_invalid:
"Spanish error message comes here"

LoadError in OmniauthCallbacksController#passthru (with devise/stripe connect)

Trying to implement Stripe Connect, and am getting the following error when I click the "connect to stripe" button.
The action 'passthru' could not be found for OmniauthCallbacksController
users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def stripe_connect
#user = current_user
if #user.update_attributes({
provider: request.env["omniauth.auth"].provider,
uid: request.env["omniauth.auth"].uid,
access_code: request.env["omniauth.auth"].credentials.token,
publishable_key: request.env["omniauth.auth"].info.stripe_publishable_key
})
# anything else you need to do in response..
sign_in_and_redirect #user, :event => :authentication
set_flash_message(:notice, :success, :kind => "Stripe") if is_navigational_format?
else
session["devise.stripe_connect_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
models/user.rb
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :omniauth_providers => [:stripe_connect]
routes.rb
devise_for :users, controllers: { registrations: 'users/registrations', :omniauth_callbacks => "users/omniauth_callbacks" }
gemfile.rb
gem 'omniauth-stripe-connect'
initializers/stripe.rb
Rails.configuration.stripe = {
:publishable_key => ENV['PUBLISHABLE_KEY'],
:secret_key => ENV['SECRET_KEY']
}
Stripe.api_key = Rails.configuration.stripe[:secret_key]
initializers/devise.rb
config.omniauth :stripe_connect,
ENV['STRIPE_CONNECT_CLIENT_ID'],
ENV['STRIPE_SECRET_KEY'],
:scope => 'read_write',
:stripe_landing => 'register'
button link:
<%= link_to image_tag('blue-on-light.png'), user_stripe_connect_omniauth_authorize_path(:stripe_connect) %>
As I understand it with my noob Ruby mind, I need to define 'passthru'? how do I define it though? when I enter:
def passthru
end
the link doesn't work / the page reloads itself. Haven't been able to find a solution on here. What am I missing?
EDIT:
Changed my connect to stripe link to:
<%= link_to image_tag('blue-on-light.png'), "/users/auth/stripe_connect" %>
The link takes me to the connect to stripe page, but when I click the "connect to stripe" button, the page cant be found, and doesn't load or redirect.
Can you try changing
# app/controllers/omniauth_callbacks_controller.rb
class OmniauthCallbacksController < ApplicationController
def stripe_connect
....
to
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def stripe_connect
#user = User.find_for_stripe_connect(request.env['omniauth.auth'], current_user)
set_notice_and_redirect
end
private
def set_notice_and_redirect
if #user.persisted?
flash[:notice] = 'Successfully signed in'
set_flash_message(:notice, :success, :kind => "Stripe") if is_navigational_format?
else
session["devise.stripe_connect_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
and in your user model
# Checks if user exists, otherwise create it
def self.find_for_stripe_connect(access_token, _ = nil)
data = access_token.info
user = User.where(email: data['email']).first_or_create(
email: data['email'],
password: Devise.friendly_token[0, 20],
provider: request.env["omniauth.auth"].provider,
uid: request.env["omniauth.auth"].uid,
access_code: request.env["omniauth.auth"].credentials.token,
publishable_key: request.env["omniauth.auth"].info.stripe_publishable_key
)
user
end
and also sing in path
<%= link_to image_tag('blue-on-light.png'), user_stripe_connect_omniauth_authorize %>
I think you don't need to define a passthru action. If you see the below two in the routes it can work. Authorize path is for redirecting user to stripe and callback is for redirecting user from stripe back to your site
$ rake routes
user_stripe_connect_omniauth_authorize /auth/stripe_connect(.:format) ....
user_stripe_connect_omniauth_callback /auth/stripe_connect/callback(.:format) ....

Using Devise and Omniauth, for facebook : Routing Error uninitialized constant OmniauthCallbacksController

I'm a newbie on rails, trying to get my login to work using devise gem and Omniauth, facebook from this tutorial Here I think the problem is in my routes.rb file but I can't spot it, can you spot it for me?
Here is my routes.rb file
Rails.application.routes.draw do
resources :requests
mount RailsAdmin::Engine => '/rabbit', as: 'rails_admin'
devise_for :users, class_name: 'FormUser', :controllers => { omniauth_callbacks: 'omniauth_callbacks',
registrations: 'registrations'}
#devise_for :users
devise_scope :user do
get '/users/auth/:provider/upgrade' => 'omniauth_callbacks#upgrade', as: :user_omniauth_upgrade
get '/users/auth/:provider/setup', :to => 'omniauth_callbacks#setup'
end
resources :listings do
resources :reviews, except: [:show, :index] do
put "upvote", to: "reviews#upvote"
put "downvote", to: "reviews#downvote"
resources :user
end
end
get 'pages/about'
get 'pages/how'
get 'pages/faqs'
get 'pages/contact'
get 'pages/privacy'
get 'pages/tos'
get 'pages/guidelines'
root 'listings#index'
resources :notifications do
collection do
post :mark_as_read
end
end
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Here is my omniauth_callback_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def instagram
generic_callback( 'instagram' )
end
def facebook
generic_callback( 'facebook' )
end
def twitter
generic_callback( 'twitter' )
end
def google_oauth2
generic_callback( 'google_oauth2' )
end
def generic_callback( provider )
#identity = Identity.find_for_oauth env["omniauth.auth"]
#user = #identity.user || current_user
if #user.nil?
#user = User.create( email: #identity.email || "" )
#identity.update_attribute( :user_id, #user.id )
end
if #user.email.blank? && #identity.email
#user.update_attribute( :email, #identity.email)
end
if #user.persisted?
#identity.update_attribute( :user_id, #user.id )
# This is because we've created the user manually, and Device expects a
# FormUser class (with the validations)
#user = FormUser.find #user.id
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
Rake routes
Here is my error
The file must be called omniauth_callbacks_controller.rb. Right now, it's called omniauth_callback_controller.rb.
i recently added facebook authentication in my app.This is how the code set up is..
my url was - my-localhost/users/auth/facebook
######in routes
devise_for :users,:controllers => {
:sessions => "users/sessions",
:registrations => "users/registrations",
:passwords => "users/passwords",
:confirmations => "users/confirmations",
omniauth_callbacks: 'users/omniauth_callbacks'
}
####in controllers/users/omniauth_callbacks_controller.rb
def facebook
logger.tagged("FACEBOOK LOGIN"){ logger.info "=====Login with facebook================"}
auth = request.env["omniauth.auth"]
# 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"])
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"]
flash[:error] = "Unable to login from Facebook because of these errors:"
flash[:error] << "#{#user.errors.full_messages.to_sentence}"
flash[:error] << ".Kindly signup"
redirect_to new_user_registration_url
end
end
####in my user.rb
class << self
##============for facebook
def find_for_facebook_oauth(auth)
#facebook_user = where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.username = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
##using Koala gem to fetch address too
#graph = Koala::Facebook::API.new(auth.credentials.token,configatron.fb_app_secret)
##Check more permissions at https://developers.facebook.com/docs/facebook-login/permissions/v2.2
user_location = #graph.get_object("me?fields=first_name,last_name,email,location")
end
return #facebook_user
end
end
Hope it helps :)

Google+ Sign Up with Devise and Rails (google_oauth2)

I'm getting the following error when trying to sign in through google+ using the google_oauth2 gem.
undefined method `find_for_google_oauth2' for #<Class:0x007ff70a337148>
Here's the three files I've altered for sign up.
user.rb
def google_oauth2
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash.notice = "Signed in Through Google!"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
flash.notice = "You are almost Done! Please provide a password to finish setting up your account"
redirect_to new_user_registration_url
end
end
omniauth_callbacks_controller.rb
def google_oauth2
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.google_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
and I've added config.omniauth :google_oauth2 in my devise.rb file.
routes.rb
devise_for :users, :controllers => { :registrations => "registrations", :sessions => "sessions", :omniauth_callbacks => "users/omniauth_callbacks" }
You are calling find_for_google_oauth2 from the omniauth_callbacks_controller, but you are using the wrong method name google_oauth2. You should replace google_oauth2 with find_for_google_oauth2.
And it seems like the code in user.rb is incorrect because it contains the controller code. Do you see it looks exactly the same like your controller code? :)
Correct code for user.rb
def self.find_for_google_oauth2(access_token, signed_in_resource=nil)
data = access_token.info
user = User.where(:email => data["email"]).first
# Uncomment the section below if you want users to be created if they don't exist
# unless user
# user = User.create(name: data["name"],
# email: data["email"],
# password: Devise.friendly_token[0,20]
# )
# end
user
end
Read more here: https://github.com/zquestz/omniauth-google-oauth2#devise

Devise's after_sign_in_path_for works after signing in normally, but does not work after signing in through Facebook or Twitter. Why is that?

I am working on a rails app right now that allows user's to log in normally, via twitter, and via Facebook as well. I am using devise, omniauth-twitter, and omniauth-facebook.
After signing up/registering, I want to redirect users to the page verify_user_email_path. I have my custom devise RegistrationsController which overrides the standard devise RegistrationsController. It looks like this:
class CustomDeviseControllers::RegistrationsController < Devise::RegistrationsController
def verify_email
flash[:notice] = ""
end
def update_email
#user = User.find(current_user.id)
params[:user].delete(:current_password)
if #user.update_without_password(devise_parameter_sanitizer.sanitize(:account_update))
flash[:notice] = "Welcome! You have signed up successfully."
sign_in #user, :bypass => true
redirect_to bookshelf_index_url
else
#error_message = "You have entered an invalid email address"
render "verify_email"
end
end
protected
def after_sign_up_path_for(resource)
# I WANT TO REDIRECT USERS HERE AFTER SIGNING UP
verify_user_email_path
end
end
As you can see, after any user signs up, they should be redirected to verify_user_email_path. However/sadly, users are only redirected if they sign up normally. So when users register on my site (they enter in an email, a password, and then confirm their password), after_sign_up_path_for correctly redirects users to verify_user_email_path. If users sign up via Facebook/Twitter, the users are simply redirected to the root_url instead. That is the bug I need to fix.
This is my custom OmniauthCallbacksController (underneath the all caps comment is where I sign users in):
class CustomDeviseControllers::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.persisted?
# HERE IS WHERE I SIGN USERS IN
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
end
Aaaand here, is my config/routes.rb:
Rails.application.routes.draw do
devise_for :users, :controllers => {
omniauth_callbacks: 'custom_devise_controllers/omniauth_callbacks',
registrations: 'custom_devise_controllers/registrations' }
devise_scope :user do
get "users/verify_email" => 'custom_devise_controllers/registrations#verify_email', :as => :verify_user_email
post "users/update_email" => 'custom_devise_controllers/registrations#update_email', :as => :update_user_email
end
Instead of using after_sign_up_path_for in my Registrations controller, I use after_sign_in_path_for in the application controller. If the number of sign in attempts equals one, that means the user just signed up.
Here is my application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
def after_sign_in_path_for(resource)
if resource.sign_in_count == 1
verify_user_email_path
else
root_url
end
end
end
So now, whenever someone first signs up, they go to verify_user_email_path. If they are logging in any other time, they go to the root_url.
Thanks, everyone!
Your OmniauthCallbacksController doesn't use the method after_sign_up_path_for so obviously you don't get the redirection you want.
Also, that class_eval thing is completely unnecessary. Here's how that controller should look:
class CustomDeviseControllers::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
omniauth = env["omniauth.auth"]
#user = User.from_omniauth(omniauth)
if #user.persisted?
sign_in #user
set_flash_message(:notice, :success, kind: omniauth.provider.capitalize) if is_navigational_format?
redirect_to verify_user_email_path
else
session["devise.#{omniauth.provider}_data"] = omniauth
redirect_to new_user_registration_url
end
end
alias_method :facebook, :all
alias_method :twitter, :all
end

Resources