diff --git a/NEWS.md b/NEWS.md
index e8dc71f01..0f6581768 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -9,11 +9,21 @@ complete changelog, see the git history for each version via the version links.
- Removed support for Ruby versions older than 2.2
- Removed support for Rails versions older than 4.2
- Removed all deprecated code from Clearance 1.x
+- Removed `User#confirmation_token`, `User#forgot_password!`, and
+ `User#generate_confirmation_token` as part of the change to expiring,
+ databaseless password reset tokens.
### Changed
+- Password resets now use expiring signed tokens that do not require persistence
+ to the `users` table. By default, the tokens are generated with
+ `ActiveSupport::MessageVerifier` and expire in 15 minutes.
- Flash messages now use `flash[:alert]` rather than `flash[:notice]` as they
were used as errors more than notices.
+### Added
+- `rails generate clearance:upgrade` generator to prepare your Clearance 1.x
+ project for Clearance 2.0
+
[2.0.0]: https://github.com/thoughtbot/clearance/compare/v1.14.1...2.0
## [1.14.1] - May 12, 2016
diff --git a/app/controllers/clearance/passwords_controller.rb b/app/controllers/clearance/passwords_controller.rb
index c3176a100..0fca6207e 100644
--- a/app/controllers/clearance/passwords_controller.rb
+++ b/app/controllers/clearance/passwords_controller.rb
@@ -10,7 +10,6 @@ def new
def create
if user = find_user_for_create
- user.forgot_password!
deliver_email(user)
end
@@ -26,9 +25,9 @@ def edit
def update
@user = find_user_for_update
- if @user.update_password password_reset_params
- sign_in @user
- redirect_to url_after_update
+ if @user.update_password(password_reset_params)
+ sign_in(@user)
+ redirect_to(url_after_update)
else
flash_failure_after_update
render template: "passwords/edit"
@@ -38,45 +37,50 @@ def update
private
def deliver_email(user)
- mail = ::ClearanceMailer.change_password(user)
- mail.deliver_later
+ ::ClearanceMailer.change_password(user).deliver_later
end
def password_reset_params
params[:password_reset][:password]
end
- def find_user_by_id_and_confirmation_token
- user_param = Clearance.configuration.user_id_parameter
-
- Clearance.configuration.user_model.
- find_by_id_and_confirmation_token params[user_param], params[:token].to_s
- end
-
def find_user_for_create
Clearance.configuration.user_model.
find_by_normalized_email params[:password][:email]
end
+ def find_user_by_password_reset_token(token)
+ verifier = Clearance.configuration.message_verifier
+ user_model = Clearance.configuration.user_model
+
+ begin
+ user_id, encrypted_password, expiration = verifier.verify(token)
+ rescue
+ expiration = 1.day.ago
+ end
+
+ if expiration.future?
+ user_model.find_by(id: user_id, encrypted_password: encrypted_password)
+ end
+ end
+
def find_user_for_edit
- find_user_by_id_and_confirmation_token
+ find_user_by_password_reset_token(params[:token])
end
def find_user_for_update
- find_user_by_id_and_confirmation_token
+ find_user_by_password_reset_token(params[:token])
end
def ensure_existing_user
- unless find_user_by_id_and_confirmation_token
- flash_failure_when_forbidden
+ unless find_user_by_password_reset_token(params[:token])
+ flash_failure_when_invalid
render template: "passwords/new"
end
end
- def flash_failure_when_forbidden
- flash.now[:alert] = translate(:forbidden,
- scope: [:clearance, :controllers, :passwords],
- default: t("flashes.failure_when_forbidden"))
+ def flash_failure_when_invalid
+ flash.now[:alert] = translate("flashes.failure_when_password_reset_invalid")
end
def flash_failure_after_update
diff --git a/app/mailers/clearance_mailer.rb b/app/mailers/clearance_mailer.rb
index ffd91aa01..5749e4f6d 100644
--- a/app/mailers/clearance_mailer.rb
+++ b/app/mailers/clearance_mailer.rb
@@ -1,6 +1,8 @@
class ClearanceMailer < ActionMailer::Base
def change_password(user)
@user = user
+ @token = generate_password_reset_token(@user)
+
mail(
from: Clearance.configuration.mailer_sender,
to: @user.email,
@@ -10,4 +12,14 @@ def change_password(user)
),
)
end
+
+ private
+
+ def generate_password_reset_token(user)
+ Clearance.configuration.message_verifier.generate([
+ user.id,
+ user.encrypted_password,
+ Clearance.configuration.password_reset_time_limit.from_now,
+ ])
+ end
end
diff --git a/app/views/clearance_mailer/change_password.html.erb b/app/views/clearance_mailer/change_password.html.erb
index bc315ecba..ed96e1b4c 100644
--- a/app/views/clearance_mailer/change_password.html.erb
+++ b/app/views/clearance_mailer/change_password.html.erb
@@ -2,7 +2,7 @@
<%= link_to t(".link_text", default: "Change my password"),
- edit_user_password_url(@user, token: @user.confirmation_token.html_safe) %>
+ edit_user_password_url(@user, token: @token) %>
<%= raw t(".closing") %>
diff --git a/app/views/clearance_mailer/change_password.text.erb b/app/views/clearance_mailer/change_password.text.erb
index ed01f74b5..39231c289 100644
--- a/app/views/clearance_mailer/change_password.text.erb
+++ b/app/views/clearance_mailer/change_password.text.erb
@@ -1,5 +1,5 @@
<%= t(".opening") %>
-<%= edit_user_password_url(@user, token: @user.confirmation_token.html_safe) %>
+<%= edit_user_password_url(@user, token: @token) %>
<%= raw t(".closing") %>
diff --git a/app/views/passwords/edit.html.erb b/app/views/passwords/edit.html.erb
index 6761ab321..d924a83ac 100644
--- a/app/views/passwords/edit.html.erb
+++ b/app/views/passwords/edit.html.erb
@@ -4,8 +4,8 @@
<%= t(".description") %>
<%= form_for :password_reset,
- url: user_password_path(@user, token: @user.confirmation_token),
- html: { method: :put } do |form| %>
+ url: user_password_path(@user, token: params[:token]),
+ html: { method: :patch } do |form| %>
<%= form.label :password %>
<%= form.password_field :password %>
diff --git a/config/locales/clearance.en.yml b/config/locales/clearance.en.yml
index da8de0daa..2068c0c9d 100644
--- a/config/locales/clearance.en.yml
+++ b/config/locales/clearance.en.yml
@@ -17,6 +17,8 @@ en:
failure_when_forbidden: Please double check the URL or try submitting
the form again.
failure_when_not_signed_in: Please sign in to continue.
+ failure_when_password_reset_invalid: Your password reset token has expired
+ or is invalid.
helpers:
label:
password:
diff --git a/db/migrate/20110111224543_create_clearance_users.rb b/db/migrate/20110111224543_create_clearance_users.rb
index 75e22bfee..11106ec5c 100644
--- a/db/migrate/20110111224543_create_clearance_users.rb
+++ b/db/migrate/20110111224543_create_clearance_users.rb
@@ -4,7 +4,6 @@ def self.up
t.timestamps null: false
t.string :email, null: false
t.string :encrypted_password, limit: 128, null: false
- t.string :confirmation_token, limit: 128
t.string :remember_token, limit: 128, null: false
end
diff --git a/db/schema.rb b/db/schema.rb
index bf7e2f009..ff34b8c90 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -13,12 +13,11 @@
ActiveRecord::Schema.define(version: 20110111224543) do
- create_table "users", force: true do |t|
+ create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", null: false
t.string "encrypted_password", limit: 128, null: false
- t.string "confirmation_token", limit: 128
t.string "remember_token", limit: 128, null: false
end
diff --git a/lib/clearance/configuration.rb b/lib/clearance/configuration.rb
index 32c5bfbd2..05768a73a 100644
--- a/lib/clearance/configuration.rb
+++ b/lib/clearance/configuration.rb
@@ -47,6 +47,17 @@ class Configuration
# @return [String]
attr_accessor :mailer_sender
+ # Used to generate and verify password reset tokens
+ # Defaults to an `ActiveSupport::MessageVerifier` instance, initialized
+ # with `secret_key_base`.
+ # @return [#generate #verify]
+ attr_accessor :message_verifier
+
+ # Determines how long password reset emails are valid for
+ # Defaults to 15 minutes
+ # @return [Integer]
+ attr_accessor :password_reset_time_limit
+
# The password strategy to use when authenticating and setting passwords.
# Defaults to {Clearance::PasswordStrategies::BCrypt}.
# @return [Module #authenticated? #password=]
@@ -93,6 +104,10 @@ def initialize
@cookie_name = "remember_token"
@httponly = false
@mailer_sender = 'reply@example.com'
+ @message_verifier = ActiveSupport::MessageVerifier.new(
+ Rails.application.secrets.secret_key_base,
+ )
+ @password_reset_time_limit = 15.minutes
@redirect_url = '/'
@routes = true
@secure_cookie = false
diff --git a/lib/clearance/user.rb b/lib/clearance/user.rb
index fbf6a6e88..efd3126ea 100644
--- a/lib/clearance/user.rb
+++ b/lib/clearance/user.rb
@@ -42,10 +42,6 @@ module Clearance
# @return [String] The value used to identify this user in their {Session}
# cookie.
#
- # @!attribute confirmation_token
- # @return [String] The value used to identify this user in the password
- # reset link.
- #
# @!attribute [r] password
# @return [String] Transient (non-persisted) attribute that is set when
# updating a user's password. Only the {#encrypted_password} is persisted.
@@ -159,20 +155,6 @@ module Callbacks
end
end
- # Generates a {#confirmation_token} for the user, which allows them to reset
- # their password via an email link.
- #
- # Calling `forgot_password!` will cause the user model to be saved without
- # validations. Any other changes you made to this user instance will also
- # be persisted, without validation. It is inteded to be called on an
- # instance with no changes (`dirty? == false`).
- #
- # @return [Boolean] Was the save successful?
- def forgot_password!
- generate_confirmation_token
- save validate: false
- end
-
# Generates a new {#remember_token} for the user, which will have the effect
# of signing all of the user's current sessions out. This is called
# internally by {Session#sign_out}.
@@ -192,8 +174,7 @@ def reset_remember_token!
# the configured password strategy. By default, this is
# {PasswordStrategies::BCrypt#password=}.
#
- # This also has the side-effect of blanking the {#confirmation_token} and
- # rotating the `#remember_token`.
+ # This also has the side-effect of rotating the `#remember_token`.
#
# Validations will be run as part of this update. If the user instance is
# not valid, the password change will not be persisted, and this method will
@@ -204,7 +185,6 @@ def update_password(new_password)
self.password = new_password
if valid?
- self.confirmation_token = nil
generate_remember_token
end
@@ -246,15 +226,6 @@ def skip_password_validation?
(encrypted_password.present? && !encrypted_password_changed?)
end
- # Sets the {#confirmation_token} on the instance to a new value generated by
- # {Token.new}. The change is not automatically persisted. If you would like
- # to generate and save in a single method call, use {#forgot_password!}.
- #
- # @return [String] The new confirmation token
- def generate_confirmation_token
- self.confirmation_token = Clearance::Token.new
- end
-
# Sets the {#remember_token} on the instance to a new value generated by
# {Token.new}. The change is not automatically persisted. If you would like
# to generate and save in a single method call, use
diff --git a/lib/generators/clearance/install/install_generator.rb b/lib/generators/clearance/install/install_generator.rb
index 92c1a6e99..ef3e186b4 100644
--- a/lib/generators/clearance/install/install_generator.rb
+++ b/lib/generators/clearance/install/install_generator.rb
@@ -75,7 +75,6 @@ def new_columns
@new_columns ||= {
email: 't.string :email',
encrypted_password: 't.string :encrypted_password, limit: 128',
- confirmation_token: 't.string :confirmation_token, limit: 128',
remember_token: 't.string :remember_token, limit: 128'
}.reject { |column| existing_users_columns.include?(column.to_s) }
end
diff --git a/lib/generators/clearance/specs/templates/features/clearance/visitor_resets_password_spec.rb.tt b/lib/generators/clearance/specs/templates/features/clearance/visitor_resets_password_spec.rb.tt
index 80deeba13..224619eba 100644
--- a/lib/generators/clearance/specs/templates/features/clearance/visitor_resets_password_spec.rb.tt
+++ b/lib/generators/clearance/specs/templates/features/clearance/visitor_resets_password_spec.rb.tt
@@ -3,7 +3,6 @@ require "support/features/clearance_helpers"
RSpec.feature "Visitor resets password" do
before { ActionMailer::Base.deliveries.clear }
-<% if defined?(ActiveJob) -%>
around do |example|
original_adapter = ActiveJob::Base.queue_adapter
@@ -11,7 +10,6 @@ RSpec.feature "Visitor resets password" do
example.run
ActiveJob::Base.queue_adapter = original_adapter
end
-<% end -%>
scenario "by navigating to the page" do
visit sign_in_path
@@ -22,7 +20,8 @@ RSpec.feature "Visitor resets password" do
end
scenario "with valid email" do
- user = user_with_reset_password
+ user = FactoryGirl.create(:user)
+ reset_password_for(user.email)
expect_page_to_display_change_password_message
expect_reset_notification_to_be_sent_to user
@@ -38,26 +37,18 @@ RSpec.feature "Visitor resets password" do
private
def expect_reset_notification_to_be_sent_to(user)
- expect(user.confirmation_token).not_to be_blank
- expect_mailer_to_have_delivery(
- user.email,
- "password",
- user.confirmation_token
- )
+ expect_mailer_to_have_delivery(user.email, "password")
end
def expect_page_to_display_change_password_message
expect(page).to have_content I18n.t("passwords.create.description")
end
- def expect_mailer_to_have_delivery(recipient, subject, body)
+ def expect_mailer_to_have_delivery(recipient, subject)
expect(ActionMailer::Base.deliveries).not_to be_empty
message = ActionMailer::Base.deliveries.any? do |email|
- email.to == [recipient] &&
- email.subject =~ /#{subject}/i &&
- email.html_part.body =~ /#{body}/ &&
- email.text_part.body =~ /#{body}/
+ email.to == [recipient] && email.subject =~ /#{subject}/i
end
expect(message).to be
diff --git a/lib/generators/clearance/specs/templates/features/clearance/visitor_updates_password_spec.rb.tt b/lib/generators/clearance/specs/templates/features/clearance/visitor_updates_password_spec.rb.tt
index 4c7504436..2895b9fbf 100644
--- a/lib/generators/clearance/specs/templates/features/clearance/visitor_updates_password_spec.rb.tt
+++ b/lib/generators/clearance/specs/templates/features/clearance/visitor_updates_password_spec.rb.tt
@@ -3,14 +3,14 @@ require "support/features/clearance_helpers"
RSpec.feature "Visitor updates password" do
scenario "with valid password" do
- user = user_with_reset_password
+ user = FactoryGirl.create(:user)
update_password user, "newpassword"
expect_user_to_be_signed_in
end
scenario "signs in with new password" do
- user = user_with_reset_password
+ user = FactoryGirl.create(:user)
update_password user, "newpassword"
sign_out
sign_in_with user.email, "newpassword"
@@ -19,7 +19,7 @@ RSpec.feature "Visitor updates password" do
end
scenario "tries with a blank password" do
- user = user_with_reset_password
+ user = FactoryGirl.create(:user)
visit_password_reset_page_for user
change_password_to ""
@@ -37,10 +37,18 @@ RSpec.feature "Visitor updates password" do
def visit_password_reset_page_for(user)
visit edit_user_password_path(
user_id: user,
- token: user.confirmation_token
+ token: token_for(user)
)
end
+ def token_for(user)
+ Clearance.configuration.message_verifier.generate([
+ user.id,
+ user.encrypted_password,
+ Clearance.configuration.password_reset_time_limit.from_now,
+ ])
+ end
+
def change_password_to(password)
fill_in "password_reset_password", with: password
click_button I18n.t("helpers.submit.password_reset.submit")
diff --git a/lib/generators/clearance/specs/templates/support/features/clearance_helpers.rb b/lib/generators/clearance/specs/templates/support/features/clearance_helpers.rb
index 13540614c..5b1a2714a 100644
--- a/lib/generators/clearance/specs/templates/support/features/clearance_helpers.rb
+++ b/lib/generators/clearance/specs/templates/support/features/clearance_helpers.rb
@@ -38,12 +38,6 @@ def expect_user_to_be_signed_in
def expect_user_to_be_signed_out
expect(page).to have_content I18n.t("layouts.application.sign_in")
end
-
- def user_with_reset_password
- user = FactoryGirl.create(:user)
- reset_password_for user.email
- user.reload
- end
end
end
diff --git a/lib/generators/clearance/upgrade/USAGE b/lib/generators/clearance/upgrade/USAGE
new file mode 100644
index 000000000..82497fca8
--- /dev/null
+++ b/lib/generators/clearance/upgrade/USAGE
@@ -0,0 +1,2 @@
+Description:
+ Prepare database for upgrade to Clearance 2.0
diff --git a/lib/generators/clearance/upgrade/templates/db/migrate/remove_confirmation_token_from_users.rb.tt b/lib/generators/clearance/upgrade/templates/db/migrate/remove_confirmation_token_from_users.rb.tt
new file mode 100644
index 000000000..19675856c
--- /dev/null
+++ b/lib/generators/clearance/upgrade/templates/db/migrate/remove_confirmation_token_from_users.rb.tt
@@ -0,0 +1,5 @@
+class RemoveConfirmationTokenFromUsers < ActiveRecord::Migration<%= migration_version %>
+ def change
+ remove_column :<%= Clearance.configuration.user_model.table_name %>, :confirmation_token, type: :string, limit: 128
+ end
+end
diff --git a/lib/generators/clearance/upgrade/upgrade_generator.rb b/lib/generators/clearance/upgrade/upgrade_generator.rb
new file mode 100644
index 000000000..6c2ec88bb
--- /dev/null
+++ b/lib/generators/clearance/upgrade/upgrade_generator.rb
@@ -0,0 +1,34 @@
+require "rails/generators/base"
+require "rails/generators/active_record"
+
+module Clearance
+ module Generators
+ class UpgradeGenerator < Rails::Generators::Base
+ include Rails::Generators::Migration
+ source_root File.expand_path("../templates", __FILE__)
+
+ # for generating a timestamp when using `create_migration`
+ def self.next_migration_number(dir)
+ ActiveRecord::Generators::Base.next_migration_number(dir)
+ end
+
+ def change_users_table
+ migration_name = "remove_confirmation_token_from_users.rb"
+
+ migration_template(
+ "db/migrate/#{migration_name}.tt",
+ "db/migrate/#{migration_name}",
+ migration_version: migration_version,
+ )
+ end
+
+ private
+
+ def migration_version
+ if Rails.version >= "5.0.0"
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb
index 79ad9d89e..3026943f2 100644
--- a/spec/configuration_spec.rb
+++ b/spec/configuration_spec.rb
@@ -146,4 +146,35 @@
expect(Clearance.configuration.reload_user_model).to be_nil
end
end
+
+ describe "#message_verifier" do
+ it "returns the configured verifier if one has been configured" do
+ verifier = Class.new.new
+ Clearance.configure { |config| config.message_verifier = verifier }
+
+ expect(Clearance.configuration.message_verifier).to be verifier
+ end
+
+ it "returns an active support message verifier instance by default" do
+ Clearance.configuration = Clearance::Configuration.new
+
+ expect(Clearance.configuration.message_verifier).to be_an_instance_of(
+ ActiveSupport::MessageVerifier,
+ )
+ end
+ end
+
+ describe "#password_reset_time_limit" do
+ it "is the configured amount if overridden" do
+ Clearance.configure { |config| config.password_reset_time_limit = 1.hour }
+
+ expect(Clearance.configuration.password_reset_time_limit).to eq 1.hour
+ end
+
+ it "is 15 minutes by default" do
+ Clearance.configuration = Clearance::Configuration.new
+
+ expect(Clearance.configuration.password_reset_time_limit).to eq 15.minutes
+ end
+ end
end
diff --git a/spec/controllers/passwords_controller_spec.rb b/spec/controllers/passwords_controller_spec.rb
index 9d1fe112a..da44fac6d 100644
--- a/spec/controllers/passwords_controller_spec.rb
+++ b/spec/controllers/passwords_controller_spec.rb
@@ -16,14 +16,6 @@
describe "#create" do
context "email corresponds to an existing user" do
- it "generates a password change token" do
- user = create(:user)
-
- post :create, password: { email: user.email.upcase }
-
- expect(user.reload.confirmation_token).not_to be_nil
- end
-
it "sends the password reset email" do
ActionMailer::Base.deliveries.clear
user = create(:user)
@@ -59,9 +51,9 @@
describe "#edit" do
context "valid id and token are supplied" do
it "renders the password form for the user" do
- user = create(:user, :with_forgotten_password)
+ user = create(:user)
- get :edit, user_id: user, token: user.confirmation_token
+ get :edit, user_id: user, token: token_for(user)
expect(response).to be_success
expect(response).to render_template(:edit)
@@ -74,18 +66,43 @@
get :edit, user_id: 1, token: ""
expect(response).to render_template(:new)
- expect(flash.now[:alert]).to match(/double check the URL/i)
+ expect(flash.now[:alert]).to match(/expired or is invalid/i)
end
end
context "invalid token is supplied" do
it "renders the new password reset form with a flash alert" do
- user = create(:user, :with_forgotten_password)
+ get :edit, user_id: 1, token: "a"
+
+ expect(response).to render_template(:new)
+ expect(flash.now[:alert]).to match(/expired or is invalid/i)
+ end
+ end
+
+ context "expired token is supplied" do
+ it "does not allow password reset to continue" do
+ user = create(:user)
+ token = token_for(user)
- get :edit, user_id: 1, token: user.confirmation_token + "a"
+ Timecop.freeze(1.day.from_now) do
+ get :edit, user_id: 1, token: token
+
+ expect(response).to render_template(:new)
+ expect(flash.now[:alert]).to match(/expired or is invalid/i)
+ end
+ end
+ end
+
+ context "password is changed before reset is complete" do
+ it "does not allow password reset to continue" do
+ user = create(:user)
+ token = token_for(user)
+ user.update_password("foobar")
+
+ get :edit, user_id: 1, token: token
expect(response).to render_template(:new)
- expect(flash.now[:alert]).to match(/double check the URL/i)
+ expect(flash.now[:alert]).to match(/expired or is invalid/i)
end
end
end
@@ -93,7 +110,7 @@
describe "#update" do
context "valid id, token, and new password provided" do
it "updates the user's password" do
- user = create(:user, :with_forgotten_password)
+ user = create(:user)
old_encrypted_password = user.encrypted_password
put :update, update_parameters(user, new_password: "my_new_password")
@@ -102,7 +119,7 @@
end
it "signs the user in and redirects" do
- user = create(:user, :with_forgotten_password)
+ user = create(:user)
put :update, update_parameters(user, new_password: "my_new_password")
@@ -113,18 +130,17 @@
context "password update fails" do
it "does not update the password" do
- user = create(:user, :with_forgotten_password)
+ user = create(:user)
old_encrypted_password = user.encrypted_password
put :update, update_parameters(user, new_password: "")
user.reload
expect(user.encrypted_password).to eq old_encrypted_password
- expect(user.confirmation_token).to be_present
end
it "re-renders the password edit form" do
- user = create(:user, :with_forgotten_password)
+ user = create(:user)
put :update, update_parameters(user, new_password: "")
@@ -140,8 +156,16 @@ def update_parameters(user, options = {})
{
user_id: user,
- token: user.confirmation_token,
- password_reset: { password: new_password }
+ token: token_for(user),
+ password_reset: { password: new_password },
}
end
+
+ def token_for(user)
+ Clearance.configuration.message_verifier.generate([
+ user.id,
+ user.encrypted_password,
+ 15.minutes.from_now,
+ ])
+ end
end
diff --git a/spec/factories.rb b/spec/factories.rb
index 204620e6a..9ccab6cc1 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -7,10 +7,6 @@
email
password 'password'
- trait :with_forgotten_password do
- confirmation_token Clearance::Token.new
- end
-
factory :user_with_optional_password, class: 'UserWithOptionalPassword' do
password nil
encrypted_password ''
diff --git a/spec/generators/clearance/upgrade/upgrade_generator_spec.rb b/spec/generators/clearance/upgrade/upgrade_generator_spec.rb
new file mode 100644
index 000000000..674092779
--- /dev/null
+++ b/spec/generators/clearance/upgrade/upgrade_generator_spec.rb
@@ -0,0 +1,16 @@
+require "spec_helper"
+require "generators/clearance/upgrade/upgrade_generator"
+
+describe Clearance::Generators::UpgradeGenerator, :generator do
+ it "copies a database migration to the host application" do
+ run_generator
+
+ migration = migration_file(
+ "db/migrate/remove_confirmation_token_from_users.rb",
+ )
+
+ expect(migration).to exist
+ expect(migration).to have_correct_syntax
+ expect(migration).to contain("remove_column :users, :confirmation_token")
+ end
+end
diff --git a/spec/mailers/clearance_mailer_spec.rb b/spec/mailers/clearance_mailer_spec.rb
index 81fae81fd..6d511efcd 100644
--- a/spec/mailers/clearance_mailer_spec.rb
+++ b/spec/mailers/clearance_mailer_spec.rb
@@ -3,7 +3,6 @@
describe ClearanceMailer do
it "is from DO_NOT_REPLY" do
user = create(:user)
- user.forgot_password!
email = ClearanceMailer.change_password(user)
@@ -12,7 +11,6 @@
it "is sent to user" do
user = create(:user)
- user.forgot_password!
email = ClearanceMailer.change_password(user)
@@ -21,7 +19,6 @@
it "sets its subject" do
user = create(:user)
- user.forgot_password!
email = ClearanceMailer.change_password(user)
@@ -30,7 +27,6 @@
it "has html and plain text parts" do
user = create(:user)
- user.forgot_password!
email = ClearanceMailer.change_password(user)
@@ -40,19 +36,23 @@
end
it "contains a link to edit the password" do
- user = create(:user)
- user.forgot_password!
- host = ActionMailer::Base.default_url_options[:host]
- link = "http://#{host}/users/#{user.id}/password/edit" \
- "?token=#{user.confirmation_token}"
-
- email = ClearanceMailer.change_password(user)
-
- expect(email.text_part.body).to include(link)
- expect(email.html_part.body).to include(link)
- expect(email.html_part.body).to have_css(
- "a",
- text: I18n.t("clearance_mailer.change_password.link_text")
- )
+ Timecop.freeze do
+ user = create(:user)
+ host = ActionMailer::Base.default_url_options[:host]
+ allow(Clearance.configuration.message_verifier).to receive(:generate).
+ with([user.id, user.encrypted_password, 15.minutes.from_now]).
+ and_return("THIS_IS_THE_TOKEN")
+ link = "http://#{host}/users/#{user.id}/password/edit" \
+ "?token=THIS_IS_THE_TOKEN"
+
+ email = ClearanceMailer.change_password(user)
+
+ expect(email.text_part.body).to include(link)
+ expect(email.html_part.body).to include(link)
+ expect(email.html_part.body).to have_css(
+ "a",
+ text: I18n.t("clearance_mailer.change_password.link_text"),
+ )
+ end
end
end
diff --git a/spec/user_spec.rb b/spec/user_spec.rb
index e3a1238e7..07bae51dc 100644
--- a/spec/user_spec.rb
+++ b/spec/user_spec.rb
@@ -70,7 +70,7 @@
describe "#update_password" do
context "with a valid password" do
it "changes the encrypted password" do
- user = create(:user, :with_forgotten_password)
+ user = create(:user)
old_encrypted_password = user.encrypted_password
user.update_password("new_password")
@@ -78,16 +78,8 @@
expect(user.encrypted_password).not_to eq old_encrypted_password
end
- it "clears the confirmation token" do
- user = create(:user, :with_forgotten_password)
-
- user.update_password("new_password")
-
- expect(user.confirmation_token).to be_nil
- end
-
it "sets the remember token" do
- user = create(:user, :with_forgotten_password)
+ user = create(:user)
user.update_password("my_new_password")
@@ -98,21 +90,13 @@
context "with blank password" do
it "does not change the encrypted password" do
- user = create(:user, :with_forgotten_password)
+ user = create(:user)
old_encrypted_password = user.encrypted_password
user.update_password("")
expect(user.encrypted_password.to_s).to eq old_encrypted_password
end
-
- it "does not clear the confirmation token" do
- user = create(:user, :with_forgotten_password)
-
- user.update_password("")
-
- expect(user.confirmation_token).not_to be_nil
- end
end
end
@@ -129,16 +113,6 @@
end
end
- describe "#forgot_password!" do
- it "generates the confirmation token" do
- user = create(:user, confirmation_token: nil)
-
- user.forgot_password!
-
- expect(user.confirmation_token).not_to be_nil
- end
- end
-
describe "a user with an optional email" do
subject { user }