Allowing your users to reset their password is one of the foundational bits of functionality of just about every type of web application. It’s usually not that interesting to implement because 9 times out 10 the functionality is exactly the same for all applications.

However, this makes a great example of something we can build using Trailblazer because it’s a well known bit of functionality and it’s generally applicable to all types of web application!

In today’s tutorial we will be looking at implementing password reset using Ruby on Rails and Trailblazer.

An overview of how this is going to work

Before we jump into the code, first I want to give a quick high-level overview of how this is going to work.

There are basically two separate actions that we are going to need to build.

First, the user should be able to request a password reset email that contains a link with a unique token. We will need to create the process for accepting the user’s email address, generating the token, and then sending the email to the user.

Secondly, the email will contain a link back to the application. We need to be able to accept this token, and then accept a new password for the user.

So as you can see, this is functionality is comprised into two distinct processes.

Setting up the routes

Before I start implementing the functionality for resetting a user’s password, first I’m going to add the routes that I’m going to need.

I often find adding something high-level such as application routes helps me clarify in my head what I need to build:

Rails.application.routes.draw do  
# Password Reset  
get "password-reset/request", to: "password_reset/request#new"  
post "password-reset/request", to: "password_reset/request#create"  
get "password-reset/reset", to: "password_reset/reset#new"  
post "password-reset/reset", to: "password_reset/reset#create"  
end  

As you can see, the two processes are sent to the request and reset URLs respectively, which are both nested under password-reset.

We need two GET request URLs for display the form for requesting a password reset email, and submitting a new password. And we need two POST request URLs for accepting the form request.

So hopefully the overview and seeing the routes has made understanding how this is going to work reasonably clear. The remainder of this tutorial will be split into two parts, requesting a token and resetting the password.

Setting up the database table

The first thing we need to do is to create a new database table to store the password reminder requests:

bin/rails g model PasswordReminder token:string user:references expires_at:timestamp  
[/bash]

Here I’m using the Rails generator to create a new `PasswordReminder` model with fields for the `token` and `expires_at`, and an association to the `User` model. This command will create the model file, the migration, and a test file.

Each password reminder will have a unique token that is generated on creation. This token will be used in the URL that is used to identify the user when they click on the link from the email.

I’m also including an expires at timestamp. This will be automatically set to 1 hour into the future when the reminder is created. This will allow us to delete out expired reminders, and avoid any unnecessary security problems that may arise if we left them hanging around.


## Requesting a password reset email

Next up we need to create a new Operation for requesting a password reset email. To do this, we will need to create a new Trailblazer Operation. We’ve looked at creating Operations in [Getting started with Operations in Trailblazer](http://culttt.com/2016/05/11/getting-started-operations-trailblazer).

Create a new directory under `concepts` called `password_reset` and another file under that directory called `request`:  
```ruby  
module PasswordReset::Request  
class Operation < Trailblazer::Operation  
end  
end  

I won’t go into a lot of detail with this class as we’ve already covered the fundamentals of Trailblazer Operations and Contracts in What are Trailblazer Contracts?.

First I instruct the Operation that this is a create request and we’re creating a new PasswordReminder:

module PasswordReset::Request  
class Operation < Trailblazer::Operation  
include Model; model PasswordReminder, :create  
end  
end  

This simply means we don’t have to manually create the new model object

Next I define the contract:

module PasswordReset::Request  
class Operation < Trailblazer::Operation  
include Model; model PasswordReminder, :create

contract do  
undef :persisted?  
attr_reader :user

property :email, virtual: true

validates :email, presence: true, email: true  
validate :find_user_by_email

def find_user_by_email  
@user = User.find_by(email: email)

errors.add(:email, :not_found) unless @user  
end  
end  
end  
end  

In this situation I’m expecting the user to submit their email address. The email address is not a property on the PasswordReminder model, and so I need to set this property as virtual.

If the email has been provided I can use it to find the user, but if the email is not a registered user I can add an error to the errors property. Otherwise I will set the user instance property

With the contract all set up I can now define the process method that will deal with the processing of this operation:

require "bcrypt"

module PasswordReset::Request  
class Operation < Trailblazer::Operation  
include Model; model PasswordReminder, :create

contract do  
undef :persisted?  
attr_reader :user

property :email, virtual: true

validates :email, presence: true, email: true  
validate :find_user_by_email

def find_user_by_email  
@user = User.find_by(email: email)

errors.add(:email, :not_found) unless @user  
end  
end

def process(params)  
validate(params[:user]) do |f|  
remove_existing_tokens  
generate_token  
set_expiry_timestamp  
associate_user  
f.save  
send_password_reset_email  
remove_expired_tokens  
end  
end

def remove_existing_tokens  
PasswordReminder.delete_all(user_id: contract.user.id)  
end

def generate_token  
model.token = SecureRandom.urlsafe_base64  
end

def set_expiry_timestamp  
model.expires_at = Time.now + 1.hour  
end

def associate_user  
model.user = contract.user  
end

def send_password_reset_email  
UserMailer.password_reset(model).deliver_later  
end

def remove_expired_tokens  
PasswordReminder.delete_all("expires_at < ‘#{Time.now}’")  
end  
end  
end  

First I will validate the incoming data and ensure that it is correct. This is basically just ensuring that the email is a valid email address for a registered user.

Next we can step through the process of the Operation:

def remove_existing_tokens  
PasswordReminder.delete_all(user_id: contract.user.id)  
end  

First I will remove any existing tokens that have already been created for that user. I only want there to be one password reset token per user.

 def generate_token  
 model.token = SecureRandom.urlsafe_base64  
 end  

Next I will generate a new token for the new PasswordReminder that is getting created in this process.

 def set_expiry_timestamp  
 model.expires_at = Time.now + 1.hour  
 end  

Next I will set the expiry timestamp to be the current time plus 1 hour.

 def associate_user  
 model.user = contract.user  
 end  

Next I will associate the user from the contract lookup method to the PasswordReminder object.

After this method has been called we have everything in place to save the new PasswordReminder to the database.

Next I will send the password reset email:

def send_password_reset_email  
UserMailer.password_reset(model).deliver_later  
end  

And finally I will use this opportunity to remove any password reminder tokens that have expired:

def remove_expired_tokens  
PasswordReminder.delete_all("expires_at < ‘#{Time.now}’")  
end  

Adding the Email

In the previous section we sent an email using the UserMailer. Before I write my tests I’m just going to add this in. Strictly speaking, I shouldn’t be doing it in this order, but life isn’t always TDD.

First I will add a new method to the UserMailer class that I generated in Building out a User Confirmation flow in Trailblazer:

class UserMailer < ApplicationMailer  
def confirm_membership(user)  
@user = user  
@url = confirmation_confirm_url(token: @user.confirmation_token)  
mail(to: @user.email, subject: "Confirm your Culttt Membership!")  
end

def password_reset(password_reminder)  
@user = password_reminder.user  
@url = password_reset_reset_url(token: password_reminder.token)  
mail(to: @user.email, subject: "Your Culttt password reset!")  
end  
end  

In this method I’m accepting the PasswordReminder, grabbing the user and setting it as an instance property of the class.

Next I’m going to generate the password reset url using the token from the reminder.

Finally I will call the mail method and pass the user’s email and my subject line.

To finish this part of the process off I will also need to create a new view for the email.

Create a new file under the user_mailer directory called password_reset.html.slim:

p Hey,  
p Click #{@url} to reset your password.  

The Operation Tests

Now that we have the Operation and the email set up, I will write a couple of tests to make sure everything is working correctly:

require "test_helper"

module PasswordReset::RequestTest  
class OperationTest < ActiveSupport::TestCase  
test "require email" do  
res, op = PasswordReset::Request::Operation.run(user: {})

assert_not(res)  
assert_includes(op.errors[:email], "can’t be blank")  
end

test "require registered email" do  
res, op = PasswordReset::Request::Operation.run(user: {email: "name@domain.com"})

assert_not(res)  
assert_includes(op.errors[:email], "not found")  
end

test "create password reset" do  
user = User::Create::Operation::Default.(user: attributes_for(:user)).model

# Seed the db to ensure that existing reminders are removed  
PasswordReset::Request::Operation.run(user: {email: user.email})

res, op = PasswordReset::Request::Operation.run(user: {email: user.email})

mail = ActionMailer::Base.deliveries.last

assert(res)  
assert_equal(op.model.user, user)  
assert_equal(user.email, mail[:to].to_s)  
assert_equal("Your Culttt password reset!", mail[:subject].to_s)  
assert_equal(1, PasswordReminder.count)  
end  
end  
end  

If you have been following along with these tutorials this should look pretty familiar by now. If not, I would recommend going back through the last couple of Trailblazer tutorials for a better understanding of what is going on here.

The first two tests are simply ensuring my validation rules are working as they should.

The final test is walking through the process of generating a new password reminder. In this test I’m also ensuring that any previous reminders for this user have been removed from the database. And I’m checking to make sure the email has been sent correctly.

Creating the Form

Next up we need to create the form that the user will submit. To do this I’m going to use a Cell. We have previously looked into using Trailblazer Cells in Getting started with Trailblazer Cells:

module PasswordReset::Request  
class Cell < Culttt::Cells::Form  
def show  
render  
end  
end  
end  

This Cell doesn’t need any fancy functionality so the Cell class is really simple.

Next I will need to create the view file

– if form.errors.any?  
ul  
– form.errors.full_messages.each do |msg|  
li = msg

= form_tag password_reset_request_url, class: "password-reset-request-form" do |f|  
div  
= label_tag :email, nil, class: "qa-email-label"  
= text_field_tag :email, nil, class: "qa-email-input"  
div  
= submit_tag "Reset your password", class: "qa-submit"  

Again this is just a typical slim form view. If you have been following a long with this series this should look fairly familiar to you by now.

The nice thing about encapsulating the form in a Cell is that we can very easily write tests to ensure the form is created correctly:

require "test_helper"

module PasswordReset::Request::CellTest  
class CellTest < Cell::TestCase  
controller Confirmation::RequestController

test "has correct markup" do  
html = concept("password_reset/request/cell", PasswordReset::Request::Operation.present({})).()

html.must_have_selector("form.password-reset-request-form")  
html.must_have_selector("label.qa-email-label")  
html.must_have_selector("input.qa-email-input")  
html.must_have_selector("input.qa-submit")  
end  
end  
end  

In this test file I’m generating the markup for the form and then making assertions to ensure that the correct input fields have been generated correctly.

Adding the Controller

Now that we have the Operation and the Cell in place we can add the Controller to coordinate the request:

module PasswordReset  
class RequestController < ApplicationController  
def new  
form PasswordReset::Request::Operation  
end

def create  
run PasswordReset::Request::Operation do |op|  
return redirect_to login_url  
end

render :new, status: 400  
end  
end  
end  

This is another great example of how beautifully simple your Controllers will be if you decide to use Trailblazer.

At this point I’m also going to write a couple of Controller tests:

require "test_helper"

module PasswordReset  
class RequestControllerTest < ActionController::TestCase  
test "display form" do  
get :new

assert_response(:success)  
end

test "fail with invalid email" do  
post :create, user: {email: "invalid email"}

assert_response(400)  
end

test "fail with not found email" do  
post :create, user: {email: "name@domain.com"}

assert_response(400)  
end

test "send password reset email" do  
@user = User::Create::Operation::Default.(user: attributes_for(:user)).model

assert_difference "ActionMailer::Base.deliveries.size" do  
post :create, user: {email: @user.email}  
end

assert_response(302)  
assert_redirected_to(login_url)  
end  
end  
end  

These tests simply walk through the process of invoking the Controller methods and asserting that the correct action is taken.

Testing the flow

Now that we have all of the request side functionality in place, I’m going to write a couple of integration tests to test the application is working from the outside in:

require "test_helper"

module PasswordResetFlowsTest  
class RequestFlowsTest < ActionDispatch::IntegrationTest  
test "attempt with invalid email" do  
get "/password-reset/request"  
assert_response :success

post_via_redirect "/password-reset/request", user: {email: ""}  
assert_equal "/password-reset/request", path  
assert_response 400  
end

test "attempt with not found email" do  
get "/password-reset/request"  
assert_response :success

post_via_redirect "/password-reset/request", user: {email: "name@domain.com"}  
assert_equal "/password-reset/request", path  
assert_response 400  
end

test "send the password reset request" do  
user = User::Create::Operation::Default.(user: attributes_for(:user)).model

get "/password-reset/request"  
assert_response :success

assert_difference "ActionMailer::Base.deliveries.size" do  
post_via_redirect "/password-reset/request", user: {email: user.email}  
end

assert_equal "/login", path  
assert_response :success  
end  
end  
end  

Hopefully these integration tests are fairly self explanatory as they should provide documentation for how the application should be used from the perspective of the user from the outside.

In each test I’m basically just asserting that the user is redirected to the correct place based upon their actions.

Resetting the password

With the request process in place we can now turn our attention to the process which will allow the user to use the reminder to reset their password.

The first thing we will create will be the operation that will handle the request:

module PasswordReset::Reset  
class Operation < Trailblazer::Operation  
include Resolver  
include Model; model PasswordReminder

def self.model!(params)  
PasswordReminder.find_by!(token: params[:token])  
end

contract do  
property :password, virtual: true

validates :password, presence: true, length: { minimum: 8 }  
end

def process(params)  
validate(params[:user]) do |f|  
generate_digest  
delete_reminder  
end  
end

def generate_digest  
model.user.password_digest = BCrypt::Password.create(contract.password)  
end

def delete_reminder  
PasswordReminder.delete(model.id)  
end  
end  
end  

There are a couple of important things to note with this operation

Firstly I’m overriding the self.model! method to find the password reminder by the token. The token should be supplied via the URL and so if the reminder is not found, we can just bail out of the operation.

Secondly, this operation is centered around the PasswordReminder model but we’re actually resetting the password on the User model. To deal with this we can set the password property to be virtual in the contract.

Finally we can use the typical process of the operation to handle the business logic. First we validate that the password meets the requirements of being present and at least 8 characters long.

Next we can generate the password digest and finally we can delete the password reminder from the database.

We can also write a couple of tests to ensure that the operation is working correctly:

require "test_helper"

module PasswordReset::ResetTest  
class OperationTest < ActiveSupport::TestCase  
test "require valid token" do  
assert_raises ActiveRecord::RecordNotFound do  
PasswordReset::Reset::Operation.run(token: "")  
end  
end

test "require password" do  
user = User::Create::Operation::Default.(user: attributes_for(:user)).model  
reset = PasswordReset::Request::Operation.(user: {email: user.email}).model

res, op = PasswordReset::Reset::Operation.run(token: reset.token, user: {})

assert_not(res)  
assert_includes(op.errors[:password], "can’t be blank")  
end

test "require valid password" do  
user = User::Create::Operation::Default.(user: attributes_for(:user)).model  
reset = PasswordReset::Request::Operation.(user: {email: user.email}).model

res, op = PasswordReset::Reset::Operation.run(token: reset.token, user: {password: "abc"})

assert_not(res)  
assert_includes(op.errors[:password], "is too short (minimum is 8 characters)")  
end

test "reset user password" do  
user = User::Create::Operation::Default.(user: attributes_for(:user)).model  
reset = PasswordReset::Request::Operation.(user: {email: user.email}).model

res, op = PasswordReset::Reset::Operation.run(token: reset.token, user: {password: "password"})

assert(res)  
assert(user.reload.password_digest, BCrypt::Password.create("password"))  
assert_equal(0, PasswordReminder.count)  
end  
end  
end  

In the first test I’m ensuring that an Exception is thrown if the token is invalid. In the second and third tests I’m ensuring that the password is required and it should be at least 8 characters long. And finally in the last test I’m ensuring that the password is reset and the password reminder is removed from the database.

Creating the Cell

Next up we need to provide a form to allow the user to submit their new password. As usual we can encapsulate this in a Cell:

module PasswordReset::Reset  
class Cell < Culttt::Cells::Form  
def show  
render  
end  
end  
end  

Once again as this is such a simple example we don’t need to add anything to the Cell. Here is the accompanying view for the form:

– if form.errors.any?  
ul  
– form.errors.full_messages.each do |msg|  
li = msg

= form_tag password_reset_reset_url, class: "password-reset-reset-form" do |f|  
div  
= label_tag :password, nil, class: "qa-password-label"  
= text_field_tag :password, nil, class: "qa-password-input"  
div  
= submit_tag "Reset your password", class: "qa-submit"  

I will also include a test to ensure that the correct markup is generated:

require "test_helper"

module PasswordReset::Reset::CellTest  
class CellTest < Cell::TestCase  
controller Confirmation::RequestController

test "has correct markup" do  
user = User::Create::Operation::Default.(user: attributes_for(:user)).model  
reset = PasswordReset::Request::Operation.(user: {email: user.email}).model

html = concept("password_reset/reset/cell", PasswordReset::Reset::Operation.present(token: reset.token)).()

html.must_have_selector("form.password-reset-reset-form")  
html.must_have_selector("label.qa-password-label")  
html.must_have_selector("input.qa-password-input")  
html.must_have_selector("input.qa-submit")  
end  
end  
end  

Adding the Controller

With the Operation and the Cell in place we can now create the Controller:


module PasswordReset  
class ResetController < ApplicationController  
def new  
form PasswordReset::Reset::Operation  
end

def create  
run PasswordReset::Reset::Operation do |op|  
return redirect_to login_url  
end

render :new, status: 400  
end  
end  
end  

Once again the beauty of Trailblazer can be seen in how simple this Controller is.

At this point I will also write a couple of tests to ensure that everything is hooked up correctly:

require "test_helper"

module PasswordReset  
class ResetControllerTest < ActionController::TestCase  
def setup  
@user = User::Create::Operation::Default.(user: attributes_for(:user)).model  
@reset = PasswordReset::Request::Operation.(user: {email: @user.email}).model  
end

test "return 404 on invalid token" do  
assert_raises ActiveRecord::RecordNotFound do  
get :new  
end  
end

test "display form" do  
get :new, token: @reset.token

assert_response(:success)  
end

test "fail with missing password" do  
post :create, token: @reset.token, user: {}

assert_response(400)  
end

test "fail with invalid password" do  
post :create, token: @reset.token, user: {password: "abc"}

assert_response(400)  
end

test "reset user password" do  
post :create, token: @reset.token, user: {password: "password"}

assert_response(302)  
assert_redirected_to(login_url)  
end  
end  
end  

As you can see, these tests are really just the same tests as the Operation tests from earlier but at a higher level of abstraction.

Testing the flow

Finally I’m going to add a couple of integration tests to verify that the functionality is working from the outside in:

class ResetFlowsTest < ActionDispatch::IntegrationTest  
def setup  
@user = User::Create::Operation::Default.(user: attributes_for(:user)).model  
@reset = PasswordReset::Request::Operation.(user: {email: @user.email}).model  
end

test "attempt with invalid token" do  
assert_raises ActiveRecord::RecordNotFound do  
get "/password-reset/reset?token=invalid"  
end  
end

test "attempt with invalid password" do  
get "/password-reset/reset?token=#{@reset.token}"  
assert_response :success

post_via_redirect "/password-reset/reset", token: @reset.token, user: {password: "abc"}  
assert_equal "/password-reset/reset", path  
assert_response 400  
end

test "reset user password" do  
get "/password-reset/reset?token=#{@reset.token}"  
assert_response :success

post_via_redirect "/password-reset/reset", token: @reset.token, user: {password: "password"}  
assert_equal "/login", path  
assert_response :success  
end  
end  

Again, I’m essentially testing the same things again but at another level of abstraction. As we saw in Writing Integration Tests in Ruby on Rails, these tests are probably the most important because they show that each component of the application is working together correctly, and they also provide documentation to show how the application should work.

I think a good rule of thumb is that you should only be doing stuff in the browser once you’re very confident everything is working. It’s much easier to write a test, than it is to keep manually going through the process in the browser.

Conclusion

Phew that was a long tutorial, well done for getting this far, I hope it was worth it!

Resetting user passwords is something that almost all applications need in one form or another. Despite this being a kinda boring thing to implement, I think it is a good illustration of many of the things we’ve been looking at over the last couple of weeks.

So I hope today’s tutorial has inspired you to take Trailblazer for a spin for your next Ruby project!