how to authenticate the user with warden/devise without password? - ruby-on-rails

In my app during login, user will provide only email, password is not required.I want to authenticate it using devise/warden.
class Users::SessionsController < Devise::SessionsController
include ExpireExistingToken
def new
super
end
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message!(:notice, :signed_in)
sign_in(resource_name, resource)
yield resource if block_given?
resource.update(language: Constant::SUPPORTED_LANGUAGES.key(params['locale'])) if params['locale'].present?
resource.send_otp(force_expire: true) if resource.email_verified?
respond_with resource, location: after_sign_in_path_for(resource)
flash.delete(:notice)
end
def destroy
session.delete(:is_otp_verified)
super
end
end
# frozen_string_literal: true
# Responsible for token expiration
module ExpireExistingToken
extend ActiveSupport::Concern
included do
before_action :remove_existing_token, only: :new
end
protected
def remove_existing_token
uuid = session[:uuid]
usr = User.find_by(uuid: uuid) if uuid
return unless usr
usr.send(:clear_reset_password_token)
usr.save
session.delete(:uuid)
end
end
I tried to override valid_password? in User model, but it didn't work. Please help me out on this.

You can try to override Devise password required validation method and return false during save. If you want to disable it only for resource or functionality specific, you need to send virtual attribute from form during save.
class User < ActiveRecord::Base
attr_accessor :skip_validation
protected
def password_required?
return false if skip_validation
super
end
end

Related

undefined method `attributes' for nil:NilClass - Wicked/Devise gems

I'm trying to implement Wicked gem with Devise as I want users to go through different steps in order to complete their profiles. I'm a complete newbie so I would appreciate if you can give me a suggestion on what could be the problem.
The error I'm getting is this one and it shows when I try to continue from "Personal" to "Style" step. I guess it's a problem with saving the data:
NoMethodError in OnboardingController#update
undefined method `attributes' for nil:NilClass
**#user.attributes(user_params)**
These are my registration and onboarding controllers:
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
'/onboarding/personal'
end
def after_update_path_for(resource)
registration_steps_path
end
def new
super
end
def create
super
end
def update
super
end
def update_resource(resource, params)
if resource.encrypted_password.blank? # || params[:password].blank?
resource.email = params[:email] if params[:email]
if !params[:password].blank? && params[:password] == params[:password_confirmation]
logger.info "Updating password"
resource.password = params[:password]
resource.save
end
if resource.valid?
resource.update_without_password(params)
end
else
resource.update_with_password(params)
end
end
end
and
class OnboardingController < ApplicationController
include Wicked::Wizard
steps :personal, :stylefirst
def show
#user = current_user
render_wizard
end
def update
#user = current_user
#user.attributes(user_params)
render_wizard #user
end
end
With Devise, current_user is nil if no user is logged in. So your problem is that you're assigning #user = current_user on your update action without verifying a user is signed in.
If you want to ensure the update action is only available to signed in users then use the authenticate_user! helper action provided by Devise:
class OnboardingController < ApplicationController
before_filter :authenticate_user!, only: [:edit, :update]
# ...
end
The authenticate_user! helper method will redirect the user to the sign in page if they're not logged in. If the user successfully signs in, current_user will be set and they will be redirected back to the page the originally tried to access.

pundit_user: undefined method `current_user' for #<User:0x007fcefbc2b150>

I have two layouts Admin and Domain. And I don't need any extra configuration in Admin layout. but if user tries to access Domain layout they must be in their valid domain.
This means that, I need to customize all of my Domain policy to include both current_user as well as current_domain. I found this can be done with UserContext and pundit_user... so here is what I have done:
application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def pundit_user
UserContext.new(current_user, current_domain)
end
def after_sign_out_path_for(resource)
root_path
end
def current_domain
#current_domain ||= Domain.where(name: requested_domain).first
end
helper_method :current_domain
private
def requested_domain
return request.env["SERVER_NAME"]
end
def user_not_authorized
# reset_session
flash[:alert] = "You are not authorized to perform this action"
redirect_to(request.referrer || root_path)
end
end
Note that, when I access Admin layout, current_domain will be nil and if I visit any routes of Domain layout, then current_domain will set to currently accessing domain.
user_context.rb
class UserContext
attr_reader :current_user, :current_domain
def initialize(current_user, current_domain)
#current_user = current_user
#current_domain = current_domain
end
end
PROBLEM
Suppose I have this policy:
user_policy.rb
class UserPolicy < ApplicationPolicy
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def index?
binding.pry # debugging
current_user.admin? ||
current_user.domain == current_domain
end
private
def current_user
# return user.is_a?(User) ? user : user.current_user
user.current_user
end
def current_domain
# return user.is_a?(User) ? nil : user.current_domain
user.current_domain
end
end
when application runs current_user and current_domain must available in UserPolicy as per documentation(https://github.com/elabs/pundit#additional-context).
But I am getting
undefined method `current_user' for #<User:0x007fcefbc2b150>
That means, still I have user object in it, not user.current_user and user.current_domain
Please let me know, if you need further description. What am I missing here?
It was my own dumb mistake.
PROBLEM
I had a before_filter call in domain/base_controller.rb something like:
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# PROBLEM: this line was updating pundit_user again to user object
raise Pundit::NotAuthorizedError unless DomainConsolePolicy.new(current_user, current_domain).authorized?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
SOLUTION:
I have used headless policy for this because now I have both current_user and current_domain set with pundit_user in application_controller
domain/base_controller.rb
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# SOLUTION
authorize :domain_console, :has_access?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
policy/domain_console_policy.rb
class DomainConsolePolicy < Struct.new(:user, :domain_console)
def has_access?
user.current_user.admin? ||
user.current_user.domain_id == user.current_domain.id
end
end
Thanks

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

Devise, all requests return 401 after updating Rails (4.1.7 to 4.1.10)

Problems with Devise!
I've been messing with it all weekend to no avail, maybe one of you guys know what's up.
I can successfully create and sign in with a user returning a 201 and the all the proper stuff (user_token and email_token), however any requests after the sign_in are rejected with a 401.
#app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include ActionController::MimeResponds
include ActionController::HttpAuthentication::Token::ControllerMethods
include AuthenticatorHelper
# # Prevent CSRF attacks by raising an exception.
# # For APIs, you may want to use :null_session instead.
# protect_from_forgery with: :exception
helper_method :after_actions
# Use Devise to authenticate user before every action
before_filter :authenticate_user!
private
def id_filters
p = params.permit(ids:[])
return nil if p.blank?
p[:ids].map {|x| x.to_i}
end
end
The code enters the authenticate user action and then stops working. The helper I have included is this:
# Authenticates users by their authentication token and email if present.
# https://github.com/simplabs/ember-simple-auth/tree/master/packages/ember-simple-auth-devise#user-content-server-side-setup
module AuthenticatorHelper
def self.included(base)
unless base.instance_of? Module
base.prepend_before_filter :authenticate_user_from_token!
end
end
def authenticate_user_from_token!
authenticate_with_http_token do |token, options|
user_email = options[:user_email].presence
user = user_email && User.find_by_email(user_email)
if user && Devise.secure_compare(user.authentication_token, token)
sign_in user, store: false
end
end
end
end
The Ember Application that sits above my Rails App is sending over the proper headers each time: Token user_token="FEHR4ZpTGzsUSFvyzyKv", user_email="taylor#yolo.com" and was not changed when I updated Rails.
Another weird thing I noticed when I entered the debugger is the before_ filter :authenticate_user is executed before the sign_in my SessionsController. And here is the code for that Controller
#app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
def create
respond_to do |format|
format.html { super }
format.json do
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
data = {
user_token: self.resource.authentication_token,
user_email: self.resource.email
}
render json: data, status: 201
end
end
end
end
Does anyone have any ideas as to what changes to Rails could have caused this problem? Any hunches? Could something else have caused the issue? Any guesses would be greatly appreciated! Thanks in advance for any help on the issue, and let me know if I you want to see any more code!

Rails / Devise customize the create method

I generate a special code which is unique to each user and generated on registration/create. I want to store it in the DB on create.
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters
def create
# insert special code into instance and ensure that code is unique in database
super # continue to devise registration to CREATE user
end
protected
def special_code
( (0...8).map { char = (65 + rand(26)).chr; }[0..rand(2...4)] << rand(1..9) ).join.downcase
end
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << [:name, :gender, :birthday]
end
end
As you can see from my code, I added :name, :gender, :birthday columns (this works fine). I just don't know how to get the special_code into the record. Please advise. Thanks!
You will need to modify the create method. First find what the user is assigned to in create, (probably resource) and then assign the special code to it
def create
# insert special code into instance and ensure that code is unique in database
super # continue to devise registration to CREATE user
resource.update special_code: special_code # may want to check model is still valid at this point and handle errors if not
end
also in routes you will need to tell it to use this controller
devise_for :users, :controllers => { :registrations => 'users/registrations' }
I did it the following way (adjusting my own code to your situation):
app/controllers/concerns/user_resource.rb
module UserResource
extend ActiveSupport::Concern
include Resource
included do
def resource_name
:user
end
def resource
if user_signed_in?
current_user
else
#resource || User.new
end
end
def build_resource(hash=nil)
custom_params = hash.present? ? hash : {}
custom_params[:special_code] = special_code
self.resource = resource_class.new_with_session(custom_params || {}, session)
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
helper_method :resource_name
helper_method :resource
helper_method :devise_mapping
protected
def special_code
( (0...8).map { char = (65 + rand(26)).chr; }[0..rand(2...4)] << rand(1..9) ).join.downcase
end
end
end
Your special code might belong into the UserResource class.
app/controllers/concerns/resource.rb
module Resource
extend ActiveSupport::Concern
included do
before_action { |c| c.resource}
def resource_name
raise NotImplementedError
end
def resource
raise NotImplementedError
end
def devise_mapping
raise NotImplementedError
end
end
end
app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
include UserResource
def create
resource = build_resource(sign_up_params)
yield resource if block_given?
if resource.save
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_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_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
flash[:error] = "Sorry, we could not save you in our database. Please contact an adminstrator. #{resource.inspect}"
clean_up_passwords resource
respond_with resource
end
end
end
The routes should look like this. It seems you've already done that.
app/config/routes.rb
devise_for :users, :controllers => {
registrations: 'users/registrations'
}
Because the code did not work when I attempted to use a shorter solution, I had to copy some code from the original Devise module. Don't ask me why my previous code didn't work, it was really weird and kind of hard to fix.

Resources