Passwords and More Security for a Rails Ecommerce Application
In this third part to a four-part series on building the security into a Ruby-on-Rails ecommerce application, we'll focus on the changes we need to make to the program so that users can change their passwords. This article is excerpted from chapter eight of the book Practical Rails Projects, written by Eldon Alameda (Apress; ISBN: 1590597818).
Passwords and More Security for a Rails Ecommerce Application (Page 1 of 4 )
Implementing the Reset Password User Story
To implement the third user story, Reset Password, we need a way to send e-mail messages from our application. The acts_as_authenticatedplugin comes with a generator for this, too. Execute the following command:
There are two interesting things created by thegenerate authenticated_mailercommand:UserNotifier(an ActionMailer object) andUserObserver(an observer). Even though neither of them is a normal ActiveRecord model, they both reside in theapp/modelsdirectory. We’ll cover the mailer part now and talk about observers after we update theUsermodel.
Using ActionMailer Mailers
Rails has a specific package for sending (and receiving, which we don’t need in this chapter) e-mail called ActionMailer. ActionMailer mailers are Rails classes stored inapp/modelsjust like normal ActiveRecord models, but they work quite differently.
TheUserNotifier mailer class we just created inapp/models/user_notifier.rblooks like this:
def activation(user) setup_email(user) @subject += 'Your account has been activated!' @body[:url] = http://YOURSITE/ end
protected def setup_email(user) @recipients = "#{user.email}" @from = "ADMINEMAIL" @subject = "[YOURSITE] " @sent_on = Time.now @body[:user] = user end end
Here,signup_notificationandactivationrepresent two different e-mail messages sent by the class. The former is sent when a new user has registered and must activate her account, and the latter is sent when the activation is complete. They both use the protectedsetup_emailmethod to prepare common header attributes of the e-mail, such asrecipients,from, andsubject. You can also set attributes for the message body, such as@body[:url]and@body[:user]. They will be available as instance variables in the e-mail templates.
We don’t need the two mail methods that exist in the mailer, so we delete them and add our own method, as follows:
class UserNotifier < ActionMailer::Base @@session = ActionController::Integration::Session.new
protected def setup_email(user) @recipients = "#{user.email}" @from = admin@emporium-books.com @subject = "[Emporium] " @sent_on = Time.now @body[:user] = user end end
forgot_passwordis the mail method we deliver when George or someone from his staff requests a password reset. In the method, we set the subject for the mail, as well as define the password-reset URL sent in the e-mail message. Note that asurl_foris an instance method forActionController controllers, we can’t call it directly from inside a mailer. However, with the trickery on the first line, we create a newActionController::Integration::Sessionobject through which we can callurl_for, and store it in a class variable, which can be used everywhere inside our mailer class. We also change thesetup_emailmethod a bit, to accommodate our application.
Next, we need to create a template for the mail body. Create a new template calledforgot_password.rhtmlinapp/views/user_notifierand put the following code in it:
Dear <%= @user.login %>,
Click the following link to reset your password at Emporium: <%= @url %>
As you can see, the@bodyhash contents from the mailer method have been extracted to instance variables in the template, so that, for example,@body[:user]became@userand@body[:url]became@url.
Now that we have a mailer class and template ready, we can deliver a password-reset e-mail message by callingUserNotifier.deliver_forgot_password(@user_object). Rails will automatically retrieve the mailer method name after thedeliver_part in the method call, and deliver the mail prepared by that method.
Tip If you want to delay the delivery of the e-mail (for example, because you have a mail sweeper that takes care of the deliveries), you can usecreate_instead ofdeliver_in the method call, and you will get aTMail object in return. For more information aboutTMail, seehttp://i.loveruby.net/en/projects/tmail.