overriding password controller in devise not working - ruby-on-rails

I have problems when overriding passwords controller in devise. I do not want to sign in the user after password is changed so i thought of overriding the password controller and i tried as follows and got an error. I could not identify what the problem is, so please help me. The following is the passwords_controller.rb
class PasswordsController < Devise::PasswordsController
prepend_before_filter :require_no_authentication
# Render the #edit only if coming from a reset password email link
append_before_filter :assert_reset_token_passed, :only => :edit
def new
super
end
def create
super
end
def edit
self.resource = resource_class.new
resource.reset_password_token = params[:reset_password_token]
end
def update
self.resource = resource_class.reset_password_by_token(resource_params)
if resource.errors.empty?
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
set_flash_message(:notice, "New password has been saved")
redirect_to new_user_session_path
else
respond_with resource
end
end
protected
# The path used after sending reset password instructions
def after_sending_reset_password_instructions_path_for(resource_name)
new_session_path(resource_name)
end
# Check if a reset_password_token is provided in the request
def assert_reset_token_passed
if params[:reset_password_token].blank?
set_flash_message(:error, :no_token)
redirect_to new_session_path(resource_name)
end
end
# Check if proper Lockable module methods are present & unlock strategy
# allows to unlock resource on password reset
def unlockable?(resource)
resource.respond_to?(:unlock_access!) &&
resource.respond_to?(:unlock_strategy_enabled?) &&
resource.unlock_strategy_enabled?(:email)
end
end
and my routes is
devise_for :users, :controllers => { :passwords => 'passwords' }
and the error i get is
NameError in PasswordsController#update
undefined local variable or method `resource_params' for #<PasswordsController:0x000001008231c8>

Your 'resource_params' is undefined. May be you should get it as a parameter :
def update
self.resource = resource_class.reset_password_by_token(params[:resource_params])
....

Finally it works. I am using devise version 1.5.3 and it does not provide resource_params method so i copied the following from devise version 1.5.3 and it works.
self.resource =
resource_class.reset_password_by_token(params[resource_name])

Related

Rails 5 / Devise - delete users as admin

I'm using devise to manage authentication. I have a User model and Admin model. I want to be able to allow both users and admins soft delete user accounts.
I have implemented the soft delete for users and everything works well, however, adding functionality for admins results in a 401 unauthorized and a redirect to the user sign in page. I'm not exactly sure how to get around this.
So far I have:
config/routes.rb
...
devise_for :users
devise_scope :user do
resources :users, only: [:destroy], controller: 'members/registrations', as: :user_registration do
get 'cancel'
end
end
...
controllers/members/registrations_controller.rb
class Members::RegistrationsController < Devise::RegistrationsController
def destroy
#user = User.find(params[:id])
not_authorized unless authorized?
#user.soft_delete
user_post_destroy if is_current_user?
end
private
def authorized?
if signed_in?
is_current_user?
else
session[:session_id] == #user.author_session_token
end
end
def not_authorized
flash[:error] = t('errors.messages.not_authorized')
flash.keep
redirect_back(fallback_location: root_path)
end
def user_post_destroy
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
set_flash_message :notice, :destroyed
yield resource if block_given?
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
end
def is_current_user?
#user == current_user
end
end
models/user.rb
...
def soft_delete
update_attribute(:deleted_at, Time.current)
end
def active_for_authentication?
super && !deleted_at
end
def inactive_message
!deleted_at ? super : :deleted_account
end
...

How to sign_in user after devise email confirmation in Rails

I am trying from last 7 hours to auto signin the user after email confirmation but when I click the confirmation link then it says "Your email address has been successfully confirmed." but not sign in the user. I have written this code in my confirmations controller and route
devise_for :users, controllers: {confirmations: 'users/confirmations'}
class ConfirmationsController < Devise::ConfirmationsController
#private
def after_confirmation_path_for(resource_name, resource)
sign_in(resource)
render plain: "here"
redirect_to "/admins/view_account", notice: "User deleted."
end
end
Any help would be highly appreciated Thanks.
Notice that automatically sign in after e-mail confirmation used to be Devise's default behavior and was then changed (after 3.1) due to security measures as you can see more here and here.
If you still want to do it, depending on the Devise's version make sure to set the line below on the config/initializers/devise.rb file within your project:
config.allow_insecure_sign_in_after_confirmation=true
If you are using latest Devise version, you may have to instead of that, extend the default controller with this code on app/controllers/users/confirmations_controller.rb as a replacement of what you have mentioned above for controller code (please mind the namespaces and path mentioned):
class Users::ConfirmationsController < Devise::ConfirmationsController
def show
super do |resource|
sign_in(resource) if resource.errors.empty?
end
end
end
And make sure the code you pasted in the beginning of the question belongs in config/routes.rb:
devise_for :users, controllers: { confirmations: 'users/confirmations' }
Hope it helps!
I have solved it myself.
routes should be like this
devise_for :users, controllers: {confirmations: 'confirmations'} do
#put "confirm_user", to: "confirmations#confirm_user"
get "confirmation", to: "confirmations#after_confirmation_path_for"
end
Controller is like this
class ConfirmationsController < Devise::ConfirmationsController
#private
def after_confirmation_path_for(resource_name, resource)
sign_in(resource)
#render plain: "here"
#redirect_to "/admins/"
end
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
if resource.errors.empty?
set_flash_message(:notice, :confirmed) if is_flashing_format?
sign_in(resource) # <= THIS LINE ADDED
redirect_to "/your path/"
else
respond_with_navigational(resource.errors, :status => :unprocessable_entity){ render :new }
end
end
end

Override Devise controller -- only user with admin role to log in?

How do you override the Devise controller to only allow 'admins' to log in?
This is what I came up with:
class SessionsController < Devise::SessionsController
def create
if current_user.admin?
# tell the user "you can't do that"
else
super
end
end
end
but the user was able to log in (probably because 'current_admin' is not defined yet?). Here is the original devise controller action:
class Devise::SessionsController < DeviseController
prepend_before_filter :require_no_authentication, only: [:new, :create]
prepend_before_filter :allow_params_authentication!, only: :create
prepend_before_filter :verify_signed_out_user, only: :destroy
prepend_before_filter only: [:create, :destroy] { request.env["devise.skip_timeout"] = true }
...
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
...
end
Edit: I don't think I should change the session controller, I think I should add a strategy to Warden. I tried this and it still logs in non admin users:
config/initializers/custom_warden_strategies.rb:
Warden::Strategies.add(:admin_only) do
def authenticate!
resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
encrypted = false
if validate(resource) { encrypted = true; resource.valid_password?(password) }
if resource.admin?
remember_me(resource)
resource.after_database_authentication
success!(resource)
end
end
mapping.to.new.password = password if !encrypted && Devise.paranoid
fail(:not_found_in_database) unless resource
end
end
config\initializers\devise.rb
config.warden do |manager|
manager.default_strategies.unshift :admin_only
end
Give this a try:
class SessionsController < Devise::SessionsController
def create
super do
if !resource.admin?
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message :notice, :signed_out if signed_out && is_flashing_format?
respond_to_on_destroy
end
end
end
end
I found a solution, but it fails with my test suite (works when I manually test it though).
config/initializers/admin_only_initializer.rb
require 'devise/strategies/authenticatable'
module Devise
module Strategies
# Default strategy for signing in a user, based on their email and password in the database.
class AdminOnly < Authenticatable
def authenticate!
resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
encrypted = false
if validate(resource){ encrypted = true; resource.valid_password?(password) }
if resource.admin?
success!(resource)
else
fail!(:not_permitted)
end
end
mapping.to.new.password = password if !encrypted && Devise.paranoid
fail(:not_found_in_database) unless resource
end
end
end
end
Warden::Strategies.add(:admin_only, Devise::Strategies::AdminOnly)
config/initializers/devise.rb
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :admin_only
end
and the I18n string (config/locales/devise.en.yml):
en:
devise:
failure:
not_permitted: "You are not permitted to complete this action."
Why prevent non-admins from logging in and not just block some actions for non-admin users ? How do you make the difference between an admin and a simple user then ?
Remember, the one true security principle is DENY then ALLOW. So to make sure you application remains safe when you keep adding stuff (AGILE development for example), I suggest the following approach
application_controller.rb
class ApplicationController
before_action :authenticate_user!
# Security policy deny then access
before_filter :access_denied
# Actually I have refactorised below code in a separate security.rb module that I include in ApplicationController, but as you wish
def access_denied
if #denied and not #authorized
flash[:alert] = 'Unauthorized access'
flash[:info] = "Authorized entities : #{#authorized_entities.join(', ')}" if #authorized_entities
flash[:warning] = "Restricted to entities : #{#restricted_entities.join(', ')}" if #restricted_entities
render 'static_pages/home', :status => :unauthorized and return
false
end
end
def allow_access_to_administrators
(#authorized_entities ||= []) << "Administrators"
#authorized = true if administrateur_logged_in?
end
def administrateur_signed_in?
user_signed_in? and current_user.administrator? # Or whatever method you use to authenticate admins
end
end
Note that I use both #authorized and #denied.
I use #authorized generally for a class of users (like admins), whereas I set #denied if, for a class of users, I want to restrict to a subset.
Then I use
your_controller_reserved_for_admins.rb
prepend_before_filter :allow_access_to_administrators

How can I redirect a user to a specific page when registration fails?

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

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