I'm attempting to display a users password along in his confirmation page sent by the Devise mailer. The confirmation page is the default
Welcome test0#test.com!
You can confirm your account email through the link below:
Confirm my account
However, I wish to have
Welcome test0#test.com!
Your password is currently DASADSADS
You can confirm your account email through the link below:
Confirm my account
How do I access the user object in the view? Do I need to override the mailer controller with a custom one? If so, how do I tell what the methods of the current mailer do (tried looking at documentation but can't find any clues)?
I noticed that #email and #resource are used in the view. Can I use any of these to access the current password in its unhashed form?
Note that I am sending this email manually with user.find(1).send_confirmation_instructions
Although this can be done, I would caution very strongly against doing so. Hashed passwords are specifically used so that the password cannot be recreated easily. Passing the original password back to the user will cause it to be sent back in plain text which sort of defeats the whole purpose. Also, shouldn't the user already know their password (they did type it in twice after all)?!?
To do this, you would need to capture the original (unhashed) password in the registration create action and send the email at that point (passing along the password). You can do this by overriding the sign_up method - you can do this in an initializer:
class Devise::RegistrationsController < DeviseController
def sign_up(resource_name, resource)
sign_in(resource_name, resource)
resource.unhashed_password = resource_params[:password]
resource.send_confirmation_instructions
end
end
Alternatively, you can derive a new controller from Devise::RegistrationsController and put this override code there (the recommended approach - but then again, this whole operation isn't really recommended). You'll need to add the unhashed_password accessor for this to work:
class User < ActiveRecord::Base
attr_accessor :unhashed_password
end
And then you can update your confirmation view (at app/views/devise/mailer/confirmation_instructions.html.erb) to contain this:
<p>Your password is currently <%= #resource.unhashed_password %></p>
Devise save password in encrypted form: You can decrypt it using,
Generate new migration:
$ rails g migration AddLegacyPasswordToUser legacy_password:boolean
invoke active_record
create db/migrate/20120508083355_add_legacy_password_to_users.rb
$ rake db:migrate
Using legacy_password method in following code you can decrypt your password:
class User < ActiveRecord::Base
...
def valid_password?(password)
if self.legacy_password?
# Use Devise's secure_compare to avoid timing attacks
if Devise.secure_compare(self.encrypted_password, User.legacy_password(password))
self.password = password
self.password_confirmation = password
self.legacy_password = false
self.save!
else
return false
end
end
super(password)
end
# Put your legacy password hashing method here
def self.legacy_password(password)
return Digest::MD5.hexdigest("#{password}-salty-herring");
end
end
You can just use request.request_parameters[:user][:password] to get the plain text password on the create or update action.
Related
In my Rails app, I use devise to manage users with devise's default configuration. I need to md5 the user-provided password before going down into the devise layer. That is, two steps are included:
(1) (2)
password_in_plain --- password_in_md5 --- password_in_bcrypt.
The first one(1) is our concern, the last one(2) is not (devise takes care of it).
I generated two devise controllers: registrations and sessions, and added a before_filter to do the job -- md5 the plain password user provided. The user can be registered with a success, but login always fail. Here is my code:
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :md5_password_params, only: [:create]
protected
def md5_password_params
params[:user][:password] = Digest::MD5.hexdigest(params[:user][:password])
params[:user][:password_confirmation] = Digest::MD5.hexdigest(params[:user][:password_confirmation])
end
end
class Users::SessionsController < Devise::SessionsController
before_filter :md5_password, only: [:create]
protected
def md5_password
params[:user][:password] = Digest::MD5.hexdigest(params[:user][:password])
end
end
What's wrong here? Please help. Thanks in advance.
Background: one of my other server apps (written in c++) will create user records in the same database directly. For safety reason, a md5 password will be sent to this server when users make registrations in their phones.
Instead of doing this on the controller level and mucking about with the params you can create your own encryptor for Devise.
One major reason you would want to do this is that if your user fills in the registration form and submits it with invalid values both the password and confirmation fields will contain a MD5 digest of what the user originally typed. This is because you are mutating the params hash before it is fed to the model.
Submitting the form again means that the password is encrypted twice and no longer matches what the user thinks the original password is. On the plus side its extremely secure since nobody can actually guess their password ;)
Based on the current version of Devise you should be able to do something like the following to ensure the password is encrypted before a digest is created and before trying to compare plaintext with the encrypted password.
Make sure to add the devise-encryptable gem to your Gemfile. Its required for anything else than default encryption.
# config/initializers/md5.rb
require 'digest/md5'
module Devise
module Encryptable
module Encryptors
class BCryptPlusMD5 < Base
def self.digest(klass, password)
super(klass, Digest::MD5.hexdigest(password))
end
def self.compare(klass, hashed_password, password)
super(klass, hashed_password, Digest::MD5.hexdigest(password))
end
end
end
end
end
You would can configure what encryptor to use in config/initializers/devise.rb - however I have not been able to find a good source on how the class lookup works.
https://github.com/plataformatec/devise/blob/master/lib/devise/encryptor.rb
I have a RoR application (Rails 4.2, Ruby 2.2.0) running Devise. I have set it up so that admin users (identified the "is_admin" boolean I added to the user model) are able to create new user account, provide them with a generated password and confirmation email. This is all working properly. I have also added the datetime column pass_changed which should be updated when a user changes their password, and then checked against created_at to make sure that the password has changed since the account was created. If the two dates are equal then the user is redirected to the password change form.
I wrote a procedure for checking that the user has changed their password and placed it in my application_controller.rb:
def check_changed_pass
#user = current_user
if #user.pass_changed == #user.created_at #make sure user has changed their password before accessing internal pages
redirect_to edit_user_registration_path, alert: "You must change your password before logging in for the first time"
end
end
Then in my internal_controller.rb (used for all internal that require the user to be logged in:
before_action :check_changed_pass
So far so good, but the problem comes with attempting to update pass_changed at the right time. I have tried various configurations, but I can't find where to put this code so that it triggers when a password is updated, but not every time the user model is updated, which includes every login and logout, when I only want it to trigger if the password gets updated.
If I place "before_save :update_pass_change, only: [:edit, :update]" in my controller it doesn't trigger on a password update, and if I place it in the User model, I have to put the procedure in the model as well and then it won't pick up current_user as its not available in the model. The ideal thing would be if Devise had a hook for after_password_edit similar to the after_database_authentication hook. I had to override Devise's registrations_controller.rb to remove the line
prepend_before_filter :require_no_authentication, only: [ :cancel]
So that admin users would be able to add new users. I tried placing update_pass_change there, but it doesn't seem to trigger before_save on a password edit.
In application_controller.rb
def update_pass_change # records that the user has changed their password
unless current_user.pass_changed != current_user.created_at
current_user.pass_changed = Time.now
end
end
Similar unanswered question: Ruby on Rails: Devise - password change on first login
You could use a callback on your model and check, before save, if the changes includes your password attribute. Something like this:
class User < ActiveRecord::Base
before_save :check_password_changed
private
def check_password_changed
self.pass_changed = Time.now if changed.include? 'encrypted_password'
end
end
You can also try this
change datatype of pass_changed to boolean with default value false
Then in your application_controller.rb
before_action :check_pass_changed
private
def check_pass_changed
unless current_user.pass_changed
redirect_to custome_password_edit_path, alert: "You must change your password before logging in for the first time"
end
end
Then Create custom action in ur users_controller to display the password change form and to update.
eg.
To display form : custome_password_edit
To update :
update_user_password
also dont forget to write above in routes.rb
after successful change update the value of pass_changed to true
If I've got a rails application and I'd like to add authentication to with Devise, how would I allow users who have a null password in the database to sign in without one?
I'm interested in hearing answers along the lines of the lifecycle and what files I'd have to author to get it done.
Step 1: Allow the record to be saved.
Step 2: Sign in the record
To allow the record to be saved, you'll want to do validations yourself. I describe here how to do custom validations: http://jessewolgamott.com/blog/2011/12/08/the-one-where-devise-validations-are-customized/ .... In your case, you'll want to remove the password validations.
To sign in the record, you'll need to have a custom sign in path. You can override the devise sessions controller, but this could do the trick:
class SessionsController < ApplicationController
def create
user = User.find_by_email!(params[:session][:email])
sign_in user
redirect_to root_path
end
end
It turns out, Devise is built on Warden. This means that I only have to create my own custom Warden strategy:
https://github.com/hassox/warden/wiki/Strategies
In my rails app, I am using devise for my authentication system. I have a situation in which I want to encrypt some data using a key that is based off of the user's password. The easiest way that I can think to do this is during a successful sign in, to generate the user's private key from their plain-text password (passed in from the login form) and store that in the user's session. I don't really want to ask the user to enter their password more than once.
Does devise provide a way to specify a callback function after a successful login? Or is there a better way to do this?
http://rubydoc.info/github/plataformatec/devise/master/Devise/Models/DatabaseAuthenticatable#after_database_authentication-instance_method
In the user model where you're using devise create a after_database_authentication instance method.
Assume you have Devise resourse User with attribut password, then you can access user password after login in after_sign_in_path_for, which is called after sucessful login.
# app/control,lers/application_controller.rb
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
password = param[:user][:password]
do_cool_stuf_with_password(password)
#...
return url_for_root
end
end
I need to be able to customise the rails devise mailer view for reset password instructions.
for this I need to do two things.
Specify a custom URL for the link, so that its a host/domain based on a certain business logic. This host and domain comes from the URL in the browser, i.e. the request object, when the user clicks forgot password. So I do not have the request object in delayed_job to process it as I need, hence I need to be able to do this at some point in the delayed_job that is sending the email.
Pass custom variables to the mailer view, so that I can add in various other logic for the view, hiding and showing bits as I need.
Can anyone help? I can see that you can generate the mailer views for devise, but I need to be able pass over various items to it also. Do I need to somehow override the functions myself in my User model and password controller for example?
Overriding the whole controller method and adding param in send_reset_password_instructions opts parameters will fix it.
#resource.send_reset_password_instructions(
email: #email,
provider: 'email',
redirect_url: #redirect_url,
client_config: params[:config_name],
parameter_passed: params[:parameter_passed],
)
You can access the param in the view as message['parameter_passed']
I struggled with this too before I realized that declaring custom variables BEFORE calling super will work.
def reset_password_instructions(record, token, opts={})
#custom_variable = "Greetings, world"
# your gorgeous code
mailer_object = super
mailer_object
end
so, after much ado and searching and hacking around with stuff... this is just not possible. so I ended up writing my own mailer and bypassing the devise reset password methods in the controllers, to generate my own reset token, set my variables I needed, called my usermailer.... and embedded the devise url in my mail to get it back calling devise once the password reset link was clicked, and all was fine then....
I hated having to rewrite the logic, but in the end its the quickest and cleanest solution.
One approach that nearly worked, was using a non activerecord attribute on my user model to store the bits I needed and "hacking" that into the #resource in the devise view, but it was causing some grief in devise doing so, as a result, I went with the option above...
I needed to add a source to be included into the reset password view, here's what I implemented:
class User < ActiveRecord::Base
prepend ResetPasswordWithSource
devise :recoverable
....
end
module User::ResetPasswordWithSource
def send_reset_password_instructions(source=nil)
#source = source
super()
end
def send_devise_notification(notification, *args)
args.last.merge!({ source: #source })
super
end
end
From here you can just call user.send_reset_password_instructions('special_source')
And can access in views via #options[:source] = 'special_source'
You just need to add a flag to display in the view mailer. From here you can just call a method and pass the parameter.
#user.send_reset_password_instructions("true")
Now override the method send_reset_password_instructions
def send_reset_password_instructions(option = nil)
token = set_reset_password_token
send_reset_password_instructions_notification(token, option)
token
end
def send_reset_password_instructions_notification(token, option = nil)
send_devise_notification(:reset_password_instructions, token, :option => option)
end
Then you can access the parameter by using:
message[:option]