I'm trying to make an app in Rails 4. I have been trying for the last 3 years (except for 10 days), to get devise to work.
I'm trying to follow this tutorial: http://sourcey.com/rails-4-omniauth-using-devise-with-twitter-facebook-and-linkedin/
Please don't recommend other tutorials / gem documentation. I have tried at least 30 other tutorials and the gem documentation is full of errors and components that I don't understand.
My current problem is that when I get to the finish sign up step in this tutorial, the form asks me for my email address.
The users controller has a finish signup method as:
def finish_signup
# authorize! :update, #user
if request.patch? && params[:user] #&& params[:user][:email]
if #user.update(user_params)
#user.skip_reconfirmation!
# sign_in(#user, :bypass => true)
# redirect_to root_path, notice: 'Your profile was successfully updated.'
# redirect_to [#user, #user.profile || #user.build_profile]
sign_in_and_redirect(#user, :bypass => true)
else
#show_errors = true
end
end
end
When I try this, I get this error:
undefined method `match' for {:host=>"localhost", :port=>3000}:Hash
The error points at this line:
<div class="intpol3"><%= link_to 'Confirm my account', confirmation_url(#resource, confirmation_token: #token) %></div>
My development environment is set up to include all the config details for my email sender.
When I try the same step in production mode, I get this error:
ActionView::Template::Error (No route matches {:action=>"show", :controller=>"profiles", :id=>nil} missing required keys: [:id]):
It's looking for a profile id because I have an after_create action in my user model as:
after_create :gen_profile
def gen_profile
Profile.create(user: self) # Associations must be defined correctly for this syntax, avoids using ID's directly.
# Profile.save
end
My other issue with this tutorial is that the fields in the identity table aren't being populated.
I'd love to find someone that has successfully implemented this tutorial or can see how to make this work.
My code is:
gemfile
gem 'devise', '3.4.1'
gem 'devise_zxcvbn'
gem 'omniauth'
gem 'omniauth-oauth2', '1.3.1'
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-linkedin-oauth2'
gem 'google-api-client', require: 'google/api_client'
routes
devise_for :users, #class_name: 'FormUser',
:controllers => {
:registrations => "users/registrations",
# :omniauth_callbacks => "users/authentications"
:omniauth_callbacks => 'users/omniauth_callbacks'
}
# get '/auth/:provider/callback' => 'users/authentications#create'
# get '/authentications/sign_out', :to => 'users/authentications#destroy'
# PER SOURCEY TUTORIAL ----------
match '/users/:id/finish_signup' => 'users#finish_signup', via: [:get, :patch], :as => :finish_signup
resources :users do
resources :profiles, only: [:new, :create]
end
user.rb
class User < ActiveRecord::Base
TEMP_EMAIL_PREFIX = 'change#me'
TEMP_EMAIL_REGEX = /\Achange#me/
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:confirmable, :lockable,
# :zxcvbnable,
:omniauthable, :omniauth_providers => [:facebook, :linkedin, :twitter, :google_oauth2 ]
# --------------- associations
has_many :authentications, :dependent => :delete_all
has_one :profile
has_many :identities
# --------------- scopes
# --------------- validations
# validates_presence_of :first_name, :last_name
validates_uniqueness_of :email
# per sourcey tutorial - how do i confirm email registrations are unique?
# this is generating an error about the options in the without function -- cant figure out the solution
validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update
# --------------- class methods
# sourcey tutorial
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 = signed_in_resource ? signed_in_resource : identity.user
# p '11111'
# Create the user if needed
if user.nil?
# p 22222
# 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 # take out this if stmt for chin yi's solution
user = User.where(:email => email).first if email
# Create the user if it's a new registration
if user.nil?
# p 33333
user = User.new(
# at least one problem with this is that each provider uses different terms to desribe first name/last name/email. See notes on linkedin above
first_name: auth.info.first_name,
last_name: auth.info.last_name,
email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
#username: auth.info.nickname || auth.uid,
password: Devise.friendly_token[0,20])
# fallback for name fields - add nickname to user table
# debugger
# if email_is_verified
user.skip_confirmation!
# end
# user.skip_confirmation!
user.save!
end
end
# Associate the identity with the user if needed
if identity.user != user
identity.user = user
identity.save!
end
user
end
def email_verified?
self.email && TEMP_EMAIL_REGEX !~ self.email
end
users controller
class UsersController < ApplicationController
before_action :set_user, only: [:index, :show, :edit, :update, :finish_signup, :destroy]
# i added finish_signup to the set_user action (not shown in tutorial)
def index
# if params[:approved] == "false"
# #users = User.find_all_by_approved(false)
# else
#users = User.all
# end
end
# GET /users/:id.:format
def show
# authorize! :read, #user
end
# GET /users/:id/edit
def edit
# authorize! :update, #user
end
# PATCH/PUT /users/:id.:format
def update
# authorize! :update, #user
respond_to do |format|
if #user.update(user_params)
sign_in(#user == current_user ? #user : current_user, :bypass => true)
format.html { redirect_to #user, notice: 'Your profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# GET/PATCH /users/:id/finish_signup
def finish_signup
# authorize! :update, #user
if request.patch? && params[:user] #&& params[:user][:email]
if #user.update(user_params)
#user.skip_reconfirmation!
# sign_in(#user, :bypass => true)
# redirect_to root_path, notice: 'Your profile was successfully updated.'
# redirect_to [#user, #user.profile || #user.build_profile]
sign_in_and_redirect(#user, :bypass => true)
else
#show_errors = true
end
end
end
# DELETE /users/:id.:format
def destroy
# authorize! :delete, #user
#user.destroy
respond_to do |format|
format.html { redirect_to root_url }
format.json { head :no_content }
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
# params.require(:user).permit(policy(#user).permitted_attributes)
accessible = [ :first_name, :last_name, :email, :avatar ] # extend with your own params
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
# accessible << [:approved] if user.admin
params.require(:user).permit(accessible)
end
end
omniauth callbacks controller
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
#user = User.find_for_oauth(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
# , current_user has been deleted from the end of line 51
#come back to put current_user into fidn by oauth so i can link other accounts - i have added this back for the purpose of solving the current problem
# puts current_user.inspect
# sign_in_and_redirect [#user, #user.profile || #user.build_profile]
# sign_in_and_redirect_user(:user, event: :authentication)
[:twitter, :facebook, :linkedin, :google_oauth2].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
end
registrations controller
class Users::RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
profile_path(resource)
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password )
end
end
Identity.rb
class Identity < ActiveRecord::Base
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
Identities controller
class IdentitiesController < ApplicationController
before_action :set_identity, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
# GET /identities
# GET /identities.json
def index
#identities = Identity.all
end
# GET /identities/1
# GET /identities/1.json
def show
end
# GET /identities/new
def new
#identity = Identity.new
end
# GET /identities/1/edit
def edit
end
# POST /identities
# POST /identities.json
def create
#identity = Identity.new(identity_params)
respond_to do |format|
if #identity.save
format.html { redirect_to #identity, notice: 'Identity was successfully created.' }
format.json { render :show, status: :created, location: #identity }
else
format.html { render :new }
format.json { render json: #identity.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /identities/1
# PATCH/PUT /identities/1.json
def update
respond_to do |format|
if #identity.update(identity_params)
format.html { redirect_to #identity, notice: 'Identity was successfully updated.' }
format.json { render :show, status: :ok, location: #identity }
else
format.html { render :edit }
format.json { render json: #identity.errors, status: :unprocessable_entity }
end
end
end
# DELETE /identities/1
# DELETE /identities/1.json
def destroy
#identity.destroy
respond_to do |format|
format.html { redirect_to identities_url, notice: 'Identity was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_identity
#identity = Identity.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def identity_params
params[:identity]
end
end
devise mailer - confirmation
<div class="intpol3"><%= link_to 'Confirm my account', confirmation_url(#resource, confirmation_token: #token) %></div>
SUMMARY OF CURRENT PROBLEMS:
In development mode: There is a problem with the link to the confirmation token. I can't find any materials that indicate why this might arise. The error is:(ActionView::Template::Error (undefined method `match' for {:host=>"localhost", :port=>3000}:Hash):
In production mode, there is an error with user looking for profile id. The error message is: ActionView::Template::Error (No route matches {:action=>"show", :controller=>"profiles", :id=>nil} missing required keys: [:id]):
My profiles routes are:
resources :profiles, only: [:show, :edit, :update, :destroy]
resources :users do
resources :profiles, only: [:new, :create]
end
None of the fields in the identity model are populating. They are all showing as nil.
THINGS DONE DIFFERENTLY THAN AS SHOWN IN THE TUTORIAL:
I also allow email sign up
I add 'finish_sign_up' to the set_user before action in the users controller
I add g+ strategy (which means my gems are slightly different)
My new user method does not use raw info. It uses oauth processed info.
My redirect in the finish sign up method is slightly different, although I've commented that out and gone back to the way it is set out in the tutorial to try to get this working (although the above problems are repeating).
I'm going crazy trying to solve these problems. I'd say 3 years is way too long to be stuck on this problem. If anyone can help, I'd pay it forward 10x and then some. Thank you.
<div class="intpol3"><%= link_to 'Confirm my account',
confirmation_url(#resource, confirmation_token: #token) %></div>
Try resource instead of #resource. AFAIK it's only a helper_method, NOT an instance variable.
I think that will solve your problem fully in production. Since #resource is not the same as resource, it hasn't been set, and you're basically calling confirmation_url(nil, confirmation_token: #token), and that nil is getting passed through to the error message.
In development, there appears to be an additional issue, which most probably has to do with how you've configured config.action_mailer.default_url_options in config/environments/development.rb and most likely is raising the exception in ActionDispatch::Http::Url.build_host_url. I suspect you have something like:
config.action_mailer.default_url_options[:host] = { host: 'localhost', port: 9000 }
Change that to:
config.action_mailer.default_url_options[:host] = 'localhost:9000'
And see if that solves everything. If I'm wrong about how config.action_mailer.default_url_options is configured, please paste your config/environments/development.rb AND a full stack trace from your development error so we can help you further.
I have recently added confirmable to my rails application with devise. It is all working smoothly however when I create a new user I am getting the
signed_up: "Welcome! You have signed up successfully."
rather than
signed_up_but_unconfirmed:"A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
even though they are unconfirmed. Was there something I needed to do when adding confirmable to change this? Thanks!
UPDATE
I am using my own user controller and routing it like so below:
routes.rb
devise_for :users, :controllers => {:registrations => "users"}
devise_scope :user do
get 'users/:id' => 'users#show', as: "user"
end
You could have your routes with this
as :user do
patch '/users/confirmation' => 'users/confirmations#update', :via => :patch, :as => :update_user_confirmation
end
and have the confirmations_controller with the following:
class Users::ConfirmationsController < Devise::ConfirmationsController
skip_before_filter :authenticate_user!
before_action :check_if_current_user
# PUT /resource/confirmation
def update
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
#confirmable.attempt_set_password(params[:user])
if #confirmable.valid? && #confirmable.password_match?
do_confirm
else
do_show
#confirmable.errors.clear #so that we wont render :new
end
else
self.class.add_error_on(self, :email, :password_already_set)
end
end
if !#confirmable.errors.empty?
render 'devise/confirmations/new' #Change this if you don't have the views on default path
end
end
# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
do_show
else
do_confirm
end
end
if !#confirmable.errors.empty?
self.resource = #confirmable
render 'devise/confirmations/new' #Change this if you don't have the views on default path
end
end
protected
def with_unconfirmed_confirmable
original_token = params[:confirmation_token]
confirmation_token = Devise.token_generator.digest(User, :confirmation_token, original_token)
#confirmable = User.find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
if !#confirmable.new_record?
#confirmable.only_if_unconfirmed { yield }
end
end
def do_show
#confirmation_token = params[:confirmation_token]
#requires_password = true
self.resource = #confirmable
render 'devise/confirmations/show' #Change this if you don't have the views on default path
end
def do_confirm
#confirmable.confirm!
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, #confirmable)
end
def check_if_current_user
if current_user.present?
render :file => "#{Rails.root.to_s}/public/confirmed.html", :status => :unauthorized
end
end
end
Hope it is helpful.
This before_filter validates before update or create record if captcha is correct.
When it's incorrect, it takes me back to previous page but all of the input data will be gone....
How can I remain the input data that was typed in at previous page?
I'd like to use before_filter and apply these 2 actions 'update' and 'create'.
It should detect where the submit is come from and switches where to re-render 'new' or 'edit'
before_filter :simple_captcha_check, :only => [:update, :create]
def simple_captcha_check
if !simple_captcha_valid?
flash[:error] = 'Wrong Captcha!'
redirect_to :back
end
end
Assuming you're creating/updating an User model, your code can look like this:
def simple_captcha_check
if !simple_captcha_valid?
flash[:error] = 'Wrong Captcha!'
if request.put? # We came from an edit request
#user = User.find(params[:id])
#user.attributes = params[:user]
render :action => :edit
elsif request.post? # We came from a new request
#user = User.new params[:user]
render :action => :new
end
end
end
I have an issues with connecting loose end in oauth and authlogic.
I'm running rails 3.0.9 with authlogic working fine and I wanted to add on twitter login.
The issue that I'm having is that after logging in on twitter instead being redirected to call back url defined in twitter dev settings. The app redirects to top domain while appending this to the url
user_sessions?oauth_token=[t_o_k_e_n]
I don't have index action in user_sessions_controller.rb, so I get the action index couldn't be found error, but I can't decipher why I'm being redirected to this url?
My user_sessions.rb
class UserSession < Authlogic::Session::Base
# def to_key
# new_record? ? nil : [ self.send(self.class.primary_key) ]
# end
#
# def persisted?
# false
# end
#
def self.oauth_consumer
OAuth::Consumer.new("asdasdsad", "asdasdasdas",
{ :site=>"http://twitter.com",
:authorize_url => "http://twitter.com/oauth/authenticate"})
end
end
My user_sessions_controller.rb
class UserSessionsController < ApplicationController
# GET /user_sessions/new
# GET /user_sessions/new.xml
def new
#user_session = UserSession.new
end
# POST /user_sessions
# POST /user_sessions.xml
def create
#user_session = UserSession.new(params[:user_session])
#user_session.save do |result|
if result
flash[:notice] = "Login successful!"
redirect_back_or_default root_path
else
render :action => :new
end
end
# respond_to do |format|
# if #user_session.save
# format.html { redirect_to(root_path, :notice => 'User session was successfully created.') }
# format.xml { render :xml => #user_session, :status => :created, :location => #user_session }
# else
# format.html { render :action => "new" }
# format.xml { render :xml => #user_session.errors, :status => :unprocessable_entity }
# end
# end
end
# DELETE /user_sessions/1
# DELETE /user_sessions/1.xml
def destroy
#user_session = UserSession.find
#user_session.destroy
respond_to do |format|
format.html { redirect_to(root_path, :notice => 'Goodbye!') }
format.xml { head :ok }
end
end
end
I even tried adding
:oauth_callback => "http://127.0.0.1:3000/"
to the Consumer.new clause, but that didn't help.
Lastly, my routes.rb looks like this:
resources :users, :user_sessions
match 'login' => 'user_sessions#new', :as => :login
match 'logout' => 'user_sessions#destroy', :as => :logout
Anyone has any ideas on how to troubleshoot this or had a similar problem?
https://dev.twitter.com/sites/default/files/images_documentation/oauth_diagram.png defines quite clearly what you should send and get from Twitters Oauth Provider.
Are you sure you get a oauth_callback_confirmed in step B, if so you might wanna contact Twitter why they validate your oauth_callback then modify it
I'm trying to do a custom override of devise confirmations so that a new user creates a password after they receive a confirmation email. This is in the devise wiki and can be found here.
When I navigate to the confirmation link, I am, however confronted by the following error
uninitialized constant ConfirmationsController
I've seen this before when I flubbed the name of a controller class (left out an s or something similar), however I can't find anything like that here. The two relevant files I can think to present are my controller and my routes, relevant to devise.
Here's my controller:
class Users::ConfirmationsController < Devise::ConfirmationsController
# Remove the first skip_before_filter (:require_no_authentication) if you
# don't want to enable logged users to access the confirmation page.
skip_before_filter :require_no_authentication
skip_before_filter :authenticate_user!
# GET /resource/confirmation/new
def new
super
end
# POST /resource/confirmation
# def create
# super
# end
# GET /resource/confirmation?confirmation_token=abcdef
# PUT /resource/confirmation
def update
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
#confirmable.attempt_set_password(params[:user])
if #confirmable.valid? and #confirmable.password_match?
do_confirm
else
do_show
#confirmable.errors.clear #so that we wont render :new
end
else
#confirmable.errors.add(:email, :password_already_set)
end
end
if !#confirmable.errors.empty?
self.resource = #confirmable
render 'devise/confirmations/new' #Change this if you don't have the views on default path
end
end
# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
do_show
else
do_confirm
end
end
unless #confirmable.errors.empty?
self.resource = #confirmable
render 'devise/confirmations/new' #Change this if you don't have the views on default path
end
end
protected
# The path used after resending confirmation instructions.
def after_resending_confirmation_instructions_path_for(resource_name)
super(resource_name)
end
# The path used after confirmation.
def after_confirmation_path_for(resource_name, resource)
super(resource_name, resource)
end
def with_unconfirmed_confirmable
#confirmable = User.find_or_initialize_with_error_by(:confirmation_token, params[:confirmation_token])
if !#confirmable.new_record?
#confirmable.only_if_unconfirmed {yield}
end
end
def do_show
#confirmation_token = params[:confirmation_token]
#requires_password = true
self.resource = #confirmable
render 'devise/confirmations/show' #Change this if you don't have the views on default path
end
def do_confirm
#confirmable.confirm!
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, #confirmable)
end
end
end
And here's the routes relevant to devise:
devise_for :users, controllers: {
sessions: 'users/sessions',
confirmations: "confirmations"
}
as :user do
patch '/user/confirmation' => 'confirmations#update', :via => :patch, :as => :update_user_confirmation
end
Please feel free to ask for any other code that you think might be helpful. Thanks in advance for any ideas.
Shouldn't you be going to 'users/confirmations#update'? not 'confirmations#update' based on your class name of Users::ConfirmationsController
I normally wrap the routes in namespaces, but for simplicity, you should probably update your patch.