Password resets resource

先建立一個 PasswordResets controller:

$ rails generate controller PasswordResets new edit --no-test-framework

和之前一樣,我們不需要產生 controller 的測試文件,直接使用整合測試。

我們需要兩個表單,一個是請求重設密碼,一個是要在 User model 裡修改密碼,所以需要的 action 有 newcreateeditupdate,為此來設定路由規則:

config/routes.rb

Rails.application.routes.draw do
  root                'static_pages#home'
  get    'help'    => 'static_pages#help'
  get    'about'   => 'static_pages#about'
  get    'contact' => 'static_pages#contact'
  get    'signup'  => 'users#new'
  get    'login'   => 'sessions#new'
  post   'login'   => 'sessions#create'
  delete 'logout'  => 'sessions#destroy'
  resources :users
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
end

設定完路由,你會有以下的 REST 路由:

HTTP request URL Action Named route
GET /password_resets/new new new_password_reset_path
POST /password_resets create password_resets_path
GET /password_resets//edit edit edit_password_reset_path(token)
PATCH /password_resets/ update password_reset_path(token)

上表中的第一個路由,就是指向到「forgot password」的連結。

接著把「forgot password」連結加進登入頁面:

app/views/sessions/new.html.erb

<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(:session, url: login_path) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= link_to "(forgot password)", new_password_reset_path %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>Remember me on this computer</span>
      <% end %>

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>

加完畫面如下:

重設密碼的資料模型和帳號啟動很像,參考 remember token 和 activation token,重設密碼也需要一個虛擬屬性 reset token (在重設的郵件中使用)和相對應的 reset digest(用來取出使用者)。如果儲存未 hash 過的 token,駭客會透過資料庫發送一封密碼重設的郵件給使用者,然後使用 reset token 和 email 瀏覽密碼重設的頁面,從而獲得帳號的控制權。

所以儲存 digest 是必要的。為了進一步保障安全,我們計畫讓重設密碼連結在經過幾小時後便失效,所以當郵件送出時,要記錄發送時間。因此我們要在 User model 建立兩個屬性:reset_digestreset_sent_at

建立遷移:

$ rails generate migration add_reset_to_users reset_digest:string \
> reset_sent_at:datetime

執行遷移:

$ bundle exec rake db:migrate