OTP
Directory: app/Services/Otp/
One-Time Password generation and verification service. Used by the setup wizard's email verification step, and by any feature that needs email-based code verification.
Overview
The OTP service provides two operations:
- Generate — creates a 6-digit numeric code, stores it in the
otpstable with a 10-minute expiry - Verify — checks a submitted code against the stored one, handles expiry and invalid codes
Why It Exists
- Security: Codes are generated using
random_int()(cryptographically secure), notrand() - Self-cleaning: Old codes are deleted before new ones are generated; verified/expired codes are deleted on read
- Traceable: Each OTP stores metadata (email, IP, user agent) for audit purposes
- Time-limited: Codes expire after 10 minutes — no stale codes lingering
Methods
Generate::handle(string $email, array $meta = []): Otp
Generates a new OTP code for the given email address.
php
public static function handle(string $email, array $meta = []): OtpAlgorithm:
- Delete all existing OTPs for this email (clean slate)
- Generate a random 6-digit code:
str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT) - Store in
otpstable with 10-minute expiry - Return the
Otpmodel instance
Example:
php
use App\Services\Otp\Generate;
$otp = Generate::handle('user@example.com', [
'ip' => request()->ip(),
]);
// Send $otp->code to the user's emailVerify::handle(string $email, string $code): array
Verifies a submitted OTP code against stored records.
php
public static function handle(string $email, string $code): arrayAlgorithm:
- Query
otpstable wherecodematches ANDmeta->emailmatches - If no match → return
{ success: false, message: 'Invalid or expired verification code.' } - If matched but expired (
$otp->isExpired()) → delete it, return{ success: false, message: 'Verification code has expired.' } - If valid → delete the OTP (one-time use), return
{ success: true, message: 'Email verified successfully.', otp: $otp }
Return values:
php
[
'success' => bool, // true if code is valid and not expired
'message' => string, // human-readable status
'otp' => Otp|null, // the OTP model (only on success)
]Example:
php
use App\Services\Otp\Verify;
$result = Verify::handle('user@example.com', '123456');
if ($result['success']) {
// Email verified — proceed with registration
} else {
// Show $result['message'] to the user
}Frontend Integration
The OTP flow in the setup wizard follows the stores→services→api pattern:
super-user-otp-form.stores.ts
↓ sendOtp(email) / verifyOtp(email, code)
otp.services.ts
↓ send() / verify()
otp.api.ts
↓ POST /api/setup/send-otp / POST /api/setup/verify-otp
OtpController (Laravel)
↓ Generate::handle() / Verify::handle()Related stores:
| Store | Purpose |
|---|---|
super-user-otp-form.stores.ts | OTP code input + send/verify state |
super-user-otp-modal.stores.ts | OTP modal open/close |
super-user-otp-resend.stores.ts | Resend countdown timer (60s) |
super-user-otp-validation.stores.ts | OTP-related validation |
Key Files
| File | Purpose |
|---|---|
app/Services/Otp/Generate.php | OTP code generation + database storage |
app/Services/Otp/Verify.php | OTP code verification + expiry check |
app/Models/Platform/Otp/Otp.php | The otps model — code, expired_at, meta (JSON) |
app/Http/Controllers/Api/OtpController.php | API controller (send + verify endpoints) |
app/Observers/OtpObserver.php | Optional: cleanup or notification triggers |
resources/js/modules/landing/setup/form/super-user/stores/otp-entry/ | Frontend OTP stores |