見出し画像

そこまで難しくないだろーとか言ってやってみたら意外とあれ?ってなってたので備忘録として書いておきますw

passwordの方が簡単なのでpasswordからいきます〜。

大まかな流れ

ルーティング設定

コントローラー設定

ビュー設定

emailの編集に必要なマイグレーション ファイルを作成

ユーザーの新規email入力部分

確認メールの送信

確認メールをクリックした時の処理

上記が正しければ、emailを更新する

というような流れで実装していきます。

目次

  1. ルーティング設定
  2. ビュー設定
  3. コントローラー設定
  4. emailの変更
  5. マイグレーション ファイルを作成
  6. 新規email入力部分
  7. 確認メールの送信
  8. app/notifications/ChangeEmail
  9. 確認メールをクリックした時の処理(emailの更新)
  10. 学んだ事

すべて表示

ルーティング設定

Route::get('/user/{user}/passwordchange_show', 'UserController@passwordchange_show')->name('passwordchange_before');
Route::post('/user/{user}/passwordchange', 'UserController@passwordchange')->name('passwordchange_after');

ビュー設定

現在のパスワード、新しいパスワード、新しいパスワードの確認を入力する部分を作ります。

             <form method="POST" action="{{ route('passwordchange_after', ['user' => $user->id]) }}">
                       @csrf

                      <input id="password" type="password" class="form-control @error('currentpassword') is-invalid @enderror" name="currentpassword" autocomplete="new-password">

                      <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" autocomplete="new-password">

                      <input id="password-confirm" type="password" class="form-control" name="password_confirmation" autocomplete="new-password">

                       <div class="form-group row mb-0">
                           <div class="col-md-6 offset-md-4">
                               <button type="submit" class="btn btn-primary">
                                   {{ __('登録する') }}
                               </button>
                               <a class="btn btn-link" href="{{ route('password.request') }}">
                                   {{ __('Forgot Your Password?') }}
                               </a>
                           </div>
                       </div>
                   </form>

コントローラー設定

   use App\Http\Requests\ChangePassword;   
   
   public function passwordchange_show(User $user)
   {
       $check_user = Auth::user();
   #1  if($check_user->id == $user->id) {
           return view('users.passwordchange')->with('user', $user);
       } else {
           return back();
       }
   }

   public function passwordchange(ChangePassword $request, User $user)
   {
       $new_password = $request->password;
       $old_password = $request->currentpassword;
   #2  if(!(Hash::check($old_password, $user->password))) {
           return redirect()->back()->with('say', '現在のパスワードが間違っています。');
       } else {
   #3      if(Hash::check($new_password, $user->password)) {
               return redirect()->back()->with('say', '新しいパスワードが、現在のパスワードと同じです。違うパスワードを設定してください。');
           } else {
               $user->password = Hash::make($request['password']);
               $user->save();
               return redirect()->route('my_page', ['user' => $user->id])->with(['user' => $user, 'say' => 'パスワードが正しく変更されました']);
           }
       }
   }

#1, if文がtrueであればビューページに飛んで行けるようにします。

#2, 現在のパスワードが入力されたものと一致しているかをチェック。

#3, 新しいパスワードと確認パスワードが一致するかチェック。

以上がうまくいけばhashを使って新しいパスワードを作成し、更新していきます。

フォームリクエストも適宜用意した方が良いかなと思います。

次はemailです。

emailの変更

emailもpasswordと同じように変更できれば良いんですけど、正しいemail(確認メール)として判定してから変更しないと不正なemailが登録されてしまう可能性があります。

そこに対応するためにテーブルやtokenを使っていきます。

Route::get('/user/{user}/emailchange_show', 'UserController@emailchange_show')->name('emailchange_before');
Route::post('/user/{user}/emailchange', 'UserController@emailchange')->name('emailchange_after');
Route::get('/reset/{token}', 'UserController@emailchange_reset')->name('emailchange_reset');

マイグレーション ファイルを作成

php artisan make:migration email_resets_table
  public function up()
   {
       Schema::create('email_resets', function (Blueprint $table) {
           $table->id();
           $table->foreignId('user_id')->constrained()->onDelete('cascade');
           $table->string('new_email')->unique();
           $table->string('token');
           $table->timestamps();
       });
   }

保存する情報はuser_id,新しいemail, そしてtokenです。このtokenは確認メールを実装する時に使用します。

モデルファイルも作ります。2つメソッドを用意します。確認メール関連です。

php artisan make:model EmailReset
use Notifiable;

   protected $fillabel = ['user_id', 'new_email', 'token'];

   public function sendEmailResetNotification($token)
   {
       $this->notify(new ChangeEmail($token));
   }

   public function routeNotificationForMail($notification)
   {
       return $this->new_email;
   }

routeNotificationForMailメソッドはメールの送信先を指定しています。

新規email入力部分

現在のメールアドレスを表示し、その下に入力画面があります。

             <form method="POST" action="{{ route('emailchange_after', ['user' => $user->id]) }}">
                       @csrf
                       
                       <h5>現在のメールアドレス</h5>
    
                       <p style="margin-top: .4rem;">{{$user->email}}</p>
                          

                       <div class="form-group row">
                         <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('新しいメールアドレス') }}</label>
                         <div class="col-md-6">
                           <input id="email" type="email" class="form-control @error('new_email') is-invalid @enderror" name="new_email" value="{{ old('new_email') }}" autocomplete="email">
                         </div>
                       </div>

                       <div class="form-group row mb-0">
                           <div class="col-md-6 offset-md-4">
                               <button type="submit" class="btn btn-primary">
                                   {{ __('登録する') }}
                               </button>
                           </div>
                       </div>
                   </form>

確認メールの送信

   use App\Http\Requests\ChangeEmail; 
   
   public function emailchange_show(User $user)
   {
       $check_user = Auth::user();
       if($check_user->id == $user->id) {
           return view('users.emailchange')->with('user', $user);
       } else {
           return back();
       }
   }

   public function emailchange(ChangeEmail $request, User $user) 
   {
       $check_user = Auth::user();
       if($check_user->id == $user->id) {
           // トークン生成
           $new_email = $request->new_email;
      #1   $token = hash_hmac(
               'sha256',
               Str::random(40) . $new_email,
               config('app.key')
           );
      #2   $email_reset = new EmailReset();
           $email_reset->user_id = $user->id;
           $email_reset->new_email = $new_email;
           $email_reset->token = $token;

           $email_reset->save();

      #3   $email_reset->sendEmailResetNotification($token);

           return redirect()->route('my_page', ['user' => $user->id])->with(['user' => $user, 'say' => '確認メールが送信されました']);
       } else {
           return redirect()->route('my_page', ['user' => $user->id])->with(['user' => $user, 'say' => 'メールの送信に失敗しました']);
       }
   }

#1, new->emailを元にtokenを作成しています。

#2, テーブルに保存するための値を代入していきます。

#3, 確認メールを送信します。モデルにあるメソッドを呼び出しています。最終的にはapp/notifications/ChangeEmailというところを動かすようになっています。

app/notifications/ChangeEmail

ここはemailが変更される時に送る確認メールを送信している場所になります。

php artisan make:notification ChangeEmail
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;

class ChangeEmail extends Notification
{
   use Queueable;
#1 public $token;
   /**
    * Create a new notification instance.
    *
    * @return void
    */
   public function __construct($token)
   {
#2    $this->token = $token;
   }

   /**
    * Get the notification's delivery channels.
    *
    * @param  mixed  $notifiable
    * @return array
    */
   public function via($notifiable)
   {
       return ['mail'];
   }

   /**
    * Get the mail representation of the notification.
    *
    * @param  mixed  $notifiable
    * @return \Illuminate\Notifications\Messages\MailMessage
    */
   public function toMail($notifiable)
   {
       return (new MailMessage)
           ->subject(Lang::get('変更認証メールです'))
           ->line(Lang::get('下記リンクをクリックしてメールアドレスの変更を完了してください'))
   #3      ->action(Lang::get('変更認証メール'), url('reset', $this->token))
           ->line(Lang::get('この変更に身に覚えがない場合は、無視してください'));
   }

   /**
    * Get the array representation of the notification.
    *
    * @param  mixed  $notifiable
    * @return array
    */
   public function toArray($notifiable)
   {
       return [
           //
       ];
   }
}

#1, この処理で$tokenをいうものを取得できるようにしている。

#2, $this->tokenとする事でtokenを使用できるように設定している。

#3, urlにtoken情報を入れて送信している。

確認メールをクリックした時の処理(emailの更新)

public function emailchange_reset(Request $request, $token)
   {
       // token情報を取得
       $email_reset = EmailReset::where('token', $token)->first();

       // レコードが存在しているかつ、有効期限内でアクセスしているかを判断
   #1  if($email_reset && !$this->tokenStatus($email_reset->created_at)) {

           // ユーザーのemailを更新して保存
           $user = User::find($email_reset->user_id);
           $user->email = $email_reset->new_email;
           $user->save();

           // token履歴は使わないので削除
           $email_reset->delete();

           return redirect()->route('my_page', ['user' => $user->id])->with(['user' => $user, 'say' => 'メールアドレスが正常に変更されました']);
       } else {
           if($email_reset) {
               $user = User::find($email_reset->user_id);
               // 新規作成が出来なくなるので削除
               $email_reset->delete();
               return redirect()->route('my_page', ['user' => $user->id])->with(['user' => $user, 'say' => 'メールアドレスの変更に失敗しました']);
           }
       }
   }

   protected function tokenStatus($created_at)
   {
       // $expiresはurlは1時間有効なので3600秒で追加
       // Carbon::parse($created_at)はtokenが作成された日時を取得
       // addSeconds($expires)で上記で取得した日時に$expiresつまり、1時間分の時間を追加
       // isPast()は上記で1時間追加した時間を現在過ぎているかどうかを判断。
       $expires = 3600;
       return Carbon::parse($created_at)->addSeconds($expires)->isPast();
   }

少し量が多かったのでコメントアウトでも書いてます。

確認メールをクリックした後にこのアクションに飛んできます。

#1, tokenStatusは確認メールをクリックしたのが発行されてから1時間以内かをチェックしています。trueであれば変更するようにしています。

trueであれば変更した後、falseであれば最初の処理でemail_resetのレコードを削除しています。不要だし、再度変更リクエストが送れなくなってしまったりするので。

処理が終わった後はredirectでviewに飛ばします。

ここまでで処理は終了です!お疲れ様でした!

データベースなどで正しく変更されているか確認してみましょう!

学んだ事

routeNotificationForMailメソッドがメールの送信先を指定してくれている。

emailの方が手順が多い。->正しいか確認するため。

まとめ

emailはパスワードに比べれば確かに処理は多いですが、1つ1つ見ていくと流れが見えてきます。

やる前はemailもpasswordみたいに出来るのかと思ってましたw

参考にしたサイト

今回はそんな感じで

以上!