Custom variables in Devise reset password instructions? - ruby-on-rails

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]

Related

Use devise for a model other than user

So, I am somewhat new to rails and devise, so I apologize in advance if this is a basic question. I couldn't find any information on this anywhere, and I searched thoroughly. This also makes me wonder if Devise is the right tool for this, but here we go:
I have an app where devise user authentication works great, I got it, implemented it correctly and it works.
In my app, users can belong to a group, and this group has a password that a user must enter to 'join' the group.
I successfully added devise :database_authenticatable to my model, and when I create it an encrypted password is created.
My problem, is that I cannot authenticate this! I have a form where the user joins the group, searching for their group, then entering the password for it.
This is what I tried:
def join
#home = Home.find_for_authentication(params[:_id]) # method i found that devise uses
if #home.valid_password?(params[:password]);
render :json => {success: true}
else
render :json => {success: false, message: "Invalid password"}
end
end
This gives me the error: can't dup NilClass
on this line: #home = Home.find_for_authentication(params[:_id])
What is the problem?
The problem will be here:
Home.find_for_authentication(params[:_id])
I've never used database_authenticatable before (will research it, thanks!), so I checked the Devise docs for you
The method they recommend:
User.find(1).valid_password?('password123') # returns true/false
--
Object?
The method you've used has a doc:
Find first record based on conditions given (ie by the sign in form).
This method is always called during an authentication process but it
may be wrapped as well. For instance, database authenticatable
provides a find_for_database_authentication that wraps a call to
this method. This allows you to customize both database
authenticatable or the whole authenticate stack by customize
find_for_authentication.
Overwrite to add customized conditions, create a join, or maybe use a
namedscope to filter records while authenticating
The actual code looks like this:
def self.find_for_authentication(tainted_conditions)
find_first_by_auth_conditions(tainted_conditions)
end
Looking at this code, it seems to me passing a single param is not going to cut it. You'll either need an object (hence User.find([id])), or you'll need to send a series of params to the method
I then found this:
class User
def self.authenticate(username, password)
user = User.find_for_authentication(:username => username)
user.valid_password?(password) ? user : nil
end
end
I would recommend doing this:
#home = Home.find_for_authentication(id: params[:_id])
...

Displaying User Password With Devise Confirmation Page

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.

Customizing Devise gem (Rails)

I am trying to add a new field "registration code" along with the email/password in sign up form. (i have the registration code field in a separate database, only a valid registration code/email pair will have to work with the sign up)
I could not able to find any controller for actions done by devise gem.
How do i customize devise to achieve this?
Thanks in advance.
It seems like your question basically has nothing to do with Devise itself (besides the views). To validate your registration code/email pairs, you surely need to add this as validation.
The easy way to validate registration code could be:
class User
validate :validate_registration_code
private
def validate_registration_code
reg_code = RegistrationCode.find_by_code(registration_code)
unless reg_code.email == record.email
errors.add(:registration_code, "Invalid registration code for #{record.email}")
end
end
end
You also might want to write simple custom validator:
class RegistrationCodeValidator < ActiveModel::Validator
def validate(record)
# actual reg code validation
# might look like:
reg_code = RegistrationCode.find_by_code(record.registration_code)
unless reg_code.email == record.email
record.errors[:registration_code] << "Invalid registration code for #{record.email}"
end
end
end
# in your User model
class User
# include registration code validator
include RegistrationCodeValidator
validates_with MyValidator
end
Devise keeps it's controllers behind the scenes inside the gem. If you want to add an action or modify one, you have to subclass it and do a little work in routes to get the action to your controller.
However you shouldn't need to do that to add a field. See goshakkk's answer

Rails devise user function

How do I execute a particular function after a user has signed up.
(I wanted to add to one of my associations, I already have it coded in a non-devise rails but now I need it here)
Device has provided helper action 'after_sign_in_path_for' you can override it within your application controller.
def after_sign_in_path_for(resource_or_scope)
.... #write your customize code or call any method
end
For sign up it would look like:
def after_sign_up_path_for(resource_or_scope)
if resource_or_scope.is_a? User # and perhaps other conditions
#... do something, go somewhere
else
super
end
end
Ofc. Assuming that your Devise user model is called User.
You can use the after_create callback in your User model.
The following guide has tons of examples: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

How to setup default attributes in a ruby model

I have a model User and when I create one, I want to pragmatically setup some API keys and what not, specifically:
#user.apikey = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
I want to be able to run User.create!(:email=>"something#test.com") and have it create a user with a randomly generated API key, and secret.
I currently am doing this in the controller, but when I tried to add a default user to the seeds.rb file, I am getting an SQL error (saying my apikey is null).
I tried overriding the save definition, but that seemed to cause problems when I updated the model, because it would override the values. I tried overriding the initialize definition, but that is returning a nil:NilClass and breaking things.
Is there a better way to do this?
use callbacks and ||= ( = unless object is not nil ) :)
class User < ActiveRecord::Base
before_create :add_apikey #or before_save
private
def add_apikey
self.apikey ||= Digest::MD5.hexdigest(BCrypt::Password.create(self.password).to_s)
end
end
but you should definitely take a look at devise, authlogic or clearance gems
What if, in your save definition, you check if the apikey is nil, and if so, you set it?
Have a look at ActiveRecord::Callbacks & in particular before_validation.
class User
def self.create_user_with_digest(:options = { })
self.create(:options)
self.apikey = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
self.save
return self
end
end
Then you can call User.create_user_with_digest(:name => "bob") and you'll get a digest created automatically and assigned to the user, You probably want to generate the api key with another library than MD5 such as SHA256 you should also probably put some user enterable field, a continuously increasing number (such as the current date-time) and a salt as well.
Hope this helps
I believe this works... just put the method in your model.
def apikey=(value)
self[:apikey] = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
end

Resources