Cole Phiper
Cole Phiper

Reputation: 349

ActiveRecord::RecordInvalid (Validation failed: Uid can't be blank) Omniauth LinkedIn Devise

While saving the User model, I receive (Validation failed: Uid can't be blank). Uid itself, belongs to the Identity object - which serves as a single identity access point for multiple identity types. In other words, you will be able to use facebook, twitter, or linkedin omniauth strategies within the app without creating multiple accounts. It doesn't seem to work as advertised. Given that I cannot create a user after logging in on LinkedIn from the app.

I tried to swap out the env reference for request.env. Because, it doesn't exist in its current scope. I attempted to locate the error, but couldn't find exactly why the Identity model was not storing the identity before creating the user. It fails right after the User object is created, instead of checking if the User exist w/an identity attached or should be created.

Server development log

Started GET "/users/auth/linkedin" for 127.0.0.1 at 2019-08-03 22:56:03 -0400
I, [2019-08-03T22:56:04.789247 #23148]  INFO -- omniauth: (linkedin) Request phase initiated.
Started GET "/users/auth/linkedin/callback?oauth_token=77--2555555-5555-4444-ssfs-dcsfsfsfsfsf93&oauth_verifier=28090" for 127.0.0.1 at 2019-08-03 22:56:31 -0400
I, [2019-08-03T22:56:32.422234 #23148]  INFO -- omniauth: (linkedin) Callback phase initiated.
Processing by OmniauthCallbacksController#linkedin as HTML
  Parameters: {"oauth_token"=>"77--2555555-5555-4444-ssfs-dcsfsfsfsfsf93", "oauth_verifier"=>"28090"}
  Identity Load (1.4ms)  SELECT  "identities".* FROM "identities" WHERE "identities"."uid" IS NULL AND "identities"."provider" = ? LIMIT ?  [["provider", "linkedin"], ["LIMIT", 1]]
  ↳ app/models/identity.rb:7
   (0.2ms)  begin transaction
  ↳ app/models/identity.rb:7
  Identity Exists (1.1ms)  SELECT  1 AS one FROM "identities" WHERE "identities"."uid" IS NULL AND "identities"."provider" = ? LIMIT ?  [["provider", "linkedin"], ["LIMIT", 1]]
  ↳ app/models/identity.rb:7
   (0.2ms)  rollback transaction
  ↳ app/models/identity.rb:7
   (0.2ms)  begin transaction
  ↳ app/models/user.rb:47
  User Exists (2.0ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "[email protected]"], ["LIMIT", 1]]
  ↳ app/models/user.rb:47
  User Create (44.3ms)  INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at", "confirmed_at") VALUES (?, ?, ?, ?, ?)  [["email", "[email protected]"], ["encrypted_password", "$2a"], ["created_at", "2019-08-04 02:56:33.928307"], ["updated_at", "2019-08-04 02:56:33.928307"], ["confirmed_at", "2019-08-04 02:56:33.921311"]]
  ↳ app/models/user.rb:47
   (134.0ms)  commit transaction
  ↳ app/models/user.rb:47
   (0.1ms)  begin transaction
  ↳ app/models/user.rb:54
  Identity Exists (2.1ms)  SELECT  1 AS one FROM "identities" WHERE "identities"."uid" IS NULL AND "identities"."provider" = ? LIMIT ?  [["provider", "linkedin"], ["LIMIT", 1]]
  ↳ app/models/user.rb:54
   (0.2ms)  rollback transaction
  ↳ app/models/user.rb:54
Completed 422 Unprocessable Entity in 879ms (ActiveRecord: 191.3ms)



ActiveRecord::RecordInvalid (Validation failed: Uid can't be blank):

app/models/user.rb:54:in `find_for_oauth'
(eval):3:in `linkedin'

omniauth_callbacks_controller.rb

class OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def self.provides_callback_for(provider)
    class_eval %Q{
      def #{provider}
        @user = User.find_for_oauth(request.env["omniauth.auth"], current_user)

        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

  [:facebook, :linkedin].each do |provider|
    provides_callback_for provider
  end

  def after_sign_in_path_for(resource)
    if resource.email_verified?
      super resource
    else
      finish_signup_path(resource)
    end
  end

  def failure
    redirect_to root_path
  end
end

identity.rb

class Identity < ApplicationRecord
  belongs_to :user
  validates_presence_of :uid, :provider
  validates_uniqueness_of :uid, :scope => :provider

  def self.find_for_oauth(auth)
    find_or_create_by(uid: auth.uid, provider: auth.provider)
  end
end

user.rb

class User < ApplicationRecord
  TEMP_EMAIL_PREFIX = 'change@me'
  TEMP_EMAIL_REGEX = /\Achange@me/

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, :trackable, :confirmable

  validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update

  def self.find_for_oauth(auth, signed_in_resource = nil)

    # Get the identity and user if they exist
    identity = Identity.find_for_oauth(auth)

    # If a signed_in_resource is provided it always overrides the existing user
    # to prevent the identity being locked with accidentally created accounts.
    # Note that this may leave zombie accounts (with no associated identity) which
    # can be cleaned up at a later date.
    user = if signed_in_resource then
             signed_in_resource
           else
             identity.user
           end

    # Create the user if needed
    if user.nil?

      # Get the existing user by email if the provider gives us a verified email.
      # If no verified email was provided we assign a temporary email and ask the
      # user to verify it on the next step via UsersController.finish_signup
      email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
      email = auth.info.email if email_is_verified
      user = User.where(:email => email).first if email

      # Create the user if it's a new registration
      if user.nil?
        user = User.new(
            name: auth.extra.raw_info.name,
            #username: auth.info.nickname || auth.uid,
            email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
            password: Devise.friendly_token[0,20]
        )
        user.skip_confirmation!
        user.save!
      end
    end

    # Associate the identity with the user if needed
    if identity.user != user
      identity.user = user  
      identity.save!     # Where error occurs
    end
    user
  end

  def email_verified?
    self.email && self.email !~ TEMP_EMAIL_REGEX
  end

  protected
  def confirmation_required?
    false
  end
end

Expected behavior: A user will successfully log into their Linkedin account and be redirected to the app to complete their registration.

Actual results: The user object creates, and then it fails to save the Identity and leaves the Uid blank.

Upvotes: 2

Views: 1392

Answers (1)

Sangram King
Sangram King

Reputation: 1

the error is a validation error, UID is null, that is the problem. So it appears you already have a user record persisted in the DB with a nil UID. Your validation is not allowing you to add another users with a nil UID. So somehow your UID is not getting passed through correctly so your validation will fail. – lacostenycoder Aug 4, 2019 at 5:29

Upvotes: 0

Related Questions