diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb
index 0acd046..70e5487 100644
--- a/app/controllers/password_resets_controller.rb
+++ b/app/controllers/password_resets_controller.rb
@@ -1,4 +1,9 @@
class PasswordResetsController < ApplicationController
+ before_action :get_user, only: [:edit, :update]
+ before_action :valid_user, only: [:edit, :update]
+ before_action :check_expiration, only: [:edit, :update]
+
+ include SessionsHelper
def new
end
@@ -17,4 +22,44 @@ class PasswordResetsController < ApplicationController
def edit
end
+
+ def update
+ if params[:user][:password].empty?
+ @user.errors.add(:password, "can't be empty")
+ render 'edit', status: :unprocessable_entity
+ elsif @user.update(user_params)
+ forget(@user)
+ reset_session
+ @user.update_attribute(:reset_digest, nil)
+ log_in @user
+ flash[:success] = "Password has been reset"
+ redirect_to @user
+ else
+ render 'edit', status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def user_params
+ params.require(:user).permit(:password, :password_confirmation)
+ end
+
+ def get_user
+ @user = User.find_by(email: params[:email])
+ end
+
+ def valid_user
+ unless @user && @user.activated? &&
+ @user.authenticated?(:reset, params[:id])
+ redirect_to root_url
+ end
+ end
+
+ def check_expiration
+ if @user.password_reset_expired?
+ flash[:danger] = "Password reset has expired"
+ redirect_to new_password_reset_url
+ end
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 1875cf8..591b6e4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -69,14 +69,18 @@ class User < ApplicationRecord
def create_reset_digest
self.reset_token = User.new_token
- update_attribute(:reset_digest, User.digest(reset_token))
- update_attribute(:reset_sent_at, Time.zone.now)
+ update_columns(reset_digest: User.digest(reset_token),
+ reset_send_at: Time.zone.now)
end
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
+ def password_reset_expired?
+ reset_send_at < 2.hours.ago
+ end
+
private
def downcase_email
diff --git a/app/views/password_resets/edit.html.erb b/app/views/password_resets/edit.html.erb
index 2d1800e..3887a78 100644
--- a/app/views/password_resets/edit.html.erb
+++ b/app/views/password_resets/edit.html.erb
@@ -1,2 +1,21 @@
-
PasswordResets#edit
-Find me in app/views/password_resets/edit.html.erb
+<% provide(:title, "Reset password") %>
+Reset password
+
+
+
+ <%= form_with(model: @user, url: password_reset_path(params[:id]),
+ local: true) do |f| %>
+ <%= render 'shared/error_messages' %>
+
+ <%= hidden_field_tag :email, @user.email %>
+
+ <%= f.label :password %>
+ <%= f.password_field :password, class: 'form-control' %>
+
+ <%= f.label :password_confirmation, "Confirmation" %>
+ <%= f.password_field :password_confirmation, class: "form-control" %>
+
+ <%= f.submit "Update password", class: "btn btn-primary" %>
+ <% end %>
+
+
diff --git a/test/integration/password_resets_test.rb b/test/integration/password_resets_test.rb
new file mode 100644
index 0000000..4260d69
--- /dev/null
+++ b/test/integration/password_resets_test.rb
@@ -0,0 +1,79 @@
+require "test_helper"
+
+class PasswordResetsTest < ActionDispatch::IntegrationTest
+ def setup
+ ActionMailer::Base.deliveries.clear
+ @user = users(:michael)
+ end
+
+ test "password reset" do
+ get new_password_reset_path
+ assert_template "password_resets/new"
+ assert_select "input[name=?]", "password_reset[email]"
+ # email is invalid
+ post password_resets_path, params: { password_reset: { email: "" } }
+ assert_not flash.empty?
+ assert_template "password_resets/new"
+ # email is valid
+ post password_resets_path,
+ params: { password_reset: { email: @user.email } }
+ assert_not_equal @user.reset_digest, @user.reload.reset_digest
+ assert_equal 1, ActionMailer::Base.deliveries.size
+ assert_not flash.empty?
+ assert_redirected_to root_url
+ # password reset form
+ user = assigns(:user)
+ # email is error
+ get edit_password_reset_path(user.reset_token, email: "")
+ assert_redirected_to root_url
+ # user is inactivated
+ user.toggle!(:activated)
+ get edit_password_reset_path(user.reset_token, email: user.email)
+ assert_redirected_to root_url
+ user.toggle!(:activated)
+ # email is right, token is wrong
+ get edit_password_reset_path("wrong token", email: user.email)
+ assert_redirected_to root_url
+ # email is right, token is right
+ get edit_password_reset_path(user.reset_token, email: user.email)
+ assert_template "password_resets/edit"
+ assert_select "input[name=email][type=hidden][value=?]", user.email
+ # password is not patch
+ patch password_reset_path(user.reset_token),
+ params: { email: user.email,
+ user: { password: "foobaz",
+ password_confirmation: "barquux" } }
+ assert_select "div#error_explanation"
+ # password is empty
+ patch password_reset_path(user.reset_token),
+ params: { email: user.email,
+ user: { password: "",
+ password_confirmation: "" } }
+ assert_select "div#error_explanation"
+ # password and password_confirmation is valid
+ patch password_reset_path(user.reset_token),
+ params: { email: user.email,
+ user: { password: "foobaz",
+ password_confirmation: "foobaz" } }
+ assert is_logged_in?
+ assert_not flash.empty?
+ assert_redirected_to user
+ assert_nil user.reload.reset_digest
+ end
+
+ test "expired token" do
+ get new_password_reset_path
+ post password_resets_path,
+ params: { password_reset: { email: @user.email } }
+
+ @user = assigns(:user)
+ @user.update_attribute(:reset_send_at, 3.hour.ago)
+ patch password_reset_path(@user.reset_token),
+ params: { email: @user.email,
+ user: { password: "foobar",
+ password_confirmation: "foobar" } }
+ assert_response :redirect
+ follow_redirect!
+ assert_match "expired", response.body
+ end
+end