diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index d396213..0000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,126 +0,0 @@
-# Release Notes
-
-## [Unreleased](https://github.com/laravel/laravel/compare/v12.10.0...12.x)
-
-## [v12.10.0](https://github.com/laravel/laravel/compare/v12.9.1...v12.10.0) - 2025-11-04
-
-* Add background driver by [@barryvdh](https://github.com/barryvdh) in https://github.com/laravel/laravel/pull/6699
-
-## [v12.9.1](https://github.com/laravel/laravel/compare/v12.9.0...v12.9.1) - 2025-10-23
-
-* [12.x] Replace Bootcamp with Laravel Learn by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6692
-* [12.x] Comment out CLI workers for fresh applications by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/laravel/pull/6693
-
-## [v12.9.0](https://github.com/laravel/laravel/compare/v12.8.0...v12.9.0) - 2025-10-21
-
-**Full Changelog**: https://github.com/laravel/laravel/compare/v12.8.0...v12.9.0
-
-## [v12.8.0](https://github.com/laravel/laravel/compare/v12.7.1...v12.8.0) - 2025-10-20
-
-* [12.x] Makes test suite using broadcast's `null` driver by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/laravel/pull/6691
-
-## [v12.7.1](https://github.com/laravel/laravel/compare/v12.7.0...v12.7.1) - 2025-10-15
-
-* Added `failover` driver to the `queue` config comment. by [@sajjadhossainshohag](https://github.com/sajjadhossainshohag) in https://github.com/laravel/laravel/pull/6688
-
-## [v12.7.0](https://github.com/laravel/laravel/compare/v12.6.0...v12.7.0) - 2025-10-14
-
-**Full Changelog**: https://github.com/laravel/laravel/compare/v12.6.0...v12.7.0
-
-## [v12.6.0](https://github.com/laravel/laravel/compare/v12.5.0...v12.6.0) - 2025-10-02
-
-* Fix setup script by [@goldmont](https://github.com/goldmont) in https://github.com/laravel/laravel/pull/6682
-
-## [v12.5.0](https://github.com/laravel/laravel/compare/v12.4.0...v12.5.0) - 2025-09-30
-
-* [12.x] Fix type casting for environment variables in config files by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6670
-* Fix CVEs affecting vite by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/laravel/pull/6672
-* Update .editorconfig to target compose.yaml by [@fredikaputra](https://github.com/fredikaputra) in https://github.com/laravel/laravel/pull/6679
-* Add pre-package-uninstall script to composer.json by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/laravel/pull/6681
-
-## [v12.4.0](https://github.com/laravel/laravel/compare/v12.3.1...v12.4.0) - 2025-08-29
-
-* [12.x] Add default Redis retry configuration by [@mateusjatenee](https://github.com/mateusjatenee) in https://github.com/laravel/laravel/pull/6666
-
-## [v12.3.1](https://github.com/laravel/laravel/compare/v12.3.0...v12.3.1) - 2025-08-21
-
-* [12.x] Bump Pint version by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6653
-* [12.x] Making sure all related processed are closed when terminating the currently command by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6654
-* [12.x] Use application name from configuration by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6655
-* Bring back postAutoloadDump script by [@jasonvarga](https://github.com/jasonvarga) in https://github.com/laravel/laravel/pull/6662
-
-## [v12.3.0](https://github.com/laravel/laravel/compare/v12.2.0...v12.3.0) - 2025-08-03
-
-* Fix Critical Security Vulnerability in form-data Dependency by [@izzygld](https://github.com/izzygld) in https://github.com/laravel/laravel/pull/6645
-* Revert "fix" by [@RobertBoes](https://github.com/RobertBoes) in https://github.com/laravel/laravel/pull/6646
-* Change composer post-autoload-dump script to Artisan command by [@lmjhs](https://github.com/lmjhs) in https://github.com/laravel/laravel/pull/6647
-
-## [v12.2.0](https://github.com/laravel/laravel/compare/v12.1.0...v12.2.0) - 2025-07-11
-
-* Add Vite 7 support by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/laravel/pull/6639
-
-## [v12.1.0](https://github.com/laravel/laravel/compare/v12.0.11...v12.1.0) - 2025-07-03
-
-* [12.x] Disable nightwatch in testing by [@laserhybiz](https://github.com/laserhybiz) in https://github.com/laravel/laravel/pull/6632
-* [12.x] Reorder environment variables in phpunit.xml for logical grouping by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6634
-* Change to hyphenate prefixes and cookie names by [@u01jmg3](https://github.com/u01jmg3) in https://github.com/laravel/laravel/pull/6636
-* [12.x] Fix type casting for environment variables in config files by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6637
-
-## [v12.0.11](https://github.com/laravel/laravel/compare/v12.0.10...v12.0.11) - 2025-06-10
-
-**Full Changelog**: https://github.com/laravel/laravel/compare/v12.0.10...v12.0.11
-
-## [v12.0.10](https://github.com/laravel/laravel/compare/v12.0.9...v12.0.10) - 2025-06-09
-
-* fix alphabetical order by [@Khuthaily](https://github.com/Khuthaily) in https://github.com/laravel/laravel/pull/6627
-* [12.x] Reduce redundancy and keeps the .gitignore file cleaner by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6629
-* [12.x] Fix: Add void return type to satisfy Rector analysis by [@Aluisio-Pires](https://github.com/Aluisio-Pires) in https://github.com/laravel/laravel/pull/6628
-
-## [v12.0.9](https://github.com/laravel/laravel/compare/v12.0.8...v12.0.9) - 2025-05-26
-
-* [12.x] Remove apc by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6611
-* [12.x] Add JSON Schema to package.json by [@martinbean](https://github.com/martinbean) in https://github.com/laravel/laravel/pull/6613
-* Minor language update by [@woganmay](https://github.com/woganmay) in https://github.com/laravel/laravel/pull/6615
-* Enhance .gitignore to exclude common OS and log files by [@mohammadRezaei1380](https://github.com/mohammadRezaei1380) in https://github.com/laravel/laravel/pull/6619
-
-## [v12.0.8](https://github.com/laravel/laravel/compare/v12.0.7...v12.0.8) - 2025-05-12
-
-* [12.x] Clean up URL formatting in README by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6601
-
-## [v12.0.7](https://github.com/laravel/laravel/compare/v12.0.6...v12.0.7) - 2025-04-15
-
-* Add `composer run test` command by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/laravel/pull/6598
-* Partner Directory Changes in ReadME by [@joshcirre](https://github.com/joshcirre) in https://github.com/laravel/laravel/pull/6599
-
-## [v12.0.6](https://github.com/laravel/laravel/compare/v12.0.5...v12.0.6) - 2025-04-08
-
-**Full Changelog**: https://github.com/laravel/laravel/compare/v12.0.5...v12.0.6
-
-## [v12.0.5](https://github.com/laravel/laravel/compare/v12.0.4...v12.0.5) - 2025-04-02
-
-* [12.x] Update `config/mail.php` to match the latest core configuration by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6594
-
-## [v12.0.4](https://github.com/laravel/laravel/compare/v12.0.3...v12.0.4) - 2025-03-31
-
-* Bump vite from 6.0.11 to 6.2.3 - Vulnerability patch by [@abdel-aouby](https://github.com/abdel-aouby) in https://github.com/laravel/laravel/pull/6586
-* Bump vite from 6.2.3 to 6.2.4 by [@thinkverse](https://github.com/thinkverse) in https://github.com/laravel/laravel/pull/6590
-
-## [v12.0.3](https://github.com/laravel/laravel/compare/v12.0.2...v12.0.3) - 2025-03-17
-
-* Remove reverted change from CHANGELOG.md by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/laravel/pull/6565
-* Improves clarity in app.css file by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6569
-* [12.x] Refactor: Structural improvement for clarity by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6574
-* Bump axios from 1.7.9 to 1.8.2 - Vulnerability patch by [@abdel-aouby](https://github.com/abdel-aouby) in https://github.com/laravel/laravel/pull/6572
-* [12.x] Remove Unnecessarily [@source](https://github.com/source) by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6584
-
-## [v12.0.2](https://github.com/laravel/laravel/compare/v12.0.1...v12.0.2) - 2025-03-04
-
-* Make the github test action run out of the box independent of the choice of testing framework by [@ndeblauw](https://github.com/ndeblauw) in https://github.com/laravel/laravel/pull/6555
-
-## [v12.0.1](https://github.com/laravel/laravel/compare/v12.0.0...v12.0.1) - 2025-02-24
-
-* [12.x] prefer stable stability by [@pataar](https://github.com/pataar) in https://github.com/laravel/laravel/pull/6548
-
-## [v12.0.0 (2025-??-??)](https://github.com/laravel/laravel/compare/v11.0.2...v12.0.0)
-
-Laravel 12 includes a variety of changes to the application skeleton. Please consult the diff to see what's new.
diff --git a/README.md b/README.md
index 274bf90..659b26a 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,571 @@
-# AuthTemplate
+# BSPDX AuthKit
-A Laravel template package that includes auth with 2FA and passkeys.
+[](https://packagist.org/packages/bspdx/authkit)
+[](https://packagist.org/packages/bspdx/authkit)
+[](https://packagist.org/packages/bspdx/authkit)
-## Features
+A comprehensive, production-ready authentication package for Laravel 12. AuthKin combines the power of Laravel Fortify, Sanctum, Spatie Laravel Permission, and Spatie Laravel Passkeys to provide a full-featured auth system with:
-This template is built on Laravel 12 and includes:
+- 🔐 **Standard Authentication** - Powered by Laravel Fortify
+- 👥 **Role-Based Access Control (RBAC)** - Using Spatie Laravel Permission
+- 📱 **TOTP Two-Factor Authentication** - Google Authenticator, Authy, etc.
+- 🔑 **Passkey Authentication** - Modern WebAuthn/FIDO2 login
+- 🛡️ **Passkey as 2FA** - Use passkeys as a second factor
+- 🎨 **Framework-Agnostic Blade Components** - Beautiful, customizable UI partials
+- 🌐 **API Support** - Full Sanctum integration for API authentication
+- 🏢 **Multi-Tenancy Ready** - Optional tenant scoping
-- **Laravel Framework ^12.0** - The latest Laravel framework
-- **Inertia.js ^2.0** - Modern monolith with server-side routing and client-side rendering
-- **Laravel Fortify ^1.31** - Backend authentication scaffolding including 2FA
-- **Laravel Sanctum ^4.0** - API token authentication
-- **Spatie Laravel Passkeys** - WebAuthn/Passkey authentication support
-- **Ziggy ^2.0** - Use Laravel named routes in JavaScript
+## Table of Contents
+
+- [Requirements](#requirements)
+- [Installation](#installation)
+- [Configuration](#configuration)
+- [Usage](#usage)
+ - [User Model Setup](#user-model-setup)
+ - [Blade Components](#blade-components)
+ - [Routes](#routes)
+ - [Middleware](#middleware)
+ - [API Usage](#api-usage)
+- [HTTPS Setup](#https-setup)
+- [Multi-Tenancy](#multi-tenancy)
+- [Testing](#testing)
+- [Credits](#credits)
+- [License](#license)
+
+## Requirements
+
+- PHP 8.2+
+- Laravel 12.0+
+- MySQL 5.7+ / PostgreSQL 9.6+ / SQLite 3.8.8+
## Installation
+### Step 1: Install via Composer
+
+```bash
+composer require bspdx/authkit
+```
+
+### Step 2: Publish Configuration & Assets
+
+```bash
+# Publish configuration
+php artisan vendor:publish --tag=authkit-config
+
+# Publish migrations
+php artisan vendor:publish --tag=authkit-migrations
+
+# Publish Blade views (optional - only if you want to customize)
+php artisan vendor:publish --tag=authkit-views
+
+# Publish example routes
+php artisan vendor:publish --tag=authkit-routes
+
+# Publish database seeders
+php artisan vendor:publish --tag=authkit-seeders
+```
+
+### Step 3: Run Migrations
+
```bash
-composer install
-cp .env.example .env
-php artisan key:generate
php artisan migrate
-npm install
-npm run build
```
-## Development
+This will create tables for:
+
+- Two-factor authentication columns in `users` table
+- Roles and permissions (Spatie)
+- Passkeys (Spatie)
+- Personal access tokens (Sanctum)
+
+### Step 4: Seed Demo Data (Optional)
+
+```bash
+php artisan db:seed --class=AuthKitSeeder
+```
+
+This creates:
+
+- 4 default roles: `super-admin`, `admin`, `editor`, `user`
+- Common permissions for each role
+- 4 demo users (all with password: `password`)
+ - `superadmin@example.com` - Super Admin
+ - `admin@example.com` - Admin
+ - `editor@example.com` - Editor
+ - `user@example.com` - Regular User
+
+### Step 5: Configure Fortify
+
+In your `config/fortify.php`, ensure these features are enabled:
+
+```php
+'features' => [
+ Features::registration(),
+ Features::resetPasswords(),
+ Features::emailVerification(),
+ Features::updateProfileInformation(),
+ Features::updatePasswords(),
+ Features::twoFactorAuthentication([
+ 'confirm' => true,
+ 'confirmPassword' => true,
+ ]),
+],
+```
+
+## Configuration
+
+The package configuration is located at `config/authkit.php`. Key settings:
+
+### Enable/Disable Features
+
+```php
+'features' => [
+ 'registration' => true,
+ 'email_verification' => true,
+ 'two_factor' => true,
+ 'passkeys' => true,
+ 'passkey_2fa' => true,
+ 'api_tokens' => true,
+],
+```
+
+### RBAC Settings
+
+```php
+'rbac' => [
+ 'multi_tenant' => false,
+ 'default_role' => 'user',
+ 'super_admin_role' => 'super-admin',
+],
+```
+
+### Passkey Settings
+
+```php
+'passkey' => [
+ 'rp_name' => env('APP_NAME', 'Laravel'),
+ 'rp_id' => env('PASSKEY_RP_ID', 'localhost'),
+ 'user_verification' => 'preferred',
+ 'allow_multiple' => true,
+ 'required_for_roles' => [
+ // 'admin',
+ ],
+],
+```
+
+### Two-Factor Settings
+
+```php
+'two_factor' => [
+ 'qr_code_size' => 200,
+ 'recovery_codes_count' => 8,
+ 'required_for_roles' => [
+ // 'admin',
+ ],
+],
+```
+
+## Usage
+
+### User Model Setup
+
+Add the `HasAuthKit` trait to your `User` model:
+
+```php
+
+```
+
+#### Register Form
+
+```blade
+
+```
+
+#### Two-Factor Challenge
+
+```blade
+
+```
+
+#### Passkey Registration
+
+```blade
+
+```
+
+#### Passkey Login
+
+```blade
+
+```
+
+### Routes
+
+AuthKit doesn't auto-register routes. Add them manually from the published examples:
+
+**Web Routes** (`routes/authkit-web.php`):
+
+```php
+// Include in your routes/web.php
+require __DIR__.'/authkit-web.php';
+```
+
+**API Routes** (`routes/authkit-api.php`):
+
+```php
+// Include in your routes/api.php
+require __DIR__.'/authkit-api.php';
+```
+
+### Middleware
+
+AuthKit provides three middleware aliases:
+
+#### Role Middleware
+
+```php
+Route::middleware(['auth', 'role:admin'])->group(function () {
+ // Only users with 'admin' role can access
+});
+
+// Multiple roles (OR logic)
+Route::middleware(['auth', 'role:admin,editor'])->group(function () {
+ // Users with 'admin' OR 'editor' role can access
+});
+```
+
+#### Permission Middleware
+
+```php
+Route::middleware(['auth', 'permission:edit-posts'])->group(function () {
+ // Only users with 'edit-posts' permission
+});
+
+// Multiple permissions
+Route::middleware(['auth', 'permission:edit-posts,publish-posts'])->group(function () {
+ // Users with either permission can access
+});
+```
+
+#### 2FA Enforcement Middleware
+
+```php
+Route::middleware(['auth', '2fa'])->group(function () {
+ // Ensures users with required roles have 2FA enabled
+});
+```
+
+### Checking Permissions in Code
+
+```php
+// Check role
+if (auth()->user()->hasRole('admin')) {
+ // User is an admin
+}
+
+// Check permission
+if (auth()->user()->can('edit-posts')) {
+ // User can edit posts
+}
+
+// Check multiple roles
+if (auth()->user()->hasAnyRole(['admin', 'editor'])) {
+ // User has at least one of these roles
+}
+
+// Super admin check
+if (auth()->user()->isSuperAdmin()) {
+ // User is super admin (bypasses all permission checks)
+}
+```
+
+### API Usage
+
+#### Authentication
+
+Use Sanctum for API authentication:
+
+```php
+// Login endpoint (you need to create this)
+Route::post('/login', function (Request $request) {
+ $credentials = $request->validate([
+ 'email' => 'required|email',
+ 'password' => 'required',
+ ]);
+
+ if (!Auth::attempt($credentials)) {
+ return response()->json(['message' => 'Invalid credentials'], 401);
+ }
+
+ $user = $request->user();
+ $token = $user->createToken('api-token')->plainTextToken;
+
+ return response()->json([
+ 'token' => $token,
+ 'user' => $user,
+ ]);
+});
+```
+
+#### API Endpoints
+
+All API routes are protected with `auth:sanctum` middleware. Example requests:
+
+**Get All Roles:**
+
+```bash
+curl -X GET http://localhost/api/roles \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "Accept: application/json"
+```
+
+**Assign Role to User:**
+
+```bash
+curl -X POST http://localhost/api/users/1/roles \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"roles": ["admin"]}'
+```
+
+**Enable 2FA:**
+
+```bash
+curl -X POST http://localhost/api/user/two-factor-authentication \
+ -H "Authorization: Bearer YOUR_TOKEN"
+```
+
+## HTTPS Setup
+
+**Passkeys require HTTPS!** See our detailed guide: [HTTPS Setup for Laravel Sail](docs/https-setup.md)
+
+Quick summary:
+
+1. Install `mkcert`:
+
+ ```bash
+ brew install mkcert # macOS
+ mkcert -install
+ ```
+
+2. Generate certificates:
+
+ ```bash
+ mkdir -p docker/ssl && cd docker/ssl
+ mkcert localhost 127.0.0.1 ::1
+ mv localhost+2.pem cert.pem
+ mv localhost+2-key.pem key.pem
+ ```
+
+3. Update `.env`:
+
+ ```env
+ APP_URL=https://localhost
+ SESSION_SECURE_COOKIE=true
+ ```
+
+4. Configure Nginx/Caddy to use the certificates
+
+See the full guide for detailed instructions.
+
+## Multi-Tenancy
+
+AuthKit is multi-tenancy ready. To enable:
+
+### Step 1: Enable in Configuration
+
+```php
+// config/authkit.php
+'multi_tenancy' => [
+ 'enabled' => true,
+ 'tenant_column' => 'tenant_id',
+ 'auto_scope' => true,
+],
+
+'rbac' => [
+ 'multi_tenant' => true,
+],
+```
+
+### Step 2: Scope Queries
+
+Use Spatie's multitenancy package or implement your own scoping logic.
+
+## Testing
+
+Run the package tests:
+
+```bash
+composer test
+```
+
+Or with PHPUnit directly:
+
+```bash
+./vendor/bin/phpunit
+```
+
+## Customization
+
+### Custom Blade Views
+
+Publish the views and modify as needed:
```bash
-npm run dev
-php artisan serve
+php artisan vendor:publish --tag=authkit-views
+```
+
+Views will be in `resources/views/vendor/authkit/`.
+
+### Custom Styling
+
+All Blade components use CSS custom properties for easy theming:
+
+```css
+:root {
+ --authkit-primary: #4f46e5;
+ --authkit-primary-hover: #4338ca;
+ --authkit-danger: #dc2626;
+ --authkit-text: #1f2937;
+ --authkit-border: #d1d5db;
+ --authkit-bg: #ffffff;
+ --authkit-radius: 0.5rem;
+}
```
+## Security
+
+If you discover any security issues, please email info@bspdx.com instead of using the issue tracker.
+
+## Credits
+
+- [BSPDX](https://github.com/TheBootstrapParadox)
+- Built on top of:
+ - [Laravel Fortify](https://github.com/laravel/fortify)
+ - [Laravel Sanctum](https://github.com/laravel/sanctum)
+ - [Spatie Laravel Permission](https://github.com/spatie/laravel-permission)
+ - [Spatie Laravel Passkeys](https://github.com/spatie/laravel-passkeys)
+
## License
-This project is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
+The MIT License (MIT). Please see [License File](LICENSE) for more information.
+
+---
+
+## Quick Start Example
+
+Here's a complete example to get you started quickly:
+
+### 1. Install Package
+
+```bash
+composer require bspdx/authkit
+php artisan vendor:publish --tag=authkit-config
+php artisan vendor:publish --tag=authkit-migrations
+php artisan migrate
+php artisan db:seed --class=AuthKitSeeder
+```
+
+### 2. Update User Model
+
+```php
+
+
+
+
+ Login
+
+
+
+
+
+
+```
+
+### 4. Add Routes
+
+```php
+// routes/web.php
+Route::get('/login', function () {
+ return view('auth.login');
+})->name('login');
+
+// Include AuthKit routes
+require __DIR__.'/authkit-web.php';
+```
+
+### 5. Test It Out
+
+```bash
+# Start server (with HTTPS for passkeys)
+./vendor/bin/sail up
+
+# Visit https://localhost/login
+# Use demo credentials: admin@example.com / password
+```
+
+That's it! You now have a complete authentication system with 2FA, passkeys, and RBAC.
+
+## Support
+
+- **Documentation:** [Full documentation](https://github.com/TheBootstrapParadox/AuthKit/wiki)
+- **Issues:** [GitHub Issues](https://github.com/TheBootstrapParadox/AuthKit/issues)
+- **Discussions:** [GitHub Discussions](https://github.com/TheBootstrapParadox/AuthKit/discussions)
diff --git a/app/Models/User.php b/app/Models/User.php
index 749c7b7..601037b 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -6,11 +6,13 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
+use BSPDX\AuthKit\Traits\HasAuthKit;
+use Spatie\LaravelPasskeys\Models\Concerns\HasPasskeys;
-class User extends Authenticatable
+class User extends Authenticatable implements HasPasskeys
{
/** @use HasFactory<\Database\Factories\UserFactory> */
- use HasFactory, Notifiable;
+ use HasFactory, Notifiable, HasAuthKit;
/**
* The attributes that are mass assignable.
diff --git a/bootstrap/providers.php b/bootstrap/providers.php
index 38b258d..b0371cf 100644
--- a/bootstrap/providers.php
+++ b/bootstrap/providers.php
@@ -2,4 +2,7 @@
return [
App\Providers\AppServiceProvider::class,
+ BSPDX\AuthKit\AuthKitServiceProvider::class,
+ Spatie\Permission\PermissionServiceProvider::class,
+ Spatie\LaravelPasskeys\PasskeyServiceProvider::class,
];
diff --git a/composer.json b/composer.json
index f868a12..5244233 100644
--- a/composer.json
+++ b/composer.json
@@ -1,19 +1,23 @@
{
"$schema": "https://getcomposer.org/schema.json",
- "name": "laravel/laravel",
- "type": "project",
- "description": "The skeleton application for the Laravel framework.",
- "keywords": ["laravel", "framework"],
+ "name": "bspdx/authkit",
+ "type": "library",
+ "description": "Complete authentication package for Laravel with Fortify, Passkeys, TOTP 2FA, and RBAC",
+ "keywords": ["laravel", "authentication", "fortify", "passkeys", "webauthn", "2fa", "totp", "rbac", "permissions"],
"license": "MIT",
+ "authors": [
+ {
+ "name": "BSPDX",
+ "email": "support@bspdx.com"
+ }
+ ],
"require": {
"php": "^8.2",
- "inertiajs/inertia-laravel": "^2.0",
"laravel/fortify": "^1.31",
"laravel/framework": "^12.0",
"laravel/sanctum": "^4.0",
- "laravel/tinker": "^2.10.1",
"spatie/laravel-passkeys": "^1.5",
- "tightenco/ziggy": "^2.0"
+ "spatie/laravel-permission": "^6.0"
},
"require-dev": {
"fakerphp/faker": "^1.23",
@@ -26,6 +30,7 @@
},
"autoload": {
"psr-4": {
+ "BSPDX\\AuthKit\\": "src/",
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
@@ -33,6 +38,7 @@
},
"autoload-dev": {
"psr-4": {
+ "BSPDX\\AuthKit\\Tests\\": "tests/",
"Tests\\": "tests/"
}
},
@@ -74,6 +80,9 @@
},
"extra": {
"laravel": {
+ "providers": [
+ "BSPDX\\AuthKit\\AuthKitServiceProvider"
+ ],
"dont-discover": []
}
},
diff --git a/composer.lock b/composer.lock
index 4cd07ee..7bfa53c 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "e480f91b7d4d3f3f963d19778df7e089",
+ "content-hash": "57e5c62e966529b35b3481d48d02a617",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -1204,76 +1204,6 @@
],
"time": "2025-08-22T14:27:06+00:00"
},
- {
- "name": "inertiajs/inertia-laravel",
- "version": "v2.0.10",
- "source": {
- "type": "git",
- "url": "https://github.com/inertiajs/inertia-laravel.git",
- "reference": "07da425d58a3a0e3ace9c296e67bd897a6e47009"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/07da425d58a3a0e3ace9c296e67bd897a6e47009",
- "reference": "07da425d58a3a0e3ace9c296e67bd897a6e47009",
- "shasum": ""
- },
- "require": {
- "ext-json": "*",
- "laravel/framework": "^10.0|^11.0|^12.0",
- "php": "^8.1.0",
- "symfony/console": "^6.2|^7.0"
- },
- "require-dev": {
- "guzzlehttp/guzzle": "^7.2",
- "larastan/larastan": "^3.0",
- "laravel/pint": "^1.16",
- "mockery/mockery": "^1.3.3",
- "orchestra/testbench": "^8.0|^9.2|^10.0",
- "phpunit/phpunit": "^10.4|^11.5",
- "roave/security-advisories": "dev-master"
- },
- "suggest": {
- "ext-pcntl": "Recommended when running the Inertia SSR server via the `inertia:start-ssr` artisan command."
- },
- "type": "library",
- "extra": {
- "laravel": {
- "providers": [
- "Inertia\\ServiceProvider"
- ]
- }
- },
- "autoload": {
- "files": [
- "./helpers.php"
- ],
- "psr-4": {
- "Inertia\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jonathan Reinink",
- "email": "jonathan@reinink.ca",
- "homepage": "https://reinink.ca"
- }
- ],
- "description": "The Laravel adapter for Inertia.js.",
- "keywords": [
- "inertia",
- "laravel"
- ],
- "support": {
- "issues": "https://github.com/inertiajs/inertia-laravel/issues",
- "source": "https://github.com/inertiajs/inertia-laravel/tree/v2.0.10"
- },
- "time": "2025-09-28T21:21:36+00:00"
- },
{
"name": "laravel/fortify",
"version": "v1.31.3",
@@ -1742,72 +1672,6 @@
},
"time": "2025-10-09T13:42:30+00:00"
},
- {
- "name": "laravel/tinker",
- "version": "v2.10.1",
- "source": {
- "type": "git",
- "url": "https://github.com/laravel/tinker.git",
- "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/laravel/tinker/zipball/22177cc71807d38f2810c6204d8f7183d88a57d3",
- "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3",
- "shasum": ""
- },
- "require": {
- "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
- "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
- "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
- "php": "^7.2.5|^8.0",
- "psy/psysh": "^0.11.1|^0.12.0",
- "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0"
- },
- "require-dev": {
- "mockery/mockery": "~1.3.3|^1.4.2",
- "phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0"
- },
- "suggest": {
- "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)."
- },
- "type": "library",
- "extra": {
- "laravel": {
- "providers": [
- "Laravel\\Tinker\\TinkerServiceProvider"
- ]
- }
- },
- "autoload": {
- "psr-4": {
- "Laravel\\Tinker\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylor@laravel.com"
- }
- ],
- "description": "Powerful REPL for the Laravel framework.",
- "keywords": [
- "REPL",
- "Tinker",
- "laravel",
- "psysh"
- ],
- "support": {
- "issues": "https://github.com/laravel/tinker/issues",
- "source": "https://github.com/laravel/tinker/tree/v2.10.1"
- },
- "time": "2025-01-27T14:24:01+00:00"
- },
{
"name": "league/commonmark",
"version": "2.7.1",
@@ -2721,64 +2585,6 @@
},
"time": "2025-08-06T21:43:34+00:00"
},
- {
- "name": "nikic/php-parser",
- "version": "v5.6.2",
- "source": {
- "type": "git",
- "url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "3a454ca033b9e06b63282ce19562e892747449bb"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
- "reference": "3a454ca033b9e06b63282ce19562e892747449bb",
- "shasum": ""
- },
- "require": {
- "ext-ctype": "*",
- "ext-json": "*",
- "ext-tokenizer": "*",
- "php": ">=7.4"
- },
- "require-dev": {
- "ircmaxell/php-yacc": "^0.0.7",
- "phpunit/phpunit": "^9.0"
- },
- "bin": [
- "bin/php-parse"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "5.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "PhpParser\\": "lib/PhpParser"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Nikita Popov"
- }
- ],
- "description": "A PHP parser written in PHP",
- "keywords": [
- "parser",
- "php"
- ],
- "support": {
- "issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
- },
- "time": "2025-10-21T19:32:17+00:00"
- },
{
"name": "nunomaduro/termwind",
"version": "v2.3.2",
@@ -3696,85 +3502,6 @@
},
"time": "2021-10-29T13:26:27+00:00"
},
- {
- "name": "psy/psysh",
- "version": "v0.12.14",
- "source": {
- "type": "git",
- "url": "https://github.com/bobthecow/psysh.git",
- "reference": "95c29b3756a23855a30566b745d218bee690bef2"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/bobthecow/psysh/zipball/95c29b3756a23855a30566b745d218bee690bef2",
- "reference": "95c29b3756a23855a30566b745d218bee690bef2",
- "shasum": ""
- },
- "require": {
- "ext-json": "*",
- "ext-tokenizer": "*",
- "nikic/php-parser": "^5.0 || ^4.0",
- "php": "^8.0 || ^7.4",
- "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4",
- "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4"
- },
- "conflict": {
- "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4"
- },
- "require-dev": {
- "bamarni/composer-bin-plugin": "^1.2",
- "composer/class-map-generator": "^1.6"
- },
- "suggest": {
- "composer/class-map-generator": "Improved tab completion performance with better class discovery.",
- "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)",
- "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well."
- },
- "bin": [
- "bin/psysh"
- ],
- "type": "library",
- "extra": {
- "bamarni-bin": {
- "bin-links": false,
- "forward-command": false
- },
- "branch-alias": {
- "dev-main": "0.12.x-dev"
- }
- },
- "autoload": {
- "files": [
- "src/functions.php"
- ],
- "psr-4": {
- "Psy\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Justin Hileman",
- "email": "justin@justinhileman.info"
- }
- ],
- "description": "An interactive shell for modern PHP.",
- "homepage": "https://psysh.org",
- "keywords": [
- "REPL",
- "console",
- "interactive",
- "shell"
- ],
- "support": {
- "issues": "https://github.com/bobthecow/psysh/issues",
- "source": "https://github.com/bobthecow/psysh/tree/v0.12.14"
- },
- "time": "2025-10-27T17:15:31+00:00"
- },
{
"name": "ralouphie/getallheaders",
"version": "3.0.3",
@@ -4112,6 +3839,89 @@
],
"time": "2025-10-28T15:20:09+00:00"
},
+ {
+ "name": "spatie/laravel-permission",
+ "version": "6.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-permission.git",
+ "reference": "9e41247bd512b1e6c229afbc1eb528f7565ae3bb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/9e41247bd512b1e6c229afbc1eb528f7565ae3bb",
+ "reference": "9e41247bd512b1e6c229afbc1eb528f7565ae3bb",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/auth": "^8.12|^9.0|^10.0|^11.0|^12.0",
+ "illuminate/container": "^8.12|^9.0|^10.0|^11.0|^12.0",
+ "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0|^12.0",
+ "illuminate/database": "^8.12|^9.0|^10.0|^11.0|^12.0",
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "laravel/passport": "^11.0|^12.0",
+ "laravel/pint": "^1.0",
+ "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0",
+ "phpunit/phpunit": "^9.4|^10.1|^11.5"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\Permission\\PermissionServiceProvider"
+ ]
+ },
+ "branch-alias": {
+ "dev-main": "6.x-dev",
+ "dev-master": "6.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/helpers.php"
+ ],
+ "psr-4": {
+ "Spatie\\Permission\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Permission handling for Laravel 8.0 and up",
+ "homepage": "https://github.com/spatie/laravel-permission",
+ "keywords": [
+ "acl",
+ "laravel",
+ "permission",
+ "permissions",
+ "rbac",
+ "roles",
+ "security",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/laravel-permission/issues",
+ "source": "https://github.com/spatie/laravel-permission/tree/6.23.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2025-11-03T20:16:13+00:00"
+ },
{
"name": "spomky-labs/cbor-php",
"version": "3.2.2",
@@ -7137,76 +6947,6 @@
],
"time": "2025-09-27T09:00:46+00:00"
},
- {
- "name": "tightenco/ziggy",
- "version": "v2.6.0",
- "source": {
- "type": "git",
- "url": "https://github.com/tighten/ziggy.git",
- "reference": "cccc6035c109daab03a33926b3a8499bedbed01f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/tighten/ziggy/zipball/cccc6035c109daab03a33926b3a8499bedbed01f",
- "reference": "cccc6035c109daab03a33926b3a8499bedbed01f",
- "shasum": ""
- },
- "require": {
- "ext-json": "*",
- "laravel/framework": ">=9.0",
- "php": ">=8.1"
- },
- "require-dev": {
- "laravel/folio": "^1.1",
- "orchestra/testbench": "^7.0 || ^8.0 || ^9.0 || ^10.0",
- "pestphp/pest": "^2.26|^3.0",
- "pestphp/pest-plugin-laravel": "^2.4|^3.0"
- },
- "type": "library",
- "extra": {
- "laravel": {
- "providers": [
- "Tighten\\Ziggy\\ZiggyServiceProvider"
- ]
- }
- },
- "autoload": {
- "psr-4": {
- "Tighten\\Ziggy\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Daniel Coulbourne",
- "email": "daniel@tighten.co"
- },
- {
- "name": "Jake Bathman",
- "email": "jake@tighten.co"
- },
- {
- "name": "Jacob Baker-Kretzmar",
- "email": "jacob@tighten.co"
- }
- ],
- "description": "Use your Laravel named routes in JavaScript.",
- "homepage": "https://github.com/tighten/ziggy",
- "keywords": [
- "Ziggy",
- "javascript",
- "laravel",
- "routes"
- ],
- "support": {
- "issues": "https://github.com/tighten/ziggy/issues",
- "source": "https://github.com/tighten/ziggy/tree/v2.6.0"
- },
- "time": "2025-09-15T00:00:26+00:00"
- },
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "v2.3.0",
@@ -8184,6 +7924,64 @@
],
"time": "2025-08-01T08:46:24+00:00"
},
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.6.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "3a454ca033b9e06b63282ce19562e892747449bb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
+ "reference": "3a454ca033b9e06b63282ce19562e892747449bb",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
+ },
+ "time": "2025-10-21T19:32:17+00:00"
+ },
{
"name": "nunomaduro/collision",
"version": "v8.8.2",
diff --git a/config/authkit.php b/config/authkit.php
new file mode 100644
index 0000000..725e64d
--- /dev/null
+++ b/config/authkit.php
@@ -0,0 +1,233 @@
+ false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Authentication Features
+ |--------------------------------------------------------------------------
+ |
+ | Toggle authentication features on/off. These control what functionality
+ | is available in your application.
+ |
+ */
+
+ 'features' => [
+ // Enable user registration
+ 'registration' => true,
+
+ // Enable email verification
+ 'email_verification' => true,
+
+ // Enable password reset functionality
+ 'password_reset' => true,
+
+ // Enable two-factor authentication (TOTP via Fortify)
+ 'two_factor' => true,
+
+ // Enable passkey authentication
+ 'passkeys' => true,
+
+ // Enable passkey as a second factor
+ 'passkey_2fa' => true,
+
+ // Enable API token authentication (Sanctum)
+ 'api_tokens' => true,
+
+ // Enable profile information updates
+ 'update_profile' => true,
+
+ // Enable password updates
+ 'update_passwords' => true,
+
+ // Enable account deletion
+ 'account_deletion' => false,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Role & Permission Settings
+ |--------------------------------------------------------------------------
+ |
+ | Configuration for the RBAC system powered by Spatie Laravel Permission.
+ |
+ */
+
+ 'rbac' => [
+ // Enable multi-tenancy support for roles/permissions
+ 'multi_tenant' => false,
+
+ // Cache expiration time for roles and permissions (in seconds)
+ 'cache_expiration' => 60 * 60 * 24, // 24 hours
+
+ // Default role assigned to new users (null = no default role)
+ 'default_role' => 'user',
+
+ // Super admin role that bypasses all permission checks
+ 'super_admin_role' => 'super-admin',
+
+ // Default permissions for API access
+ 'api_permissions' => [
+ 'view-roles',
+ 'view-permissions',
+ 'assign-roles',
+ 'assign-permissions',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Passkey Settings
+ |--------------------------------------------------------------------------
+ |
+ | Configuration for WebAuthn/Passkey authentication.
+ |
+ */
+
+ 'passkey' => [
+ // Relying Party name (your application name)
+ 'rp_name' => env('APP_NAME', 'Laravel'),
+
+ // Relying Party ID (your domain)
+ 'rp_id' => env('APP_URL') ? parse_url(env('APP_URL'), PHP_URL_HOST) : 'localhost',
+
+ // Timeout for passkey operations (in milliseconds)
+ 'timeout' => 60000,
+
+ // User verification requirement: 'required', 'preferred', or 'discouraged'
+ 'user_verification' => 'preferred',
+
+ // Attestation conveyance: 'none', 'indirect', or 'direct'
+ 'attestation' => 'none',
+
+ // Allow users to have multiple passkeys
+ 'allow_multiple' => true,
+
+ // Require passkey for specific user roles
+ 'required_for_roles' => [
+ // 'admin',
+ // 'super-admin',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Two-Factor Authentication Settings
+ |--------------------------------------------------------------------------
+ |
+ | Configuration for TOTP-based 2FA (Google Authenticator, Authy, etc.)
+ |
+ */
+
+ 'two_factor' => [
+ // QR code size (in pixels)
+ 'qr_code_size' => 200,
+
+ // Number of recovery codes to generate
+ 'recovery_codes_count' => 8,
+
+ // Window of time to accept TOTP codes (in periods, 1 period = 30 seconds)
+ 'window' => 1,
+
+ // Require 2FA for specific user roles
+ 'required_for_roles' => [
+ // 'admin',
+ // 'super-admin',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Redirect Paths
+ |--------------------------------------------------------------------------
+ |
+ | Define where users should be redirected after various auth actions.
+ |
+ */
+
+ 'redirects' => [
+ 'login' => '/dashboard',
+ 'logout' => '/',
+ 'register' => '/dashboard',
+ 'password_reset' => '/login',
+ 'email_verification' => '/dashboard',
+ 'two_factor_challenge' => '/dashboard',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Rate Limiting
+ |--------------------------------------------------------------------------
+ |
+ | Configure rate limiting for authentication attempts.
+ |
+ */
+
+ 'rate_limiting' => [
+ // Maximum login attempts before lockout
+ 'max_login_attempts' => 5,
+
+ // Lockout duration in minutes
+ 'lockout_duration' => 1,
+
+ // Maximum 2FA attempts
+ 'max_2fa_attempts' => 3,
+
+ // Maximum passkey attempts
+ 'max_passkey_attempts' => 3,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Multi-Tenancy Settings (Optional)
+ |--------------------------------------------------------------------------
+ |
+ | Enable these settings if you're using Spatie's Laravel Multitenancy
+ | or a similar multi-tenant architecture.
+ |
+ */
+
+ 'multi_tenancy' => [
+ // Enable multi-tenant support
+ 'enabled' => false,
+
+ // Tenant column name in database tables
+ 'tenant_column' => 'tenant_id',
+
+ // Automatically scope queries by tenant
+ 'auto_scope' => true,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Settings
+ |--------------------------------------------------------------------------
+ |
+ | Additional session configuration for enhanced security.
+ |
+ */
+
+ 'session' => [
+ // Regenerate session ID after login
+ 'regenerate_on_login' => true,
+
+ // Remember me duration (in minutes)
+ 'remember_duration' => 60 * 24 * 30, // 30 days
+
+ // Require password confirmation for sensitive operations (in minutes)
+ 'password_timeout' => 10800, // 3 hours
+ ],
+
+];
diff --git a/database/migrations/2024_01_01_000001_add_authkit_fields_to_users_table.php b/database/migrations/2024_01_01_000001_add_authkit_fields_to_users_table.php
new file mode 100644
index 0000000..55c3aee
--- /dev/null
+++ b/database/migrations/2024_01_01_000001_add_authkit_fields_to_users_table.php
@@ -0,0 +1,36 @@
+text('two_factor_secret')->nullable()->after('password');
+ $table->text('two_factor_recovery_codes')->nullable()->after('two_factor_secret');
+ $table->timestamp('two_factor_confirmed_at')->nullable()->after('two_factor_recovery_codes');
+
+ $table->unsignedBigInteger('tenant_id')->nullable()->after('id');
+ $table->index('tenant_id');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void {
+ Schema::table('users', function (Blueprint $table) {
+ $table->dropColumn([
+ 'two_factor_secret',
+ 'two_factor_recovery_codes',
+ 'two_factor_confirmed_at',
+ 'tenant_id',
+ ]);
+ });
+ }
+};
diff --git a/database/seeders/AuthKitSeeder.php b/database/seeders/AuthKitSeeder.php
new file mode 100644
index 0000000..d3c4de0
--- /dev/null
+++ b/database/seeders/AuthKitSeeder.php
@@ -0,0 +1,159 @@
+forgetCachedPermissions();
+
+ // Create permissions
+ $permissions = [
+ // User permissions
+ 'view-users',
+ 'create-users',
+ 'edit-users',
+ 'delete-users',
+
+ // Role & Permission management
+ 'view-roles',
+ 'create-roles',
+ 'edit-roles',
+ 'delete-roles',
+ 'assign-roles',
+
+ 'view-permissions',
+ 'create-permissions',
+ 'edit-permissions',
+ 'delete-permissions',
+ 'assign-permissions',
+
+ // Content management examples
+ 'view-posts',
+ 'create-posts',
+ 'edit-posts',
+ 'delete-posts',
+ 'publish-posts',
+
+ // Settings
+ 'view-settings',
+ 'edit-settings',
+ ];
+
+ foreach ($permissions as $permission) {
+ Permission::firstOrCreate(['name' => $permission]);
+ }
+
+ // Create roles and assign permissions
+
+ // Super Admin - has all permissions
+ $superAdmin = Role::firstOrCreate(['name' => 'super-admin']);
+ $superAdmin->givePermissionTo(Permission::all());
+
+ // Admin - most permissions except user/role management
+ $admin = Role::firstOrCreate(['name' => 'admin']);
+ $admin->givePermissionTo([
+ 'view-users',
+ 'create-users',
+ 'edit-users',
+ 'view-roles',
+ 'view-permissions',
+ 'view-posts',
+ 'create-posts',
+ 'edit-posts',
+ 'delete-posts',
+ 'publish-posts',
+ 'view-settings',
+ 'edit-settings',
+ ]);
+
+ // Editor - content management only
+ $editor = Role::firstOrCreate(['name' => 'editor']);
+ $editor->givePermissionTo([
+ 'view-posts',
+ 'create-posts',
+ 'edit-posts',
+ 'publish-posts',
+ ]);
+
+ // User - basic read permissions
+ $user = Role::firstOrCreate(['name' => 'user']);
+ $user->givePermissionTo([
+ 'view-posts',
+ ]);
+
+ // Create demo users
+ $this->createDemoUsers($superAdmin, $admin, $editor, $user);
+
+ $this->command->info('AuthKit roles, permissions, and demo users created successfully!');
+ }
+
+ /**
+ * Create demo users for each role.
+ */
+ protected function createDemoUsers($superAdmin, $admin, $editor, $user): void
+ {
+ // Super Admin user
+ $superAdminUser = User::firstOrCreate(
+ ['email' => 'superadmin@example.com'],
+ [
+ 'name' => 'Super Admin',
+ 'password' => Hash::make('password'),
+ 'email_verified_at' => now(),
+ ]
+ );
+ $superAdminUser->assignRole($superAdmin);
+
+ // Admin user
+ $adminUser = User::firstOrCreate(
+ ['email' => 'admin@example.com'],
+ [
+ 'name' => 'Admin User',
+ 'password' => Hash::make('password'),
+ 'email_verified_at' => now(),
+ ]
+ );
+ $adminUser->assignRole($admin);
+
+ // Editor user
+ $editorUser = User::firstOrCreate(
+ ['email' => 'editor@example.com'],
+ [
+ 'name' => 'Editor User',
+ 'password' => Hash::make('password'),
+ 'email_verified_at' => now(),
+ ]
+ );
+ $editorUser->assignRole($editor);
+
+ // Regular user
+ $regularUser = User::firstOrCreate(
+ ['email' => 'user@example.com'],
+ [
+ 'name' => 'Regular User',
+ 'password' => Hash::make('password'),
+ 'email_verified_at' => now(),
+ ]
+ );
+ $regularUser->assignRole($user);
+
+ $this->command->info('');
+ $this->command->info('Demo users created:');
+ $this->command->info('Super Admin: superadmin@example.com / password');
+ $this->command->info('Admin: admin@example.com / password');
+ $this->command->info('Editor: editor@example.com / password');
+ $this->command->info('User: user@example.com / password');
+ $this->command->info('');
+ }
+}
diff --git a/docs/https-setup.md b/docs/https-setup.md
new file mode 100644
index 0000000..a33fac0
--- /dev/null
+++ b/docs/https-setup.md
@@ -0,0 +1,278 @@
+# HTTPS Setup for Laravel Sail
+
+Passkeys (WebAuthn) **require HTTPS** to function properly. This guide will help you set up HTTPS for your local development environment using Laravel Sail.
+
+## Why HTTPS is Required
+
+WebAuthn/Passkey authentication is only available over HTTPS connections (except for `localhost` which is treated as a secure context). To properly test passkey functionality, you need to set up HTTPS locally.
+
+## Method 1: Using mkcert (Recommended)
+
+[mkcert](https://github.com/FiloSottile/mkcert) is a simple tool for making locally-trusted development certificates.
+
+### Step 1: Install mkcert
+
+**macOS:**
+```bash
+brew install mkcert
+brew install nss # if you use Firefox
+```
+
+**Linux:**
+```bash
+# Ubuntu/Debian
+sudo apt install libnss3-tools
+wget -O mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64
+chmod +x mkcert
+sudo mv mkcert /usr/local/bin/
+```
+
+**Windows:**
+```powershell
+choco install mkcert
+```
+
+### Step 2: Install Local CA
+
+```bash
+mkcert -install
+```
+
+This installs a local Certificate Authority (CA) in your system's trust store.
+
+### Step 3: Generate Certificates
+
+Navigate to your Laravel project root and create an `ssl` directory:
+
+```bash
+mkdir -p docker/ssl
+cd docker/ssl
+```
+
+Generate certificates for your local domain:
+
+```bash
+mkcert localhost 127.0.0.1 ::1
+```
+
+This creates two files:
+- `localhost+2.pem` (certificate)
+- `localhost+2-key.pem` (private key)
+
+Rename them for clarity:
+
+```bash
+mv localhost+2.pem cert.pem
+mv localhost+2-key.pem key.pem
+```
+
+### Step 4: Update docker-compose.yml
+
+Modify your `docker-compose.yml` to use HTTPS:
+
+```yaml
+services:
+ laravel.test:
+ build:
+ context: ./vendor/laravel/sail/runtimes/8.3
+ dockerfile: Dockerfile
+ args:
+ WWWGROUP: '${WWWGROUP}'
+ image: sail-8.3/app
+ extra_hosts:
+ - 'host.docker.internal:host-gateway'
+ ports:
+ - '${APP_PORT:-80}:80'
+ - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
+ - '443:443' # Add HTTPS port
+ environment:
+ WWWUSER: '${WWWUSER}'
+ LARAVEL_SAIL: 1
+ XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
+ XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
+ IGNITION_LOCAL_SITES_PATH: '${PWD}'
+ volumes:
+ - '.:/var/www/html'
+ - './docker/ssl:/etc/nginx/ssl' # Mount SSL certificates
+ networks:
+ - sail
+ depends_on:
+ - mysql
+ - redis
+```
+
+### Step 5: Update Nginx Configuration
+
+Create a new Nginx configuration file at `docker/nginx/default.conf`:
+
+```nginx
+server {
+ listen 80;
+ listen [::]:80;
+ server_name localhost;
+ return 301 https://$server_name$request_uri;
+}
+
+server {
+ listen 443 ssl http2;
+ listen [::]:443 ssl http2;
+ server_name localhost;
+ root /var/www/html/public;
+
+ ssl_certificate /etc/nginx/ssl/cert.pem;
+ ssl_certificate_key /etc/nginx/ssl/key.pem;
+
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_ciphers HIGH:!aNULL:!MD5;
+
+ add_header X-Frame-Options "SAMEORIGIN";
+ add_header X-Content-Type-Options "nosniff";
+
+ index index.php;
+
+ charset utf-8;
+
+ location / {
+ try_files $uri $uri/ /index.php?$query_string;
+ }
+
+ location = /favicon.ico { access_log off; log_not_found off; }
+ location = /robots.txt { access_log off; log_not_found off; }
+
+ error_page 404 /index.php;
+
+ location ~ \.php$ {
+ fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
+ fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
+ include fastcgi_params;
+ }
+
+ location ~ /\.(?!well-known).* {
+ deny all;
+ }
+}
+```
+
+Mount this configuration in your `docker-compose.yml`:
+
+```yaml
+volumes:
+ - '.:/var/www/html'
+ - './docker/ssl:/etc/nginx/ssl'
+ - './docker/nginx/default.conf:/etc/nginx/sites-available/default'
+```
+
+### Step 6: Update .env
+
+Update your `.env` file to use HTTPS:
+
+```env
+APP_URL=https://localhost
+ASSET_URL=https://localhost
+SESSION_SECURE_COOKIE=true
+```
+
+### Step 7: Restart Sail
+
+```bash
+./vendor/bin/sail down
+./vendor/bin/sail up -d
+```
+
+Your application should now be accessible at `https://localhost`
+
+## Method 2: Using Caddy (Alternative)
+
+Laravel Sail can also use Caddy, which automatically handles HTTPS:
+
+### Step 1: Create a Caddyfile
+
+Create `docker/caddy/Caddyfile`:
+
+```
+localhost {
+ reverse_proxy laravel.test:80
+ tls internal
+}
+```
+
+### Step 2: Add Caddy to docker-compose.yml
+
+```yaml
+services:
+ caddy:
+ image: caddy:latest
+ ports:
+ - "443:443"
+ - "443:443/udp"
+ volumes:
+ - ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile
+ - caddy_data:/data
+ - caddy_config:/config
+ networks:
+ - sail
+ depends_on:
+ - laravel.test
+
+volumes:
+ caddy_data:
+ caddy_config:
+```
+
+### Step 3: Trust Caddy's Root Certificate
+
+```bash
+# macOS
+./vendor/bin/sail exec caddy caddy trust
+
+# Linux
+sudo ./vendor/bin/sail exec caddy caddy trust
+```
+
+## Verifying HTTPS Setup
+
+1. Visit `https://localhost` in your browser
+2. Check that the connection is secure (padlock icon)
+3. Test passkey registration to ensure WebAuthn is working
+
+## Troubleshooting
+
+### Certificate Not Trusted
+
+- **Browser:** Clear browser cache and restart
+- **System:** Re-run `mkcert -install`
+- **Firefox:** Firefox uses its own certificate store. Install `libnss3-tools` and re-run `mkcert -install`
+
+### Port Already in Use
+
+```bash
+# Check what's using port 443
+sudo lsof -i :443
+
+# Kill the process or change the port in docker-compose.yml
+```
+
+### WebAuthn Still Not Working
+
+- Ensure `APP_URL` is set to `https://localhost`
+- Check browser console for errors
+- Verify that `window.isSecureContext` returns `true` in browser console
+
+## Production Considerations
+
+For production deployments:
+
+1. Use a real SSL certificate (Let's Encrypt, CloudFlare, etc.)
+2. Set up proper DNS records
+3. Configure `APP_URL` to your production domain
+4. Update `authkit.php` passkey configuration:
+ ```php
+ 'rp_id' => env('PASSKEY_RP_ID', 'yourdomain.com'),
+ ```
+
+## Additional Resources
+
+- [mkcert Documentation](https://github.com/FiloSottile/mkcert)
+- [Laravel Sail Documentation](https://laravel.com/docs/sail)
+- [WebAuthn Guide](https://webauthn.guide/)
+- [Caddy Documentation](https://caddyserver.com/docs/)
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..0f03090
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2376 @@
+{
+ "name": "AuthKit",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "@tailwindcss/vite": "^4.0.0",
+ "axios": "^1.11.0",
+ "concurrently": "^9.0.1",
+ "laravel-vite-plugin": "^2.0.0",
+ "tailwindcss": "^4.0.0",
+ "vite": "^7.0.7"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
+ "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz",
+ "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz",
+ "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz",
+ "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz",
+ "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz",
+ "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz",
+ "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
+ "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz",
+ "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
+ "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz",
+ "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz",
+ "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz",
+ "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
+ "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz",
+ "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz",
+ "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
+ "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz",
+ "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz",
+ "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz",
+ "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz",
+ "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz",
+ "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz",
+ "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.17"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz",
+ "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.17",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.17",
+ "@tailwindcss/oxide-darwin-x64": "4.1.17",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.17",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.17",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.17",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.17",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.17",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.17"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz",
+ "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz",
+ "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz",
+ "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz",
+ "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz",
+ "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz",
+ "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz",
+ "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz",
+ "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz",
+ "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz",
+ "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.6.0",
+ "@emnapi/runtime": "^1.6.0",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.0.7",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz",
+ "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz",
+ "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz",
+ "integrity": "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.1.17",
+ "@tailwindcss/oxide": "4.1.17",
+ "tailwindcss": "4.1.17"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concurrently": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz",
+ "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "4.1.2",
+ "rxjs": "7.8.2",
+ "shell-quote": "1.8.3",
+ "supports-color": "8.1.1",
+ "tree-kill": "1.2.2",
+ "yargs": "17.7.2"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.3",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/laravel-vite-plugin": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz",
+ "integrity": "sha512-zQuvzWfUKQu9oNVi1o0RZAJCwhGsdhx4NEOyrVQwJHaWDseGP9tl7XUPLY2T8Cj6+IrZ6lmyxlR1KC8unf3RLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picocolors": "^1.0.0",
+ "vite-plugin-full-reload": "^1.1.0"
+ },
+ "bin": {
+ "clean-orphaned-assets": "bin/clean.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^7.0.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.53.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz",
+ "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.53.2",
+ "@rollup/rollup-android-arm64": "4.53.2",
+ "@rollup/rollup-darwin-arm64": "4.53.2",
+ "@rollup/rollup-darwin-x64": "4.53.2",
+ "@rollup/rollup-freebsd-arm64": "4.53.2",
+ "@rollup/rollup-freebsd-x64": "4.53.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.2",
+ "@rollup/rollup-linux-arm64-musl": "4.53.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.2",
+ "@rollup/rollup-linux-x64-gnu": "4.53.2",
+ "@rollup/rollup-linux-x64-musl": "4.53.2",
+ "@rollup/rollup-openharmony-arm64": "4.53.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.2",
+ "@rollup/rollup-win32-x64-gnu": "4.53.2",
+ "@rollup/rollup-win32-x64-msvc": "4.53.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/shell-quote": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
+ "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
+ "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/vite": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
+ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-full-reload": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz",
+ "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picocolors": "^1.0.0",
+ "picomatch": "^2.3.1"
+ }
+ },
+ "node_modules/vite-plugin-full-reload/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ }
+ }
+}
diff --git a/resources/views/components/login-form.blade.php b/resources/views/components/login-form.blade.php
new file mode 100644
index 0000000..3aee7ee
--- /dev/null
+++ b/resources/views/components/login-form.blade.php
@@ -0,0 +1,212 @@
+
+
+@if ($showPasskeyOption && config('authkit.features.passkeys'))
+
+@endif
diff --git a/resources/views/components/passkey-login.blade.php b/resources/views/components/passkey-login.blade.php
new file mode 100644
index 0000000..125db4c
--- /dev/null
+++ b/resources/views/components/passkey-login.blade.php
@@ -0,0 +1,106 @@
+
+
+
diff --git a/resources/views/components/passkey-register.blade.php b/resources/views/components/passkey-register.blade.php
new file mode 100644
index 0000000..1973d1a
--- /dev/null
+++ b/resources/views/components/passkey-register.blade.php
@@ -0,0 +1,100 @@
+
+
+
diff --git a/resources/views/components/register-form.blade.php b/resources/views/components/register-form.blade.php
new file mode 100644
index 0000000..3960359
--- /dev/null
+++ b/resources/views/components/register-form.blade.php
@@ -0,0 +1,87 @@
+
diff --git a/resources/views/components/two-factor-challenge.blade.php b/resources/views/components/two-factor-challenge.blade.php
new file mode 100644
index 0000000..926c26d
--- /dev/null
+++ b/resources/views/components/two-factor-challenge.blade.php
@@ -0,0 +1,93 @@
+
+
+@if ($showRecoveryCodeOption)
+
+@endif
diff --git a/routes/api.example.php b/routes/api.example.php
new file mode 100644
index 0000000..1d7d42d
--- /dev/null
+++ b/routes/api.example.php
@@ -0,0 +1,100 @@
+prefix('api')->group(function () {
+ // View roles and permissions
+ Route::get('/roles', [RolePermissionController::class, 'roles'])
+ ->middleware('permission:view-roles')
+ ->name('api.roles.index');
+
+ Route::get('/permissions', [RolePermissionController::class, 'permissions'])
+ ->middleware('permission:view-permissions')
+ ->name('api.permissions.index');
+
+ // Create roles and permissions
+ Route::post('/roles', [RolePermissionController::class, 'createRole'])
+ ->middleware('permission:create-roles')
+ ->name('api.roles.store');
+
+ Route::post('/permissions', [RolePermissionController::class, 'createPermission'])
+ ->middleware('permission:create-permissions')
+ ->name('api.permissions.store');
+
+ // Delete roles and permissions
+ Route::delete('/roles/{role}', [RolePermissionController::class, 'deleteRole'])
+ ->middleware('permission:delete-roles')
+ ->name('api.roles.destroy');
+
+ Route::delete('/permissions/{permission}', [RolePermissionController::class, 'deletePermission'])
+ ->middleware('permission:delete-permissions')
+ ->name('api.permissions.destroy');
+
+ // Assign roles and permissions to users
+ Route::post('/users/{user}/roles', [RolePermissionController::class, 'assignRoles'])
+ ->middleware('permission:assign-roles')
+ ->name('api.users.roles.assign');
+
+ Route::post('/users/{user}/permissions', [RolePermissionController::class, 'assignPermissions'])
+ ->middleware('permission:assign-permissions')
+ ->name('api.users.permissions.assign');
+
+ // Get user roles and permissions
+ Route::get('/users/{user}/roles-permissions', [RolePermissionController::class, 'userRolesPermissions'])
+ ->middleware('permission:view-users')
+ ->name('api.users.roles-permissions');
+
+ // Assign permissions to roles
+ Route::post('/roles/{role}/permissions', [RolePermissionController::class, 'assignPermissionsToRole'])
+ ->middleware('permission:assign-permissions')
+ ->name('api.roles.permissions.assign');
+});
+
+// Two-Factor Authentication API
+Route::middleware(['auth:sanctum'])->prefix('api/user')->group(function () {
+ Route::post('/two-factor-authentication', [TwoFactorAuthController::class, 'store'])
+ ->name('api.two-factor.store');
+
+ Route::post('/confirmed-two-factor-authentication', [TwoFactorAuthController::class, 'confirm'])
+ ->name('api.two-factor.confirm');
+
+ Route::delete('/two-factor-authentication', [TwoFactorAuthController::class, 'destroy'])
+ ->name('api.two-factor.destroy');
+
+ Route::get('/two-factor-recovery-codes', [TwoFactorAuthController::class, 'recoveryCodes'])
+ ->name('api.two-factor.recovery-codes');
+
+ Route::post('/two-factor-recovery-codes', [TwoFactorAuthController::class, 'regenerateRecoveryCodes'])
+ ->name('api.two-factor.recovery-codes.regenerate');
+});
+
+// Passkey API
+Route::middleware(['auth:sanctum'])->prefix('api/user')->group(function () {
+ Route::get('/passkeys', [PasskeyAuthController::class, 'index'])
+ ->name('api.passkeys.index');
+
+ Route::post('/passkeys/options', [PasskeyAuthController::class, 'registerOptions'])
+ ->name('api.passkeys.register.options');
+
+ Route::post('/passkeys', [PasskeyAuthController::class, 'store'])
+ ->name('api.passkeys.store');
+
+ Route::delete('/passkeys/{passkey}', [PasskeyAuthController::class, 'destroy'])
+ ->name('api.passkeys.destroy');
+});
diff --git a/routes/web.example.php b/routes/web.example.php
new file mode 100644
index 0000000..44fe892
--- /dev/null
+++ b/routes/web.example.php
@@ -0,0 +1,94 @@
+group(function () {
+ // Enable 2FA
+ Route::get('/user/two-factor-authentication', [TwoFactorAuthController::class, 'create'])
+ ->name('two-factor.enable');
+
+ Route::post('/user/two-factor-authentication', [TwoFactorAuthController::class, 'store'])
+ ->name('two-factor.store');
+
+ // Confirm 2FA
+ Route::post('/user/confirmed-two-factor-authentication', [TwoFactorAuthController::class, 'confirm'])
+ ->name('two-factor.confirm');
+
+ // Disable 2FA
+ Route::delete('/user/two-factor-authentication', [TwoFactorAuthController::class, 'destroy'])
+ ->name('two-factor.destroy');
+
+ // Recovery codes
+ Route::get('/user/two-factor-recovery-codes', [TwoFactorAuthController::class, 'recoveryCodes'])
+ ->name('two-factor.recovery-codes');
+
+ Route::post('/user/two-factor-recovery-codes', [TwoFactorAuthController::class, 'regenerateRecoveryCodes'])
+ ->name('two-factor.recovery-codes.regenerate');
+});
+
+// Passkey Routes
+Route::middleware(['web'])->group(function () {
+ // Passkey login (guest)
+ Route::get('/passkey/login', [PasskeyAuthController::class, 'loginView'])
+ ->name('passkeys.login')
+ ->middleware('guest');
+
+ Route::post('/passkey/login/options', [PasskeyAuthController::class, 'loginOptions'])
+ ->name('passkeys.login.options')
+ ->middleware('guest');
+
+ Route::post('/passkey/authenticate', [PasskeyAuthController::class, 'authenticate'])
+ ->name('passkeys.authenticate')
+ ->middleware('guest');
+
+ // Passkey management (authenticated)
+ Route::middleware(['auth'])->group(function () {
+ Route::get('/user/passkeys', [PasskeyAuthController::class, 'registerView'])
+ ->name('passkeys.register.view');
+
+ Route::post('/user/passkeys/options', [PasskeyAuthController::class, 'registerOptions'])
+ ->name('passkeys.register.options');
+
+ Route::post('/user/passkeys', [PasskeyAuthController::class, 'store'])
+ ->name('passkeys.register');
+
+ Route::delete('/user/passkeys/{passkey}', [PasskeyAuthController::class, 'destroy'])
+ ->name('passkeys.destroy');
+ });
+});
+
+// Example protected routes using AuthKit middleware
+Route::middleware(['web', 'auth', '2fa'])->group(function () {
+ // Routes that require 2FA to be enabled (if required by role)
+ Route::get('/dashboard', function () {
+ return view('dashboard');
+ })->name('dashboard');
+});
+
+// Example RBAC protected routes
+Route::middleware(['web', 'auth', 'role:admin'])->group(function () {
+ Route::get('/admin', function () {
+ return 'Admin Dashboard';
+ });
+});
+
+Route::middleware(['web', 'auth', 'permission:edit-posts'])->group(function () {
+ Route::get('/posts/edit', function () {
+ return 'Edit Posts';
+ });
+});
diff --git a/src/AuthKitServiceProvider.php b/src/AuthKitServiceProvider.php
new file mode 100644
index 0000000..4be0a4b
--- /dev/null
+++ b/src/AuthKitServiceProvider.php
@@ -0,0 +1,82 @@
+mergeConfigFrom(
+ __DIR__.'/../config/authkit.php',
+ 'authkit'
+ );
+ }
+
+ /**
+ * Bootstrap any application services.
+ */
+ public function boot(): void
+ {
+ // Load package routes if enabled
+ if (config('authkit.load_routes', false)) {
+ $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
+ $this->loadRoutesFrom(__DIR__.'/../routes/api.php');
+ }
+
+ // Load migrations
+ $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
+
+ // Load views
+ $this->loadViewsFrom(__DIR__.'/../resources/views', 'authkit');
+
+ // Publish configuration
+ $this->publishes([
+ __DIR__.'/../config/authkit.php' => config_path('authkit.php'),
+ ], 'authkit-config');
+
+ // Publish migrations
+ $this->publishes([
+ __DIR__.'/../database/migrations' => database_path('migrations'),
+ ], 'authkit-migrations');
+
+ // Publish views
+ $this->publishes([
+ __DIR__.'/../resources/views' => resource_path('views/vendor/authkit'),
+ ], 'authkit-views');
+
+ // Publish seeders
+ $this->publishes([
+ __DIR__.'/../database/seeders' => database_path('seeders'),
+ ], 'authkit-seeders');
+
+ // Publish example routes
+ $this->publishes([
+ __DIR__.'/../routes/web.example.php' => base_path('routes/authkit-web.php'),
+ __DIR__.'/../routes/api.example.php' => base_path('routes/authkit-api.php'),
+ ], 'authkit-routes');
+
+ // Register middleware aliases
+ $router = $this->app['router'];
+ $router->aliasMiddleware('role', EnsureHasRole::class);
+ $router->aliasMiddleware('permission', EnsureHasPermission::class);
+ $router->aliasMiddleware('2fa', EnsureTwoFactorEnabled::class);
+
+ // Register Blade components
+ $this->loadViewComponentsAs('authkit', [
+ \BSPDX\AuthKit\View\Components\LoginForm::class,
+ \BSPDX\AuthKit\View\Components\RegisterForm::class,
+ \BSPDX\AuthKit\View\Components\TwoFactorChallenge::class,
+ \BSPDX\AuthKit\View\Components\PasskeyRegister::class,
+ \BSPDX\AuthKit\View\Components\PasskeyLogin::class,
+ ]);
+ }
+}
diff --git a/src/Http/Controllers/PasskeyAuthController.php b/src/Http/Controllers/PasskeyAuthController.php
new file mode 100644
index 0000000..38aeeb3
--- /dev/null
+++ b/src/Http/Controllers/PasskeyAuthController.php
@@ -0,0 +1,153 @@
+ $request->user(),
+ 'passkeys' => $request->user()->passkeys,
+ ]);
+ }
+
+ /**
+ * Generate passkey registration options.
+ */
+ public function registerOptions(Request $request): JsonResponse
+ {
+ $options = Passkey::registerOptions($request->user());
+
+ return response()->json($options);
+ }
+
+ /**
+ * Store a new passkey for the authenticated user.
+ */
+ public function store(Request $request): RedirectResponse|JsonResponse
+ {
+ $validated = $request->validate([
+ 'name' => ['required', 'string', 'max:255'],
+ 'credential' => ['required', 'array'],
+ ]);
+
+ try {
+ Passkey::register($request->user(), $validated['credential'], $validated['name']);
+
+ if ($request->wantsJson()) {
+ return response()->json([
+ 'message' => 'Passkey registered successfully.',
+ ]);
+ }
+
+ return redirect()->back()->with('status', 'passkey-registered');
+ } catch (\Exception $e) {
+ if ($request->wantsJson()) {
+ return response()->json([
+ 'message' => 'Failed to register passkey: ' . $e->getMessage(),
+ ], 422);
+ }
+
+ return redirect()->back()->withErrors([
+ 'passkey' => 'Failed to register passkey. Please try again.',
+ ]);
+ }
+ }
+
+ /**
+ * Delete a passkey.
+ */
+ public function destroy(Request $request, string $passkeyId): RedirectResponse|JsonResponse
+ {
+ $passkey = $request->user()->passkeys()->findOrFail($passkeyId);
+ $passkey->delete();
+
+ if ($request->wantsJson()) {
+ return response()->json([
+ 'message' => 'Passkey deleted successfully.',
+ ]);
+ }
+
+ return redirect()->back()->with('status', 'passkey-deleted');
+ }
+
+ /**
+ * Display the passkey login view.
+ */
+ public function loginView(): View
+ {
+ return view('authkit::passkeys.login');
+ }
+
+ /**
+ * Generate passkey authentication options.
+ */
+ public function loginOptions(Request $request): JsonResponse
+ {
+ $options = Passkey::authenticationOptions();
+
+ return response()->json($options);
+ }
+
+ /**
+ * Authenticate a user using a passkey.
+ */
+ public function authenticate(Request $request): RedirectResponse|JsonResponse
+ {
+ $validated = $request->validate([
+ 'credential' => ['required', 'array'],
+ ]);
+
+ try {
+ $user = Passkey::authenticate($validated['credential']);
+
+ auth()->login($user, remember: true);
+
+ if ($request->wantsJson()) {
+ return response()->json([
+ 'message' => 'Authentication successful.',
+ 'redirect' => config('authkit.redirects.login', '/dashboard'),
+ ]);
+ }
+
+ return redirect()->intended(config('authkit.redirects.login', '/dashboard'));
+ } catch (\Exception $e) {
+ if ($request->wantsJson()) {
+ return response()->json([
+ 'message' => 'Authentication failed: ' . $e->getMessage(),
+ ], 401);
+ }
+
+ return redirect()->back()->withErrors([
+ 'passkey' => 'Authentication failed. Please try again.',
+ ]);
+ }
+ }
+
+ /**
+ * Get all passkeys for the authenticated user.
+ */
+ public function index(Request $request): JsonResponse
+ {
+ $passkeys = $request->user()->passkeys()->get()->map(function ($passkey) {
+ return [
+ 'id' => $passkey->id,
+ 'name' => $passkey->name,
+ 'created_at' => $passkey->created_at->toDateTimeString(),
+ 'last_used_at' => $passkey->last_used_at?->toDateTimeString(),
+ ];
+ });
+
+ return response()->json(['passkeys' => $passkeys]);
+ }
+}
diff --git a/src/Http/Controllers/RolePermissionController.php b/src/Http/Controllers/RolePermissionController.php
new file mode 100644
index 0000000..98ef74a
--- /dev/null
+++ b/src/Http/Controllers/RolePermissionController.php
@@ -0,0 +1,214 @@
+get()->map(function ($role) {
+ return [
+ 'id' => $role->id,
+ 'name' => $role->name,
+ 'guard_name' => $role->guard_name,
+ 'permissions' => $role->permissions->pluck('name'),
+ 'users_count' => $role->users()->count(),
+ 'created_at' => $role->created_at->toDateTimeString(),
+ ];
+ });
+
+ return response()->json(['roles' => $roles]);
+ }
+
+ /**
+ * Get all permissions.
+ */
+ public function permissions(): JsonResponse
+ {
+ $permissions = Permission::with('roles')->get()->map(function ($permission) {
+ return [
+ 'id' => $permission->id,
+ 'name' => $permission->name,
+ 'guard_name' => $permission->guard_name,
+ 'roles' => $permission->roles->pluck('name'),
+ 'created_at' => $permission->created_at->toDateTimeString(),
+ ];
+ });
+
+ return response()->json(['permissions' => $permissions]);
+ }
+
+ /**
+ * Create a new role.
+ */
+ public function createRole(Request $request): JsonResponse
+ {
+ $validated = $request->validate([
+ 'name' => ['required', 'string', 'unique:roles,name'],
+ 'guard_name' => ['nullable', 'string'],
+ 'permissions' => ['nullable', 'array'],
+ 'permissions.*' => ['string', 'exists:permissions,name'],
+ ]);
+
+ $role = Role::create([
+ 'name' => $validated['name'],
+ 'guard_name' => $validated['guard_name'] ?? 'web',
+ ]);
+
+ if (isset($validated['permissions'])) {
+ $role->syncPermissions($validated['permissions']);
+ }
+
+ return response()->json([
+ 'message' => 'Role created successfully.',
+ 'role' => $role->load('permissions'),
+ ], 201);
+ }
+
+ /**
+ * Create a new permission.
+ */
+ public function createPermission(Request $request): JsonResponse
+ {
+ $validated = $request->validate([
+ 'name' => ['required', 'string', 'unique:permissions,name'],
+ 'guard_name' => ['nullable', 'string'],
+ ]);
+
+ $permission = Permission::create([
+ 'name' => $validated['name'],
+ 'guard_name' => $validated['guard_name'] ?? 'web',
+ ]);
+
+ return response()->json([
+ 'message' => 'Permission created successfully.',
+ 'permission' => $permission,
+ ], 201);
+ }
+
+ /**
+ * Assign roles to a user.
+ */
+ public function assignRoles(Request $request, User $user): JsonResponse
+ {
+ $validated = $request->validate([
+ 'roles' => ['required', 'array'],
+ 'roles.*' => ['string', 'exists:roles,name'],
+ ]);
+
+ $user->syncRoles($validated['roles']);
+
+ return response()->json([
+ 'message' => 'Roles assigned successfully.',
+ 'user' => [
+ 'id' => $user->id,
+ 'name' => $user->name,
+ 'roles' => $user->roles->pluck('name'),
+ ],
+ ]);
+ }
+
+ /**
+ * Assign permissions to a user.
+ */
+ public function assignPermissions(Request $request, User $user): JsonResponse
+ {
+ $validated = $request->validate([
+ 'permissions' => ['required', 'array'],
+ 'permissions.*' => ['string', 'exists:permissions,name'],
+ ]);
+
+ $user->syncPermissions($validated['permissions']);
+
+ return response()->json([
+ 'message' => 'Permissions assigned successfully.',
+ 'user' => [
+ 'id' => $user->id,
+ 'name' => $user->name,
+ 'permissions' => $user->getAllPermissions()->pluck('name'),
+ ],
+ ]);
+ }
+
+ /**
+ * Assign permissions to a role.
+ */
+ public function assignPermissionsToRole(Request $request, Role $role): JsonResponse
+ {
+ $validated = $request->validate([
+ 'permissions' => ['required', 'array'],
+ 'permissions.*' => ['string', 'exists:permissions,name'],
+ ]);
+
+ $role->syncPermissions($validated['permissions']);
+
+ return response()->json([
+ 'message' => 'Permissions assigned to role successfully.',
+ 'role' => $role->load('permissions'),
+ ]);
+ }
+
+ /**
+ * Get user's roles and permissions.
+ */
+ public function userRolesPermissions(User $user): JsonResponse
+ {
+ return response()->json([
+ 'user' => [
+ 'id' => $user->id,
+ 'name' => $user->name,
+ 'email' => $user->email,
+ 'roles' => $user->roles->map(fn($role) => [
+ 'id' => $role->id,
+ 'name' => $role->name,
+ ]),
+ 'permissions' => $user->getAllPermissions()->map(fn($permission) => [
+ 'id' => $permission->id,
+ 'name' => $permission->name,
+ ]),
+ 'direct_permissions' => $user->permissions->pluck('name'),
+ ],
+ ]);
+ }
+
+ /**
+ * Remove a role.
+ */
+ public function deleteRole(Role $role): JsonResponse
+ {
+ $superAdminRole = config('authkit.rbac.super_admin_role', 'super-admin');
+
+ if ($role->name === $superAdminRole) {
+ return response()->json([
+ 'message' => 'Cannot delete the super admin role.',
+ ], 403);
+ }
+
+ $role->delete();
+
+ return response()->json([
+ 'message' => 'Role deleted successfully.',
+ ]);
+ }
+
+ /**
+ * Remove a permission.
+ */
+ public function deletePermission(Permission $permission): JsonResponse
+ {
+ $permission->delete();
+
+ return response()->json([
+ 'message' => 'Permission deleted successfully.',
+ ]);
+ }
+}
diff --git a/src/Http/Controllers/TwoFactorAuthController.php b/src/Http/Controllers/TwoFactorAuthController.php
new file mode 100644
index 0000000..e9145d9
--- /dev/null
+++ b/src/Http/Controllers/TwoFactorAuthController.php
@@ -0,0 +1,154 @@
+ $request->user(),
+ ]);
+ }
+
+ /**
+ * Enable two-factor authentication for the user.
+ */
+ public function store(Request $request): RedirectResponse|JsonResponse
+ {
+ $user = $request->user();
+
+ // Generate 2FA secret
+ $user->forceFill([
+ 'two_factor_secret' => encrypt(app('pragmarx.google2fa')->generateSecretKey()),
+ 'two_factor_recovery_codes' => encrypt(json_encode($this->generateRecoveryCodes())),
+ ])->save();
+
+ if ($request->wantsJson()) {
+ return response()->json([
+ 'message' => 'Two-factor authentication has been enabled.',
+ 'qr_code' => $user->twoFactorQrCodeSvg(),
+ 'recovery_codes' => $this->getRecoveryCodes($user),
+ ]);
+ }
+
+ return redirect()->back()->with([
+ 'status' => 'two-factor-enabled',
+ 'recovery_codes' => $this->getRecoveryCodes($user),
+ ]);
+ }
+
+ /**
+ * Confirm two-factor authentication for the user.
+ */
+ public function confirm(Request $request): RedirectResponse|JsonResponse
+ {
+ $request->validate([
+ 'code' => ['required', 'string'],
+ ]);
+
+ $user = $request->user();
+
+ if (!app('pragmarx.google2fa')->verifyKey(
+ decrypt($user->two_factor_secret),
+ $request->code
+ )) {
+ if ($request->wantsJson()) {
+ return response()->json(['message' => 'The provided code was invalid.'], 422);
+ }
+
+ return redirect()->back()->withErrors([
+ 'code' => 'The provided code was invalid.',
+ ]);
+ }
+
+ $user->forceFill([
+ 'two_factor_confirmed_at' => now(),
+ ])->save();
+
+ if ($request->wantsJson()) {
+ return response()->json(['message' => 'Two-factor authentication has been confirmed.']);
+ }
+
+ return redirect()->back()->with('status', 'two-factor-confirmed');
+ }
+
+ /**
+ * Disable two-factor authentication for the user.
+ */
+ public function destroy(Request $request): RedirectResponse|JsonResponse
+ {
+ $request->user()->forceFill([
+ 'two_factor_secret' => null,
+ 'two_factor_recovery_codes' => null,
+ 'two_factor_confirmed_at' => null,
+ ])->save();
+
+ if ($request->wantsJson()) {
+ return response()->json(['message' => 'Two-factor authentication has been disabled.']);
+ }
+
+ return redirect()->back()->with('status', 'two-factor-disabled');
+ }
+
+ /**
+ * Display the user's recovery codes.
+ */
+ public function recoveryCodes(Request $request): JsonResponse
+ {
+ return response()->json([
+ 'recovery_codes' => $this->getRecoveryCodes($request->user()),
+ ]);
+ }
+
+ /**
+ * Regenerate the user's recovery codes.
+ */
+ public function regenerateRecoveryCodes(Request $request): RedirectResponse|JsonResponse
+ {
+ $request->user()->forceFill([
+ 'two_factor_recovery_codes' => encrypt(json_encode($this->generateRecoveryCodes())),
+ ])->save();
+
+ $codes = $this->getRecoveryCodes($request->user());
+
+ if ($request->wantsJson()) {
+ return response()->json(['recovery_codes' => $codes]);
+ }
+
+ return redirect()->back()->with([
+ 'status' => 'recovery-codes-regenerated',
+ 'recovery_codes' => $codes,
+ ]);
+ }
+
+ /**
+ * Generate new recovery codes.
+ */
+ protected function generateRecoveryCodes(): array
+ {
+ $count = config('authkit.two_factor.recovery_codes_count', 8);
+
+ return Collection::times($count, function () {
+ return Str::random(10) . '-' . Str::random(10);
+ })->toArray();
+ }
+
+ /**
+ * Get the user's recovery codes.
+ */
+ protected function getRecoveryCodes($user): array
+ {
+ return json_decode(decrypt($user->two_factor_recovery_codes), true);
+ }
+}
diff --git a/src/Http/Middleware/EnsureHasPermission.php b/src/Http/Middleware/EnsureHasPermission.php
new file mode 100644
index 0000000..acfd962
--- /dev/null
+++ b/src/Http/Middleware/EnsureHasPermission.php
@@ -0,0 +1,34 @@
+user()) {
+ return redirect()->route('login');
+ }
+
+ // Super admins bypass all permission checks
+ if (method_exists($request->user(), 'isSuperAdmin') && $request->user()->isSuperAdmin()) {
+ return $next($request);
+ }
+
+ if (!$request->user()->hasAnyPermission($permissions)) {
+ abort(403, 'You do not have the required permission to access this resource.');
+ }
+
+ return $next($request);
+ }
+}
diff --git a/src/Http/Middleware/EnsureHasRole.php b/src/Http/Middleware/EnsureHasRole.php
new file mode 100644
index 0000000..96ffcbe
--- /dev/null
+++ b/src/Http/Middleware/EnsureHasRole.php
@@ -0,0 +1,34 @@
+user()) {
+ return redirect()->route('login');
+ }
+
+ // Super admins bypass all role checks
+ if (method_exists($request->user(), 'isSuperAdmin') && $request->user()->isSuperAdmin()) {
+ return $next($request);
+ }
+
+ if (!$request->user()->hasAnyRole($roles)) {
+ abort(403, 'You do not have the required role to access this resource.');
+ }
+
+ return $next($request);
+ }
+}
diff --git a/src/Http/Middleware/EnsureTwoFactorEnabled.php b/src/Http/Middleware/EnsureTwoFactorEnabled.php
new file mode 100644
index 0000000..1e292e4
--- /dev/null
+++ b/src/Http/Middleware/EnsureTwoFactorEnabled.php
@@ -0,0 +1,33 @@
+user()) {
+ return redirect()->route('login');
+ }
+
+ // Check if 2FA is required for the user's role
+ if (method_exists($request->user(), 'requires2FA') && $request->user()->requires2FA()) {
+ // Check if 2FA is enabled
+ if (!method_exists($request->user(), 'hasTwoFactorEnabled') || !$request->user()->hasTwoFactorEnabled()) {
+ return redirect()->route('two-factor.enable')
+ ->with('warning', 'Two-factor authentication is required for your account. Please enable it to continue.');
+ }
+ }
+
+ return $next($request);
+ }
+}
diff --git a/src/Traits/HasAuthKit.php b/src/Traits/HasAuthKit.php
new file mode 100644
index 0000000..6c00649
--- /dev/null
+++ b/src/Traits/HasAuthKit.php
@@ -0,0 +1,91 @@
+two_factor_secret) &&
+ !is_null($this->two_factor_confirmed_at);
+ }
+
+ /**
+ * Determine if the user has registered any passkeys.
+ */
+ public function hasPasskeysRegistered(): bool
+ {
+ return $this->passkeys()->exists();
+ }
+
+ /**
+ * Determine if 2FA is required for this user based on their roles.
+ */
+ public function requires2FA(): bool
+ {
+ $requiredRoles = config('authkit.two_factor.required_for_roles', []);
+
+ if (empty($requiredRoles)) {
+ return false;
+ }
+
+ return $this->hasAnyRole($requiredRoles);
+ }
+
+ /**
+ * Determine if passkeys are required for this user based on their roles.
+ */
+ public function requiresPasskey(): bool
+ {
+ $requiredRoles = config('authkit.passkey.required_for_roles', []);
+
+ if (empty($requiredRoles)) {
+ return false;
+ }
+
+ return $this->hasAnyRole($requiredRoles);
+ }
+
+ /**
+ * Get the user's authentication methods.
+ */
+ public function getAuthenticationMethods(): array
+ {
+ return [
+ 'password' => true,
+ 'two_factor' => $this->hasTwoFactorEnabled(),
+ 'passkey' => $this->hasPasskeysRegistered(),
+ ];
+ }
+
+ /**
+ * Determine if the user is a super admin.
+ */
+ public function isSuperAdmin(): bool
+ {
+ $superAdminRole = config('authkit.rbac.super_admin_role', 'super-admin');
+
+ return $this->hasRole($superAdminRole);
+ }
+
+ /**
+ * Check if user can bypass permission checks (super admin).
+ */
+ public function canBypassPermissions(): bool
+ {
+ return $this->isSuperAdmin();
+ }
+}
diff --git a/src/View/Components/LoginForm.php b/src/View/Components/LoginForm.php
new file mode 100644
index 0000000..e6082dc
--- /dev/null
+++ b/src/View/Components/LoginForm.php
@@ -0,0 +1,24 @@
+action = $action ?? route('login');
+ }
+
+ public function render(): View
+ {
+ return view('authkit::components.login-form');
+ }
+}
diff --git a/src/View/Components/PasskeyLogin.php b/src/View/Components/PasskeyLogin.php
new file mode 100644
index 0000000..f8e7ef1
--- /dev/null
+++ b/src/View/Components/PasskeyLogin.php
@@ -0,0 +1,22 @@
+loginOptionsUrl = $loginOptionsUrl ?? route('passkeys.login.options');
+ $this->authenticateUrl = $authenticateUrl ?? route('passkeys.authenticate');
+ }
+
+ public function render(): View
+ {
+ return view('authkit::components.passkey-login');
+ }
+}
diff --git a/src/View/Components/PasskeyRegister.php b/src/View/Components/PasskeyRegister.php
new file mode 100644
index 0000000..860e347
--- /dev/null
+++ b/src/View/Components/PasskeyRegister.php
@@ -0,0 +1,22 @@
+registerOptionsUrl = $registerOptionsUrl ?? route('passkeys.register.options');
+ $this->registerUrl = $registerUrl ?? route('passkeys.register');
+ }
+
+ public function render(): View
+ {
+ return view('authkit::components.passkey-register');
+ }
+}
diff --git a/src/View/Components/RegisterForm.php b/src/View/Components/RegisterForm.php
new file mode 100644
index 0000000..d8bd82a
--- /dev/null
+++ b/src/View/Components/RegisterForm.php
@@ -0,0 +1,22 @@
+action = $action ?? route('register');
+ }
+
+ public function render(): View
+ {
+ return view('authkit::components.register-form');
+ }
+}
diff --git a/src/View/Components/TwoFactorChallenge.php b/src/View/Components/TwoFactorChallenge.php
new file mode 100644
index 0000000..77a24a4
--- /dev/null
+++ b/src/View/Components/TwoFactorChallenge.php
@@ -0,0 +1,21 @@
+action = $action ?? route('two-factor.login');
+ }
+
+ public function render(): View
+ {
+ return view('authkit::components.two-factor-challenge');
+ }
+}
diff --git a/tests/Feature/AuthKitTest.php b/tests/Feature/AuthKitTest.php
new file mode 100644
index 0000000..9e51bbc
--- /dev/null
+++ b/tests/Feature/AuthKitTest.php
@@ -0,0 +1,174 @@
+forgetCachedPermissions();
+
+ // Register test routes for middleware testing
+ \Illuminate\Support\Facades\Route::middleware(['web', 'auth', 'role:admin'])
+ ->get('/test-admin-route', function () {
+ return response()->json(['message' => 'Success']);
+ });
+ }
+
+ /** @test */
+ public function user_can_be_assigned_a_role()
+ {
+ $user = User::factory()->create();
+ $role = Role::create(['name' => 'admin']);
+
+ $user->assignRole('admin');
+
+ $this->assertTrue($user->hasRole('admin'));
+ }
+
+ /** @test */
+ public function user_can_be_assigned_a_permission()
+ {
+ $user = User::factory()->create();
+ $permission = Permission::create(['name' => 'edit-posts']);
+
+ $user->givePermissionTo('edit-posts');
+
+ $this->assertTrue($user->can('edit-posts'));
+ }
+
+ /** @test */
+ public function role_can_have_permissions()
+ {
+ $role = Role::create(['name' => 'editor']);
+ $permission = Permission::create(['name' => 'publish-posts']);
+
+ $role->givePermissionTo($permission);
+
+ $this->assertTrue($role->hasPermissionTo($permission));
+ }
+
+ /** @test */
+ public function user_inherits_permissions_from_role()
+ {
+ $user = User::factory()->create();
+ $role = Role::create(['name' => 'editor']);
+ $permission = Permission::create(['name' => 'publish-posts']);
+
+ $role->givePermissionTo($permission);
+ $user->assignRole($role);
+
+ $this->assertTrue($user->can('publish-posts'));
+ }
+
+ /** @test */
+ public function super_admin_can_be_identified()
+ {
+ $user = User::factory()->create();
+ $superAdminRole = Role::create(['name' => 'super-admin']);
+
+ $user->assignRole($superAdminRole);
+
+ $this->assertTrue($user->isSuperAdmin());
+ }
+
+ /** @test */
+ public function user_can_check_two_factor_status()
+ {
+ $user = User::factory()->create();
+
+ $this->assertFalse($user->hasTwoFactorEnabled());
+
+ // Simulate enabling 2FA
+ $user->forceFill([
+ 'two_factor_secret' => encrypt('test-secret'),
+ 'two_factor_confirmed_at' => now(),
+ ])->save();
+
+ $this->assertTrue($user->hasTwoFactorEnabled());
+ }
+
+ /** @test */
+ public function user_can_check_if_two_factor_is_required_for_role()
+ {
+ config(['authkit.two_factor.required_for_roles' => ['admin']]);
+
+ $user = User::factory()->create();
+ $adminRole = Role::create(['name' => 'admin']);
+
+ $this->assertFalse($user->requires2FA());
+
+ $user->assignRole($adminRole);
+
+ $this->assertTrue($user->requires2FA());
+ }
+
+ /** @test */
+ public function user_can_get_authentication_methods()
+ {
+ $user = User::factory()->create();
+
+ $methods = $user->getAuthenticationMethods();
+
+ $this->assertArrayHasKey('password', $methods);
+ $this->assertArrayHasKey('two_factor', $methods);
+ $this->assertArrayHasKey('passkey', $methods);
+ $this->assertTrue($methods['password']);
+ $this->assertFalse($methods['two_factor']);
+ $this->assertFalse($methods['passkey']);
+ }
+
+ /** @test */
+ public function middleware_blocks_users_without_required_role()
+ {
+ $user = User::factory()->create();
+
+ $this->actingAs($user)
+ ->get('/test-admin-route')
+ ->assertStatus(403);
+ }
+
+ /** @test */
+ public function middleware_allows_users_with_required_role()
+ {
+ $user = User::factory()->create();
+ $adminRole = Role::create(['name' => 'admin']);
+ $user->assignRole($adminRole);
+
+ $this->actingAs($user)
+ ->get('/test-admin-route')
+ ->assertStatus(200)
+ ->assertJson(['message' => 'Success']);
+ }
+
+ /** @test */
+ public function middleware_blocks_users_without_required_permission()
+ {
+ $user = User::factory()->create();
+ $permission = Permission::create(['name' => 'edit-posts']);
+
+ $this->assertFalse($user->can('edit-posts'));
+ }
+
+ /** @test */
+ public function super_admin_bypasses_permission_checks()
+ {
+ $user = User::factory()->create();
+ $superAdminRole = Role::create(['name' => 'super-admin']);
+ $user->assignRole($superAdminRole);
+
+ // Super admin should bypass permission checks
+ $this->assertTrue($user->canBypassPermissions());
+ }
+}
diff --git a/tests/Unit/HasAuthKitTraitTest.php b/tests/Unit/HasAuthKitTraitTest.php
new file mode 100644
index 0000000..a34c549
--- /dev/null
+++ b/tests/Unit/HasAuthKitTraitTest.php
@@ -0,0 +1,138 @@
+forgetCachedPermissions();
+ }
+
+ /** @test */
+ public function it_can_check_if_two_factor_is_enabled()
+ {
+ $user = User::factory()->create();
+
+ $this->assertFalse($user->hasTwoFactorEnabled());
+
+ $user->forceFill([
+ 'two_factor_secret' => encrypt('test-secret'),
+ 'two_factor_confirmed_at' => now(),
+ ])->save();
+
+ $this->assertTrue($user->hasTwoFactorEnabled());
+ }
+
+ /** @test */
+ public function it_returns_false_when_two_factor_secret_is_null()
+ {
+ $user = User::factory()->create([
+ 'two_factor_secret' => null,
+ 'two_factor_confirmed_at' => null,
+ ]);
+
+ $this->assertFalse($user->hasTwoFactorEnabled());
+ }
+
+ /** @test */
+ public function it_can_check_if_passkeys_are_registered()
+ {
+ $user = User::factory()->create();
+
+ $this->assertFalse($user->hasPasskeysRegistered());
+ }
+
+ /** @test */
+ public function it_can_check_if_two_factor_is_required()
+ {
+ config(['authkit.two_factor.required_for_roles' => ['admin']]);
+
+ $user = User::factory()->create();
+ $adminRole = Role::create(['name' => 'admin']);
+
+ $this->assertFalse($user->requires2FA());
+
+ $user->assignRole($adminRole);
+
+ $this->assertTrue($user->requires2FA());
+ }
+
+ /** @test */
+ public function it_returns_false_when_no_roles_require_two_factor()
+ {
+ config(['authkit.two_factor.required_for_roles' => []]);
+
+ $user = User::factory()->create();
+ $adminRole = Role::create(['name' => 'admin']);
+ $user->assignRole($adminRole);
+
+ $this->assertFalse($user->requires2FA());
+ }
+
+ /** @test */
+ public function it_can_check_if_passkey_is_required()
+ {
+ config(['authkit.passkey.required_for_roles' => ['admin']]);
+
+ $user = User::factory()->create();
+ $adminRole = Role::create(['name' => 'admin']);
+
+ $this->assertFalse($user->requiresPasskey());
+
+ $user->assignRole($adminRole);
+
+ $this->assertTrue($user->requiresPasskey());
+ }
+
+ /** @test */
+ public function it_can_get_authentication_methods()
+ {
+ $user = User::factory()->create();
+
+ $methods = $user->getAuthenticationMethods();
+
+ $this->assertIsArray($methods);
+ $this->assertArrayHasKey('password', $methods);
+ $this->assertArrayHasKey('two_factor', $methods);
+ $this->assertArrayHasKey('passkey', $methods);
+ }
+
+ /** @test */
+ public function it_can_identify_super_admin()
+ {
+ config(['authkit.rbac.super_admin_role' => 'super-admin']);
+
+ $user = User::factory()->create();
+ $superAdminRole = Role::create(['name' => 'super-admin']);
+
+ $this->assertFalse($user->isSuperAdmin());
+
+ $user->assignRole($superAdminRole);
+
+ $this->assertTrue($user->isSuperAdmin());
+ }
+
+ /** @test */
+ public function it_can_check_if_user_can_bypass_permissions()
+ {
+ $user = User::factory()->create();
+ $superAdminRole = Role::create(['name' => 'super-admin']);
+
+ $this->assertFalse($user->canBypassPermissions());
+
+ $user->assignRole($superAdminRole);
+
+ $this->assertTrue($user->canBypassPermissions());
+ }
+}