graphql-devise / graphql_devise

GraphQL interface on top devise_token_auth
MIT License
199 stars 42 forks source link

Sign in user with phone and otp #173

Closed suryaxan closed 3 years ago

suryaxan commented 3 years ago

Question

Hello!

I have the following use case:

User signs up with either email or phone Login user based on either email/password or phone/otp

Note: I have implemented custom sign up and everything is working fine to sign up a user with either email/phone, so I am not posting any code related to that.

To implement custom login, I have done the following: -> Added a custom Login mutation and inherited from GraphqlDevise::Mutations::Login -> Updated the mount options in my custom graphql_schema file to use custom login mutation for the Login operation

custom_schema.rb looks like this

class CustomSchema < GraphQL::Schema
  use GraphqlDevise::SchemaPlugin.new(
    query:            Types::QueryType,
    mutation:         Types::MutationType,
    resource_loaders: [
      GraphqlDevise::ResourceLoader.new('User', operations: {login: Mutations::Login})
    ],
    authenticate_default: false
  )

  mutation(Types::MutationType)
  query(Types::QueryType)
end

Mutations::Login looks like this

module Mutations
  class Login < GraphqlDevise::Mutations::Login
    argument :email,                 String, required: false
    argument :password,              String, required: false
    argument :phone,                 String, required: false
    argument :country_code,          String, required: false
    argument :otp_code,              String, required: false

    def resolve(email: nil, password: nil, **attrs)
      if email.present?
        super(email: email, password: password)
      else
        resource = resource_class.find_by(phone: attrs[:phone], country_code: attrs[:country_code])
        if resource && resource.authenticate_otp(attrs[:otp_code], drift: 600)
          new_headers = set_auth_headers(resource)
          controller.sign_in(:user, resource, store: false, bypass: false) # this line throws error 401

          { authenticatable: resource, credentials: new_headers }
        else
          raise_user_error('Invalid Otp')
        end
      end
    end
  end
end

So, in my custom login mutation, when I try to controller.sign_in(:user, resource, store: false, bypass: false), it throws 401 Unauthorized

To understand the issue, I tried checking what is happening in controller.sign_in, I found that when I try login mutation with phone and otp, only the user records with email verified are going through, but for those user records which do not have email, the sign_in fails.

In my use case, there is a chance that email is blank. User signs up with only phone number, is there a way I can sign in the user who has only phone number?

00dav00 commented 3 years ago

Hi @suryaxan, loooks like a devise related question.

Have you tried overwriting confirmation_required in your resource model?

To be active for auth a model usually needs these 3 things

      def active_for_authentication?
        super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
      end

https://github.com/heartcombo/devise/blob/master/lib/devise/models/confirmable.rb#L106

suryaxan commented 3 years ago

Hi @00dav00, thanks for the quick response.

This is exactly what I missed. Thank you so much, this fixed my problem.