email_otp_code_hash = Hash::make($plainCode); $ope->email_otp_expires_at = now()->addMinutes(self::OTP_VALIDITY_MINUTES); $ope->email_otp_last_sent_at = now(); $ope->save(); return $plainCode; } /** * OTP コードを検証 * * 入力されたコードが正しく、且つ有効期限内かを確認します * * @param Ope $ope オペレータモデル * @param string $inputCode ユーザーが入力したコード * @return bool 検証結果 */ public function verify(Ope $ope, string $inputCode): bool { // 有効期限チェック if (!$ope->email_otp_expires_at || $ope->email_otp_expires_at < now()) { return false; } // hash化されたコードと比較 if (!Hash::check($inputCode, $ope->email_otp_code_hash)) { return false; } // 検証成功:検証完了時刻を記録し、OTPコードをクリア $ope->email_otp_verified_at = now(); $ope->email_otp_code_hash = null; $ope->email_otp_expires_at = null; $ope->save(); return true; } /** * OTP が最近検証されたかを確認(24時間以内) * * @param Ope $ope オペレータモデル * @return bool true: 24時間以内に検証済み、false: 未検証または24時間以上経過 */ public function isOtpRecent(Ope $ope): bool { if (!$ope->email_otp_verified_at) { return false; } return $ope->email_otp_verified_at > now()->subHours(self::OTP_FREE_PERIOD_HOURS); } /** * 重発可能かを確認 * * 前回送信から60秒以上経過しているかを確認します * * @param Ope $ope オペレータモデル * @return bool true: 重発可能、false: 待機中 */ public function canResend(Ope $ope): bool { if (!$ope->email_otp_last_sent_at) { return true; } return $ope->email_otp_last_sent_at <= now()->subSeconds(self::OTP_RESEND_DELAY_SECONDS); } /** * 次の重発までの待機時間を取得(秒) * * @param Ope $ope オペレータモデル * @return int 待機時間(秒)、0以下は重発可能 */ public function getResendWaitSeconds(Ope $ope): int { if (!$ope->email_otp_last_sent_at) { return 0; } $diffSeconds = now()->diffInSeconds($ope->email_otp_last_sent_at, absolute: false); $waitSeconds = self::OTP_RESEND_DELAY_SECONDS - abs($diffSeconds); return max(0, $waitSeconds); } /** * OTP コードのマスク済みメールアドレスを取得 * * abc@example.com -> abc***@example.com のようなマスク表示用 * * @param string $email メールアドレス * @return string マスク済みメールアドレス */ public function maskEmail(string $email): string { [$name, $domain] = explode('@', $email); // 名前部分の最初の1文字を残して、残りは*でマスク $maskedName = substr($name, 0, 1) . str_repeat('*', max(0, strlen($name) - 1)); return "{$maskedName}@{$domain}"; } }