My app has a pre-subscription system to allow users to subscribe without password.
But malicious users who haven't finished their subscription (= don't have a password yet) could follow the password recovery process to set theirselves a password and brake the usual subscription process.
So I would like Devise to provide password recovery to only the users who are validated (namely users who finished their subscription).
If a non-validated user is entering his email to try to get a new password, I would like to show him the "email not found" error, as if he wasn't found at all.
I didn't find where to override Devise to add such a scope.
This method from the Recoverable seems to fit but I haven't been able to customize it:
# Attempt to find a user by its email. If a record is found, send new
# password instructions to it. If user is not found, returns a new user
# with an email not found error.
# Attributes must contain the user's email
def send_reset_password_instructions(attributes={})
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
recoverable.send_reset_password_instructions if recoverable.persisted?
recoverable
end
So for the moment I overrode the PasswordsController but this is not a clean solution.
class Users::PasswordsController < Devise::PasswordsController
def create
if resource_params[:email].present? && resource_class.registration_complete.where(email: resource_params[:email]).none?
self.resource = resource_class.new
self.resource.errors.add(:email, :not_found)
render :new
else
super
end
end
end
Related
I have rails app with:
Admin table with Devise authentication
User table with email and name without authentication (but session to remember them)
User can browse anywhere but now on certain pages I would like to enhance it and add authentication - allow user to create password and only with password it will be accessible but I am quite lost what is the best way to do it with the current setting?
I allow users to add their details like name and email and I am creating a cookie to remember them without any authentication or password:
UsersController
def create
user = User.find_or_create_by(email: params[:user][:email])
cookies.permanent.signed[:user_id] = user.id
session[:user_id] = user.id # for users/edit temporary
render json: user
end
Let's say I have this following method in User:
before_filter :authenticate_user!, only: :your_order
def your_order
end
If User will visit this page and didn't set up password before, how can I prompt him to create one and how can I require for him to login after with Devise? I am thinking of more solutions but none of them are perfect.
As per the specifications given the below mentioned criteria might help you.
def your_order #before_filter
if user.password.present?
# authenticate using valid_password? method of devise
else
#redirect user to say set_password
end
end
def set_password
#set the user password in this method and after successful completion redirect to login page where before filter your_order will be called
end
I'm working on google authentication for a rails app. Currently using the omniauth-google-oauth2 gem to implement Google auth. I've managed to have users sign in using google. However, I'd also like users to be able to sign up using google. My problem is that I've matched the google callback URL to a particular controller action (sessions#create).
Is it possible to choose between 2 redirect URIs based on whether users are signing in or signing up? Currently, my only idea is to create new google client credentials to be used for sign up, I hope there is a better way.
You don't need to have 2 redirect uris, you just need to do some more work when receiving the callback. For instance:
class SessionsController < ApplicationController
...
def create
email = auth_hash['info']['email'] # assuming your omniauth hash is auth_hash and you're requiring the email scope
#user = User.find_by(email: email) if !email.blank? # assuming your user model is User
if #user
login_user(#user) # use your login method
elsif !email.blank?
#user = User.new(name: auth_hash['info']['name'], email: email)
unless #user.save!(validate: false) # validate false because I'm enforcing passwords on devise - hence I need to allow passwordless register here)
# deal with error on saving
end
else
# deal with no found user and no email
end
end
protected
def auth_hash
request.env['omniauth.auth']
end
end
I've written all steps but the creation process can be shortened to:
#user = User.create_with(name: auth_hash['info']['name']).find_or_initialize_by(email: email)
#user.save! if #user.new_record?
if #user
login_user(#user)
else
# deal with no user
end
Nonetheless, you can't be sure the user is going to give you scope access to the email, so personally I think the first version, even if a bit lengthier is more robust. Then on the shorter version there's also the problem of, if #user is false, why is so? And will require you to add more logic to figure out why is that, whereas in the first one it's much easier to apply the correct response to each situation.
After a user signs up, I want to lead them to their dashboard. For full features they need to confirm their email.
Right now, when I add :confirmable to the User model, the user is unable to login.
How can I achieve this functionality?
just write in devise.rb
config.allow_unconfirmed_access_for = nil
write an validation in application.rb
def is_confirmed?
if user_signed_in?
if current_user.confirmed?
return true
else
flash[:notice] = "You are not allow to view this page."
redirect_to dashboard_path # your dashboard path
end
end
end
use in your contoller
before_action :is_confirmed?, except: [:dashbord]
I think you can achieve this with devise and allowing-unconfirmed-access
Just add extreamely long period to allow them to login and then just check if the user has confirmed email — show them all dashboard content and vice versa.
Or you can just skip :confirmable validation at all.
Hope this helps.
Check out this wiki page https://github.com/plataformatec/devise/wiki/How-To:-Add-:confirmable-to-Users
# Solution 1
Your can config in devise.rb something like:
config.allow_unconfirmed_access_for = nil
So it means user can access without any confirmation, but for instance:
config.allow_unconfirmed_access_for = 1.hour
So they can access in 1 hour without confirmation
# Solution 2
# in User.rb
protected
def confirmation_required?
false
end
So now use can access anything without confirmation but I think the solution #1 is more efficient!
I am using devise gem. Devise send reset password token in mail when user clicks on forget password link. User follow the link and reset his password by entering new password and confirm new password.
When I follow the same mail link again, it again allow the user to reset password in the same way as above.
Now, I want the reset password token to clear once it is used. So that when your follow the previously used send link from old mail, he must get message that "Invalid token"
How can I do this?
Thanks in advance.
Easier and safer solution than what was proposed:
Create your own passwords controller, I chose to place it under controllers/auth
controllers/auth/passwords_controller.rb
class Auth::PasswordsController < Devise::PasswordsController
def update
super do |resource|
if resource.reset_password_token_changed? and resource.reset_password_token_was.nil?
resource.reset_password_token = nil
end
end
end
end
This fix many problem with papertrail for example, and anyhow save one access to the DB
You can try either of the following methods
# reset_password_within = 1.day and reset_password_sent_at = today
reset_password_period_valid? # returns true
# reset_password_within = 5.days and reset_password_sent_at = 4.days.ago
reset_password_period_valid? # returns true
# reset_password_within = 5.days and reset_password_sent_at = 5.days.ago
reset_password_period_valid? # returns false
# reset_password_within = 0.days
reset_password_period_valid? # will always return false
or you can call the instance methods like clear reset password token or by calling
clear_reset_password_token
or after_password_reset methods.
I think this hack should be more easier if you do the following in your user model or the model that has been used by devise.
class YourModel < ActiveRecord::Base
...
def after_password_reset
self.clear_reset_password_token if not (self.reset_password_token.nil? and self.reset_password_sent_at.nil?)
end
end
I suggest not to use your controller to perform business operation. This after_password_reset password is used to called after clear_reset_password token in devise. Here is the reference:
https://github.com/plataformatec/devise/blob/master/lib/devise/models/recoverable.rb#L39
Hope this will help.
I have achieved above by overriding Devise::PasswordsController in application.
Devise handle reset password on PasswordController#edit action.
On edit, I have checked if the reset password token is valid or not. If its valid I allow user to reset password otherwise redirect user to sign in page with "Password token is invalid message".
For devise 3.0
class Users::PasswordsController < Devise::PasswordsController
def edit
self.resource = resource_class.find_or_initialize_with_error_by(:reset_password_token, params[:reset_password_token])
if !resource.errors.empty?
flash[:alert] = "Password token is invalid"
redirect_to new_session_path(resource_name)
end
end
end
For devise 3.1
class Users::PasswordsController < Devise::PasswordsController
def edit
original_token = params[:reset_password_token]
reset_password_token = Devise.token_generator.digest(self, :reset_password_token, original_token)
self.resource = resource_class.find_or_initialize_with_error_by(:reset_password_token, reset_password_token)
if !resource.errors.empty?
flash[:alert] = "Password token is invalid"
redirect_to new_session_path(resource_name)
end
end
end
I have a Rails app that uses Devise and Omniauth to handle authentications. As prescribed I am receiving callbacks from the providers in my omniauth_callbacks_controller, where I check if the authentication already exists, and whether a user with the email given by the provider already exists, creating a new user if necessary.
I need a valid email for each user. My problem comes with callbacks from Twitter. Twitter doesn't provide an email for its users, so I can't create a valid user. To solve this I store the provider's data in a session and I send the user the new registrations page, asking them to submit their email address so I can create a valid user. When this form is submitted I hit a problem. The form has created a new user, but it is perfectly possible that a user with that email already exists (in which case I would need to add the authentication to that user).
At the moment I am checking to see if a user with the same email as the new user already exists. If so, I am ignoring the new user and applying the authentication to the user that already exists. However this feels really hacky.
How should I be doing this?
class Users::RegistrationsController < Devise::RegistrationsController
def build_resource(*args)
super
if session[:omniauth]
#If a user with this email already exists then use them instead
existing_user = User.find_by_email(#user.email)
if(existing_user)
existing_user.email = #user.email
#user = existing_user
end
#If there is a session available, we know it contains data for adding an authentication
#user.apply_omniauth_data_as_authentication(session[:omniauth])
#Ensure validations are passed on to next page
#user.valid?
end
end
Here you go. I'll try and remember where I got the fix from and post a link if I do:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# This controller handles callbacks from Omniauth providers. If a user already exists with this provider we sign them in.
# Otherwise we either add an authentication to the current user, or create a new user if one doesn't exist.
def facebook
authorize
end
def twitter
authorize
end
private
def authorize
omniauth_data = request.env["omniauth.auth"]
#Check to see if we have an authentication for this provider already stored for any User
authentication = Authentication.find_by_provider_and_uid(omniauth_data['provider'], omniauth_data['uid'])
#If an authentication already exists, sign the owning User in
if authentication
flash[:notice] = "Signed in successfully with " + omniauth_data['provider'].titleize
sign_in_and_redirect(:user, authentication.user)
#Otherwise if there is a current User, add a new authentication for them
elsif current_user
current_user.authentications.create(:provider => omniauth_data['provider'], :uid => omniauth_data['uid'], :token => omniauth_data['token'])
flash[:notice] = "Authentication successful"
redirect_to user_profile_url
#Otherwise we check if a user exists with the email address provided by the provider (if provided at all))
else
email = omniauth_data['info']['email']
user = User.find_by_email(email)
#If no user exists, create a new one
if(!email || !user)
user = User.new
end
user.apply_omniauth_data_as_authentication(omniauth_data)
#If they save successfully (meaning we have enough data from the authorisation) sign them in
if user.email?
#We got all we needed from the provider so we don't need them to confirm'
user.skip_confirmation!
user.save!
flash[:notice] = "Signed in successfully with " + omniauth_data['provider']
sign_in_and_redirect(:user, user)
#Otherwise we need to have the visitor manually submit missing information
else
#save the omniauth data in a session so we can add the authentication once registration is complete
flash[:alert] = "Please complete registration"
session[:omniauth] = omniauth_data.except('extra')
redirect_to new_user_registration_url
end
end
end
end
I like the solution used by this link http://www.orhancanceylan.com/rails-twitter-and-facebook-authentications-with-omniauth-and-devise/
It uses the provider credentials to register in the normal devise process (as well as filling out provider and uid attributes on the user or on a separate table). Thus, when a new user tries to register using a provider, they will be required to put in an email and password, creating less headaches down the road (like wanting to log in the normal way, but not having a password to type in, and then you having to handle that).
While this solution somewhat negates the convenience of registering with social media the FIRST time, it creates long term beneficial effects with minimal effort.
If you absolutely do not want this kind of requirement for fear of losing signups or having your website seem less polished, find another solution and do all the work to handle those downstream cases.