Closes Module 1: 9 auth endpoints under /api/v1/auth, OTP via SMS (Semaphore + log + fake drivers), role middleware, role + admin seeders, 27 feature tests passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
63 lines
1.3 KiB
PHP
63 lines
1.3 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
class OtpCode extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
public const CHANNEL_SMS = 'sms';
|
|
public const CHANNEL_EMAIL = 'email';
|
|
|
|
public const PURPOSE_REGISTER = 'register';
|
|
public const PURPOSE_LOGIN = 'login';
|
|
public const PURPOSE_PHONE_VERIFY = 'phone_verify';
|
|
public const PURPOSE_PASSWORD_RESET = 'password_reset';
|
|
|
|
public const MAX_ATTEMPTS = 5;
|
|
|
|
protected $fillable = [
|
|
'user_id',
|
|
'channel',
|
|
'destination',
|
|
'purpose',
|
|
'code_hash',
|
|
'attempts',
|
|
'expires_at',
|
|
'consumed_at',
|
|
];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'expires_at' => 'datetime',
|
|
'consumed_at' => 'datetime',
|
|
'attempts' => 'integer',
|
|
];
|
|
}
|
|
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
public function isExpired(): bool
|
|
{
|
|
return $this->expires_at->isPast();
|
|
}
|
|
|
|
public function isConsumed(): bool
|
|
{
|
|
return $this->consumed_at !== null;
|
|
}
|
|
|
|
public function isExhausted(): bool
|
|
{
|
|
return $this->attempts >= self::MAX_ATTEMPTS;
|
|
}
|
|
}
|