I am looking for a customization in devise where if we click on forgot password it should send the mail to any e-mail id . Something like it happens in Gmail, irrespective of the email id exists or not.
Screen 1
Screen 2
Currently what i have is this in which it tries to validate with the valid users in the system.
The Devise, recoverable module takes care of this
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
How can i remove this validation and have email sent to any Email id?
There is a Devise configuration called paranoid that, when set to true, would change the message in a way to avoid e-mail enumeration. Just set config.paranoid = true in your Devise configuration.
My solution would be to extend/override Devise's passwords controller. To do this, create a controller (let's call it passwords) that inherits from Devise's passwords controller, like this:
class PasswordsController < Devise::PasswordsController
Then edit your routes file so this change takes effect:
devise_for :users, :controllers => { :passwords => 'passwords' }
Now, you'll want to override the create action. There are several ways you could do this but since I'm not sure of what you want to do, I'll show you 2 things you could do:
You only want to prevent the "Email not found" error so that people can't find which emails exist or not in your database:
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name))
end
You really want to send emails to any entered email:
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
unless successfully_sent?(resource)
Devise::Mailer.reset_password_instructions(resource).deliver
end
respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name))
end
Now, the problem with this last solution is that you are sending an email to a user that doesn't exist... And so when the user comes back, he won't be able to enter his new password since his user account can't be found. But if you really want to do this, hopefully I set you on the right track.
Related
I have set confirmable in my devise user model but when I try to register email, which is already registered, I get error that email is registered or waiting for confirmation.
This is correct but for security reasons I want to always show something like "confirmation email has been sent to email address" so nobody can figure out which emails are already registered in the app.
Is this somewhere in devise config? Or do I have to manually modify registration controller to not throw any error if email exists in db?
The best way is way is to manually modify from registration controller (https://github.com/heartcombo/devise#configuring-controllers), you can rescue the error and changes the flash message.
I borrowed has_duplicate_email? from this post , checked devise source code and then modified create in registration controller and routes to use it. I hope it helps somebody
def has_duplicate_email?
return false unless resource.errors.has_key?(:email)
resource.errors.details[:email].any? do |hash|
hash[:error] == :taken
end
end
def create
super do |resource|
if has_duplicate_email?
set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
expire_data_after_sign_in!
redirect_to root_path and return
end
end
end
devise_for :users, :controllers => {:registrations => "users/registrations"}
Kind of new to rails and I'm in need of some help!
I'm using the devise gem and I'm trying to find the current user that just signed-up or that is trying to sign-in by setting up a cookies[:email].
I have the following inside the sessions_contoller.rb and the registrations_contoller.rb
def create
cookies[:email] = params[:user][:email]
#and some other code as well
end
def destroy
super
cookies.delete :email
end
The following method is to find the user according to the email:
def current_client
if cookies["email"].present?
User.find_by_email(params[:email])
end
end
If I check from the browser I can see the email cookie is there, not sure how to check from the rails c though.
But the current_client method doesn't seem to be working.
Any idea what might be wrong here?
Thanks in advance!
While setting the value of cookie, try to set it as below:
cookies[:email] = {:value => params[:user][:email], :domain => :your_domain, :expires => 30.days.from_now}
Also you can check the cookie by printing its value in controller to verify it was set or not.
I currently use Devise for user registration/authentication in a Rails project.
When a user wants to cancel their account, the user object is soft deleted in a way like the following.
How to "soft delete" user with Devise
My implmenetation has a small difference this way.
User model has an attribute 'deleted_flag'.
And, soft_delete method executes "update_attribtue(:deleted_flag, true)"
But, I have to implment sign_in action.
In my implmenetation is the following.
class SessionsController < Devise::SessionsController
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
if resource.deleted_flag
p "deleted account : " + resource.deleted_flag.to_s
sign_out(resource)
render :controller => :users, :action => :index
else
if is_navigational_format?
if resource.sign_in_count == 1
set_flash_message(:notice, :signed_in_first_time)
else
set_flash_message(:notice, :signed_in)
end
end
sign_in(resource_name, resource)
respond_with resource, :location => redirect_location(resource_name, resource)
end
end
end
I think this code has strange points.
If deleted user tries to sing in,
the system permit logging and make log out immediately.
And, the system cann't display flash[:alert] message...
I want to know two points.
How do I implement to prohibit deleted users to login?
How do I implement to display flash[:alert] when deleted user tries to login?
To stop a user that has been 'soft deleted', the best way is to overwrite the find_for_authentication class method on the user model. Such as:
Class User < ActiveRecord::Base
def self.find_for_authentication(conditions)
super(conditions.merge(:deleted_flag => false))
end
This will generate a invalid email or password flash message by devise (because it cannot find the user to authenticate)
As far as your second question though, you'll need some for of method in your controller to add a particular flash message. However, in my opinion you should treat users that are 'soft' deleted the same as if they didn't exist in the database at all. Thus if they tried to log in, they should just get an valid email or password message.
See my solution here: https://stackoverflow.com/a/24365051/556388
Basically you need to override the active_for_authentication? method on the devise model (User).
I haven't tried anything like that but it seems if you want to catch the user before authentication you'll either have to write a Devise authentication strategy or a before_filter to be run before authenticate_user!. Something like:
before_filter :no_deleted_users
def no_deleted_users
if User.find(params[:email]).deleted?
redirect_to root_path, :flash => { :error => "Your user was deleted. You cannot log in." }
end
end
Although it might be more complex to get the user than that. I haven't played with Devise pre-authentication.
The modern and correct answer is this:
class User < ApplicationRecord
def active_for_authentication?
super && !discarded? # or whatever...
end
end
See the documentation here.
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.
In my application I want users to be able to create a group and invite other users to that group for collaboration. The important thing is that these groups are separated so their posts are not mixed. I have looked for awhile and I am not really sure how to get started on this problem. Any help will be appreciated!
TIA
I found this link but not sure how to apply it.
http://www.icoretech.org/2010/03/rails-users-groups-memberships-enter-workflow/
That link has a very sophisticated implementation of user groups and memberships. It even shows how to use the awesome Workflow gem to implement a state machine to track the process of joining a group. Honestly, I doubt you'll get a much better answer. I suggest you just take the code in the blog post as a starting point and make modifications to suit your needs.
The only thing missing is invitations. I would keep it simple and just add an invitation_token column to Group. When an invitation is sent, the token is used to generate a SHA-1 hash which can be part of the link sent to the invited user. When the link is clicked, the controller can check if the invitation code is valid and add the user to the group.
Here's a little sample code to give an idea of the implementation. I'm sure there is plenty of room for improvement, but hope it gives you some direction:
# in your Group model
def redeem_token(some_code, invitee_name)
invitation_token == decode_invitation_code(some_code, invitee_name)
end
def decode_invitation_code(encrypted, salt)
# use EzCrypto or something similar : http://ezcrypto.rubyforge.org/
# use the invitation_token as the password
# and the invitee name as the salt
EzCrypto::Key.decrypt_with_password invitation_token, salt, encrypted
end
def generate_invitation_for(user)
# use invitee name as salt
# and invitation_token as both password and content
EzCrypto::Key.encrypt_with_password invitation_token,
user.name,
invitation_token
end
# in your routes.rb do something like
resources :groups do
member do
get 'invitation/:invitation_token', :action => :invitation
end
# ...
end
# in your groups_controller.rb
def invitation
#group = Group.find(:id)
if #group.redeem_token(params[:invitation_token], current_user.name)
#group.add_member(current_user)
redirect_to root_path, :alert => "You were added to the group!"
else
redirect_to root_path, :alert => Invitation code not valid!"
end
end
Hope you find this helpful.