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!
Related
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
I'm building an app that uses Devise to manage user state. I'm also building an API in that same app that receives a username and password from a POST request
What I'm trying to accomplish is:
Get the user by username from the database (done, straightforward)
Use Devise::Models::DatabaseAuthenticatable to take the password the user passed in, encrypt it, compare it against the encrypted_password field on the User model and if they're the same, proceed with the rest of my code
The second bullet above is what I'm having trouble with. In a console, I can't seem to get an instance of the module Devise::Models::DatabaseAuthenticatable to try the various instance methods that you can find here in the docs.
Any help would be greatly appreciated!
If I understood your question correctly, you can use .valid_password? devise method. Something like that:
#assuming you'll receive nested params like user[email], user[password]...
user_params = params.require(:user).permit(:email, :password)
user = User.find_by(email: user_params[:email])
return head 403 if user.nil?
valid = user.valid_password?(user_params[:password]) #true or false...
return head 403 unless valid
sign_in(user) #devise helper: if you want to sign in that user
You can also check another approachs, like devise token auth gem.
We have a customer that wants to use their current Wordpress site at the "source" for their user table.
(If it makes a difference, the rails app will be the primary app interface for a web front end as well as an iOS and Android front ends.)
So, the user will login through the Website and the idea is that an API call would be made to Wordpress with the email/pwd. It would return an authentication successful. I would then issue a token or something like this to the Mobile platforms to allow them continued access.
Any thoughts on how to make the authentication piece work between rails -> wordpress?
In case anyone else wants to accomplish the same thing. Here is how I solved the problem. First, my wordpress instance and rails instances are sitting on the same box, which makes this solution viable.
1) I am using devise for authentication on the rails side. I have created an override for the "authenticate!" method, which checks wordpress.
require 'devise/strategies/authenticatable'
module Devise
module Strategies
class DeviseOverride < Authenticatable
def valid?
true
end
def authenticate!
if params[:user]
user = User.find_by_email(params[:user][:email])
# user = User.first
if user # && user.encrypted_password == params[:user][:password]
#check password with Wordpress to verify it is a good user
result = WordpressApi.verify_user(params[:user][:email], params[:user][:password])
if result
success!(user)
else
fail!("Couldn't verify your login. Please try again.")
end
else
fail!("Could not log in")
end
else
fail!("")
end
end
end
end
end
Warden::Strategies.add(:local_override, Devise::Strategies::DeviseOverride)
2) This calls a simple method where I just call over to the wordpress instance to verify the user exists. (I was trying to find a way check the DB table directly, but the WP password hashing isn't something I wanted to tackle)
3) On the wordpress side (along with some other stuff):
$user = get_user_by('email', $email);
// print $user->data->user_email;
if ($user && wp_check_password( $pwd, $user->data->user_pass, $user->ID) )
return_json_success('Valid User', 'user', $user);
else{
return_json_error('Passwords do not match', 200);
// print "Password: {$pwd}, User: {$user} UserPass: {$user->data->user_pass} UserID: {$user->ID}";
// print 'Passwords do not match';
}
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
I'm using Devise with my rails 3 app. The app requires users to validate their email before continuing.
How can I redirect users to a specific url like /gettingstarted after they successfully validate their email address via the email confirmation msg they receive?
Thanks
When a user clicks on the confirm link they are taken to a confirm page which checks the confirmation token and if it's valid automatically logs them into the application. You could overwrite the after_sign_in_path_for method in your ApplicationController (as shown on the Devise wiki) and then redirect them to your getting started page the first time a user logs in.
def after_sign_in_path_for(resource_or_scope)
if resource_or_scope.is_a?(User) && first login
getting_started_path
else
super
end
end
For "first login" you could test if the confirmed_at timestamp is within a couple minutes of now, if your also using the trackable module in devise you can check if the sign_in_count is 1 or you could create your own field in the user model that tracks this information.
I'm checking devise source code at https://github.com/plataformatec/devise/blob/master/app/controllers/devise/confirmations_controller.rb
and seems that we have a callback to do it "after_confirmation_path_for"but I couldn't get it working without rewrite Devise::ConfirmationController
I hope that helps and if somebody get it working just defining after_confirmation_path_for just let us know.
I'm using the last_sign_in_at field from the 'trackable' model to achieve this. I've got the following code in my root action:
if current_user.last_sign_in_at.nil? then
redirect_to :controller => :users, :action => :welcome
end
http://rubydoc.info/github/plataformatec/devise/master/Devise/Models/Trackable
Seems to work reasonably well.
inside the 'after_sign_in_path_for' the current_user.last_sign_in_at.nil? will not work since it is alerady after the first sign-in. However this will work
if current_user.sign_in_count == 1
# do 1 thing
else
# do another thing
end