redirect_to doesn't work properly - ruby-on-rails

I use devise and tried to do the next thing:
when the user sign in/up, I want to redirect him by his role_id (I let id of 1 for some users and 2 for the others).
if his role_id is 1, redirect him to tasksadmins_path, otherwise to workers_path.
so I tried something like:
routes.rb:
devise_for :users, :controllers => { :sessions => 'user_sessions'} do
get '/users/sign_out' => 'devise/sessions#destroy'
root to: "workers#index"
end
resources :tasksadmins
resources :workers
root to: "workers#index"
and this is my application_controller.rb:
class ApplicationController < ActionController::Base
include ApplicationHelper
protect_from_forgery
before_filter :authenticate_user!
rescue_from CanCan::AccessDenied do |exception|
if current_user.role_ids == [2]
redirect_to root_url
else
redirect_to tasksadmins_path
end
end
end

Devise has special method for this situation. You can overwrite after_sign_in_path_for. In ApplicationController
def after_sign_in_path_for(resource_or_scope)
if resource_or_scope.is_a?(User)
town_path
else
users_path
end
end

the after_sign_in_path_for doesn't work, so I added to 'create' the next lines:
in the beginning, I wrote:
resource = warden.authenticate!(:scope => resource_name)
and then I wrote in the end of the 'create' function:
sign_in(resource_name, resource)
if current_user.role_ids == [2]
respond_with resource, :location => workers_path
else
respond_with resource, :location => tasksadmins_path
end
so my create looks so:
class UserSessionsController < Devise::SessionsController
include ApplicationHelper
def create
resource = warden.authenticate!(:scope => resource_name)
require "uri"
require "net/http"
## get the user id from the database
user_id = session['warden.user.user.key'][1][0];
## get the row by id
user = User.find(user_id)
# ============================
# Ensure that a user exists
# ============================
code, body = http_request(Net::HTTP::Put.new("/api/v1/users/external/#{user_id}"), email: user.email);
if code != 200
Rails.logger.error("Unable to register user #{current_user.email} at Licensario");
end
sign_in(resource_name, resource)
if current_user.role_ids == [2]
respond_with resource, :location => workers_path
else
respond_with resource, :location => tasksadmins_path
end
end
end

Related

Custom devise controller is redirecting to wrong path

My sign up routes are users/sign_up/speed1 etc. However if there is a devise error such as password does not match it redirects the user to '/users' path. Functionality it should redirect back to the same page and let the user re-enter a new password. Any update on this?
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
super
end
def update
super
end
def speed1
build_resource
yield resource if block_given?
respond_with resource
end
def speed2
build_resource
yield resource if block_given?
respond_with resource
end
def speed3
build_resource
yield resource if block_given?
respond_with resource
end
def speed4
build_resource
yield resource if block_given?
respond_with resource
end
def speed5
build_resource
yield resource if block_given?
respond_with resource
end
protected
def after_sign_up_path_for(resource)
if current_user.role == 'speed1' || current_user.role == 'speed2' || current_user.role == 'speed3' || current_user.role == 'speed4' || current_user.role == 'speed5'
'/subscription/new' # Or :prefix_to_your_route
else
'/'
end
end
end
Here is my route file that I have
devise_for :users, :controllers => {:registrations => "registrations"}
devise_scope :user do
get 'users/sign_up/speed1', to: 'registrations#speed1'
get 'users/sign_up/speed2', to: 'registrations#speed2'
get 'users/sign_up/speed3', to: 'registrations#speed3'
get 'users/sign_up/speed4', to: 'registrations#speed4'
get 'users/sign_up/speed5', to: 'registrations#speed5'
end
You can add this to your application_controller.rb, it will return to the last page the user visited, or root_path
private
# If your model is called User
def after_sign_in_path_for(resource)
session["user_return_to"] || root_path
end

RESTful login with devise (Rails 4)

How do I do RESTful sign-up and sign-in using devise in Ruby on Rails (I am using version 4)?
I could not find any documentation with regards to the parameters (e.g. email, password) that I should POST to the server.
It seems that RESTful login using JSON data (e.g. via AJAX) is not supported out of the box in the current version of devise - the default behavior is to send back a whole HTML page for any type of request made, instead of a JSON object for handling JSON request specifically.
Does this mean that I need to create/extend custom controller for handling user registration and login for RESTful apps using JSON? If so, please elaborate.
If you're using Devise, all of the RESTful actions have been provided through their forms & controller (if you browse to http://your-app.com/users/sign_up, you'll be able to register etc)
If you're looking to make your Devise handle JSON, you are correct in that it's not handled "out of the box", but fortunately, there is a way to do it:
Live Code
#config/routes (notice custom controllers)
devise_for :users, :path => '', :controllers => {:sessions => 'sessions', :registrations => 'registrations'}
#app/controllers/registrations_controller.rb
class RegistrationsController < DeviseController
prepend_before_filter :require_no_authentication, :only => [ :new, :create, :cancel ]
prepend_before_filter :authenticate_scope!, :only => [:edit, :update, :destroy]
before_filter :configure_permitted_parameters
prepend_view_path 'app/views/devise'
# GET /resource/sign_up
def new
build_resource({})
respond_with self.resource
end
# POST /resource
def create
build_resource(sign_up_params)
if resource.save
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
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}" if is_navigational_format?
expire_session_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
respond_to do |format|
format.json { render :json => resource.errors, :status => :unprocessable_entity }
format.html { respond_with resource }
end
end
end
# GET /resource/edit
def edit
render :edit
end
# PUT /resource
# We need to use a copy of the resource because we don't want to change
# the current user in place.
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
if update_resource(resource, account_update_params)
if is_navigational_format?
flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ?
:update_needs_confirmation : :updated
set_flash_message :notice, flash_key
end
sign_in resource_name, resource, :bypass => true
respond_with resource, :location => after_update_path_for(resource)
else
clean_up_passwords resource
respond_with resource
end
end
# DELETE /resource
def destroy
resource.destroy
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
set_flash_message :notice, :destroyed if is_navigational_format?
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
def cancel
expire_session_data_after_sign_in!
redirect_to new_registration_path(resource_name)
end
protected
# Custom Fields
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:first_name, :last_name,
:email, :password, :password_confirmation)
end
end
def update_needs_confirmation?(resource, previous)
resource.respond_to?(:pending_reconfirmation?) &&
resource.pending_reconfirmation? &&
previous != resource.unconfirmed_email
end
# By default we want to require a password checks on update.
# You can overwrite this method in your own RegistrationsController.
def update_resource(resource, params)
resource.update_with_password(params)
end
# Build a devise resource passing in the session. Useful to move
# temporary session data to the newly created user.
def build_resource(hash=nil)
self.resource = resource_class.new_with_session(hash || {}, session)
end
# Signs in a user on sign up. You can overwrite this method in your own
# RegistrationsController.
def sign_up(resource_name, resource)
sign_in(resource_name, resource)
end
# The path used after sign up. You need to overwrite this method
# in your own RegistrationsController.
def after_sign_up_path_for(resource)
after_sign_in_path_for(resource)
end
# The path used after sign up for inactive accounts. You need to overwrite
# this method in your own RegistrationsController.
def after_inactive_sign_up_path_for(resource)
respond_to?(:root_path) ? root_path : "/"
end
# The default url to be used after updating a resource. You need to overwrite
# this method in your own RegistrationsController.
def after_update_path_for(resource)
signed_in_root_path(resource)
end
# Authenticates the current scope and gets the current resource from the session.
def authenticate_scope!
send(:"authenticate_#{resource_name}!", :force => true)
self.resource = send(:"current_#{resource_name}")
end
def sign_up_params
devise_parameter_sanitizer.sanitize(:sign_up)
end
def account_update_params
devise_parameter_sanitizer.sanitize(:account_update)
end
end
This is probably excessive, but it works (try registering or logging in at http://firststop.herokuapp.com). You could probably achieve the same thing with this code (completely untested):
def create #-> completely untested & speculative
super do |format|
format.json { render :json => resource.errors, :status => :unprocessable_entity }
format.html { respond_with resource }
end
end
Login Code
class SessionsController < DeviseController
prepend_before_filter :require_no_authentication, :only => [ :new, :create ]
prepend_before_filter :allow_params_authentication!, :only => :create
prepend_before_filter { request.env["devise.skip_timeout"] = true }
prepend_view_path 'app/views/devise'
# GET /resource/sign_in
def new
self.resource = resource_class.new(sign_in_params)
clean_up_passwords(resource)
respond_with(resource, serialize_options(resource))
end
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_to do |format|
format.json { render :json => {}, :status => :ok }
format.html { respond_with resource, :location => after_sign_in_path_for(resource) }
end
end
# DELETE /resource/sign_out
def destroy
redirect_path = after_sign_out_path_for(resource_name)
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message :notice, :signed_out if signed_out && is_navigational_format?
# We actually need to hardcode this as Rails default responder doesn't
# support returning empty response on GET request
respond_to do |format|
format.all { head :no_content }
format.any(*navigational_formats) { redirect_to redirect_path }
end
end
protected
def sign_in_params
devise_parameter_sanitizer.sanitize(:sign_in)
end
def serialize_options(resource)
methods = resource_class.authentication_keys.dup
methods = methods.keys if methods.is_a?(Hash)
methods << :password if resource.respond_to?(:password)
{ :methods => methods, :only => [:password] }
end
def auth_options
{ :scope => resource_name, :recall => "#{controller_path}#new" }
end
end

Devise how to redirect to different page (based on some parameter) after sign in?

In my application, I have two different login forms from two controller which will both sign_in through the Devise::SessionsController, the problem is after successful sign in (or failure) I need to redirect to different pages that specific to the controller. How can I do this. I currently have this in my Devise::SessionsController, which
class SessionsController < Devise::SessionsController
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
return sign_in_and_redirect(resource_name, resource)
end
def sign_in_and_redirect(resource_or_scope, resource=nil)
scope = Devise::Mapping.find_scope!(resource_or_scope)
resource ||= resource_or_scope
sign_in(scope, resource) unless warden.user(scope) == resource
redirect_to dashboard_path
end
def failure
redirect_to index_path
end
end
In application controller
before_filter :store_location
def store_location
unless params[:controller] == "devise/sessions"
url = #calculate the url here based on a params[:token] which you passed in
session[:user_return_to] = url
end
end
def stored_location_for(resource_or_scope)
session[:user_return_to] || super
end
def after_sign_in_path_for(resource)
stored_location_for(resource) || root_path
end
If anyone is still looking for a work around. I was able to solve this by:
First, create a Sessions controller inheriting from Devise::SessionsController
class SessionsController < Devise::SessionsController
def new
get_pre_login_url(request.referer)
super
end
def create
##referer_url
super
end
private
def get_pre_login_url(url)
##referer_url = url
end
def after_sign_in_path_for(resource)
# ##referer_url
sign_in_url = url_for(:action => 'new', :controller => 'sessions', :only_path => false, :protocol => 'http')
if ##referer_url == sign_in_url
super
else
stored_location_for(resource) || ##referer_url || root_path
end
end
end
You'll notice I'm setting a class variable (##variable_name), which I'm not keen on, but this is what I came up with after 4 hours of trying various other ways of solving this. I'm also trying to be careful not to screw with Devise's controller too much and using super and only including the actions I care about.
Next, in routes you can now point Devise's defaults to the Controller above. You don't need it exactly like below for the devise_for, just the part referencing the controllers: { sessions: "sessions" }
MyPortfolio::Application.routes.draw do
devise_for :users, path_names: { sign_in: "login",
sign_out: "logout" },
controllers: { omniauth_callbacks: "omniauth_callbacks",
sessions: "sessions" }
resources :posts do
resources :comments
end
resources :projects do
resources :comments
end
resources :users
root :to => 'home#index'
get 'login' => 'devise/sessions#new'
get 'about_me' => 'about#index'
end
It might not be the DRYest solution around, but it was the only one that I was able to come up with to point the redirect back to the page of origin instead of root or an infinite loop.
You can do this by defining after_sign_in_path_for method in your controller, where you want to customize this redirect path.
The following is more global solution but it can be used in your case.
It uses attr_accessor. No extra params to after_sign_in_path_for action and no class variable required.
class User < ActiveRecord::Base
attr_accessor :redirect_to
end
Then, with a dedicated sessions_controller file (override as recommended by Devise), copy-paste the original devise action and add the line resource.redirect_to = sign_in_params[:redirect_to] before respond_with.
Add the new redirect to the after_sign_in_path_for action and add the :redirect_to params to devise allowed signin params.
# app/controllers/devise/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
before_filter :configure_sign_in_params, only: [:create]
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message!(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
# set redirect_to
resource.redirect_to = sign_in_params[:redirect_to]
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
# set url to redirect_to url if present
def after_sign_in_path_for(resource_or_scope)
return resource_or_scope.redirect_to if resource_or_scope.redirect_to.present? && !resource_or_scope.respond_to?(:devise_scope)
stored_location_for(resource_or_scope) || signed_in_root_path(resource_or_scope)
end
# add redirect_to to devise signin allowed params
def configure_sign_in_params
devise_parameter_sanitizer.for(:sign_in) << :redirect_to
end
end
Finally, update your view to set a hidden param for the redirect_to url
# app/views/users/sessions/new.html.haml
# from params in url
<% if (param_redirect_to = params[:redirect_to]).present?
<%= f.hidden_field :redirect_to, value: param_redirect_to %>
<% end %>
# or hardcoded if you have 2 sign-in forms
<%= f.hidden_field :redirect_to, value: 'your custom redirect url' %>
GET http://localhost:3000/login?redirect_to=www.test.com will redirect your user to www.test.com after successful signing

How do I login a user with devise?

I have my rails application and I am running into a major issue with devise. I have a controller:
class Users::SessionsController < Devise::SessionsController
prepend_before_filter :require_no_authentication, :only => [ :new, :create ]
include Devise::Controllers::InternalHelpers
def new
clean_up_passwords(build_resource)
respond_to do |format|
format.html { render :layout => "sessions" }
format.mobile
end
end
# POST /resource/sign_in
def create
resource = User.find_by_email(params[:user][:email])
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
set_flash_message :notice, :signed_in
sign_in_and_redirect(resource_name, resource)
end
end
The problem is it never logs the user in, it always stops at this line
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
I even put tons of loggers in the actual gem files to see if I could see anything off but nothing and I really have no idea how to fix this. If I comment this line out then the user gets logged in but fails if the email is not in the db and works for any password (which is definitely not the right solution)
How do I fix this?
UPDATE
this works but seems very hackish
# POST /resource/sign_in
def create
resource = User.find_by_email(params[:user][:email])
redirect_to(new_user_session_path, :notice => 'Invalid Email Address or Password. Password is case sensitive.') and return if resource.encrypted_password.blank?
bcrypt = BCrypt::Password.new(resource.encrypted_password)
password = BCrypt::Engine.hash_secret("#{params[:user][:password]}#{resource.class.pepper}", bcrypt.salt)
valid = Devise.secure_compare(password, resource.encrypted_password)
# resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
if valid
set_flash_message :notice, :signed_in
sign_in_and_redirect(resource_name, resource)
else
redirect_to(new_user_session_path, :notice => 'Invalid Email Address or Password. Password is case sensitive.') and return
end
end
If you want to sign in a user, use the sign_in helper inside your controller's action:
sign_in(:user, user)
resource = warden.authenticate!(:scope => resource_name)
sign_in(resource_name, resource)
I found this post useful for setting up a login for request specs.
https://makandracards.com/makandra/37161-rspec-devise-how-to-sign-in-users-in-request-specs
module DeviseRequestSpecHelpers
include Warden::Test::Helpers
def sign_in(resource_or_scope, resource = nil)
resource ||= resource_or_scope
scope = Devise::Mapping.find_scope!(resource_or_scope)
login_as(resource, scope: scope)
end
def sign_out(resource_or_scope)
scope = Devise::Mapping.find_scope!(resource_or_scope)
logout(scope)
end
end
Include it in your spec_helper
RSpec.configure do |config|
config.include DeviseRequestSpecHelpers, type: :request
end
And sign in as needed
sign_in create(:user, name: 'John Doe')
Here is how standard create actions works:
# POST /resource/sign_in
def create
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)
end
https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb#L18

Uninitialized Constant ConfirmationsController

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.

Resources