【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 アカウントを有効化する」から。
【125日目】【1日20分のRailsチュートリアル】【第10章】アカウント有効化メールのプレビューを確認する
今日は「10.1.2 AccountActivationsメイラーメソッド」のUserメイラーのプレビューファイルを更新するところから。
10.1.2 AccountActivationsメイラーメソッド
developmentサーバーを再起動してリスト10.14の設定を読み込んだら、次は10.1.2で自動生成したUserメイラーのプレビューファイルの更新が必要です。
リスト10.11で定義したaccount_activationの引数には有効な (=実在する) ユーザーオブジェクトを渡す必要があるため、リスト10.15はこのままでは動きません。
あー、そういえばメソッドに引数追加したわ。
ユーザーオブジェクトはデータベースの最初のユーザーになるようにして渡す。
リスト10.16ではuser.activation_tokenにも値を代入している点にご注目ください。リスト10.12やリスト10.13のアカウント有効化テンプレートではアカウント有効化トークンが必要なので、代入は省略できません。
アカウント有効化メールでトークンを必要とするようにしたから、ってことだよね。。。
test/mailers/previews/user_mailer_preview.rb
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/account_activation def account_activation user = User.first user.activation_token = User.new_token UserMailer.account_activation(user) end
ここまでできたらアカウント有効化メールのプレビューが確認できるらしい。
サーバーを起動して
$ rails server -b $IP -p $PORT
http://<ローカルアドレス>/rails/mailers/user_mailer/account_activation
にアクセス。(URLってどっから分かるの、、と思ってたらさっき編集したファイルにコメントで書かれてた)
HTML版もテキスト版も見れました。
今日の学習時間は【20分】。
次は「10.1.2 AccountActivationsメイラーメソッド」のメールプレビューのテストを作成するところから。
【124日目】【1日20分のRailsチュートリアル】【第10章】アカウント有効化メールのビューを作成する
今日は「10.1.2 AccountActivationsメイラーメソッド」のメールにアカウント有効化リンクを追加するところから。
10.1.2 AccountActivationsメイラーメソッド
ここでは挨拶文にユーザー名を含め、カスタムの有効化リンクを追加します。
この後、Railsサーバーでユーザーをメールアドレスで検索して有効化トークンを認証できるようにしたいので、リンクにはメールアドレスとトークンを両方含めておく必要があります。
account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com
こんな感じでURLにトークンとメールアドレスを含めておくようにする。
ここまでできれば、リスト10.11で定義した@userインスタンス変数、editへの名前付きルート、ERBを組み合わせて、必要なリンクを作成できます (リスト10.12とリスト10.13)。
○テキストビュー
app/views/user_mailer/account_activation.text.erb
Hi <%= @user.name %>, Welcome to the Sample App! Click on the link below to activate your account: <%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
○HTMLビュー
app/views/user_mailer/account_activation.html.erb
<h1>Sample App</h1> <p>Hi <%= @user.name %>,</p> <p> Welcome to the Sample App!Click on the link below to activate your account: </p> <%= link_to "Activate", edit_account_activation_url(@user.activation_token, email: @user.email) %>
今更だけどテキストビューとHTMLビューがあるのは送り先によってテキストメール送るかHTMLメールで送るか違うからなのかな。
リスト10.12やリスト10.13で定義したテンプレートの実際の表示を簡単に確認するために、メールプレビューという裏技を使ってみましょう。
へー、そんな機能が。動作確認のために実際にメール送らないといけないのかと思ってた。
これを利用するには、アプリケーションのdevelopment環境の設定に手を加える必要があります (リスト10.14)。
元々あったconfig.action_mailer.raise_delivery_errors = false
は削除した。
config/environments/development.rb
: # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :test host = 'rails-tutorial-c9-mhartl.c9.io' config.action_mailer.default_url_options = { host: host } :
今日の学習時間は【25分】。
次は「10.1.2 AccountActivationsメイラーメソッド」のUserメイラーのプレビューファイルの更新をするところから。
ここまで来ると1項が長いね…。
【123日目】【1日20分のRailsチュートリアル】【第10章】UserMailerを生成してテンプレートをカスタマイズする
今日は「10.1.2 AccountActivationsメイラーメソッド」から。
10.1.2 AccountActivationsメイラーメソッド
メイラーの構成はコントローラのアクションとよく似ており、メールのテンプレートをビューと同じ要領で定義できます。
この節ではメイラーとビューを定義して、有効化トークンとメールアドレス (=有効にするアカウントのアドレス) を含むリンクをその中で使用します。
「メイラー」がそもそもよく分からないんだけど、アプリケーションでメール送るためのクラスみたいな感じかな…??
メイラーは、モデルやコントローラと同様にrails generateで生成できます。
下記コマンドで「UserMailer」を生成。
$ rails generate mailer UserMailer account_activation password_reset
生成したメイラーごとに、ビューテンプレートが2つずつ生成されます。1つはテキストメール用のテンプレート、1つはHTMLメール用テンプレートです。
下記テンプレートが生成されたことを確認。
app/views/user_mailer/account_activation.text.erb
UserMailer#account_activation <%= @greeting %>, find me in app/views/user_mailer/account_activation.text.erb
app/views/user_mailer/account_activation.html.erb
<h1>UserMailer#account_activation</h1> <p> <%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb </p>
リスト10.8には、デフォルトのfromアドレス (アプリケーション全体で共通) があります。
リスト10.9の各メソッドには宛先メールアドレスもあります。リスト10.8ではメールのフォーマットに対応するメイラーレイアウトも使用されています。
デフォルトのfromアドレスは"from@example.com"になってる。
app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base default from: "from@example.com" layout 'mailer' end
宛先メールアドレスは"to@example.org"になってるけどここは相手によって変えるとこかな…?
app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer # Subject can be set in your I18n file at config/locales/en.yml # with the following lookup: # # en.user_mailer.account_activation.subject # def account_activation @greeting = "Hi" mail to: "to@example.org" end # Subject can be set in your I18n file at config/locales/en.yml # with the following lookup: # # en.user_mailer.password_reset.subject # def password_reset @greeting = "Hi" mail to: "to@example.org" end end
最初に、生成されたテンプレートをカスタマイズして、実際に有効化メールで使えるようにします (リスト10.10)。
デフォルトのfromアドレスを"noreply@example.com"に変更。
app/mailers/application_mailer.rb
default from: "noreply@example.com"
次に、ユーザーを含むインスタンス変数を作成してビューで使えるようにし、user.emailにメール送信します (リスト10.11)。
account_activationメソッドを引数ありに変更して、user.emailにメール送信するように変更。ついでにメールの件名も設定。
app/mailers/user_mailer.rb
def account_activation(user) @user = user mail to: user.email, subject: "Account activation" end
途中だけど今日はここまで。
今日の学習時間は【25分】。
次は「10.1.2 AccountActivationsメイラーメソッド」のメールにアカウント有効化リンクを追加するところから。
【122日目】【1日20分のRailsチュートリアル】【第10章】アカウントの有効化トークンやダイジェストを作成する処理を追加する
今日は「10.1.1 AccountActivationsリソース」のアカウントの有効化について考えるところから。
10.1.1 AccountActivationsリソース
ユーザーが新しい登録を完了するためには必ずアカウントの有効化が必要になるのですから、有効化トークンや有効化ダイジェストはユーザーオブジェクトが作成される前に作成しておく必要があります。
before_create
コールバックを使うとユーザーオブジェクト作成前に有効化トークンや有効化ダイジェストを作成できるそう。
Userモデルに有効化トークンとダイジェストを作成する処理を追加する。
ついでにメールアドレスを小文字に変換する処理もちょっと書き換える。
app/models/user.rb
class User < ActiveRecord::Base attr_accessor :remember_token, :activation_token before_save :downcase_email before_create :create_activation_digest : private # メールアドレスをすべて小文字にする def downcase_email self.email = email.downcase end # 有効化トークンとダイジェストを作成および代入する def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end end
先に進む前に、サンプルデータとフィクスチャも更新し、テスト時のサンプルとユーザーを事前に有効化しておきましょう (リスト10.4とリスト10.5)。
サンプルデータ更新。
db/seeds.rb
User.create!(name: "Example User", : activated: true, activated_at: Time.zone.now) 99.times do |n| : activated: true, activated_at: Time.zone.now) end
フィクスチャの各ユーザーに下記コードを追加。
test/fixtures/users.yml
: activated: true activated_at: <%= Time.zone.now %> :
いつものようにデータベースを初期化して、サンプルデータを再度生成し直し、リスト10.4の変更を反映します。
下記を実行してサンプルデータを再度生成し直しました。
$ bundle exec rake db:migrate:reset $ bundle exec rake db:seed
これでようやく準備的なものは終わりなのかな…??
今日の学習時間は【19分】。
次は「10.1.2 AccountActivationsメイラーメソッド」から。