【132日目】【1日20分のRailsチュートリアル】【第10章】パスワード再設定フォームを追加する
今日は「10.2.2 PasswordResetsコントローラとフォーム」から。
10.2.2 PasswordResetsコントローラとフォーム
ログインフォームを参考に、パスワード再設定フォームのビューを実装する。
app/views/password_resets/new.html.erb
<% provide(:title, "Forgot password") %> <h1>Forgot password</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(:password_reset, url: password_resets_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.submit "Submit", class: "btn btn-primary" %> <% end %> </div> </div>
サーバーを起動して
$ rails server -b $IP -p $PORT
ログインフォームの「forgot password」から
<ローカルアドレス>/password_resets/new
にアクセス。
パスワード再設定フォームが表示されました。
今日はかなり短いけどここまで。。。
今日の学習時間は【7分】。
次は「10.2.2 PasswordResetsコントローラとフォーム」のフォームからメールアドレスを送信するところから。
【131日目】【1日20分のRailsチュートリアル】【第10章】ログイン画面にパスワード再設定画面へのリンクを追加する
今日は「10.2 パスワードの再設定」から。
10.2 パスワードの再設定
パスワード再設定の仕組みは、アカウント有効化と似ている部分が多く、10.1で学んだ手法の多くをここでも適用できます。
1passwordを導入してからパスワード忘れること減ったけど、それより前にサービス登録したやつなんか結構パスワード再発行(再設定)したりする。。。
アカウント有効化の際と似ていて、PasswordResetsリソースを作成して、再設定用のトークンとそれに対応するダイジェストを保存するのが今回の目的となります。
ふーむ、パスワード再設定フォーム表示するまでの処理は似てる、ってことかな。
10.2.1 PasswordResetsリソース
アカウント有効化 (10.1.1) の場合と同様、最初に新しいリソースで使用するコントローラを生成します。
下記コマンドでコントローラを生成。
$ rails generate controller PasswordResets new edit --no-test-framework create app/controllers/password_resets_controller.rb route get 'password_resets/edit' route get 'password_resets/new' invoke erb create app/views/password_resets create app/views/password_resets/new.html.erb create app/views/password_resets/edit.html.erb invoke helper create app/helpers/password_resets_helper.rb invoke assets invoke coffee create app/assets/javascripts/password_resets.coffee invoke scss create app/assets/stylesheets/password_resets.scss
新しいパスワードを再設定するためのフォーム (図10.8) と、Userモデル内のパスワードを変更するためのフォーム (図10.9) が両方必要になるので、今回はnew、create、edit、updateのルーティングも必要になります。
deleteは要らないからnew、create、edit、updateを指定、ってことなのかな…。
config/routes.rb
resources :password_resets, only: [:new, :create, :edit, :update]
ログイン画面のパスワード入力フォームの上にパスワード再設定画面へのリンクを追加。
app/views/sessions/new.html.erb
<%= link_to "(forgot password)", new_password_reset_path %>
ちゃんとリンク追加できたか見た目確認してみよう。
サーバーを起動して
$ rails server -b $IP -p $PORT
<ローカルアドレス>/login
にアクセス。
テキストリンク追加されてます。
パスワード再設定画面はまだViewいじってないのでデフォルトのまま。
セキュリティ上の注意点をもうひとつ。再設定用のリンクはなるべく短時間 (数時間以内) で期限切れになるようにしなければなりません。そのために、再設定メールの送信時刻も記録する必要があります。
パスワード再設定は期限切れ設定するのか。まぁそうだよね…!
パスワード再設定処理向けにUserモデルにreset_digest属性とreset_sent_at属性を追加する。
$ rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime invoke active_record create db/migrate/20170410023324_add_reset_to_users.rb $ bundle exec rake db:migrate -- add_column(:users, :reset_digest, :string) -> 0.0011s -- add_column(:users, :reset_sent_at, :datetime) -> 0.0004s
追加できたー。
今日の学習時間は【27分】。
次は「10.2.2 PasswordResetsコントローラとフォーム」から。
【130日目】【1日20分のRailsチュートリアル】【第10章】アカウント有効化処理のリファクタリング
今日は「10.1.4 有効化のテストとリファクタリング」のリファクタリングするところから。
10.1.4 有効化のテストとリファクタリング
今日はリファクタリング。
activateメソッドを作成してユーザーの有効化属性を更新し、send_activation_emailメソッドを作成して有効化メールを送信します。
activateメソッド欲しいなー、と思ってました。ソースの可読性上がるよね。
Userモデルにメソッド2つ追加して
app/models/user.rb
# アカウントを有効にする def activate update_attribute(:activated, true) update_attribute(:activated_at, Time.zone.now) end # 有効化用のメールを送信する def send_activation_email UserMailer.account_activation(self).deliver_now end private :
Userコントローラーのメール送信処理をsend_activation_emailメソッドに置き換えて
app/controllers/users_controller.rb
def create @user = User.new(user_params) if @user.save @user.send_activation_email :
アカウント有効化コントローラーのアカウント有効化処理をactivateメソッドに置き換えた。
app/controllers/account_activations_controller.rb
def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.activate :
リスト10.33ではuser.という記法を使用していないことにご注目ください。Userモデルにはそのような変数はないので、これがあるとエラーになります。
置き換え前はuser.という記法使ってたけど、Userモデルに移動させたことでuser→selfになるからいらなくなるよ、ってことか…。
処理コピペすると変更するの忘れそう。。。
リファクタリングできたのでテスト実行。問題なし。
$ bundle exec rake test 43 tests, 185 assertions, 0 failures, 0 errors, 0 skips
ついにアカウントの有効化を実装できました。きりのよい所でGitにコミットしておきましょう。
コミットしました。
$ git add -A
$ git commit -m "Add account activations"
今日の学習時間は【18分】。
次は「10.2 パスワードの再設定」から。
【129日目】【1日20分のRailsチュートリアル】【第10章】アカウント有効化の統合テストを追加する
今日は「10.1.4 有効化のテストとリファクタリング」から。
10.1.4 有効化のテストとリファクタリング
この節では、アカウント有効化の統合テストを追加します。
正しい情報でユーザー登録を行った場合のテスト (リスト7.26) は既にあるので、7.4.4で開発したテストに若干手を加えることにします。
setupメソッドを追加して、
“invalid signup information” はそのまま?(でも警告表示のテストが抜けてたので追加した)
“valid signup information with account activation"は後半新規追加。
test/integration/users_signup_test.rb
class UsersSignupTest < ActionDispatch::IntegrationTest def setup ActionMailer::Base.deliveries.clear end : test "valid signup information with account activation" do : post users_path, user: { name: "Example User", : assert_equal 1, ActionMailer::Base.deliveries.size user = assigns(:user) assert_not user.activated? # 有効化していない状態でログインしてみる log_in_as(user) assert_not is_logged_in? # 有効化トークンが不正な場合 get edit_account_activation_path("invalid token") assert_not is_logged_in? # トークンは正しいがメールアドレスが無効な場合 get edit_account_activation_path(user.activation_token, email: 'wrong') assert_not is_logged_in? # 有効化トークンが正しい場合 get edit_account_activation_path(user.activation_token, email: user.email) assert user.reload.activated? follow_redirect! assert_template 'users/show' assert is_logged_in? end
有効化トークンに関するテスト、この順番が重要なんだろうな。正しい場合からテストすると期待してる動作にはならないもんね。たぶん
リスト10.31のassignsメソッドは本チュートリアル初登場です。
第8章の演習 (8.6) で説明したように、assignsメソッドを使用すると、対応するアクション内にあるインスタンス変数にアクセスできるようになります。
Usersコントローラのcreateアクションでは@userというインスタンス変数が定義されていて、そのインスタンス変数にアクセスできる、ってことらしい。
うーん、難しいな…。
テストは通ることを確認。
$ bundle exec rake test 43 tests, 185 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【23分】。
【128日目】【1日20分のRailsチュートリアル】【第10章】アカウント有効化の動作を確認する
今日は「10.1.3 アカウントを有効化する」のesitアクション書くところから。
10.1.3 アカウントを有効化する
authenticated?がリスト10.24のようになったことで、やっとeditアクションを書く準備ができました。
ようやく!
正当であろうとなかろうと、有効化が行われるとユーザーはログイン状態になります。
もしこのコードがなければ、攻撃者がユーザーの有効化リンクを後から盗みだしてクリックするだけで、本当のユーザーとしてログインできてしまいます。そうした攻撃を防ぐためにこのコードは非常に重要です。
有効化が行われてもログイン状態にしないのもありかな?と思ったけど、ユーザーがサービスに登録するときのこと考えるとユーザビリティを損なうよねぇ…。
(有効化してもログインは別に実施してね、ってサービスたまにあるけどめんどくさい)
有効化のチェック大事だな。
editアクションを下記の通り追加。
app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.update_attribute(:activated, true) user.update_attribute(:activated_at, Time.zone.now) log_in user flash[:success] = "Account activated!" redirect_to user else flash[:danger] = "Invalid activation link" redirect_to root_url end end end
トークンが無効になるようなことは実際にはめったにありませんが、もしそうなった場合はルートURLにリダイレクトされる仕組みです。
よく有効化メールって有効期限あるけどそれは設けないのかな…??
すでにアカウント有効化したのに有効化リンクにアクセスするとトークンが無効として判断されるんだろうな。
リスト10.29のコードを使用すると、リスト10.23にあるURLを貼り付けてユーザーを有効化できます。
URL分かんなくなっちゃったや…。
再度ユーザー登録試してみるか。
サーバーを起動して
$ rails server -b $IP -p $PORT
サインアップ画面から自分のメールアドレス入力してユーザー登録。
……あれ、ログに出力されるアカウント有効化リンクが間違ってる…???アクセスしたらエラー画面表示された。
http://rails-tutorial-c9-mhartl.c9.io/account_activations/twAnDr7ZAiBz0KsdRZ-2cA/edit?email=<メールアドレス>
あ、メール設定のホスト名間違ってた。自分の環境に合わせないといけなかった。
↓このとき設定したやつ。
ちゃんと自分の環境に合わせたホスト名に修正。
config/environments/development.rb
host = 'rails-tutorial-kt-zoe.c9users.io/'
ホスト名修正できたらサーバーを起動し直して再度ユーザー登録。
(さっき登録しようとしたアカウントはデータベースいじってそっと消した)
ログに正しいアカウント有効化リンクが出力されるようになったので、出力された有効化リンクにアクセス。
できたー!動いたー!
ユーザーの有効化が役に立つためには、ユーザーが有効である場合にのみログインできるようにログイン方法を変更する必要があります。
今はアカウント有効化してなくてもログインできるのでアカウント有効化が役に立つようにログイン方法を変更。
app/controllers/sessions_controller.rb
def create @user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) if @user.activated? log_in @user params[:session][:remember_me] == '1' ? remember(@user) : forget(@user) redirect_back_or @user else message = "Account not activated. " message += "Check your email for the activation link." flash[:warning] = message redirect_to root_url end else :
user
じゃなくて@user
を使ってたのでチュートリアルの例からはちょっと修正。
うーん、これどっちも動くんだろうけどどっちが正しいのか…よく分からない。。。
アカウント有効化してないユーザーでログインを試みる。
警告表示されました!
今日の学習時間は【45分】。
キリのいいところまで進めておきたかったので結構長め。
次は「10.1.4 有効化のテストとリファクタリング」から。
【127日目】【1日20分のRailsチュートリアル】【第10章】アカウント有効化向けにauthenticated?メソッドを変更する
今日は「10.1.3 アカウントを有効化する」から。
10.1.3 アカウントを有効化する
リスト10.23のとおりにメールが生成できたら、今度はAccountActivationsコントローラのeditアクションを書いて、実際にユーザーを有効化できるようにする必要があります。
editアクションが必要なのか。
メタプログラミングはRubyが有するきわめて強力な機能であり、Railsの一見魔法のような機能 (訳注: 「黒魔術」と呼ばれることもあります) の多くは、Rubyのメタプログラミングによって実現されています。
ここで重要なのは、sendメソッドの強力きわまる機能です。
何を言っているのか分からないよぉ…。
例を見てもイマイチピンとこない。
「sendメソッドすごい!」って覚えておけばいいんだろうか。。。。
sendメソッドの動作原理がわかったので、それに基いてauthenticated?メソッドを書き換えます。
def authenticated?(attribute, token) digest = self.send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end
トークンが何種類かあって、それぞれ認証が必要だけど処理は似てるから一般化するためにsendメソッド使う、ってことだろうか。。。
使わなくても書けるんだよね。似たようなメソッド増えるけど、てことかな。
確かにメソッドとかまとめられた方がいいもんね。うーん、不思議だな~
以上の説明を実際のUserモデルに適用してできた、一般化されたauthenticated?メソッドをリスト10.24に示します。
authenticated?メソッドを下記のように変更。
app/models/user.rb
class User < ActiveRecord::Base : # トークンがダイジェストと一致したらtrueを返す def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end : end
リスト10.24のキャプションに示されているとおり、テストスイートはREDになります。
なりますね。エラー出てる。
$ bundle exec rake test 43 tests, 171 assertions, 0 failures, 3 errors, 0 skips
テストが失敗する理由は、current_userメソッド (リスト8.36) とnilダイジェストのテスト (リスト8.43) の両方で、authenticated?が古いままになっており、引数も2つではなくまだ1つのままです。
authenticated?メソッド使ってる箇所を修正。パスはテスト実行時のエラーに出力されてるのでそこ見れば分かる。
にしてもリファクタリングする度修正するのってちょっとめんどくさいけど…実際に作るときは始めから気をつければいいんだろうな。。。
そしてテストしっかり書く。だいじ。
app/helpers/sessions_helper.rb
: # 現在ログイン中のユーザーを返す (いる場合) def current_user : if user && user.authenticated?(:remember, cookies[:remember_token]) :
test/models/user_test.rb
test "authenticated? should return false for a user with nil digest" do assert_not @user.authenticated?(:remember, '') end
エラー出なくなりました。
$ bundle exec rake test 43 tests, 175 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【30分】。
次は「10.1.3 アカウントを有効化する」のeditアクション書くところから。
【126日目】【1日20分のRailsチュートリアル】【第10章】メールプレビューのテストを作成する
今日は「10.1.2 AccountActivationsメイラーメソッド」のメールプレビューのテストを作成するところから。
10.1.2 AccountActivationsメイラーメソッド
最後に、このメールプレビューのテストも作成して、プレビューをダブルチェックできるようにします。
テスト例がRailsによって自動生成されているので、これを利用してテストを作成する。
元々あった"password_reset"のテストは今は要らないと思うのでコメントアウトした。
test/mailers/user_mailer_test.rb
test "account_activation" do user = users(:michael) user.activation_token = User.new_token mail = UserMailer.account_activation(user) assert_equal "Account activation", mail.subject assert_equal [user.email], mail.to assert_equal ["noreply@example.com"], mail.from assert_match user.name, mail.body.encoded assert_match user.activation_token, mail.body.encoded assert_match CGI::escape(user.email), mail.body.encoded end
このテストがパスするには、テストファイル内のドメイン名を正しく設定する必要があります (リスト10.19)。
テストで送り元のメールアドレス(mail.from)見てるけど、メールアドレスなんてどこで設定してるんだろうと思ってた。
テスト向けにはここで設定するのかー。
config/environments/test.rb
config.action_mailer.default_url_options = { host: 'example.com' }
これでテスト通りました。
$ bundle exec rake test:mailers 1 tests, 9 assertions, 0 failures, 0 errors, 0 skips
あとはユーザー登録を行うcreateアクションに数行追加するだけで、メイラーをアプリケーションで実際に使うことができます。
createアクションのユーザー新規作成時の処理を変更。
変更前は、ユーザーのプロファイルページ (7.4) にリダイレクトしていましたが、アカウント有効化を実装するうえでは無意味な動作なので、リダイレクト先をルートURLに変更してあります。
アカウント有効化してもらわないとプロファイルページにリダイレクトしても見れない…かな…。無意味ですね。
app/controllers/users_controller.rb
: def create @user = User.new(user_params) if @user.save UserMailer.account_activation(@user).deliver_now flash[:info] = "Please check your email to activate your account." redirect_to root_url else render 'new' end end :
リスト10.21ではリダイレクト先をプロファイルページからルートURLに変更し、かつユーザーは以前のようにログインしないようになっています。
(中略)
失敗が発生するテストの行をひとまずコメントアウトしておきます (リスト10.22)。コメントアウトした部分は、10.1.4でアカウント有効化のテストをパスさせるときに元に戻します。
アプリケーションの挙動変えちゃったもんね。
test/integration/users_signup_test.rb
: # assert_template 'users/show' # assert is_logged_in?
コメントアウトした状態でテストが通ることを確認。
$ bundle exec rake test 43 tests, 175 assertions, 0 failures, 0 errors, 0 skips
この状態で実際に新規ユーザーとして登録してみると、リダイレクトされて図10.4のようになり、リスト10.23のようなメールが生成されます。
新規ユーザーを登録してみよう。
サーバーを起動して
$ rails server -b $IP -p $PORT
適当なユーザー名とメールアドレスとパスワードでユーザー登録。
flashメッセージ表示されました!
ただし、実際にメールが生成されるわけではないのでご注意ください。
実際にメール送信されるならメールアドレスはちゃんとしたの使わないとな、と思ってた。実際のメール送信はまだなにか必要なのか。
ここに引用したのはサーバーログに出力されたメールです。
長いから引用はしないけどサーバー起動したターミナルにメール内容出力されてた。処理は動いてそうだね。
今日の学習時間は【26分】。
メイラーメソッド使う準備するの長かった…。
次は「10.1.3 アカウントを有効化する」から。