If email is already taken, update the user concerned Devise - ruby-on-rails

I'm currently building an app wich is working with Devise. Some actions allows the users to invite other users by entering their emails. But sometimes the users are not registered yet.
To avoid a multiplication of tables and conditions (between registered and non-registered users), I chose to create empty User (with email, id, and registred(false) variables). This allows me to always use the same user for different invitations, even if the user is not yet registered.
The idea is that when the user registers, it automatically retrieves all the information previously recorded by other users (in this case invitations)
However, Devise email is defined by default as uniq (across method :validatable).
During registration, I need to set the following condition:
if email does not exist
New User
elsif email exist && not registred
Update User with this email
else
message:'email already token'
end
I tried several solutions like :
RegistrationsController#Create :
#previous_account = User.where(email: params[:user][:email]).first
if #previous_account == nil && #previous_account.registred != true
#user = User.new(params[:user])
else
# This update the user with his password and other informations, but keeping the same id (to keep all the previous invitations)
#previous_account.update(params[:user])
end
or
User Model :
before_create :verif_exist
def verif_exist
pre_user = User.where(email: self.email).first
if pre_user != nil && pre_user.registred != true
pre_user.update(encrypted_password: self.encrypted_password, registred: true)
else
self.save
end
end
I don't know how to solve this issue, if anyone has the same specificities in his app, I would love to see your solution.

Related

Ruby on Rails - Sorcery override login method

I have a user table with guest column(boolean) to distinguish user's identity.
I don't want user with guest? => true able to login.
Is it possible to override Sorcery's login method?
I want it work like
User.where(guest: false).authenticate(email, password)
Another way I'm thinking is to separate User and GuestUser using polymorphic association.
However, I don't really want to create GuestUser with same columns as User.
Please give me some suggestions.
# find specific user and check guest or not
user = User.find_by_email(email)
if user && user.guest == false
User.authenticate(email, password)
end

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

Registration if email already exist with devise

I've a variable registered(boolean) in my table User. Because when a connected user want to use an Email, if this email is not existing into the table User, a new line is created in the Users (only with the variable email, and registered = false).
So I would like that when a user registers, he will not be blocked if(email exist && registered == false).
In this case, the password will be replaced, and the variable registered change to true.
I would like to run something like that :
def create
#user_registred = User.find(email: params[user: :email])
if #user_registred.any?
if #user_registred.registred == false
#user = User.update(params[:user])
else
notice: "Email already taked"
end
else
#user = User.new(params[:user])
end
end
I think devise already provide uniqueness on email. Apart from this instead of having another column you can put validation of uniqueness also here. Apart from this you can also set database level uniqueness constraint.
Update :
have a look at the below code.
user = User.where(email: 'test#email.com').first_or_initialize
if user.new_record?
user.save
Do your stuff.
else
user.update_attributes(attributes goes here)
and do your other stuff
end

Why Rails session is being shared between multiple users

I am trying to use session mechanism to store information of an user that is logged like this: session[:user_id]=#user_id , and that its ok.
But when a new user login in the app, the variable session[:user_id] is updated to the new user id, making the first one perform requests with an invalid id.
I used different browsers, private browsers, a browser in a Virtual Machine and another one in the host, and still got the problem.
I appreciate some suggestions. Is it normal the session being shared between multiple users? There is another way to store some specific data, and prevent the share between users? I thought that session was unique, why that variable is changing? The same happens for cookies variable.
EDIT:
application_controller
def sign_in
if(password != "" && #user_id!= "" && jenkinsip != "")
#client = JenkinsApi::Client.new(:server_url => jenkinsip, :username=> #user_id, :password=> password)
if(#client.get_jenkins_version != nil)
session[:user_id]=#user_id
end
end
end
in html
Every time session[:user_id]=#user_id is called, the session[:user_id] is being set to whatever the #user_id variable is set as.
Try using||= instead of =
set the session withsession[:user_id]||= #user_id to only set session[:user_id] to #user_id when session[:user_id] is undefined.
Follow the excellent answer of koxtra and
also have a look on devise gem for the user authentication.
Devise will do everything for you like users signin, signup, creating sessions and many more functions. You have to only install the Devise
in your rails application.

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!

Resources