Using before_action :authenticate_user! to check if user logged in. But it sends users to login instead of signup.
Tried different ways of directing user to signup instead of login, but they do not send the user back to the original page after successful signup.
How can I send a user to signup and direct a user back to the original page afterwards?
Attempts:
before_filter :auth_user
def auth_user
redirect_to new_user_registration_url unless user_signed_in?
end
Routes File
Rails.application.routes.draw do
devise_for :installs
resources :orders
resources :products
devise_for :users
get 'dashboard' => 'pages#dashboard'
get 'contact' => 'pages#contact'
get 'cart' => 'carts#index'
root 'pages#home'
What you are looking for is a referer.
Having your before_filter working, you can override the Devise's after_sign_up_path_for:
def after_sign_up_path_for(user)
request.referer
end
EDIT
Since request.referer is somehow not working, I think you could go with a session-based solution:
def auth_user
session[:before_sign_up_path] = request.fullpath
redirect_to new_user_registration_url unless user_signed_in?
end
def after_sign_up_path_for(user)
session[:before_sign_up_path]
end
Devise uses warden for authentication. Thus to override the whole behavior you have to override the way devise asks it to handle authentication failures.
Create a class that extends Devise::FailureApp
class RedirectToSignUp < Devise::FailureApp
def redirect_url
new_user_registration_url
end
def respond
if http_auth?
http_auth
else
redirect_to new_user_registration_url
end
end
end
Add this to your devise initializer config/initializers/devise.rb
config.warden do |manager|
manager.failure_app = RedirectToSignUp
end
I believe this will solve your problem and will redirect you to the page you were at since you are overriding only the redirect route.
I haven't tried it but this seems to be what you are looking for. Note that it says that it is out of date. So you should see the source code if you want to understand what's going on.
Navigating through the source code of Devise(this and this) my guess is that you can extend Devise::RegistrationsController and then override the method after_sign_up_path_for to be stored_location_for(:user) || root_path.
the way I like to set up my users area using devise is:
# config/routes.rb
devise_for :users, :controllers => {
registrations: 'users/registrations',
sessions: "users/sessions",
passwords: 'users/passwords',
confirmations: 'users/confirmations'
}
authenticate :provider do
namespace :providers do
....
end
end
Then I have a controller that manages all other users controllers like this
#app/controllers/user_controller.rb
class UserController < ApplicationController
before_filter :authenticate_user!
layout 'users/default'
before_filter :check_user_active
private
def check_user_active
unless current_user.active
flash[:notice]= t(:user_not_active)
sign_out current_user
redirect_to new_user_session_path
end
end
end
all my other user controllers look inherit from it like
#app/controllers/users/users_controller.rb
class Users::UsersController < UserController
...
end
I hope that this helps.
This is very simple
User store_location_for method
Like
before_filter :auth_user
def auth_user
unless user_signed_in?
store_location_for(:user, request.fullpath)
redirect_to new_user_registration_url
end
end
Related
I have Devise Admin & Devise User;
I want to use namespaces;
What I want to achieve:
only devise admin can create devise user
registerable for user is not deleted so that he can edit only page
user can see only current_user/show page
What I have
routes:
Rails.application.routes.draw do root :to => 'dashboard#index'
devise_for :users, controllers: { registrations: 'user_registrations' }
devise_for :admins, controllers: { registrations: 'admin_registrations' }
get 'dashboard/index'
namespace :admin do
root 'dashboard#index'
resources :users
end
user_registration_controller:
class UserRegistrationsController < Devise::RegistrationsController
end
users_controller:
class UsersController < ApplicationController
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def edit
end
def create
#user = User.new(user_params)
respond_to do |format|
if #guest.save
format.html { redirect_to users_path }
else
format.html { render :new }
end
end
end
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user }
else
format.html { render :edit }
end
end
end
def destroy
user = User.find(params[:id])
user.destroy
redirect_to users_path
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
+ I have user views as they would be in a normal scaffold.
=> With this setup, anyone can create a user
Any ideas how to solve the questions on top?..
Don't use separate user classes with Devise, use roles instead. Devise is only really made to authenticate a single class, while you can hack it into using two classes its a mess. You have to override all the logic of serializing/desearializing users from the session among other things so that devise knows if it should load the Admin or User class.
Its also a bad solution since you are push down a authorization problem into the authentication layer. Devise's job is to verify that the user is who she/he claims to be, which is no small feat. Authorization, on the other hand is rules about what a user can do. "Only admins can create users" is a clear cut authorization rule.
The simplest possible role based authorization would be something like this:
class AddRoleToUser < ActiveRecord::Migration
def change
add_column :users, :role, :integer, default: 0
add_index :users, :role
end
end
class User
# ...
enum role: [:visitor, :admin]
end
We use an enum which is a single bitmask column to store the users role. Declaring it as an enum column also gives us a few methods for free:
user.visitor?
user.admin?
user.admin!
So lets create a basic authorization check:
def create
unless current_user.admin?
redirect_to root_path, status: 401, error: 'You are not authorized to perform this action' and return
end
# ...
end
But we don't want to repeat that every time we want to authorize, so lets clean it up:
class AuthorizationError < StandardError; end
class ApplicationController
rescue_from AuthorizationError, with: :deny_access!
private
def authorize_admin!
raise AuthorizationError, unless current_user.admin?
end
def deny_access!
redirect_to root_path,
status: 401,
error: 'You are not authorized to perform this action'
end
end
So then we can setup the controller with a filter to check the authorization before the action is performed:
class UsersController < ApplicationController
before_action :authorize_admin!, except: [:show]
# ...
end
However instead of reinventing the wheel you might want to have a look at Pundit or CanCanCan which are solid authorization libraries with great communities. You also might want to look at Rolify.
I want to allow Users to have two edit pages, one which is the default Devise page that requires a password and the other edit page which allows them to edit some parts of their profile without a password.
I have seen their guide pages like this which allows them to edit their profile without providing a password. However I want to have both options available to the user instead of just one.
How would I go about doing this?
My Attempt
I have tried to create my own update route in the Users controller which will solve the problem but this creates a problem when a User resets their password as it gets routed to the User#update which will cause an error as there is no User available during the password reset in Devise.
class UsersController < ApplicationController
before_action :authenticate_user!, :only => [:crop] #:edit , :update
def show
#user = User.find(params[:id])
authorize #user
end
def update
#user = User.find(params[:id])
authorize #user
if #user.update(user_params)
flash[:success] = "You have successfully updated your profile!"
redirect_to user_path(#user)
else
render :edit
end
end
def crop
#user = User.find(params[:id])
authorize #user
end
def index
end
private
def user_params
params.require(:user).permit(:poster_image_crop_x, :poster_image_crop_y, :poster_image_crop_w, :poster_image_crop_h)
end
end
Routes
Rails.application.routes.draw do
resources :users,only: [:show] do
member do
get :crop
end
end
devise_for :users, :controllers => { :omniauth_callbacks => "callbacks",:registrations => :registrations,:passwords => "passwords" }
as :user do
get "/login" => "devise/sessions#new"
get "/register" => "devise/registrations#new"
get "/edit" => "devise/registrations#edit"
delete "/logout" => "devise/sessions#destroy"
end
The code by Devise is suggesting to create your own controller. They probably always require password to be passed if it comes from an action in the UsersController. So you should create a seperate controller, let's call it ProfilesController, this controller is like your normal controller although it does not update a Profile model, but the User model directly... nothing special actually, just check authorization and let the User update any field you'd like directly on the User mode, do not forget to authorize the fields you'd wish to let the user update:
class ProfilesController < ApplicationController
def index
end
....... more code
def update
#user = User.find(params[:id])
authorize #user
if #user.update(user_params)
flash[:success] = "You have successfully updated your profile!"
redirect_to user_path(#user)
else
render :edit
end
end
....... more code
private
def user_params
params.require(:user).permit(:poster_image_crop_x, :poster_image_crop_y, :poster_image_crop_w, :poster_image_crop_h)
end
end
And add resources :profiles to your routes file.
I have a Rails app which uses Devise for user sign up / authentication. Both the sign up form and the login form are at the root of my domain.
When user registration fails (for example because they enter an email address which is already taken), by default Devise redirects to /users.
How can I change that? I would like the user to be directed to /
I have implemented this successfully for a failed login attempt, with the following code:
class CustomFailure < Devise::FailureApp
def redirect_url
"/"
end
def respond
if http_auth?
http_auth
else
redirect
end
end
end
and:
config.warden do |manager|
manager.failure_app = CustomFailure
end
As detailed on the project's homepage.
Is there any way to extend / alter this so that failed registrations also redirect to the root of my domain?
I'm using Ruby 2.2.0, Rails 4.2.0 and Devise 3.4.1.
You will probably need to subclass Devise::RegistrationsController and override the create action. Just copy over the create method from here and modify the redirect on failure to save.
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def create
build_resource
if resource.save
set_flash_message :notice, :inactive_signed_up, :reason => inactive_reason(resource) 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_with_navigational(resource) { render_with_scope :new }
end
end
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)
new_user_session_path
end
Change your routes to tell Devise to use your controller:
# config/routes.rb
devise_for :users, :controllers => {:registrations => "registrations"}
I believe you can take a look at this question. You can override Devise RegistrationsController and add your redirect_to method to an else when User is not saved.
For example:
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
if #user.save?
#something
else
redirect_to your_path, error: 'Registration failed'
end
Devise is behaving strangely. It shows the red or the error messages, but it does not show the green or success messages.
This happened after I made changes to the routing and controller to redirect to sign-in page or home page according to whether the user is signed-in or not.
So the routes.rb now has:
devise_for :users
root :to => "questions#redirect_on_visit"
match 'home', :to => "questions#index"
and the controller:
before_filter :authenticate_user!, :except => [:redirect_on_visit]
def redirect_on_visit
if user_signed_in?
redirect_to home_path
else
redirect_to new_user_session_path
end
end
Also changed application controller to redirect to appropriate pages after sign-in/sign-out:
protected
def after_sign_in_path_for(resource)
stored_location_for(:user) || root_path
end
private
def after_sign_out_path_for(resource)
stored_location_for(:user) || root_path
end
I was being extremely numb. The fact that I have root :to => "questions#redirect_on_visit", will redirect each time a user visits, signs in or signs out. Redirect was clearly washing away the flash messages.
Finally I found the solution I was looking for. It is to preserve the flash messages flash.keep(:notice) as mentioned here.
Your problem is the multiple redirection from login => root => home
as flash object has a life leave only for a single request
I guess It would be better of writing redirect_on_visit routing in after_sign_in_path_for and after_sign_out_path_for
something like this
def after_sign_in_path_for(resource)
stored_location_for(:user) || home_path
end
private
def after_sign_out_path_for(resource)
stored_location_for(:user) || new_user_session_path
end
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