From 5df6c31b86e79f06ed668745924704bb4249a2c0 Mon Sep 17 00:00:00 2001 From: "OU.ZAIKOU" Date: Thu, 29 Jan 2026 00:02:45 +0900 Subject: [PATCH] =?UTF-8?q?=E3=80=8C=E3=83=91=E3=82=B9=E3=83=AF=E3=83=BC?= =?UTF-8?q?=E3=83=89=E5=BF=98=E3=82=8C=E3=80=8D=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Auth/ForgotPasswordController.php | 46 ++++++++++++++++-- .../Auth/ResetPasswordController.php | 47 +++++++++++++++++-- 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 9941d01..2b34eb9 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -8,6 +8,7 @@ use Illuminate\Support\Str; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Log; +use Carbon\Carbon; class ForgotPasswordController extends Controller { @@ -38,14 +39,39 @@ class ForgotPasswordController extends Controller return back()->withErrors(['email' => '該当するユーザーが見つかりません。']); } + // 5分間隔のメール送信制限チェック(最新のトークンを対象) + $lastToken = DB::table('password_reset_tokens') + ->where('ope_mail', $user->ope_mail) + ->orderByDesc('created_at') + ->first(); + if ($lastToken) { + // タイムゾーンを明示的に指定(デフォルトはUTCで解析される可能性がある) + $lastCreatedAt = Carbon::parse($lastToken->created_at, config('app.timezone')); + $now = now(); + + // 経過秒数で判定 + $diffSeconds = $lastCreatedAt->diffInSeconds(now(), false); + $limitSeconds = 5 * 60; // 5分 + if ($diffSeconds < $limitSeconds) { + $remainSeconds = $limitSeconds - $diffSeconds; + // 残り秒を「分」に変換:端数は切り上げ(例:1秒残りでも1分と表示) + $waitMinutes = (int) ceil($remainSeconds / 60); + return back()->withErrors([ + 'email' => "パスワード再設定メールは5分以上の間隔を置いて送信してください。{$waitMinutes}分後に再度お試しください。" + ]); + } + } + // トークン生成 $token = Str::random(60); + // SHA256ハッシュで保存(セキュリティ向上) + $tokenHash = hash('sha256', $token); // トークン保存(既存レコードがあれば更新) DB::table('password_reset_tokens')->updateOrInsert( ['ope_mail' => $user->ope_mail], [ - 'token' => $token, + 'token' => $tokenHash, 'created_at' => now(), ] ); @@ -54,10 +80,24 @@ class ForgotPasswordController extends Controller try { $resetUrl = url('/reset-password?token=' . $token . '&email=' . urlencode($user->ope_mail)); - Mail::raw("下記URLからパスワード再設定を行ってください。\n\n{$resetUrl}", function ($message) use ($user) { + $body = $user->ope_name . " 様\n\n" . + "So-Managerをご利用いただき、ありがとうございます。\n\n" . + "本メールは、パスワード再設定のご依頼を受けてお送りしております。\n\n" . + "以下のURLをクリックし、新しいパスワードを設定してください。\n\n" . + $resetUrl . "\n\n" . + "※このURLの有効期限は、24時間です。\n" . + "※有効期限を過ぎた場合は、再度パスワード再設定手続きを行ってください。\n" . + "※本メールにお心当たりがない場合は、本メールを破棄してください。\n\n" . + "_________________________________\n" . + "So-Manager サポートセンター\n" . + "E-mail : support@so-manager.com\n" . + "URL : https://www.so-manager.com/\n" . + "_________________________________"; + + Mail::raw($body, function ($message) use ($user) { $message->to($user->ope_mail) ->from(config('mail.from.address'), config('mail.from.name')) - ->subject('パスワード再設定のご案内'); + ->subject('【【So-Manager】パスワード再設定のご案内】'); }); } catch (\Throwable $e) { Log::error('ForgotPassword mail send failed', [ diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 3b4f1e0..7641fc2 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -14,6 +14,33 @@ class ResetPasswordController extends Controller { $token = $request->query('token'); $email = $request->query('email'); + + // トークンのハッシュ化 + $tokenHash = hash('sha256', $token); + + // トークン・メール・24時間以内の有効性をチェック + $record = DB::table('password_reset_tokens') + ->where('ope_mail', $email) + ->where('token', $tokenHash) + ->first(); + + if (!$record) { + return redirect()->route('forgot_password') + ->withErrors(['email' => 'URLの有効期限(24時間)が切れました。再度お手続きを行ってください。']); + } + + // 24時間チェック + $createdAt = \Carbon\Carbon::parse($record->created_at); + if ($createdAt->addHours(24)->isPast()) { + // 期限切れトークンを削除 + DB::table('password_reset_tokens') + ->where('ope_mail', $email) + ->delete(); + + return redirect()->route('forgot_password') + ->withErrors(['email' => 'URLの有効期限(24時間)が切れました。再度お手続きを行ってください。']); + } + return view('auth.reset-password', compact('token', 'email')); } @@ -25,14 +52,28 @@ class ResetPasswordController extends Controller 'password' => 'required|confirmed|min:8', ]); - // トークンチェック + // トークンのハッシュ化 + $tokenHash = hash('sha256', $request->token); + + // トークン・メール・24時間以内の有効性をチェック $record = DB::table('password_reset_tokens') ->where('ope_mail', $request->email) - ->where('token', $request->token) + ->where('token', $tokenHash) ->first(); if (!$record) { - return back()->withErrors(['email' => '無効なトークンまたはメールアドレスです。']); + return back()->withErrors(['email' => 'URLの有効期限(24時間)が切れました。再度お手続きを行ってください。']); + } + + // 24時間チェック + $createdAt = \Carbon\Carbon::parse($record->created_at); + if ($createdAt->addHours(24)->isPast()) { + // 期限切れトークンを削除 + DB::table('password_reset_tokens') + ->where('ope_mail', $request->email) + ->delete(); + + return back()->withErrors(['email' => 'URLの有効期限(24時間)が切れました。再度お手続きを行ってください。']); } // パスワード更新