139 lines
4.6 KiB
PHP
139 lines
4.6 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Auth;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Mail\EmailOtpMail;
|
||
use App\Models\Ope;
|
||
use App\Services\EmailOtpService;
|
||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Illuminate\Support\Facades\Mail;
|
||
|
||
/**
|
||
* OTP メール認証コントローラー
|
||
*
|
||
* ログイン後の OTP(ワンタイムパスワード)検証プロセスを処理します
|
||
*/
|
||
class EmailOtpController extends Controller
|
||
{
|
||
use ValidatesRequests;
|
||
|
||
protected EmailOtpService $otpService;
|
||
|
||
/**
|
||
* コンストラクタ
|
||
*/
|
||
public function __construct(EmailOtpService $otpService)
|
||
{
|
||
$this->otpService = $otpService;
|
||
}
|
||
|
||
/**
|
||
* OTP 入力フォームを表示
|
||
*
|
||
* ログイン直後、ユーザーに6桁の OTP コードを入力させるページを表示します
|
||
* メールアドレスはマスク表示(例:a***@example.com)
|
||
*/
|
||
public function show(Request $request)
|
||
{
|
||
/** @var Ope */
|
||
$user = $request->user();
|
||
|
||
// メールアドレスをマスク(最初の1文字のみ表示)
|
||
$maskedEmail = $this->otpService->maskEmail($user->ope_mail);
|
||
|
||
// 次の重発までの待機時間
|
||
$resendWaitSeconds = $this->otpService->getResendWaitSeconds($user);
|
||
|
||
return view('auth.otp', [
|
||
'maskedEmail' => $maskedEmail,
|
||
'resendWaitSeconds' => $resendWaitSeconds,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* OTP コード検証
|
||
*
|
||
* ユーザーが入力した6桁のコードを検証します
|
||
*
|
||
* 成功時:email_otp_verified_at を更新し、ホームページにリダイレクト
|
||
* 失敗時:エラーメッセージと共に OTP 入力フォームに戻す
|
||
*/
|
||
public function verify(Request $request)
|
||
{
|
||
// 入力値を検証
|
||
$validated = $this->validate($request, [
|
||
'code' => ['required', 'string', 'size:6', 'regex:/^\d{6}$/'],
|
||
], [
|
||
'code.required' => 'OTPコードは必須です。',
|
||
'code.size' => 'OTPコードは6桁である必要があります。',
|
||
'code.regex' => 'OTPコードは6桁の数字である必要があります。',
|
||
]);
|
||
|
||
/** @var Ope */
|
||
$user = $request->user();
|
||
|
||
// OTP コードを検証
|
||
if ($this->otpService->verify($user, $validated['code'])) {
|
||
// 検証成功:ホームページにリダイレクト
|
||
return redirect()->intended(route('home'))
|
||
->with('success', 'OTP認証が完了しました。');
|
||
}
|
||
|
||
// 検証失敗:エラーメッセージと共に戻す
|
||
return back()
|
||
->withInput()
|
||
->with('error', '無効なまたは有効期限切れのOTPコードです。');
|
||
}
|
||
|
||
/**
|
||
* OTP コード再送
|
||
*
|
||
* ユーザーが OTP コード再送をリクエストした場合に実行
|
||
* 60秒以内の連続再送はブロックします
|
||
*/
|
||
public function resend(Request $request)
|
||
{
|
||
/** @var Ope */
|
||
$user = $request->user();
|
||
|
||
// 重発可能か確認
|
||
if (!$this->otpService->canResend($user)) {
|
||
$waitSeconds = $this->otpService->getResendWaitSeconds($user);
|
||
|
||
return back()->with('error', "後 {$waitSeconds} 秒待機してからリクエストしてください。");
|
||
}
|
||
|
||
try {
|
||
// 新しい OTP コードを発行
|
||
$otpCode = $this->otpService->issue($user);
|
||
|
||
// ope_mail はセミコロン区切りで複数アドレスを保持する可能性があるため、最初のアドレスのみ抽出
|
||
$operatorEmails = explode(';', trim($user->ope_mail));
|
||
$primaryEmail = trim($operatorEmails[0] ?? $user->ope_mail);
|
||
|
||
Log::info('OTP 再送メール送信開始: ' . $primaryEmail);
|
||
|
||
// メール送信
|
||
Mail::to($primaryEmail)->send(new EmailOtpMail(
|
||
$otpCode,
|
||
$user->name ?? 'ユーザー'
|
||
));
|
||
|
||
Log::info('OTP 再送メール送信完了: ' . $primaryEmail);
|
||
|
||
return back()->with('success', 'OTPコードを再送信しました。');
|
||
} catch (\Exception $e) {
|
||
Log::error('OTP resend error: ' . $e->getMessage(), [
|
||
'exception' => $e,
|
||
'user_id' => $user->ope_id ?? null,
|
||
'user_email' => $user->ope_mail ?? null,
|
||
]);
|
||
|
||
return back()->with('error', 'OTP送信に失敗しました。もう一度お試しください。');
|
||
}
|
||
}
|
||
}
|