feat(backend): complete Module 1 auth (register/login/OTP/reset)
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>
This commit is contained in:
62
app/Models/OtpCode.php
Normal file
62
app/Models/OtpCode.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user