rails API error: Validation failed: Email is invalid - ruby-on-rails

I hope you are good. I try to implement google authentication to my rails API. I get error
ActiveRecord::RecordInvalid (Validation failed: Email is invalid)
My user.rb:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:confirmable, :trackable,
:omniauthable, omniauth_providers: [:google_oauth2]
def self.create_user_for_google(data)
where(uid: data['email']).first_or_initialize.tap do |user|
user.provider = 'google_oauth2'
user.uid = data['email']
user.email = data['email']
user.password = Devise.friendly_token[0, 20]
user.password_confirmation = user.password
user.save!
end
end
end
and my users_controller.rb looks:
class UsersController < ApplicationController
def google_oauth2
url = "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=#{params['id_token']}"
response = HTTParty.get(url, format: :plain)
user = User.create_user_for_google(response.parsed_response)
render json: user
end
end

My guess is there's no email in response.parsed_response
You're not supposed to make the request from your backend the way you do:
HTTParty.get "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=#{params['id_token']}"
You're supposed to redirect user's browser to user_google_oauth2_omniauth_authorize_url (I think this URL helper comes from devise in routes.rb (look for devise_for:)
Then your user's browser shows the Google log in page (or, if user was already logged in, next step) then (still google) asks for their consent to share data, and only after receiving this consent google would redirect your user back to your app to an address that is called a callback (it looks like omniauth_callbacks: "users/omniauth_callbacks")
And only in the controller/action that handles this callback, you'll see an email and a token you can save with your user and use it to signing in with google's oAuth protocol.
Don't worry, oAuth is confusing when you do it for the first time.
One of the clearest step-by-step example how to implement omni auth I could find is here: https://www.digitalocean.com/community/tutorials/how-to-configure-devise-and-omniauth-for-your-rails-application

Related

Use OTP instead of password in devise

I have 3 different devise models. Two using email & password for login and works great. But For the third one I need to use mobile & OTP based login instead of email & password. So the devise session controller only receives mobile & the otp as params.
I have changed from email to mobile by using authentication_keys
class Customer < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, authentication_keys: [:mobile]
def email_required?
false
end
end
I am using devise-jwt for jwt response & overriding sessions_controller like this
class Api::Customers::V1::Auth::SessionsController < Devise::SessionsController
def create
super { #token = current_token }
end
private
def current_token
request.env['warden-jwt_auth.token']
end
end
But now am getting password blank validation error. How can I disable password validation & change to do an otp verification instead?
PS: I have seen several resources on two factor auth . But they all work with password validation.

Error Ruby on Rails: Users::OmniauthCallbacksController#facebook is missing a template for this request format and variant

I'm trying to add an external login with facebook, but whenever I realize the home page correctly directs me to the facebook page, but then I get the following error, and could not understand what it can be.
"Users::OmniauthCallbacksController#facebook is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot."
"That's what you'll get from an XHR or API request. Give it a shot."
raise ActionController::UnknownFormat, message
else
logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
super
this is my controller
class Users::OmniauthCallbacksController < ApplicationController
def facebook
#User = User.from_omniauth(request.env["omniauth.auth"])
if #User.persisted?
#User.remember_me = true
sign_in_and_redirect #User, event: :authentication
end
end
end
this is the user model.
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,:omniauthable, :omniauth_providers => [:facebook]
def self.from_omniauth(auth)
where(provider: auth[:provider], uid: auth[:uid]).first_or_create do |user|
if auth[:info]
user.email = auth[:info][:email]
user.name = auth[:info][:name]
end
user.password = Devise.friendly_token[0,20]
end
end
has_many :articles , :dependent => :destroy
end
and i put this line in config/initializers/divise.rb
config.omniauth :facebook, '504432376574467', 'b2bb80641fcc2ca4d28e48c5ce*******'
My guess would be that User.from_omniauth fails to create the user (possibly due to user.password and user.password_confirmation not matching), which causes the Users::OmniauthCallbacksController#facebook to reach the end of the method without going inside the if clause.
To check, you could for example add an else clause to your Facebook callback and raise an error in there.

rails 4, activeAdmin, Devise not saving reset_password_token for admin_user

I am using ActiveAdmin to create other admin members by supplying email, and having a block to send a password reset to the newly added email:
class AdminUser < ActiveRecord::Base
after_create { |admin| admin.send_reset_password_instructions }
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable
def password_required?
new_record? ? false : super
end
end
However, I started getting a reset token invalid error, and on checking, discovered that the reset_password_token is not getting saved.
When I tried to debug, I traced the error to the method of the set_reset_password_token method of the Recoverable module ( recoverable.rb file ) of devise:
def set_reset_password_token
raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
self.reset_password_token = enc
self.reset_password_sent_at = Time.now.utc
self.save(validate: false)
raw
end
the token is getting generated, but when I checked self just before the self.save(validate: false) line, there was nothing attached to self.reset_password_token, it was nil.
To finally complicate it for me, when i now manually run the line self.reset_password_token = enc and self.reset_password_token actually now holds the token, after saving it, and checking the database, the reset_password_token of the newly saved AdminUser was still nil.
What could be going on here?
Note however: devise test suit on git shows that this should not be the case as shown below:
test 'should not clear reset password token for new user' do
user = new_user
assert_nil user.reset_password_token
user.send_reset_password_instructions
assert_present user.reset_password_token
user.save
assert_present user.reset_password_token
end

Configuring rails grape api with devise token

I am trying to configure token generation with devise in a grape api rails app. Since I have the current version of devise, token generation has been disabled. I am having several issues. First, is that when I submit a username and password to the sessions controller, it gives me an error that "ensure_authentication_token":
undefined method `ensure_authentication_token!' for #<User:0x007f880cca9090>
This is so strange because as you can see below, I have it defined in my user model and when I manually create Users in rails console, it works properly.
Is that a scope issue or why is that occurring?
User Model:
class User < ActiveRecord::Base
before_save :ensure_authentication_token
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.where(authentication_token: token).first
end
end
end
Sessions Grape API Controller:
module API
module V1
class Sessions < Grape::API
include API::V1::Defaults
resource :sessions do
params do
requires :email, type: String, desc: "Email"
requires :password, type: String, desc: "Password"
end
post do
email = params[:email]
password = params[:password]
if email.nil? or password.nil?
error!({error_code: 404, error_message: "Invalid Email or Password."},401)
return
end
user = User.where(email: email.downcase).first
if user.nil?
error!({error_code: 404, error_message: "Invalid Email or Password."},401)
return
end
if !user.valid_password?(password)
error!({error_code: 404, error_message: "Invalid Email or Password."},401)
return
else
user.ensure_authentication_token!
user.save
{status: 'ok', token: user.authentication_token}.to_json
end
end
end
end
end
end
The second problem is that when I follow this blog, it says that I need to add the following authentication check in my defaults.rb in the base api controller. When I add the "before do" section, I get access denied error even if I enter in the right credentials and it doesn't even go on to the rest of the sessions controller that I mentioned above.
before do
error!("401 Unauthorized, 401") unless authenticated
end
helpers do
def warden
env['warden']
end
def authenticated
return true if warden.authenticated?
params[:access_token] && #user = User.find_by_authentication_token(params[:access_token])
end
def current_user
warden.user || #user
end
end
Thanks for any help you can give!
EDIT: Phillip was absolutely correct that one of these issues was due to the bang versus non banged version of ensure_authentication_token. Removing the ! from the controller fixed that issue. The other problem was indeed from adding the "before do" loop I had.
This is so close to working, I can give and receive tokens in my api but when it connects to ember, it complains about a lack of a csrf token even though I have "protect_from_forgery with: :null_session" set in my application.rb
In your User model, you define a method called ensure_authentication_token.
In your Session controller, you call a method called ensure_authentication_token!.
These are not the same method: Why are exclamation marks used in Ruby methods?
This is preventing you from generating an authentication token, which probably explains the "401 Unauthorized, 401" error.

Google UID changes on login with Rails and Devise

For some reason, after I create a user via Omniauth, I can't login to that user again, because Google returns a different UID.
Essentially I first login with Google. If the user doesn't exist, I create a user, and save the UID. On the next login, I look for the user via provider and uid, and if the user has already "registered" then those two attributes should be able find the user. For some reason, Google returns a different UID than from the first login.
user.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :registerable, :rememberable, :trackable, :omniauthable, :omniauth_providers => [:google_oauth2]
def self.from_omniauth auth
user = User.find_by_provider_and_uid(auth.provider, auth.uid) || User.create_with_omniauth(auth)
end
def self.create_with_omniauth auth
create! do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.email = auth.info.email
user.avatar = auth.info.image
end
end
end
OmniAuth Callback Controller:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
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
flash.alert = "Something went wrong"
redirect_to root_url
end
end
end
Anyone know why this is happening or how to fix it?
I have not faced such issue with google, but you can still handle it since the google's email is unique.
def self.from_omniauth auth
user = User.find_by_provider_and_uid(auth.provider, auth.uid) || User.find_by_provider_and_email(auth.provider, auth.info[:email]) || User.create_with_omniauth(auth)
end

Resources