### Install OTPz and Run Migrations Source: https://context7.com/benbjurstrom/otpz/llms.txt Install the package using Composer, then publish and run the database migrations. ```bash composer require benbjurstrom/otpz php artisan vendor:publish --tag="otpz-migrations" php artisan migrate ``` -------------------------------- ### Configure Custom User Resolver Source: https://context7.com/benbjurstrom/otpz/llms.txt Configuration example for setting a custom user resolver in `config/otpz.php`. ```php // config/otpz.php 'user_resolver' => App\Actions\StrictUserResolver::class, ``` -------------------------------- ### Install OTPz Package Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Install the OTPz package using Composer. This is the first step to integrating OTP authentication into your Laravel application. ```bash composer require benbjurstrom/otpz ``` -------------------------------- ### Custom User Resolver Example Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Implement a custom user resolver class to define how users are found or created by email address. This example throws a validation error if a user is not found. ```php namespace App\Actions; use App\Models\User; use BenBjurstrom\Otpz\Models\Concerns\Otpable; use Illuminate\Validation\ValidationException; class MyUserResolver { public function handle(string $email): Otpable { $user = User::where('email', $email)->first(); if($user){ return $user; } throw ValidationException::withMessages([ 'email' => 'No user found with that email address.', ]); } } ``` -------------------------------- ### OTPz Configuration File Example Source: https://github.com/benbjurstrom/otpz/blob/main/README.md This is the content of the published OTPz configuration file. You can modify expiration times, throttling limits, default models, mailables, and email templates here. ```php 5, // Minutes 'limits' => [ ['limit' => 1, 'minutes' => 1], ['limit' => 3, 'minutes' => 5], ['limit' => 5, 'minutes' => 30], ], /* |-------------------------------------------------------------------------- | Model Configuration |-------------------------------------------------------------------------- | | This setting determines the model used by Otpz to store and retrieve | one-time passwords. By default, it uses the 'App\Models\User' model. | */ 'models' => [ 'authenticatable' => App\Models\User::class, ], /* |-------------------------------------------------------------------------- | Mailable Configuration |-------------------------------------------------------------------------- | | This setting determines the Mailable class used by Otpz to send emails. | Change this to your own Mailable class if you want to customize the email | sending behavior. | */ 'mailable' => BenBjurstrom\Otpz\Mail\OtpzMail::class, /* |-------------------------------------------------------------------------- | Template Configuration |-------------------------------------------------------------------------- | | This setting determines the email template used by Otpz to send emails. | Switch to 'otpz::mail.notification' if you prefer to use the default | Laravel notification template. | */ 'template' => 'otpz::mail.otpz', // 'template' => 'otpz::mail.notification', /* |-------------------------------------------------------------------------- | User Resolver |-------------------------------------------------------------------------- | | Defines the class responsible for finding or creating users by email address. | The default implementation will create a new user when an email doesn't exist. | Replace with your own implementation for custom user resolution logic. | */ 'user_resolver' => BenBjurstrom\Otpz\Actions\GetUserFromEmail::class, ]; ``` -------------------------------- ### Custom User Resolver Implementation Source: https://context7.com/benbjurstrom/otpz/llms.txt Example of a custom user resolver that rejects unknown email addresses, throwing a `ValidationException`. ```php namespace App\Actions; use BenBjurstrom\Otpz\Models\Concerns\Otpable; use Illuminate\Validation\ValidationException; class StrictUserResolver { public function handle(string $email): Otpable { $user = \App\Models\User::where('email', $email)->first(); if (! $user) { throw ValidationException::withMessages([ 'email' => 'No account found for that email address.', ]); } return $user; } } ``` -------------------------------- ### Register OTPz Routes for Guest Middleware Source: https://context7.com/benbjurstrom/otpz/llms.txt Register the necessary named routes for OTPz authentication within the `guest` middleware group. This example shows routes for React/Vue (Inertia). ```php // routes/web.php use App\Http\Controllers\Auth\OtpzController; // React or Vue // use App\Http\Controllers\Auth\PostOtpController; // Livewire Route::middleware('guest')->group(function () { // React / Vue (Inertia) Route::get('otpz', [OtpzController::class, 'index'])->name('otpz.index'); Route::post('otpz', [OtpzController::class, 'store'])->name('otpz.store'); Route::get('otpz/{id}', [OtpzController::class, 'show']) ->name('otpz.show') ->middleware('signed'); Route::post('otpz/{id}', [OtpzController::class, 'verify']) ->name('otpz.verify') ->middleware('signed'); // Livewire alternative // Volt::route('otpz', 'auth.otpz-login')->name('otpz.index'); // Volt::route('otpz/{id}', 'auth.otpz-verify')->middleware('signed')->name('otpz.show'); // Route::post('otpz/{id}', PostOtpController::class)->middleware('signed')->name('otpz.verify'); }); ``` -------------------------------- ### Customizing OtpzMail with a Custom Mailable Source: https://context7.com/benbjurstrom/otpz/llms.txt Customize email notifications by switching to a custom Mailable class or overriding the default template via configuration. The example shows a custom Mailable that formats the code for display. ```php // config/otpz.php — switch to Laravel's built-in notification template 'template' => 'otpz::mail.notification', // Or use a fully custom Mailable: 'mailable' => App\Mail\MyOtpMail::class, ``` ```php // app/Mail/MyOtpMail.php — custom mailable example namespace App\Mail; use BenBjurstrom\Otpz\Models\Otp; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; class MyOtpMail extends Mailable { public function __construct(protected Otp $otp, protected string $code) {} public function envelope(): Envelope { return new Envelope(subject: 'Your login code'); } public function content(): Content { // $this->code is the plain-text 10-char code (before hashing) // Format for display: substr_replace($this->code, '-', 5, 0) → "ABCDE-FGHIJ" return new Content( markdown: 'mail.my-otp', with: [ 'code' => substr_replace($this->code, '-', 5, 0), 'email' => $this->otp->user->email, ] ); } } ``` -------------------------------- ### Run OTPz Migrations Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Publish and run the OTPz migrations to set up the necessary database tables for OTP management. Ensure you have configured your database connection. ```bash php artisan vendor:publish --tag="otpz-migrations" php artisan migrate ``` -------------------------------- ### Replace Fortify Login (Livewire) Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Comment out the default login view and update the route in `routes/web.php` to use OTPz login for Livewire applications. ```php // Fortify::loginView(fn () => view('livewire.auth.login')); ``` ```php Volt::route('login', 'auth.otpz-login') ->name('login'); // Changed path and name from 'otpz' ``` -------------------------------- ### Add OTPz Routes (Livewire/Volt) Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Integrate OTPz with Livewire using Volt. Add these routes to `routes/web.php`. Note the use of `Volt::route` and `PostOtpController`. ```php use App\Http\Controllers\Auth\PostOtpController; use Livewire\Volt\Volt; Route::middleware('guest')->group(function () { Volt::route('otpz', 'auth.otpz-login') ->name('otpz.index'); Volt::route('otpz/{id}', 'auth.otpz-verify') ->middleware('signed') ->name('otpz.show'); Route::post('otpz/{id}', PostOtpController::class) ->middleware('signed') ->name('otpz.verify'); }); ``` -------------------------------- ### Publish Frontend Components for Livewire Source: https://context7.com/benbjurstrom/otpz/llms.txt Publish the frontend components and controllers for Livewire (Volt) integration. ```bash # Livewire (Volt) — publishes PostOtpController.php + .blade.php Volt components php artisan vendor:publish --tag="otpz-livewire" ``` -------------------------------- ### Publish OTPz Configuration File Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Run this command to publish the OTPz configuration file to your application. This allows you to customize expiration, throttling, model, and mailer settings. ```bash php artisan vendor:publish --tag="otpz-config" ``` -------------------------------- ### Publish OTPz Translations File Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Run this command to publish the OTPz translations file, which contains standard translations for the package. ```bash php artisan vendor:publish --tag="otpz-translations" ``` -------------------------------- ### Run Package Tests Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Execute this command in your terminal to run the test suite for the OTPz package. ```bash composer test ``` -------------------------------- ### Publish OTPz Livewire Components Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Use this Artisan command to publish the Livewire components for OTPz. This includes views for email and OTP entry, and a controller. ```bash php artisan vendor:publish --tag="otpz-livewire" ``` -------------------------------- ### Publish Frontend Components for React Source: https://context7.com/benbjurstrom/otpz/llms.txt Publish the frontend components and controllers for React (Inertia.js) integration. ```bash # React (Inertia.js) — publishes OtpzController.php + .tsx pages php artisan vendor:publish --tag="otpz-react" ``` -------------------------------- ### Send OTP Email using SendOtp Action Source: https://context7.com/benbjurstrom/otpz/llms.txt Use the `SendOtp` action to resolve or create a user, generate an OTP, store it, and send an email. Handles throttling and returns a signed URL for verification. ```php use BenBjurstrom\Otpz\Actions\SendOtp; use BenBjurstrom\Otpz\Exceptions\OtpThrottleException; use Illuminate\Validation\ValidationException; // Typical usage inside a controller store() method public function store(Request $request): \Symfony\Component\HttpFoundation\Response { $request->validate([ 'email' => ['required', 'string', 'email'], 'remember' => ['boolean'], ]); try { $otp = (new SendOtp)->handle( email: $request->input('email'), remember: $request->boolean('remember') // default false ); } catch (OtpThrottleException $e) { // Message: "Too many codes requested. Please wait X minutes and Y seconds..." throw ValidationException::withMessages(['email' => $e->getMessage()]); } // $otp->url is a temporarySignedRoute valid for 5 minutes, // containing the OTP UUID and the current session ID. // Example: https://app.test/otpz/018f1b2c-...?sessionId=abc123&signature=...&expires=... return redirect($otp->url); // Or with Inertia: return Inertia::location($otp->url); } ``` -------------------------------- ### Replace Fortify Login View with OTPz Source: https://context7.com/benbjurstrom/otpz/llms.txt Integrate OTPz by replacing the default Fortify login view with the OTPz email-entry page for React, Vue, or Livewire applications. ```php // app/Providers/FortifyServiceProvider.php use Laravel\Fortify\Fortify; // React Fortify::loginView(fn () => Inertia::render('auth/otpz-login', [])); // Vue Fortify::loginView(fn () => Inertia::render('auth/OtpzLogin', [])); // Livewire — comment out the default view, then rename the OTPz route: // Fortify::loginView(fn () => view('livewire.auth.login')); ← remove this ``` ```php // routes/web.php (Livewire) Volt::route('login', 'auth.otpz-login')->name('login'); ``` -------------------------------- ### Publish Frontend Components for Vue Source: https://context7.com/benbjurstrom/otpz/llms.txt Publish the frontend components and controllers for Vue (Inertia.js) integration. ```bash # Vue (Inertia.js) — publishes OtpzController.php + .vue pages php artisan vendor:publish --tag="otpz-vue" ``` -------------------------------- ### Add React OTPz Routes Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Define the necessary routes in `routes/web.php` to handle OTPz login and verification flows using the published `OtpzController`. ```php use App\Http\Controllers\Auth\OtpzController; Route::middleware('guest')->group(function () { Route::get('otpz', [OtpzController::class, 'index']) ->name('otpz.index'); Route::post('otpz', [OtpzController::class, 'store']) ->name('otpz.store'); Route::get('otpz/{id}', [OtpzController::class, 'show']) ->name('otpz.show') ->middleware('signed'); Route::post('otpz/{id}', [OtpzController::class, 'verify']) ->name('otpz.verify') ->middleware('signed'); }); ``` -------------------------------- ### Publish OTPz Email Views Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Execute this command to publish the email view files for OTPz. This allows you to customize the styling of the emails sent by the package. ```bash php artisan vendor:publish --tag="otpz-views" ``` -------------------------------- ### Replace Fortify Login (React) Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Update the `loginView` method in `app/Providers/FortifyServiceProvider.php` to use OTPz login for React applications. ```php Fortify::loginView(fn (Request $request) => Inertia::render('auth/otpz-login', [])); ``` -------------------------------- ### Replace Fortify Login (Vue) Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Update the `loginView` method in `app/Providers/FortifyServiceProvider.php` to use OTPz login for Vue applications. ```php Fortify::loginView(fn (Request $request) => Inertia::render('auth/OtpzLogin', [])); ``` -------------------------------- ### Publish React OTPz Components Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Publish the React components for OTPz login and verification. These files are copied to your application's resources and app directories. ```bash php artisan vendor:publish --tag="otpz-react" ``` -------------------------------- ### OTPz Configuration Options Source: https://context7.com/benbjurstrom/otpz/llms.txt Configure OTP expiration, rate limiting, user models, mailables, templates, and user resolvers. ```php // config/otpz.php return [ // OTP expiration window in minutes (default: 5) 'expiration' => 5, // Multi-tier rate limiting: [max OTPs, within N minutes] // Default: max 1 per minute, 3 per 5 min, 5 per 30 min 'limits' => [ ['limit' => 1, 'minutes' => 1], ['limit' => 3, 'minutes' => 5], ['limit' => 5, 'minutes' => 30], ], // The Eloquent model that implements Otpable (must use HasOtps trait) 'models' => [ 'authenticatable' => \App\Models\User::class, ], // Mailable class used to send OTP emails 'mailable' => \BenBjurstrom\Otpz\Mail\OtpzMail::class, // Blade template for the email body 'template' => 'otpz::mail.otpz', // 'template' => 'otpz::mail.notification', // Laravel notification style // Class responsible for finding/creating a user from an email address 'user_resolver' => \BenBjurstrom\Otpz\Actions\GetUserFromEmail::class, ]; ``` -------------------------------- ### CreateOtp::handle() — Generate and Persist an OTP Source: https://context7.com/benbjurstrom/otpz/llms.txt Generates and persists a new OTP. This action checks throttle limits, supersedes any existing active OTPs for the user, and creates a new OTP record with a bcrypt-hashed code. It returns the OTP object and the plain text code. ```APIDOC ## `CreateOtp::handle()` — Generate and Persist an OTP Lower-level action called by `SendOtp`. Checks multi-tier throttle limits, supersedes any existing active OTPs for the user, then creates a new `Otp` record with a bcrypt-hashed 10-character random code. Returns `[$otp, $plainTextCode]`. ```php use BenBjurstrom\Otpz\Actions\CreateOtp; use BenBjurstrom\Otpz\Exceptions\OtpThrottleException; // $user must implement Otpable (i.e., User model with HasOtps + Otpable interface) try { [$otp, $plainCode] = (new CreateOtp)->handle( user: $user, remember: false ); // $otp->status === OtpStatus::ACTIVE // $otp->code === bcrypt hash of $plainCode // $plainCode === e.g. "AB3K9-XZ7M2" (displayed to user in email) // Previous ACTIVE otps for $user are now OtpStatus::SUPERSEDED } catch (OtpThrottleException $e) { // Thrown when limits are exceeded, e.g.: // >1 OTP in the last 1 minute // >3 OTPs in the last 5 minutes // >5 OTPs in the last 30 minutes echo $e->getMessage(); // "Too many codes requested. Please wait 0 minutes and 42 seconds..." } ``` ``` -------------------------------- ### Publishing OTPz Views Source: https://context7.com/benbjurstrom/otpz/llms.txt Publish the OTPz package views using the `vendor:publish` Artisan command to customize the default email template. ```bash # Publish views to customise the default template php artisan vendor:publish --tag="otpz-views" # Publishes to resources/views/vendor/otpz/mail/otpz.blade.php # resources/views/vendor/otpz/mail/notification.blade.php # resources/views/vendor/otpz/components/template.blade.php ``` -------------------------------- ### Update Configuration with Custom User Resolver Source: https://github.com/benbjurstrom/otpz/blob/main/README.md After creating your custom user resolver, update the 'user_resolver' key in your `config/otpz.php` file to point to your new class. ```php 'user_resolver' => App\Actions\MyUserResolver::class, ``` -------------------------------- ### Integrate Otpable Interface and HasOtps Trait Source: https://context7.com/benbjurstrom/otpz/llms.txt Add the `Otpable` interface and `HasOtps` trait to your User model to enable OTP authentication. ```php // app/Models/User.php namespace App\Models; use BenBjurstrom\Otpz\Models\Concerns\HasOtps; use BenBjurstrom\Otpz\Models\Concerns\Otpable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable implements Otpable { use HasOtps; // ... } ``` -------------------------------- ### Configure User Model for OTPz Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Add the `HasOtps` trait and `Otpable` interface to your User model to enable OTP functionality. This allows the User model to manage OTPs. ```php // app/Models/User.php namespace App\Models; use BenBjurstrom\Otpz\Models\Concerns\HasOtps; use BenBjurstrom\Otpz\Models\Concerns\Otpable; // ... class User extends Authenticatable implements Otpable { use HasFactory, Notifiable, HasOtps; // ... } ``` -------------------------------- ### Publish Vue OTPz Components Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Publish the Vue components for OTPz login and verification. These files are copied to your application's resources and app directories. ```bash php artisan vendor:publish --tag="otpz-vue" ``` -------------------------------- ### AttemptOtp::handle() — Verify a Submitted Code Source: https://context7.com/benbjurstrom/otpz/llms.txt Validates a submitted OTP code against a signed URL, session, OTP status, expiration, and attempt count. On success, it marks the OTP as USED and returns it. On failure, it throws an OtpAttemptException. ```APIDOC ## `AttemptOtp::handle()` — Verify a Submitted Code Validates the signed URL, session match, OTP status, expiration, attempt count, and bcrypt code hash in sequence. On success, marks the OTP as `USED` and returns it; on any failure, throws `OtpAttemptException` with a localised error message. ```php use BenBjurstrom\Otpz\Actions\AttemptOtp; use BenBjurstrom\Otpz\Exceptions\OtpAttemptException; use BenBjurstrom\Otpz\Http\Requests\OtpRequest; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Session; use Illuminate\Validation\ValidationException; // OtpRequest validates: code (string, size:10, uppercased), sessionId (string) public function verify(OtpRequest $request, string $id): \Illuminate\Http\RedirectResponse { try { $data = $request->safe()->only(['code', 'sessionId']); // Throws OtpAttemptException on: invalid signature, wrong session, // non-ACTIVE status, expiry, >3 failed attempts, or wrong code. $otp = (new AttemptOtp)->handle( id: $id, code: $data['code'], // 10-char uppercase alphanumeric sessionId: $data['sessionId'] ); Auth::loginUsingId($otp->user_id, $otp->remember); // fires Auth\Events\Login Session::regenerate(); // Auto-verify email on first passwordless login if (! $otp->user->hasVerifiedEmail()) { $otp->user->markEmailAsVerified(); } return redirect()->intended('/dashboard'); } catch (OtpAttemptException $e) { // Possible messages (translatable via resources/lang/vendor/otpz/en/otp.php): // "The route signature is invalid." // "The sign-in code was requested in a different session." // "The active code has expired. Please request a new code." // "Too many attempts. Please request a new code." // "The given code is invalid." throw ValidationException::withMessages(['code' => $e->getMessage()]); } } ``` ``` -------------------------------- ### Default User Resolver by Email Source: https://context7.com/benbjurstrom/otpz/llms.txt Finds a user by email, creating a new one with a random password if not found. This behavior can be customized via the `otpz.user_resolver` configuration. ```php use BenBjurstrom\Otpz\Actions\GetUserFromEmail; // Default behaviour: create user if not found $user = (new GetUserFromEmail)->handle('jane@example.com'); // Returns existing User or creates one with: // email = 'jane@example.com' // password = Str::random(32) (unusable password — only OTP login works) // name = '' ``` -------------------------------- ### Generate and Persist an OTP Source: https://context7.com/benbjurstrom/otpz/llms.txt Creates a new OTP record with a bcrypt-hashed code. Supersedes existing active OTPs for the user. Throws `OtpThrottleException` if rate limits are exceeded. ```php use BenBjurstrom\Otpz\Actions\CreateOtp; use BenBjurstrom\Otpz\Exceptions\OtpThrottleException; // $user must implement Otpable (i.e., User model with HasOtps + Otpable interface) try { [$otp, $plainCode] = (new CreateOtp)->handle( user: $user, remember: false ); // $otp->status === OtpStatus::ACTIVE // $otp->code === bcrypt hash of $plainCode // $plainCode === e.g. "AB3K9-XZ7M2" (displayed to user in email) // Previous ACTIVE otps for $user are now OtpStatus::SUPERSEDED } catch (OtpThrottleException $e) { // Thrown when limits are exceeded, e.g.: // >1 OTP in the last 1 minute // >3 OTPs in the last 5 minutes // >5 OTPs in the last 30 minutes echo $e->getMessage(); // "Too many codes requested. Please wait 0 minutes and 42 seconds..." } ``` -------------------------------- ### GetUserFromEmail::handle() — Default User Resolver Source: https://context7.com/benbjurstrom/otpz/llms.txt Resolves a user based on their email address. By default, it finds an existing user or creates a new one with a random password, enabling passwordless registration. This behavior can be customized. ```APIDOC ## `GetUserFromEmail::handle()` — Default User Resolver Finds an existing user by email or creates a new one with a random password (enabling true passwordless registration). Can be replaced with a custom implementation via the `otpz.user_resolver` config key. ```php use BenBjurstrom\Otpz\Actions\GetUserFromEmail; // Default behaviour: create user if not found $user = (new GetUserFromEmail)->handle('jane@example.com'); // Returns existing User or creates one with: // email = 'jane@example.com' // password = Str::random(32) (unusable password — only OTP login works) // name = '' // --- Custom resolver: reject unknown emails --- namespace App\Actions; use BenBjurstrom\Otpz\Models\Concerns\Otpable; use Illuminate\Validation\ValidationException; class StrictUserResolver { public function handle(string $email): Otpable { $user = \App\Models\User::where('email', $email)->first(); if (! $user) { throw ValidationException::withMessages([ 'email' => 'No account found for that email address.', ]); } return $user; } } ``` ```php // config/otpz.php 'user_resolver' => App\Actions\StrictUserResolver::class, ``` ``` -------------------------------- ### Switch Email Template in Configuration Source: https://github.com/benbjurstrom/otpz/blob/main/README.md Modify the 'template' key in your `config/otpz.php` file to switch between the custom OTPz email template and the default Laravel notification template. ```php 'template' => 'otpz::mail.notification', // Use Laravel's default styling ``` -------------------------------- ### OTPz Translation Strings Source: https://context7.com/benbjurstrom/otpz/llms.txt Customize the English translation strings for OTP status messages, throttle exceptions, and email subjects. ```php // lang/vendor/otpz/en/otp.php return [ 'status' => [ 'expired' => 'The active code has expired. Please request a new code.', 'attempted' => 'Too many attempts. Please request a new code.', 'invalid' => 'The given code is invalid.', 'session' => 'The sign-in code was requested in a different session. Please login using the same browser that requested the code.', 'signature' => 'The route signature is invalid.', 'superseded' => 'The active code has been superseded. Please request a new code.', 'used' => 'The active code has already been used. Please request a new code.', ], 'exception' => [ 'throttle' => 'Too many codes requested. Please wait :minutes minutes and :seconds seconds before trying again.', ], 'mail' => [ 'otpz' => [ 'subject' => 'Sign in to ', // appended with config('app.name') ], ], ]; ``` -------------------------------- ### OtpStatus Enum Values and Error Messages Source: https://context7.com/benbjurstrom/otpz/llms.txt Utilize the OtpStatus enum for OTP lifecycle states. Each case provides a localized `errorMessage()` for exceptions. ```php use BenBjurstrom\Otpz\Enums\OtpStatus; OtpStatus::ACTIVE->value; // 0 — awaiting verification OtpStatus::SUPERSEDED->value; // 1 — replaced by a newer OTP OtpStatus::EXPIRED->value; // 2 — past the expiration window OtpStatus::ATTEMPTED->value; // 3 — invalidated after 3 failed attempts OtpStatus::USED->value; // 4 — successfully redeemed OtpStatus::INVALID->value; // 5 — wrong code submitted OtpStatus::SIGNATURE->value; // 6 — signed URL validation failed OtpStatus::SESSION->value; // 7 — OTP used in a different browser session OtpStatus::EXPIRED->errorMessage(); // "The active code has expired. Please request a new code." OtpStatus::SESSION->errorMessage(); // "The sign-in code was requested in a different session..." ``` -------------------------------- ### Verify Submitted OTP Code Source: https://context7.com/benbjurstrom/otpz/llms.txt Validates OTPs by checking signature, session, status, expiry, and attempt count. Throws `OtpAttemptException` on failure. Requires `OtpRequest` for input validation. ```php use BenBjurstrom\Otpz\Actions\AttemptOtp; use BenBjurstrom\Otpz\Exceptions\OtpAttemptException; use BenBjurstrom\Otpz\Http\Requests\OtpRequest; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Session; use Illuminate\Validation\ValidationException; // OtpRequest validates: code (string, size:10, uppercased), sessionId (string) public function verify(OtpRequest $request, string $id): \Illuminate\Http\RedirectResponse { try { $data = $request->safe()->only(['code', 'sessionId']); // Throws OtpAttemptException on: invalid signature, wrong session, // non-ACTIVE status, expiry, >3 failed attempts, or wrong code. $otp = (new AttemptOtp)->handle( id: $id, code: $data['code'], // 10-char uppercase alphanumeric sessionId: $data['sessionId'] ); Auth::loginUsingId($otp->user_id, $otp->remember); // fires Auth\Events\Login Session::regenerate(); // Auto-verify email on first passwordless login if (! $otp->user->hasVerifiedEmail()) { $otp->user->markEmailAsVerified(); } return redirect()->intended('/dashboard'); } catch (OtpAttemptException $e) { // Possible messages (translatable via resources/lang/vendor/otpz/en/otp.php): // "The route signature is invalid." // "The sign-in code was requested in a different session." // "The active code has expired. Please request a new code." // "Too many attempts. Please request a new code." // "The given code is invalid." throw ValidationException::withMessages(['code' => $e->getMessage()]); } } ``` -------------------------------- ### Otp Model Attributes and URL Accessor Source: https://context7.com/benbjurstrom/otpz/llms.txt Access attributes of the Otp model, including its UUID primary key, status, attempts, and IP address. The `url` accessor generates a temporary signed verification URL. ```php use BenBjurstrom\Otpz\Models\Otp; use BenBjurstrom\Otpz\Enums\OtpStatus; $otp = Otp::find('018f1b2c-dead-beef-cafe-000000000001'); $otp->id; // UUID string $otp->user_id; // FK to users table $otp->status; // OtpStatus enum: ACTIVE(0), SUPERSEDED(1), EXPIRED(2), // ATTEMPTED(3), USED(4) $otp->attempts; // int, incremented on each wrong code (max 3) $otp->remember; // bool — passed to Auth::loginUsingId() $otp->ip_address; // IP that requested the OTP $otp->created_at; // Carbon — used for expiry check // Computed accessor — generates a 5-minute temporary signed URL: $otp->url; // "https://app.test/otpz/018f1b2c-...?sessionId=sess_abc&expires=1714000000&signature=..." // Relationship $otp->user; // the Otpable user model ``` -------------------------------- ### HasOtps Trait - OTP Relationship Source: https://context7.com/benbjurstrom/otpz/llms.txt Utilize the `HasOtps` trait to add an `otps()` Eloquent HasMany relationship to user models, enabling access to OTP history and filtering by status. ```php use BenBjurstrom\Otpz\Models\Concerns\HasOtps; use BenBjurstrom\Otpz\Enums\OtpStatus; // Access a user's OTP history $user->otps()->get(); // Filter by status $user->otps()->where('status', OtpStatus::ACTIVE)->first(); // Count OTPs within a time window (used internally by throttle logic) $user->otps() ->where('status', '!=', OtpStatus::USED) ->where('created_at', '>=', now()->subMinutes(5)) ->count(); ``` -------------------------------- ### OtpRequest Validation and Sanitization Source: https://context7.com/benbjurstrom/otpz/llms.txt The OtpRequest form request validates and sanitizes OTP submissions. It automatically uppercases and strips non-alphanumeric characters from the code before validation. ```php use BenBjurstrom\Otpz\Http\Requests\OtpRequest; // OtpRequest::rules(): // [ // 'code' => ['required', 'string', 'size:10'], // after sanitisation // 'sessionId' => ['required', 'string'], // ] // prepareForValidation() automatically: // - Uppercases the submitted code // - Strips everything except 0-9 and A-Z // So "ab3k9-xz7m2" becomes "AB3K9XZ7M2" before the size:10 rule is applied. public function verify(OtpRequest $request, string $id): RedirectResponse { $data = $request->safe()->only(['code', 'sessionId']); // $data['code'] === "AB3K9XZ7M2" // $data['sessionId'] === "session_id_from_signed_url" // ... } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.