I am starting with using omniauth with devise User model. Integration works well but a case is failing.
This is related code from omniauth callback controller:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
before_action :find_user, only: [:facebook]
def facebook
process_omniauth_response('Facebook', request.env['omniauth.auth'], #user)
end
def failure
redirect_to root_path, flash: { error: 'Authentication \
failed, please try again!' }
end
private
def find_user
#user = User.from_omniauth(request.env['omniauth.auth'])
end
def process_omniauth_response(provider, env_data, user)
if user.persisted?
sign_in_and_redirect user, event: :authentication
set_flash_message(:notice, :success,
kind: provider) if is_navigational_format?
else
session["devise.#{provider.downcase}_data"] = env_data
redirect_to new_user_registration_url
end
end
end
And the User model methods:
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
info = auth.info
user.email = info.email
user.remote_profile_picture_url = info.image
user.name = info.name
user.password = Devise.friendly_token[0, 20]
end
end
def self.new_with_session(params, session)
super.tap do |user|
auth_data =
session['devise.facebook_data'] || session['devise.twitter_data']
fill_with_omniauth_data(user, auth_data) if auth_data
end
end
def self.fill_with_omniauth_data(user, auth_data)
data_info = auth_data['info']
user.email = data_info['email'] if user.email.blank?
user.name = data_info['name'] if user.name.blank?
...
end
So everything works fine i.e if a new user clicks on login by Facebook, after authentication by facebook he will come to signup page with pre-filled data but in case of this below case it is going to sign in page after omniauth callback action:
context 'when facebook email doesn\'t exist in the system' do
before(:each) do
stub_env_for_omniauth
get :facebook
end
it { response.should redirect_to new_user_registration_path }
it 'should create authentication with facebook id' do
authentication = User.where(provider: 'facebook', uid: '1234').first
puts authentication.inspect
authentication.should_not be_nil
end
end
And the stub method:
def stub_env_for_omniauth
request.env['devise.mapping'] = Devise.mappings[:user]
request.env['omniauth.auth'] = OmniAuth::AuthHash.new(
provider: 'facebook', uid: '1234', info: {
email: 'ghost#nobody.com', name: 'mock user'
},
extra: { raw_info: { age: 23 } }
)
end
I got this message:
Expected response to be a redirect to http://test.host/users/sign_up but was a redirect to http://test.host/users/sign_in
From debugging I got that user object is a new , not persisted when I check it in the 'find_user' method of controller but in case of spec it is a persisted one.
I would appreciate any help.
Thanks
Related
I can not understand, why not pass the tests, it performs the first redirect and not the second, as described in the controller code itself works correctly, redirections occur exactly as described.
Rspec 3.5.1
Rails 5
Ruby 2.3.1
spec/features/authentication_pages_spec.rb
describe "autorization", type: :request do
describe "for non-signed-in users" do
let(:user) { FactoryGirl.create(:user) }
describe "in the Users controller" do
describe "visiting the edit page" do
before { visit edit_user_path(user) }
it { should have_title('Log in') }
end
describe "submitting to the update action" do
before { patch user_path(user) }
specify { expect(response).to redirect_to(login_path) }
end
end
end
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
before { log_in user, no_capybara: true }
describe "submitting a GET request to the Users#edit action" do
before { get edit_user_path(wrong_user) }
specify { expect(response.body).not_to match(full_title('Edit user')) }
specify { expect(response).to redirect_to(root_url) }
end
describe "submitting a PATCH request to the User#update action" do
before { patch user_path(wrong_user) }
specify { expect(response).to redirect_to(root_url) }
end
end
end
end
The test fails with the following error:
1) AuthenticationPages autorization as wrong user submitting a GET request to the Users#edit action should redirect to "http://www.example.com/"
Failure/Error: specify { expect(response).to redirect_to(root_url) }
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/login>.
Expected "http://www.example.com/" to be === "http://www.example.com/login".
2) AuthenticationPages autorization as wrong user submitting a PATCH request to the User#update action should redirect to "http://www.example.com/"
Failure/Error: specify { expect(response).to redirect_to(root_url) }
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/login>.
Expected "http://www.example.com/" to be === "http://www.example.com/login".
I cannot fathom why it's redirecting to the signin url when it should redirect to the root url - the user is signed in in the spec.
Here is the UserController:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
# Before filters
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to login_url
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
Here are the relevant bits of my sessions_helper.rb file:
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
# Returns the current logged-in user (if any).
def current_user
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(id: session[:user_id])
end
def current_user?(user)
user == current_user
end
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
def log_out
session.delete(:user_id)
#current_user = nil
end
end
User class:
class User < ApplicationRecord
has_secure_password
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+(\.[a-z]+)*\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },uniqueness: { case_sensitive: false }
validates :password, length: { minimum: 6 }
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = User.encrypt(User.new_remember_token)
end
end
spec/support/utilities.rb
include ApplicationHelper
def valid_login(user)
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
end
RSpec::Matchers.define :have_error_message do |message|
match do |page|
expect(page).to have_selector('div.alert.alert-error', text: message)
end
end
def log_in(user, options={})
if options[:no_capybara]
# Sign in when not using Capybara.
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
else
visit login_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
end
end
I suspect your issue is related to
before { log_in user, no_capybara: true }
try changing it to
before { session[:user_id] = user.id }
The problem is with your log_in method (in the spec, not the helper code), which is not actually logging anybody in. That's why you hit the login error. You mention that this code "creates a user with a different email address from the default", but this is not what it should be doing; it should be setting the session.
Simply creating a user will not set the session, and without the session being set, logged_in? will be false and you will get redirected to the login page.
Try something like this instead:
before { #request.session[:user_id] = user.id }
This sets the session explicitly on the request to the user'd id, so that logged_in? will return true in your controller.
I want to get my tweets using user_timeline()and save them to my application database.
I'm not sure where to call that function and how to save each tweet associating with my user.
This is the way I'm saving users to the database
class SessionsController < ApplicationController
def create
auth = request.env['omniauth.auth']
session[:omniauth] = auth.except('extra')
user = User.sign_in_from_omniauth(auth)
session[:user_id] = user.id
redirect_to root_url, notice: "SIGNED IN"
end
end
Here's my model...
class User < ActiveRecord::Base
def self.sign_in_from_omniauth(auth)
find_by(provider: auth['provider'], uid: auth['uid']) || create_user_from_omniauth(auth)
end
def self.create_user_from_omniauth(auth)
create(
provider: auth['provider'],
uid: auth['uid'],
name: auth['info']['name'],
screen_name: auth['info']['nickname'],
oauth_token: auth['credentials']['token'],
oauth_secret: auth['credentials']['secret']
)
end
def twitter
Twitter::REST::Client.new do |config|
config.consumer_key = 'XXXXXXXXXXXXXXXX'
config.consumer_secret = 'XXXXXXXXXXXXXXXX'
config.access_token = oauth_token
config.access_token_secret = oauth_secret
end
end
end
Where I should call user_timeline() and save all tweets?
Approach #1
Send email reminder to <%= email_field_tag :email, nil, placeholder: 'Enter Email', class: "send-email" %> # :email belongs to the users table
Approach #2
Send email reminder to <%= f.email_field :default_email, class: "send-email", placeholder: "Enter Email" %> # :default_email belongs to the challenges table. The point of this is to try something in the create process like current_user.email == #challenge.default_email
The user is then redirected to signup url
If a user signs up via Facebook a random email is generated so that he can get through the validation process: user.email = SecureRandom.hex + "#mailinator.com"
user.rb
def self.from_omniauth(auth)
# Sets 60 day auth token
oauth = Koala::Facebook::OAuth.new("125402372971231236229929", "ee917abf2e8f1c98274cdafssadffddffaebb1346f4")
new_access_info = oauth.exchange_access_token_info auth.credentials.token
new_access_token = new_access_info["access_token"]
new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.image = auth.info.image
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = new_access_token # auth.credentials.token <- your old token. Not needed anymore.
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.password = (0...8).map { (65 + rand(26)).chr }.join
user.email = SecureRandom.hex + "#mailinator.com" unless user.email.present?
user.activated = true
user.save!
end
end
But how can we make the email the user inputted in the challenges/create.html.erb form seen above override the SecureRandom email?
users_controller
def create
#user = User.new(user_params)
if #user.save
action = session.delete(:challenge_action)
deadline = session.delete(:challenge_deadline)
committed = session.delete(:challenge_committed)
date_started = session.delete(:challenge_date_started)
order = session.delete(:challenge_order)
days_challenged = session.delete(:challenge_days_challenged)
why = session.delete(:challenge_why)
conceal = session.delete(:challenge_conceal)
#user.challenges.create(action: action, deadline: deadline, why: why, conceal: conceal, date_started: date_started, committed: committed, days_challenged: days_challenged)
end
redirect_to root_url
else
render 'new'
end
end
challenges_controller
before_action :update_user_email, if: proc {|c| c.current_user.present? && c.params[:email].present? }
def create
#challenge = Challenge.new(challenge_params)
if params[:step] == '2'
if current_user == nil
# If there is no user, store values to the session.
session[:challenge_action] = challenge_params[:action]
session[:challenge_committed] = challenge_params[:committed]
session[:challenge_deadline] = [params["challenge"]["deadline(3i)"], params["challenge"]["deadline(2i)"], params["challenge"]["deadline(1i)"]].join('/')
session[:challenge_date_started] = [params["challenge"]["date_started(3i)"], params["challenge"]["date_started(2i)"], params["challenge"]["date_started(1i)"]].join('/')
session[:challenge_order] = challenge_params[:order]
session[:challenge_days_challenged] = challenge_params[:days_challenged]
session[:challenge_why] = challenge_params[:why]
session[:challenge_conceal] = challenge_params[:conceal]
redirect_to signup_path
else
#challenge = current_user.challenges.build(challenge_params)
#challenge.save
redirect_to root_path
end
else
respond_modal_with #challenge
end
end
private
def update_user_email
email = params[:email]
current_user.update_attribute(:email, email)
end
sessions_controller
class SessionsController < ApplicationController
def new
end
def facebook
user = User.from_omniauth(env["omniauth.auth"])
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
redirect_to root_url
end
def create
assign_email(cookies[:challenges_email])
user = User.find_by(email: params[:session][:email].downcase)
assign_email(cookies[:challenges_email])
if user && user.authenticate(params[:session][:password])
assign_email(cookies[:challenges_email])
log_in user
params[:session][:remember_me] == 'nil' ? forget(user) : remember(user)
redirect_to root_url
else
flash.now[:info] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
We talked about setting it as a cookie in another question. If you're taking that approach, you could pass the cookie to a method. Something like what is discussed in this question:
before_create :set_user_email
def assign_email(email)
#email = email
end
def set_user_email
self.email = #email
end
then in the controller action where you have access to the cookie:
assign_email(cookies[:challenge_email])
#joseph had answered it pretty simple but i think you can just get email in the field by setting it in the controller like;
User.new(:email => "email you want to show in the field")
no need to pass nil user object pass a new object.
And in the view file it will shown in email field with
Send email reminder to <%= f.email_field :email, class: "send-email", placeholder: "Enter Email" %>
On My current app that i'm developing, i can sign up using Facebook and Google+. But when I added the ability for signing up with just a regular email, i got this error, I'll appreciate some help please ...
Here are some of my code
#SessionsController
def create
#user = User.from_omniauth(env["omniauth.auth"])
# session[:user_id] = #user.id
# redirect_to root_path
#user = User.find_by_email params[:email]
if #user && #user.authenticate(params[:password])
session[:user_id] = #user.id
redirect_to root_path
else
flash[:alert] = "Wrong email or password"
render :new
end
end
#user.rb
class User < ActiveRecord::Base
has_secure_password
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.email = auth.info.email
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.image = auth.info.image
user.gender = auth.extra.raw_info.gender
user.location = auth.extra.raw_info.location.name
user.location = auth.extra.raw_info.locale
# user.url = auth_hash['info']['urls'][user.provider.capitalize]
user.save
end
end
So When attempting to signUp with using the email, here's what happen !
enter image description here
I'm guessing that error coming because i'm calling from_omniauth and i'm not passing a provider, which i don't need to use in this case.
You might consider starting fresh - using https://github.com/plataformatec/devise and then adding OmniAuth on top of this (https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview).
Here is my controller
def social_login
user = User.from_omniauth(env["omniauth.auth"])
session_params = user.attributes.merge("email" => user.email, "password" => user.crypted_password)
#user_session ||= UserSession.new(session_params, true)
if #user_session.save
user = User.where(email: #user_session.email).first
redirect_to root_path, :notice => "Signed in succesfully from #{env["omniauth.auth"].provider.titleize}. Greetings #{user.name.titleize} ;)"
else
flash.now[:alert] = "Sign in failed."
render "new"
end
end
here is the model to handle the omniauth process
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.email = auth.info.email
user.password = auth.credentials.token
user.password_confirmation = auth.credentials.token
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
I always getting error when trying to save the session. It says:
Authlogic::Session::Existence::SessionInvalidError: Your session is invalid and has the following errors: Email is not valid
can you guys help me? thanks
Did you enable the email permission in your facebook app config/rails's initializion?
Like this:
config.omniauth :facebook, "APP_ID", "APP_SECRET", {:scope => 'email,...'}