Rails: Error when creating devise session - ruby-on-rails

In my application I am trying to manually make devise sessions unusable by setting a session_validity_token.
How I do it:
The devise User model has a column named session_validity_token
I also have a SeesionHistory model which has the same column
In my devise initialization ...
Warden::Manager.after_set_user except: :fetch do |user, warden, opts|
user.update_attribute(:session_validity_token, Devise.friendly_token) if user.session_validity_token.nil?
warden.raw_session["validity_token"] = user.session_validity_token
end
Warden::Manager.after_fetch do |user, warden, opts|
unless user.session_histories.unblocked.where(session_validity_token: warden.raw_session["validity_token"]).exists?
warden.logout
end
end
... when a user signs in or up I set the validity_token of the stored Cookie to the Users session_validity_token. If the User doesn't have one yet (signup), I create a token.... when a URL gets fetched I check before authorizing the User if a unblocked session to that token exists. If not, the User gets logged out.
In the ApplicationController ...
def after_sign_in_path_for(resource_or_scope)
session = SessionHistory.create(user_id: current_user.id, session_validity_token: current_user.session_validity_token)
current_user.update_attribute(:session_validity_token, Devise.friendly_token)
request.env['omniauth.origin'] || stored_location_for(resource) || root_path
end
... after a User gets signed in, I create a SessionHistory Record and simply set it's session_validity_token to the Users token and then recreate the Users token.
Unfortunately I get the following error:
NoMethodError in Users::SessionsController#create
undefined method `session_validity_token' for nil:NilClass
Here is the SessionController#Create Action:
def create
if User.where("email = '#{params[:user][:login]}' or username = '#{params[:user][:login]}'").exists?
#user = User.find(User.where("email = '#{params[:user][:login]}' or username = '#{params[:user][:login]}'").first.id)
if #user.confirmed? || ((Time.now - #user.created_at).to_i / (24 * 60 * 60)) < 1
super
else
redirect_to new_user_confirmation_path(q: "unconfirmed")
end
else
flash[:alert] = "The email or username does not match any accounts"
redirect_to new_user_session_path
end
end
So I guess I did something wrong when handling the tokens with Warden ...
Please ask if you need additional Information.

You may have a namespace collision between two customizations named session_validity_token. This is not naturally in the Devise model (and is not in the source for devise--I checked that).
If that is the case, and you have power over the source, consider changing the name of one, or both of the session_validity_token symbols to clarify the specific usage and relieve the conflict.

Related

Devise - how to check if reset password is token is valid

I'm trying to figure out how I can check if a user reset token is valid BEFORE loading the reset password form. The issue is, currently users don't find out until after they submit.
Here is what I have
class PasswordsController < Devise::PasswordsController
before_action :check_valid_token
private
def check_valid_token
resetCode = (params['resetCode'])
reset_password_token = Devise.token_generator.digest(self, :reset_password_by_token, resetCode)
user = User.find_by(reset_password_token: #reset_password_token)
if user == nil
redirect_to root_path
end
end
end
This doesn't work and I can't find much documentation.
Devise reset password token will be stored as hashed value. You need to decode it.
def check_valid_token
token = Devise.token_generator.digest(User, :reset_password_token, params['reset_password_token'])
user = User.find_by(reset_password_token: token)
user.present?
end
This method will return, true or false
I would do something basic, like this:
def check_valid_token
#user = User.find_by!(reset_password_token: params[:token])
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
so you will have #user instance if token fits and if not it will redirect user to the root_path. You can also add some message before redirecting, like
flash.now[:error] = "Some message here"

Is there a way to pass timeout parameter in sign in function of devise?

I want to sign in as another user, but I want that session automatically expire after some time like 5 minutes.
But my default expire time is 1 hour which I set in devise.rb as
config.timeout_in = 1.hour
Forexample I have a user list.
one is test#test.com
another is test2#test.com
I am logged in as test#test.com.
Now I want to login as test2#test.com,
so I will sign out, and then sign as as
sign_out
sign_in (:user, test_user_2)
But since my original account is test#test.com, and I am just logging in as test2#test.com for development purpose, I want that the session automatically timeout after some time (like 5 minutes) instead of 1 hour by default.
Is there a way to pass timeout parameter in the sign in function of devise ?
This is a bit difficult to do. You can dynamically set timeout_in the users model by overriding the def timeout_in Devise method.
# You could add a boolean onto the user if testing and set it to true once signed in with a testing user.
# You can then somehow set it to false once the user signs out after testing a user.
def timeout_in
if self.testing?
5.minutes
else
30.minutes
end
end
Another workaround to this problem, although it doesn't answer your question directly would be to allow the previous admin the ability to resign into his previous account whenever he/she is done with testing. You could set a session[:admin_logged_in] with the previous user's id when becoming another user. If session[:admin_logged_in] is present you could create a route to make the user an admin again and sign out the testing user. After the user becomes an admin user again you could just clear the session[:admin_logged_in].
def become_other_user
session[:admin_logged_in] = current_user.id
sign_out current_user
user = User.find(params[:id])
sign_in(:user, user, { :bypass => true })
redirect_to something_path
end
def become_admin_user
if session[:admin_logged_in].present?
if current_user
admin_user = User.find_by_id(session[:admin_logged_in])
sign_out current_user
sign_in(:user, admin_user, { :bypass => true })
session[:admin_logged_in].clear
flash.clear if flash.present?
end
end
end
# erb nav file
<% if session[:admin_logged_in].present? %>
<%= link_to 'Switch To Admin', become_admin_user_path, class: '#' %>
<% end %>

When authenticated using API, Devise redirects to its default login view page every second time on failure

I am using token based authentication system for my APIs which is built based on Devise. The user is authorised to access the pages only when the email and mobile number linked with his/her account has been verified. If anyone of the above is not verified, the user gets the 403 access forbidden error message.
def authenticate_user!
unless current_user
authorization_failed_error
end
if current_user.confirmed_at.blank?
unconfirmed_email_error
elsif current_user.mobile_confirmed_at.blank?
unconfirmed_mobile_error
end
end
When an user with a unconfirmed email is trying to access a content through the API, error message is thrown. But if the same user tries for the second time, Devise redirects the user to the login view page.
Above scenarios occur alternatively every time.
current_user is set by the set_user_by_token function of the Devise, as below.
def set_user_by_token(mappings=nil)
rc = resource_class(:user)
# no default user defined
return unless rc
#gets the headers names, which was set in the initialize file
uid_name = 'Uid'
access_token_name = 'Access-Token'
client_name = 'Client'
expiry_name = 'Expiry'
# parse header for values necessary for authentication
uid = request.headers[uid_name] || params[uid_name]
#token ||= request.headers[access_token_name] || params[access_token_name]
#client_id ||= request.headers[client_name] || params[client_name]
# client_id isn't required, set to 'default' if absent
#client_id ||= 'default'
# check for an existing user, authenticated via warden/devise, if enabled
devise_warden_user = warden.user(rc.to_s.underscore.to_sym)
if devise_warden_user && devise_warden_user.tokens[#client_id].nil?
#used_auth_by_token = false
#resource = devise_warden_user
#resource.create_new_auth_token
end
# user has already been found and authenticated
return #resource if #resource and #resource.class == rc
# ensure we clear the client_id
if !#token
#client_id = nil
return
end
return false unless #token
# mitigate timing attacks by finding by uid instead of auth token
user = uid && rc.find_by_email(uid)
if user && user.valid_token?(#token, #client_id)
# sign_in with bypass: true will be deprecated in the next version of Devise
if self.respond_to? :bypass_sign_in
bypass_sign_in(user, scope: :user)
else
sign_in(:user, user, store: false, bypass: true)
end
#resource = user
return #resource
else
# zero all values previously set values
#client_id = nil
return #resource = nil
end
end
I am not sure why Devise(or Warden) behaves like this every second time. It would be of great help if someone could figure this out.

BCrypt::Errors::InvalidHash in SessionsController#create when I try to sign in a user? (Ruby on Rails)

I get this error when I try to sign in a user, and can't figure out why. It's weird because when I run the following code I get the BCrypt Error, however when I change the find_by line (line 7) from can_email (candidate's email) to can_name (candidate's first name) I don't get the error at all, it just doesn't sign in the user presenting an "invalid password/email combination" error message on the webpage regardless if the combination is right or not. It's something to do with the password but I can't pin point it.
class SessionsController < ApplicationController
def new
end
def create
candidate = Candidate.find_by_can_email(params[:can_email])
if candidate && candidate.authenticate(params[:password]) **Error highlights this line**
session[:candidate_id] = candidate.id
redirect_to candidate
else
flash.now[:error]="Invalid email/password combination"
render 'new'
end
end
def destroy
if signed_in?
session[:candidate_id] = nil
else
flash[:notice] = "You need to log in first"
end
redirect_to login_path
end
end
Having the SessionController i am assuming you have a route as follows
# This is just a sample
post 'login' => "sessions#create" # gives login_path
Since there will be no session model i assume you have the form as follows
form_for(:session, url: login_path)
Now if you are collecting eg can_email and password you get
{session: {password: 'foo', can_email: 'foo#bar.com'}}
Accessing params[:session] returns the hash containing email and passowrd
So i think you should obtain them as follows
def create
candidate = Candidate.find_by(can_email: params[:session][:can_email])
if candidate && candidate.authenticate(params[:session][:password])
# login the user
else
# whatever
end
end
I got this error too, but in my case it was the result of myself having changed the encrypted_password value of my user in the database a while back and then forgetting about it.
This was easily fixed just by updating the password :)

Devise: When is too much customization?

I'm writing an application for myself, so I've got no rush and the my only target is to do things properly.
For authentication I use devise, but I turned out customizing it a lot.
I've seen some good features coming in Rails 3.1 that could make easier to implement auth myself.
In general, when does Devise stops to be useful and starts getting in your way?
Here is a list of customization I have at the moment, beside views of course, but I still would like to implement at least SEO friendly urls.
# model/User.rb
#this method is called by devise to check for "active" state of the model
def active?
#remember to call the super
#then put our own check to determine "active" state using
#our own "is_active" column
super and self.is_active?
end
protected #====================================================================
# find in db the user with username or email login
def self.find_record(login)
where(attributes).where(["name = :value OR email = :value", { :value => login }]).first
end
# allow no case sensitive email
# (saved downcase, fetched downcase)
def self.find_for_authentication(conditions)
conditions[:email].downcase!
super(conditions)
end
# find the user in the db by username or email
def self.find_for_database_authentication(conditions)
login = conditions.delete(:login)
where(conditions).where(["name = :value OR email = :value", { :value => login }]).first
end
# Attempt to find a user by it's email. If a record is found, send new
# password instructions to it. If not user is found, returns a new user
# with an email not found error.
def self.send_reset_password_instructions(attributes={})
recoverable = find_recoverable_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
recoverable.send_reset_password_instructions if recoverable.persisted?
recoverable
end
def self.find_recoverable_or_initialize_with_errors(required_attributes, attributes, error=:invalid)
case_insensitive_keys.each { |k| attributes[k].try(:downcase!) }
attributes = attributes.slice(*required_attributes)
attributes.delete_if { |key, value| value.blank? }
if attributes.size == required_attributes.size
if attributes.has_key?(:login)
login = attributes.delete(:login)
record = find_record(login)
else
record = where(attributes).first
end
end
unless record
record = new
required_attributes.each do |key|
value = attributes[key]
record.send("#{key}=", value)
record.errors.add(key, value.present? ? error : :blank)
end
end
record
end
# password not required on edit
# see: https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
def password_required?
new_record?
end
# controllers/registrations_controller.rb
# devise controller for registration
class RegistrationsController < Devise::RegistrationsController
# update_attributes (with final S) without providing password
# overrides devise
# see: https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
def update
# Devise use update_with_password instead of update_attributes.
# This is the only change we make.
if resource.update_attributes(params[resource_name])
set_flash_message :notice, :updated
# Line below required if using Devise >= 1.2.0
sign_in resource_name, resource, :bypass => true
redirect_to after_update_path_for(resource)
else
clean_up_passwords(resource)
render_with_scope :edit
end
end
end
Thank you
I'd just stick with devise for the time being, your changes aren't huge. However, I'd fork devise and extract the changes you've made into new features. Then attempt to get them pulled into devise itself. That way maintaining them doesn't fall on you, it can fall on the many.
Maintaining a full authentication system can be a real headache and ultimately its reinventing the wheel. It only takes one mistake can leave you wide open.
Also your new find_for_authentication method, this has now been supported in devise, put in your devise initializer...
config.case_insensitive_keys = [ :email ]
Good question - My view would probably be that as long as it makes things easier it's useful. You can always fork devise on github and put your customisation in there to some extent - which is what I've done on one project. I'm also a bit nervous about rolling my own authentication when it can be so important to get it right, especially if other people want to see stuff they shouldn't. But I'll be interested to see what others think.

Resources