0
votes

I'm working on multi vendor authentication on Ruby on Rails application using Devise and omniauth-*.

I, mostly followed this link except for omniauth_callbacks_controller.rb where I added:

class OmniauthCallbacksController < Devise::OmniauthCallbacksController
    def after_sign_in_path_for(resource)
        if resource.email_verified?
            super resource
        else
            finish_signup_path(resource)
        end
    end

    def all
        puts request.env["omniauth.auth"]
        user = User.find_for_oauth(request.env["omniauth.auth"], current_user)
        if user.persisted?
            sign_in_and_redirect user, notice: "Signed in!"
        else
            session["devise.all"] = request.env["omniauth.auth"]
            redirect_to new_user_registration_url
        end
    end
    alias_method :twitter, :all
    alias_method :google_oauth2, :all
    alias_method :facebook, :all
    alias_method :instagram, :all
end

and app/models/user.rb I modified self.find_for_oauth to :

def self.find_for_oauth(auth, signed_in_resource = nil)
    identity = Identity.find_for_oauth(auth)

    user = signed_in_resource ? signed_in_resource :identity.user
    if user.nil?
        email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
        email = auth.info.email || if auth.info.nickname then auth.info.nickname + "@twitter.org" end
        user = User.where("email = ?", email).first if email

        if user.nil?
            user = User.new(
                first_name: auth.extra.raw_info.name,
                email: if auth.info.email != nil and auth.info.email != "" then auth.info.email else if auth.info.nickname then auth.info.nickname + "@twitter.org" end end ,
                password: Devise.friendly_token[0, 20]
            )
            user.skip_confirmation!
            user.save!
        end
    end

    if identity.user != user
        identity.user = user

        identity.save!
    end
    user
end

I succeeded to make a user able to sign in using different social media created with the same email address by creating identity model in which user has_many identities.

However, Now, I can't sign up (with email and passwords) to an email that is already had by a user object created using social media.

How can I solve this issue?

UPDATE

The approach I'm trying now is checking if the user has already signed in before with the same email. If so, the user account should be updated with sign up data and given on more identity object with provider set to blank.

UPDATE 2

I now can signup user with email and password by customizing create method in registrations_controller.rb to :

def create
    build_resource(sign_up_params)

    resource[:provider] = "<default provider>"
    if !User.find_by_email(resource.email) then
        resource.save
        yield resource if block_given?
        if resource.persisted?
            if resource.active_for_authentication?
                set_flash_message! :notice, :signed_up
                sign_up(resource_name, resource)
                respond_with resource, location: after_sign_up_path_for(resource)
            else
                set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
                expire_data_after_sign_in!
                respond_with resource, location: after_inactive_sign_up_path_for(resource)
            end
        else
            set_flash_message! :error, :"Something went wrong"
            clean_up_passwords resource
            set_minimum_password_length
            respond_with resource
        end
    else
        user = User.find_by_email(resource.email)
        if user.identities.count > 0 and user.provider != "<default provider>" then
        @identity = Identity.new
        @identity.provider = "made up provider"
        @identity.save
        user.password = resource.password
        if resource.country != nil and resource.country != "" then
             user.country = resource.country
        end
        if resource.title != nil and resource.title != "" then
            if resource.title != nil and resource.title != "" then
                user.title = resource.title
            end
            if resource.first_name != nil and resource.first_name != "" then
                user.first_name = resource.first_name
            end
            if resource.last_name != nil and resource.last_name != "" then
                user.last_name = resource.last_name
            end
            if resource.phone_number != nil and resource.phone_number != "" then
                user.phone_number = resource.phone_number
            end
            if resource.country_code != nil and resource.country_code != "" then
                user.country_code = resource.country_code
            end
            user.provider = "<default provider>"

            user.save
            yield user if block_given?
            if user.persisted?
                if resource.active_for_authentication?
                    set_flash_message! :notice, :signed_up
                    respond_with resource, location: after_sign_up_path_for(resource)
                else
                    set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
                    expire_data_after_sign_in!
                    respond_with resource, location: after_inactive_sign_up_path_for(user)
                end
            else
            set_flash_message! :error, :"Something went wrong"
            clean_up_passwords resource
            set_minimum_password_length
            respond_with resource
        end
    else
        set_flash_message! :error, "This Account already exists"
        redirect_to new_user_session_path
    end
end

However, a message with "confirmation mail is sent to your email", while no confirmation mail is actually sent.

How can I make it send confirmation e-mail on when user enteres password for the first time (user signup with email and password)?

UPDATE3

My current question is :

Using Devise, how can I send confirmation email (identical to signup confirmation email) on first password update (or on a specific action)?

1

1 Answers

0
votes

I fixed the registration and confirmation issues by modifying Devise registrations_controller.rb, confirmations_controller.rb and sessions_controller.rb

In registrations_controller.rb I modified create method to:

def create
    build_resource(sign_up_params)

    resource[:provider] = "<default provider>"
    if !User.find_by_email(resource.email) then
      resource.save
      yield resource if block_given?
      if resource.persisted?
        if resource.active_for_authentication?
          set_flash_message! :notice, :signed_up
          sign_up(resource_name, resource)
          respond_with resource, location: after_sign_up_path_for(resource)
        else
          set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
          expire_data_after_sign_in!
          respond_with resource, location: after_inactive_sign_up_path_for(resource)
        end
      else
        set_flash_message! :error, :"Something went wrong"
        clean_up_passwords resource
        set_minimum_password_length
        respond_with resource
      end
    else
      user = User.find_by_email(resource.email)
      if user.identities.count > 0 and (user.provider != "<default provider>" or user.provider != "<default provider>-confirmed") then
        @identity = Identity.new
        @identity.provider = "<default provider>"
        @identity.save
        user.password = resource.password
        if resource.username != nil and resource.username != "" then
            user.username = resource.username
        end
        if resource.phone_number != nil and resource.phone_number != "" then
            user.phone_number = resource.phone_number
        end
        ...
        user.provider = "<default provider>"
        user.save
        user.send_confirmation_instructions
        yield user if block_given?
        if user.persisted?
          if resource.active_for_authentication?
            set_flash_message! :notice, :signed_up
            respond_with resource, location: after_sign_up_path_for(resource)
          else
            set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
            expire_data_after_sign_in!
            respond_with resource, location: after_inactive_sign_up_path_for(user)
          end
        else
          set_flash_message! :error, :"Something went wrong"
          clean_up_passwords resource
          set_minimum_password_length
          respond_with resource
        end
      else
        set_flash_message! :error, "This Account already exists"
        redirect_to new_user_session_path
      end
    end
end

In confirmations_controller.rb I modified create and show methods :

# POST /resource/confirmation
def create
  self.resource = resource_class.send_confirmation_instructions(resource_params)
  yield resource if block_given?

  if successfully_sent?(resource)
    respond_with({}, location: after_resending_confirmation_instructions_path_for(resource_name))
  else
    if User.find_by_email(resource.email).provider != "<default provider>-confirmed" then
     User.find_by_email(resource.email).send_confirmation_instructions
     respond_with({}, location: after_resending_confirmation_instructions_path_for(resource_name))
    else
      respond_with(resource)
    end
  end
end

# GET /resource/confirmation?confirmation_token=abcdef
def show
  self.resource = resource_class.confirm_by_token(params[:confirmation_token])
  yield resource if block_given?

  if resource.errors.empty?
    resource.provider = "<default provider>-confirmed"
    resource.save
    set_flash_message!(:notice, :confirmed)
    respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) }
  else
    if User.find_by_email(resource.email).provider != "<default provider>-confirmed" then
      a = User.find_by_email(resource.email)
      a.provider = "<defualt provider>-confirmed"
      a.save
      set_flash_message!(:notice, :confirmed)
      respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) }
    else
      respond_with_navigational(resource.errors, status: :unprocessable_entity){ render :new }
    end
  end
end

In sessions_controller.rb I also modified create method :

def create
    self.resource = warden.authenticate!(auth_options)
    if User.find_by_email(resource.email) and User.find_by_email(resource.email).provider == "<default provier>-confirmed" then
      self.resource = warden.authenticate!(auth_options)
      set_flash_message!(:notice, :signed_in)
      sign_in(resource_name, resource)
      yield resource if block_given?
      respond_with resource, location: after_sign_in_path_for(resource)
    else
      signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
      set_flash_message!(:error, "You have not yet confirmed your email");
      redirect_to root_path
    end
end