Files
core/src/Auth/Services/OtpService.php
T

66 lines
1.8 KiB
PHP
Raw Normal View History

2026-07-01 18:35:39 +03:00
<?php
namespace Modules\Core\Auth\Services;
2026-09-30 11:15:27 +03:00
use Illuminate\Support\Facades\Hash;
2026-07-01 18:35:39 +03:00
use Illuminate\Support\Facades\Mail;
use Modules\Core\Auth\Mail\OtpMail;
use Modules\Core\Auth\Models\Staff;
class OtpService
{
private const EXPIRY_MINUTES = 10;
private const CODE_LENGTH = 6;
/**
* $purpose is forwarded as-is to OtpMail, which only recognizes a
* fixed set of keys (see its own COPY_BY_PURPOSE) — an unrecognized
* value there just falls back to 'login' rather than failing here, so
* this method has nothing of its own to validate.
*/
public function generateAndSend(string $email, string $purpose = 'login'): bool
2026-07-01 18:35:39 +03:00
{
$staff = Staff::where('email', $email)->first();
if (! $staff) {
return false;
}
$code = str_pad((string) random_int(0, 999999), self::CODE_LENGTH, '0', STR_PAD_LEFT);
2026-09-30 11:15:27 +03:00
// otp_code_hash's 'hashed' cast (see Staff's own $casts) hashes
// this automatically on assignment, same as password — never
// stored or compared in plaintext.
$staff->otp_code_hash = $code;
2026-07-01 18:35:39 +03:00
$staff->otp_expires_at = now()->addMinutes(self::EXPIRY_MINUTES);
$staff->save();
Mail::to($staff->email)->send(new OtpMail($staff->first_name, $code, $purpose));
2026-07-01 18:35:39 +03:00
return true;
}
public function validate(string $email, string $code): ?Staff
{
$staff = Staff::where('email', $email)->first();
if (! $staff) {
return null;
}
2026-09-30 11:15:27 +03:00
if (! $staff->otp_code_hash || ! $staff->otp_expires_at || now()->isAfter($staff->otp_expires_at)) {
2026-07-01 18:35:39 +03:00
return null;
}
2026-09-30 11:15:27 +03:00
if (! Hash::check($code, $staff->otp_code_hash)) {
return null;
}
$staff->otp_code_hash = null;
2026-07-01 18:35:39 +03:00
$staff->otp_expires_at = null;
$staff->save();
return $staff;
}
}