「パスワード忘れ」修正
All checks were successful
Deploy main / deploy (push) Successful in 23s

This commit is contained in:
OU.ZAIKOU 2026-01-29 00:02:45 +09:00
parent 8b42340915
commit 5df6c31b86
2 changed files with 87 additions and 6 deletions

View File

@ -8,6 +8,7 @@ use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
class ForgotPasswordController extends Controller class ForgotPasswordController extends Controller
{ {
@ -38,14 +39,39 @@ class ForgotPasswordController extends Controller
return back()->withErrors(['email' => '該当するユーザーが見つかりません。']); 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); $token = Str::random(60);
// SHA256ハッシュで保存セキュリティ向上
$tokenHash = hash('sha256', $token);
// トークン保存(既存レコードがあれば更新) // トークン保存(既存レコードがあれば更新)
DB::table('password_reset_tokens')->updateOrInsert( DB::table('password_reset_tokens')->updateOrInsert(
['ope_mail' => $user->ope_mail], ['ope_mail' => $user->ope_mail],
[ [
'token' => $token, 'token' => $tokenHash,
'created_at' => now(), 'created_at' => now(),
] ]
); );
@ -54,10 +80,24 @@ class ForgotPasswordController extends Controller
try { try {
$resetUrl = url('/reset-password?token=' . $token . '&email=' . urlencode($user->ope_mail)); $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) $message->to($user->ope_mail)
->from(config('mail.from.address'), config('mail.from.name')) ->from(config('mail.from.address'), config('mail.from.name'))
->subject('パスワード再設定のご案内'); ->subject('【【So-Manager】パスワード再設定のご案内');
}); });
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('ForgotPassword mail send failed', [ Log::error('ForgotPassword mail send failed', [

View File

@ -14,6 +14,33 @@ class ResetPasswordController extends Controller
{ {
$token = $request->query('token'); $token = $request->query('token');
$email = $request->query('email'); $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')); return view('auth.reset-password', compact('token', 'email'));
} }
@ -25,14 +52,28 @@ class ResetPasswordController extends Controller
'password' => 'required|confirmed|min:8', 'password' => 'required|confirmed|min:8',
]); ]);
// トークンチェック // トークンのハッシュ化
$tokenHash = hash('sha256', $request->token);
// トークン・メール・24時間以内の有効性をチェック
$record = DB::table('password_reset_tokens') $record = DB::table('password_reset_tokens')
->where('ope_mail', $request->email) ->where('ope_mail', $request->email)
->where('token', $request->token) ->where('token', $tokenHash)
->first(); ->first();
if (!$record) { 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時間が切れました。再度お手続きを行ってください。']);
} }
// パスワード更新 // パスワード更新