LoginSignup
0
0

More than 3 years have passed since last update.

(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第12章】

Last updated at Posted at 2020-09-20

前提

・Railsチュートリアルは第4版
・今回の学習は3周目(9章以降は2周目)
・著者はProgate一通りやったぐらいの初学者

基本方針

・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる(記事最下段・用語集)。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。

 
 認証システム開発・第6段回目、最終回の第12章です!世間は4連休ですが、学習に休みはありません。ばりばりやっていきましょう。不断の努力を。
 
本日の一曲はこちら。
SUPERCAR "PLANET short ver."
良い曲は何年経っても色褪せません。

 

【12.1.1 PasswordResetsコントローラ 演習】

1. この時点で、テストスイートが greenになっていることを確認してみましょう。
→ GREENです。

 
2. 表 12.1の名前付きルートでは、_pathではなく_urlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: アカウント有効化で行った演習 (11.1.1.1) と同じ理由です。
→ 完全なURL(絶対パス)が必要だから。

 

【12.1.2 新しいパスワードの設定 演習】

1. リスト 12.4のform_forメソッドでは、なぜ@password_resetではなく:password_resetを使っているのでしょうか? 考えてみてください。
→ password_resetモデルがないから。モデルがある場合はその変数(@userなど)が使えるけど、今回はそういった変数がない。

 

【12.1.3 createアクションでパスワード再設定 演習】

1. 試しに有効なメールアドレスをフォームから送信してみましょう (図 12.6)。どんなエラーメッセージが表示されたでしょうか?
→ ArgumentError in PasswordResetsController#create
wrong number of arguments (given 1, expected 0)

 
2. コンソールに移り、先ほどの演習課題で送信した結果、(エラーと表示されてはいるものの) 該当するuserオブジェクトにはreset_digestとreset_sent_atがあることを確認してみましょう。また、それぞれの値はどのようになっていますか?
→ ありました。下記。

>> user = User.find_by(email: "kawa@kawa.com")
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "kawa@kawa.com"], ["LIMIT", 1]]
=> #<User id: 102, name: "kawa", email: "kawa@kawa.com", created_at: "2020-09-17 22:39:47",
 updated_at: "2020-09-20 04:23:52", password_digest: "$2a$10$kgv1Loz8fVDaaZvtUMtkZOUBnbCcHZNIBQBrgb18QMj...", remember_digest: nil, admin: false, activation_digest: "$2a$10$bmgQ2XztK7kgePhH8pVDiuKenXFDEl51XktqmfPUwHv...", activated: true, activated_at: "2020-09-17 22:40:33", 
reset_digest: "$2a$10$iuW.1GDheym2P5Nkuo7QUu7YjCs1DyooYonE0RY2lck...", reset_sent_at: "2020-09-20 04:23:52">

 

【12.2.1 パスワード再設定のメールとテンプレート 演習】

1. ブラウザから、送信メールのプレビューをしてみましょう。「Date」の欄にはどんな情報が表示されているでしょうか?
→ Date: Sun, 20 Sep 2020 04:49:13 +0000

2. パスワード再設定フォームから有効なメールアドレスを送信してみましょう。また、Railsサーバーのログを見て、生成された送信メールの内容を確認してみてください。
→ 下記

----==_mimepart_5f66df073210_17841d9a2dc334fd
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

To reset your password click the link below:

https://545f54b8b0d74dfd8bcc26b33cb0f3fe.vfs.cloud9.us-east-2.amazonaws.com/password_resets/TaQ06fMRyqZk-wHJp8fnyw/edit?email=kawa%40kawa.com

This link will expire in two hours.

if you did not request your password to be reset, 
please ignore this email and your password will stay as it is.

 
3. コンソールに移り、先ほどの演習課題でパスワード再設定をしたUserオブジェクトを探してください。オブジェクトを見つけたら、そのオブジェクトが持つreset_digestとreset_sent_atの値を確認してみましょう。
→ 下記

reset_digest: "$2a$10$JUgxhUTG.XKFk7BnZqfLHeU8fdUIU/cnMvBGaAs.RCX...", 
reset_sent_at: "2020-09-20 04:48:06">

 

【12.2.2 送信メールのテスト 演習】

1. メイラーのテストだけを実行してみてください。このテストは greenになっているでしょうか?
→ $ rails test test/mailers/user_mailer_test.rb でGREEN

 
2. リスト 12.12にある2つ目のCGI.escapeを削除すると、テストが redになることを確認してみましょう。
→ REDでした。

 

【12.3.1 editアクションで再設定 演習】

1. 12.2.1.1で示した手順に従って、Railsサーバーのログから送信メールを探し出し、そこに記されているリンクを見つけてください。そのリンクをブラウザから表示してみて、図 12.11のように表示されるか確かめてみましょう。
→ Reset password画面が表示されました。

 
2. 先ほど表示したページから、実際に新しいパスワードを送信してみましょう。どのような結果になるでしょうか?
→ Unknown action
The action 'update' could not be found for PasswordResetsController

 

【12.3.2 パスワードを更新する 演習】

1. 12.2.1.1で得られたリンク (Railsサーバーのログから取得) をブラウザで表示し、passwordとconfirmationの文字列をわざと間違えて送信してみましょう。どんなエラーメッセージが表示されるでしょうか?
→ Password confirmation doesn't match Password

 
2. コンソールに移り、パスワード再設定を送信したユーザーオブジェクトを見つけてください。見つかったら、そのオブジェクトのpassword_digestの値を取得してみましょう。次に、パスワード再設定フォームから有効なパスワードを入力し、送信してみましょう (図 12.13)。パスワードの再設定は成功したら、再度password_digestの値を取得し、先ほど取得した値と異なっていることを確認してみましょう。ヒント: 新しい値はuser.reloadを通して取得する必要があります。
→ およ、パスワードを再設定しようとしたら「SQLite3::BusyException: database is locked: commit transaction」が。こちらの記事を参考に、RailsコンソールでActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;") を打ったら動作しました。結果下記。異なってますね。
 変更前:\$2a$10$kgv1Loz8fVDaaZvtUMtkZOUBnbCcHZNIBQBrgb18QMjyvnK.U3vlW
 変更後:\$2a$10$/lAIMLbkR84Zg0rBkVmcIeWn6u/UBEOaHGrU34rLR8ZMnmcIEomiu

 

【12.3.3 パスワードの再設定をテストする 演習】

1. リスト 12.6にあるcreate_reset_digestメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 12.20に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう (これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 greenになることも確認してください。ちなみにリスト 12.20にあるコードには、前章の演習 (リスト 11.39) の解答も含まれています。
→ 下記

user.rb
  def create_reset_digest
    self.reset_token = User.new_token
    update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now)
  end

 
2. リスト 12.21のテンプレートを埋めて、期限切れのパスワード再設定で発生する分岐 (リスト 12.16) を統合テストで網羅してみましょう (12.21 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです)。期限切れをテストする方法はいくつかありますが、リスト 12.21でオススメした手法を使えば、レスポンスの本文に「expired」という語があるかどうかでチェックできます (なお、大文字と小文字は区別されません)。
→ 下記(/~/iって何?調べてもわからん)

password_resets_test.rb
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_sent_at, 3.hours.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/i, response.body
  end

 
3. 2時間経ったらパスワードを再設定できなくする方針は、セキュリティ的に好ましいやり方でしょう。しかし、もっと良くする方法はまだあります。例えば、公共の (または共有された) コンピューターでパスワード再設定が行われた場合を考えてみてください。仮にログアウトして離席したとしても、2時間以内であれば、そのコンピューターの履歴からパスワード再設定フォームを表示させ、パスワードを更新してしまうことができてしまいます (しかもそのままログイン機構まで突破されてしまいます!)。この問題を解決するために、リスト 12.22のコードを追加し、パスワードの再設定に成功したらダイジェストをnilになるように変更してみましょう。
→ @user.update_attribute(:reset_digest, nil)を入れるだけ。

 
4. リスト 12.18に1行追加し、1つ前の演習課題に対するテストを書いてみましょう。ヒント: リスト 9.25のassert_nilメソッドとリスト 11.33のuser.reloadメソッドを組み合わせて、reset_digest属性を直接テストしてみましょう。
→ 該当箇所だけ下記

password_resets_test.rb
    # 有効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
        params: { email: user.email,
            user: { password:        "foobaz",
              password_confirmation: "foobaz" } }
    assert is_logged_in?
    assert_nil user.reload.reset_digest
    assert_not flash.empty?
    assert_redirected_to user

 

【12.4 本番環境でのメール送信 演習】

 ここはカット!!理由は前章同様!!

 

第12章まとめ

・前章同様にリソースでモデル化。
・再設定リンクに有効期限を設けるため、Userモデルにreset_sent_atも追加(これを基準時にするため)
・hidden_field_tagで、一度送信後(editアクション)もメールアドレスを保持。
・@user.errors.add(:password, :blank)で空文字列を弾く。

 
 ついに認証システム開発が終わりました。ここまでやってるといやでもトークンとかダイジェストとかの用語が身に染み付いてきますね。ちょっと分からなくても、とにかく食べて食べまくってたら消化して血肉になっていきます。がんばりましょう。
 次章は投稿機能を実装していきます!

 
⇨ 第13章へ!
⇦ 第11章はこちら
学習にあたっての前提・著者ステータスはこちら
 

なんとなくイメージを掴む用語集

今回はありません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0