restful_authenticationでパスワードを忘れた時の処理を考える
パスワードは必ず忘れられる運命にある...。開発者側の立場であれば「パスワードくらい自分でちゃんと管理しておくべき」と言いたくなる。しかし、自分がユーザーであれば、パスワードを忘れて不便を感じている時「パスワードを忘れた場合」なんてリンクがあると、多少は救われた気がする。そこをクリックすれば、パスワードをリセットなり、再発行して、再びログインできることを夢見ている...。
ところで、restful_authenticationには「パスワードを忘れた場合」の処理は特にない。特に無いけども、最初にユーザー登録した時、アクティベーションを促すメールで、ユーザー名とパスワードも送信している。開発者側の立場であれば「過去にメールを送信しています。ご自分で検索してみてください。」で終わりにしたい。しかし、自分がユーザーであれば、「過去のメールは消えてしまった。」とか「自分のパソコンじゃないから検索できない」とかいろいろ理由があって、「ご自分で検索してみてください。」を見た日にはガックリくる...。
やはり、パスワードをリセット、または再発行する仕組みは必要か...。
アクティベーションを利用する
その仕組みを、restful_authenticationでいかに簡単に実現するか悩んでみて、アクティベーションを利用する方法を思い付いた。まずは既存の動作を再確認してみた。
restful_authenticationのアクティベーションの流れ
- 新規ユーザー登録直後に、登録されたメールアドレス宛に確認メールを自動送信する。
- そのメールに記載のURLリンクをクリックすることで、新規ユーザーを利用可能な状態にする。
- URLリンクをクリックしたアクティベーション直後のwebページでは、そのユーザーはログイン状態になっている。
アクティベーション前の状態にするコード
- まずは「Forget password >>」のリンクを作った。
<%# ---------- app/views/sessions/new.html.erb ---------- %> ...(中略)... <p><%= link_to 'Signup >>', new_user_path %></p> <p><%= link_to 'Forget password >>', input_email_address_new_user_path %></p>
- input_email_address、deactivateアクション用のルートを設定した。
# ---------- config/routes.rb ---------- ActionController::Routing::Routes.draw do |map| map.resources :users, :new=>{:input_email_address=>:get, :deactivate=>:put} map.resource :session map.resources :todos map.activate '/activate/:activation_code', :controller => 'users', :action => 'activate' ...(中略)...
- input_email_addressアクションで、メールアドレスを入力してもらう。
- deactivateアクションで、入力したメールアドレスが存在すれば、アクティベーション前の状態に戻す。
# ---------- app/controllers/users_controller.rb ---------- ...(中略)... def input_email_address end def deactivate if forget_user = User.find_by_email(params[:user][:email]) forget_user.deactivate end flash[:notice] = "Email is sent to your address!" render :action => 'input_email_address' end end
<%# ---------- app/views/users/input_email_address.html.erb ---------- %> <% form_for :user, :url=>deactivate_new_user_path, :html=>{:method=>:put} do |f| %> <%= f.error_messages %> <p><label for="email">Email</label><br/> <%= f.text_field :email, :autocomplete=>'off' %></p> <%= f.submit 'Send' %> <% end %> <p><%= link_to '<< Cancel', new_session_path %></p>
# ---------- app/models/user.rb ---------- require 'digest/sha1' class User < ActiveRecord::Base ...(中略)... def deactivate @activated = false self.activated_at = nil self.make_activation_code # 引数をfalseにすると、validationが無効になる # save(perform_validation=true) save(false) end ...(中略)...
- saveメソッド後のUserモデルを監視して、必要なメールを送信する。
# ---------- app/models/user_observer.rb ---------- class UserObserver < ActiveRecord::Observer def after_create(user) UserMailer.deliver_signup_notification(user) end def after_save(user) return if user.recently_activated?.nil? UserMailer.deliver_activation(user) if user.recently_activated? UserMailer.deliver_change_password_notification(user) unless user.recently_activated? end end
- パスワード変更を促すメール送信用のメソッドを追加した。
# ---------- app/models/user_mailer.rb ---------- class UserMailer < ActionMailer::Base ...(中略)... def change_password_notification(user) setup_email(user) @subject += 'Please change your password' @body[:url] = "http://localhost:3000/activate/#{user.activation_code}" end ...(中略)...
- パスワード変更を促すメールの雛形を追加した。
<%# ---------- app/views/user_mailer/change_password_notification.html.erb ---------- %> Visit this url to change your password: <%= @url %>
以上で、ひとまず完了した感じ。試してみる。
パスワードを忘れた場合の動作確認
- Forget passwordのリンクをクリックして...
- メールアドレスを入力して送信ボタンを押すと...
- メールを送信したというメッセージが表示された。順調。
- そして、パスワード変更を促すメールが届いた!
差出人: my_account@gmail.com 件名: [YOURSITE] Please change your password 日時: 2008年7月28日 17:43:47:JST 宛先: test_account@mail.com Visit this url to change your password: http://localhost:3000/activate/c76f15176d68b075cadca3be506ccae5d98f943b
- メール記載のURLリンクをクリックすると、welcomページへログイン状態でアクセスできた。
- この状態でユーザーはパスワードを自由に変更することが出来るので、再設定すればパスワード忘れの問題は解決する。
ユーザー情報を編集するコード
肝心なユーザー情報を変更する部分の実装がまだだった...。
- Logoutリンクの左に、ユーザー名のリンクを表示して、クリックするとユーザー情報の編集ページにアクセスするようにしてみた。
<%# ---------- app/views/layouts/application.html.erb ---------- %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <title><%= controller.controller_name %>: <%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %> </head> <body> <div align="right"> <% if logged_in? %> <%= link_to current_user.login, edit_user_path(current_user) %> | <% end %> <%= link_to_if logged_in?, 'Logout >>', session_path, :method => :delete do end %> </div> <p style="color: green;background: lightgreen;"><%= flash[:notice] %></p> <p style="color: red;background: pink;"><%= flash[:alert] %></p> <%= yield %> </body> </html>
# ---------- app/controllers/users_controller.rb ---------- class UsersController < ApplicationController ...(中略)... def edit end def update updated_at = current_user.updated_at params[:user].delete(:email) if current_user.update_attributes(params[:user]) if current_user.updated_at != updated_at flash[:notice] = 'User was successfully updated.' redirect_to(todos_path) else flash.now[:alert] = 'Nothing was changed.' render :action => 'edit' end else render :action => 'edit' end end ...(中略)...
- メールアドレスだけは変更できないようにした。
<%# ---------- app/views/users/edit.html.erb ---------- %> <% form_for current_user do |f| %> <%= f.error_messages %> <p><label for="login">Login</label><br/> <%= f.text_field :login, :autocomplete=>'off' %></p> <p><label for="email">Email</label><br/> <%= f.text_field :email, :autocomplete=>'off', :disabled=>true %></p> <p><label for="password">Password</label><br/> <%= f.password_field :password %></p> <p><label for="password_confirmation">Confirm Password</label><br/> <%= f.password_field :password_confirmation %></p> <p><%= f.submit %></p> <% end %> <p><%= link_to '<< Cancel', todos_path %></p>