# OTP Authentication Staff authentication uses a passwordless OTP flow. No password is stored or required. --- ## Flow 1. Staff enters their email on the login page 2. A 6-digit code is generated, stored on the `staff` record, and emailed 3. Staff enters the code — if valid and not expired, they are logged in --- ## Implementation ### OtpService `Modules\Core\Services\OtpService` handles generation and validation. ```php // Generate and email a code $otpService->generateAndSend(string $email): bool // Validate a submitted code — returns the Staff model on success, null on failure $otpService->validate(string $email, string $code): ?Staff ``` Codes expire after **10 minutes**. After a successful validation the code is cleared from the record. ### Database Two columns on the `lunar_staff` table (added by `2026_05_06_000001_add_otp_to_lunar_staff_table`): | Column | Type | Purpose | |---|---|---| | `otp_code` | string, nullable | The generated code | | `otp_expires_at` | timestamp, nullable | Expiry time | ### Login Page `Modules\Core\Filament\Pages\Login` is a Filament `SimplePage` registered as the panel login via `AppServiceProvider`: ```php LunarPanel::panel(fn (Panel $panel) => $panel->login(Login::class)); ``` It has two steps rendered in `core::filament.pages.login`: - **Step 1** — email form, submits to `requestOtp()` - **Step 2** — code form, submits to `authenticate()` After a valid code `canAccessPanel()` is checked before the session is established. ### Email `Modules\Core\Mail\OtpMail` sends the code using the `core::mail.otp` view. --- ## Disabling 2FA Lunar's built-in 2FA plugin is disabled in `AppServiceProvider` since the OTP login replaces it: ```php LunarPanel::disableTwoFactorAuth(); ```