Authlogic Perishable Token Resetting on Failed Login - ruby-on-rails

I am using the perishable token magic in authlogic to do password resets. However, it seems that the token is getting reset when a user tries to log in and fails. This is because authlogic is incrementing failed login attempts on the user record. So if the user requests a new password and then tries to log in before resetting the password, the perishable token changes.
Any ideas to get around this?

So we eventually figured out a way around this.
First move was to disable the automatic perishable token handling:
#############
## Authlogic
acts_as_authentic do |c|
.....
c.disable_perishable_token_maintenance = true
.....
end
Then we created our own before_filter on user to mimic the same functionality as the auto handler but ignore changes to the failed_login_count field:
before_save :handle_perishable_token
def handle_perishable_token
unless failed_login_count_changed?
reset_perishable_token
end
end
This basically allows a user to fail at logging in and not reset the perishable token.

I had a similar problem and I like your solution, but in my opinion it's good to add:
before_save :handle_perishable_token
def handle_perishable_token
unless failed_login_count_changed? && changed.size == 1
reset_perishable_token
end
end
because when you reset the password, between other attributes, failed_login_count is also changed (set to 0) and in this case perishable_token should be reseted.

Related

Devise - how to get user when redirecting with a custom Devise::FailureApp

I let users log in initially without confirming their email address - but after 7 days, if they haven't confirmed - I block access until they confirm their address.
(Note - this is achieved by setting config.allow_unconfirmed_access_for = 7.days in the Devise initialiser)
If they hit the 'grace' limit (e.g. they don't confirm and 7 days pass) then I want to:
send them to a page which explains what is going on (I can do this
part)
automatically re-send the confirmation email
to do #2 I need to access the user to get the email address.
Devise obviously 'knows' who the user is - that's how it knows they have passed the confirmation expiry.
If the user has just tried to log in, then I can get this by looking in the params.
However if the user already has a live login token in their session, then when they pass the magical week - they'll suddenly start being rejected by devise. How do I access the user in this case?
#based on
#https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated
#https://stackoverflow.com/questions/9223555/devise-with-confirmable-redirect-user-to-a-custom-page-when-users-tries-to-sig
class CustomFailure < Devise::FailureApp
def redirect_url
if warden_message == :unconfirmed
user = User.find_by_email(params.dig(:user,:email))
user&.send_confirmation_instructions
if user.nil?
#if the user had a valid login session when they passed the grace period, they will end up here
!! how do I get the user in this scenario !!
end
confirmation_required_info_path(params: {found: !user.nil?})
elsif warden_message == :invalid
new_user_session_path(user:{email: params.dig(:user,:email)})
else
super
end
end
# You need to override respond to eliminate recall
def respond
if http_auth?
http_auth
else
redirect
end
end
end
This achieves goal #1, but it only achieves goal #2 if if the failure is the result of new signup
is there a direct way to access the user when they have a live session, but have passed the expiry date?
(current_user is not available, env['warden'].user is nil)
thank you
Rails 5.0.6
devise 4.2
edit: Updating to clarify with an example scenario where I need help:
day 0: User signs up with email/password. I let them in without confirming their email. They have a 7-day grace period to confirm their email.
day 2: They log out
day 7 (morning): They log in again
day 7 (later in the day): They do some action. Their login token is still valid - devise recognises it, finds their user record and checks if they have confirmed their email address. They have not - so devise refuses to authorise the action, giving the error :unconfirmed
In this scenario - they come through to the failure app. I will redirect them to a page which says 'you have passed your 7-day grace period, you really need to confirm your email address now'.
In the failure app, I want to know what their email address is so that I can automatically re-send the confirmation email. How do I get this?
Note - in this scenario, devise has refused authorisation. current_user is nil. However Devise clearly 'knows' who the user is - because it was able to look up their record in the database, and check that they had gone past the grace period for unconfirmed email addresses. How do I access that same 'knowledge'
I think there are better ways of achieving the same result without creating a Devise::FailureApp:
This could be achieved by overriding the confirmed? method from Devise's resource extension present in the Confirmable module.
A simple example would be:
Add a delayed_confirmation_expiry_date datetime field to your model's table, via migration.
This field will be used to store the expiry datetime when the user first registers into your app. You will have to override the SessionsController#create method for that, so it can call the #delay_confirmation! method on your resource.
Add inside your User model equivalent :
# Will update the field you have added with the new temporary expiration access datetime
def delay_confirmation!(expiry_datetime=7.days.from_now)
self.delayed_confirmation_expiry_date = expiry_datetime
self.save
end
# Override that will make sure that, once the user is confirmed, the delayed confirmation information is cleared
def confirm(args={})
clear_delay_confirmation!
super
end
# Self-explanatory
def clear_delay_confirmation!
self.delayed_confirmation_expiry_date = nil
self.save
end
# Used on your controllers to show messages to the user warning him about the presence of the confirmation delay
def confirmation_is_delayed?
self.confirmed? && !self.confirmed_at.present?
end
# Overrides the default implementation to allow temporary access for users who haven't confirmed their accounts within the time limit
def confirmed?
if !self.confirmation_is_delayed?
super
else
self.delayed_confirmation_expiry_date >= DateTime.now.in_time_zone
end
end

Devise Reset Authentication Token

I am using Devise and I have an authentication_token that I pass in the header of my API calls
How do I reset that token when the user logs out?
I want a new token generated every time they are logging in.
You can try using the after_database_authentication callback on the model.
def after_database_authentication
self.update_attribute(:auth_token, generated_token)
end
def generated_token
...
end

how to set email_required = false on signup with omniauth-twitter

In a Rails 3.2 app I'm using Devise on a User model, with omniauth-twitter. A User has many Authentications.
Login is via email + password, or via Twitter.
I'm trying to modify the email_required method so that email is not required if a User is signing up with Twitter (Twitter does not provide email in its email response). The User will then be taken to a settings screen where he will need to set his email.
I'm having trouble working out what my condition should be here. I'm thinking something like
def email_required?
if user.sign_in_count == 1 && self.authentications.first.provider == "twitter"
false
else
super
end
end
but (1) the Authentication record is built but not created when this validation is passed, so not available; and (2) I want email to be required immediately after the User is created (i.e., the signin count may still be 1)
Another option may be to check if Twitter params are in the session, something like (pseudocode)
def email_required?
if session["devise.twitter_data"]
false
else
super
end
end
but accessing the session from a model doesn't seem right.
Would appreciate any suggestions or thoughts, I have a feeling I'm overlooking something obvious!

Preventing Devise from sending an email when changing password

I am using Devise with my Rails 3 application. The current behavior for resetting a password, is to click on the "Forgot your password?" link. The link here is:
(url)/password/new.user
Which will call the following method in the Devise passwords_controller.rb:
def new
build_resource({})
end
This method will do:
generates the password reset token and adds it to the database,
sends an email to the person with a link that includes the token:
(url)/password/edit?reset_password_token=xxxxxxxxxxxxxxx
Is there any way to convince Devise to perform step 1 ONLY and not step 2? Are there any security issues I should be aware of if this is possible, and I did take this approach in an effort to simplify a portion of the web site.
I would recommend overriding send_devise_notification on your User (?) model and return true when the notification value is :reset_password_instructions. Something like this:
# app/models/user.rb
def send_devise_notification(notification)
return true if notification == :reset_password_instructions
end
Check their example on how to override/customize behavior for sending emails
https://github.com/plataformatec/devise/blob/master/lib/devise/models/authenticatable.rb#L127
You can disable it at instance level:
# disable all notifications
user.define_singleton_method(:send_devise_notification) { |*_| true }
# disable the one you want
user.define_singleton_method(:send_devise_notification) do |*args|
return true if args[0] == :reset_password_instructions
super
end
The title of the question is general, but the question itself is more specific. This is the answer to the general question as of 2021.
To prevent a password changed email notification from being sent when changing a user password, call skip_password_change_notification! on the user before saving the user.
user = User.find(123)
user.skip_password_change_notification!
user.password = 'DoNotUse$123'
user.save

Rails expire password within 24 hours

In my rails 3.1 application, I want to create and expire random password for users.I am using devise gem for that.Any plugin available for expiring password withing some duration?
Or else Please give me some logical advice to implement this feature.
Please consider me as a newbie.
It sounds like you just want to expire the password once. If you're looking to do it at regular intervals (e.g. every couple of months) or if you want to prevent users from re-using passwords, it gets more complicated.
Taken from an app I'm working on:
app/models/user.rb (assuming that's what you name your model):
def password_should_expire?
# your logic goes here, remember it should return false after the password has been reset
end
app/controllers/application_controller.rb
before_filter :check_password_expiry
def check_password_expiry
return if !current_user || ["sessions","passwords"].include?(controller_name)
# do nothing if not logged in, or viewing an account-related page
# otherwise you might lock them out completely without being able to change their password
if current_user.password_should_expire?
#expiring_user = current_user # save him for later
#expiring_user.generate_reset_password_token! # this is a devise method
sign_out(current_user) # log them out and force them to use the reset token to create a new password
redirect_to edit_password_url(#expiring_user, :reset_password_token => #expiring_user.reset_password_token, :forced_reset => true)
end
end
When you create a password, note the time it was created. Then, when the password is being used, check that the password was created less than 24 hours ago.
Depending on what frameworks you are using, this functionality (or something similar) may already exist within the framework, or perhaps as a plugin. If not, it isn't particularly difficult to implement. All you would need is an extra column in your data store to hold the password creation date/time, and a bit of extra logic on password creation and on password use.
Check out the Devise Security Extension gem:
https://github.com/phatworx/devise_security_extension
I've been using it for expiring passwords and archiving passwords (to make sure an old password is not reused) with no problems.
#Jeriko's answer contains some old code, here are the edits
In model/user.rb:
def password_should_expire?
if DateTime.now() > password_changed_at + 30.seconds
return true;
else
return false;
end
end
In Application Controller:
before_filter :check_password_expiry
def check_password_expiry
return if !current_user || ["sessions","passwords"].include?(controller_name)
# do nothing if not logged in, or viewing an account-related page
# otherwise you might lock them out completely without being able to change their password
if current_user.password_should_expire?
#expiring_user = current_user # save him for later
#expiring_user.set_reset_password_token! # this is a devise method
sign_out(current_user) # log them out and force them to use the reset token to create a new password
redirect_to edit_password_url(#expiring_user, :reset_password_token => #expiring_user.reset_password_token, :forced_reset => true)
end
end

Resources