Password resets resource
先建立一個 PasswordResets controller:
$ rails generate controller PasswordResets new edit --no-test-framework
和之前一樣,我們不需要產生 controller 的測試文件,直接使用整合測試。
我們需要兩個表單,一個是請求重設密碼,一個是要在 User model 裡修改密碼,所以需要的 action 有 new、 create、edit、update,為此來設定路由規則:
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_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_digest、reset_sent_at:

建立遷移:
$ rails generate migration add_reset_to_users reset_digest:string \
> reset_sent_at:datetime
執行遷移:
$ bundle exec rake db:migrate