From 5d66fc942d051dfa2b47e29f44ac5ec9a2254a92 Mon Sep 17 00:00:00 2001 From: Sourov Biswas Date: Sun, 20 Sep 2026 01:29:24 +0600 Subject: [PATCH 1/2] Add OIDC identity provider for the 3AG suite. Accounts owns login, registration, 2FA, and passkeys, and issues authorization codes so SalesReport and the other apps can sign clients in once. Co-authored-by: Cursor --- .claude/skills/fortify-development/SKILL.md | 151 + .claude/skills/passport-development/SKILL.md | 197 + .env.example | 12 +- app/Actions/Fortify/CreateNewUser.php | 47 + .../Fortify/PasswordValidationRules.php | 19 + app/Actions/Fortify/ResetUserPassword.php | 32 + app/Actions/Fortify/UpdateUserPassword.php | 35 + .../Fortify/UpdateUserProfileInformation.php | 61 + app/Entities/IdentityEntity.php | 45 + app/Http/Controllers/HomeController.php | 25 + app/Models/OAuthClient.php | 21 + app/Models/User.php | 13 +- app/Providers/AppServiceProvider.php | 10 +- app/Providers/FortifyServiceProvider.php | 64 + boost.json | 2 + bootstrap/app.php | 1 + bootstrap/providers.php | 4 + composer.json | 15 +- composer.lock | 5329 ++++++++++++----- config/auth.php | 5 + config/fortify.php | 180 + config/openid.php | 105 + config/passport.php | 48 + config/suite.php | 34 + ..._add_two_factor_columns_to_users_table.php | 42 + ...026_09_19_184557_create_passkeys_table.php | 35 + ...9_184603_create_oauth_auth_codes_table.php | 39 + ...84604_create_oauth_access_tokens_table.php | 41 + ...4605_create_oauth_refresh_tokens_table.php | 37 + ...9_19_184606_create_oauth_clients_table.php | 42 + ...184607_create_oauth_device_codes_table.php | 42 + database/seeders/OidcClientSeeder.php | 103 + .../views/auth/confirm-password.blade.php | 19 + .../views/auth/forgot-password.blade.php | 25 + resources/views/auth/login.blade.php | 47 + .../views/auth/oauth/authorize.blade.php | 46 + resources/views/auth/register.blade.php | 42 + resources/views/auth/reset-password.blade.php | 32 + .../views/auth/two-factor-challenge.blade.php | 31 + resources/views/auth/verify-email.blade.php | 23 + .../views/components/auth-card.blade.php | 24 + .../views/components/input-error.blade.php | 5 + .../views/components/input-label.blade.php | 5 + .../views/components/primary-button.blade.php | 3 + .../components/secondary-button.blade.php | 3 + .../views/components/session-status.blade.php | 7 + .../views/components/text-input.blade.php | 6 + resources/views/home.blade.php | 33 + resources/views/layouts/app.blade.php | 44 + routes/api.php | 8 + routes/web.php | 7 +- tests/Feature/Entities/IdentityEntityTest.php | 34 + tests/Feature/ExampleTest.php | 15 +- .../Http/Controllers/HomeControllerTest.php | 34 + tests/Feature/Models/OAuthClientTest.php | 87 + 55 files changed, 5826 insertions(+), 1590 deletions(-) create mode 100644 .claude/skills/fortify-development/SKILL.md create mode 100644 .claude/skills/passport-development/SKILL.md create mode 100644 app/Actions/Fortify/CreateNewUser.php create mode 100644 app/Actions/Fortify/PasswordValidationRules.php create mode 100644 app/Actions/Fortify/ResetUserPassword.php create mode 100644 app/Actions/Fortify/UpdateUserPassword.php create mode 100644 app/Actions/Fortify/UpdateUserProfileInformation.php create mode 100644 app/Entities/IdentityEntity.php create mode 100644 app/Http/Controllers/HomeController.php create mode 100644 app/Models/OAuthClient.php create mode 100644 app/Providers/FortifyServiceProvider.php create mode 100644 config/fortify.php create mode 100644 config/openid.php create mode 100644 config/passport.php create mode 100644 config/suite.php create mode 100644 database/migrations/2026_09_19_184556_add_two_factor_columns_to_users_table.php create mode 100644 database/migrations/2026_09_19_184557_create_passkeys_table.php create mode 100644 database/migrations/2026_09_19_184603_create_oauth_auth_codes_table.php create mode 100644 database/migrations/2026_09_19_184604_create_oauth_access_tokens_table.php create mode 100644 database/migrations/2026_09_19_184605_create_oauth_refresh_tokens_table.php create mode 100644 database/migrations/2026_09_19_184606_create_oauth_clients_table.php create mode 100644 database/migrations/2026_09_19_184607_create_oauth_device_codes_table.php create mode 100644 database/seeders/OidcClientSeeder.php create mode 100644 resources/views/auth/confirm-password.blade.php create mode 100644 resources/views/auth/forgot-password.blade.php create mode 100644 resources/views/auth/login.blade.php create mode 100644 resources/views/auth/oauth/authorize.blade.php create mode 100644 resources/views/auth/register.blade.php create mode 100644 resources/views/auth/reset-password.blade.php create mode 100644 resources/views/auth/two-factor-challenge.blade.php create mode 100644 resources/views/auth/verify-email.blade.php create mode 100644 resources/views/components/auth-card.blade.php create mode 100644 resources/views/components/input-error.blade.php create mode 100644 resources/views/components/input-label.blade.php create mode 100644 resources/views/components/primary-button.blade.php create mode 100644 resources/views/components/secondary-button.blade.php create mode 100644 resources/views/components/session-status.blade.php create mode 100644 resources/views/components/text-input.blade.php create mode 100644 resources/views/home.blade.php create mode 100644 resources/views/layouts/app.blade.php create mode 100644 routes/api.php create mode 100644 tests/Feature/Entities/IdentityEntityTest.php create mode 100644 tests/Feature/Http/Controllers/HomeControllerTest.php create mode 100644 tests/Feature/Models/OAuthClientTest.php diff --git a/.claude/skills/fortify-development/SKILL.md b/.claude/skills/fortify-development/SKILL.md new file mode 100644 index 0000000..2c4e84f --- /dev/null +++ b/.claude/skills/fortify-development/SKILL.md @@ -0,0 +1,151 @@ +--- +name: fortify-development +description: 'ACTIVATE when the user works on authentication in Laravel. This includes login, registration, password reset, email verification, two-factor authentication (2FA/TOTP/QR codes/recovery codes), passkeys, profile updates, password confirmation, or any auth-related routes and controllers. Activate when the user mentions Fortify, auth, authentication, login, register, signup, forgot password, verify email, 2FA, passkeys, WebAuthn, or references app/Actions/Fortify/, CreateNewUser, UpdateUserProfileInformation, FortifyServiceProvider, config/fortify.php, or auth guards. Fortify is the frontend-agnostic authentication backend for Laravel that registers all auth routes and controllers. Also activate when building SPA or headless authentication, customizing login redirects, overriding response contracts like LoginResponse, or configuring login throttling. Do NOT activate for Laravel Passport (OAuth2 API tokens), Socialite (OAuth social login), or non-auth Laravel features.' +license: MIT +metadata: + author: laravel +--- + +# Laravel Fortify Development + +Fortify is a headless authentication backend that provides authentication routes and controllers for Laravel applications. + +## Documentation + +Use `search-docs` for detailed Laravel Fortify patterns and documentation. + +## Usage + +- **Routes**: Use `list-routes` with `only_vendor: true` and `action: "Fortify"` to see all registered endpoints +- **Actions**: Check `app/Actions/Fortify/` for customizable business logic (user creation, password validation, etc.) +- **Config**: See `config/fortify.php` for all options including features, guards, rate limiters, and username field +- **Contracts**: Look in `Laravel\Fortify\Contracts\` for overridable response classes (`LoginResponse`, `LogoutResponse`, etc.) +- **Views**: All view callbacks are set in `FortifyServiceProvider::boot()` using `Fortify::loginView()`, `Fortify::registerView()`, etc. + +## Available Features + +Enable in `config/fortify.php` features array: + +- `Features::registration()` - User registration +- `Features::resetPasswords()` - Password reset via email +- `Features::emailVerification()` - Requires User to implement `MustVerifyEmail` +- `Features::updateProfileInformation()` - Profile updates +- `Features::updatePasswords()` - Password changes +- `Features::twoFactorAuthentication()` - 2FA with QR codes and recovery codes +- `Features::passkeys()` - Passwordless authentication with WebAuthn passkeys + +> Use `search-docs` for feature configuration options and customization patterns. + +## Setup Workflows + +### Two-Factor Authentication Setup + +``` +- [ ] Add TwoFactorAuthenticatable trait to User model +- [ ] Enable feature in config/fortify.php +- [ ] If the `*_add_two_factor_columns_to_users_table.php` migration is missing, publish via `php artisan vendor:publish --tag=fortify-migrations` and migrate +- [ ] Set up view callbacks in FortifyServiceProvider +- [ ] Create 2FA management UI +- [ ] Test QR code and recovery codes +``` + +> Use `search-docs` for TOTP implementation and recovery code handling patterns. + +### Passkeys Setup + +``` +- [ ] Add PasskeyAuthenticatable trait to User model and implement PasskeyUser +- [ ] Enable passkeys feature in config/fortify.php +- [ ] If the passkeys table migration is missing, publish via `php artisan vendor:publish --tag=fortify-migrations` and migrate +- [ ] Configure passkeys relying_party_id, allowed_origins, user_handle_secret, and timeout if defaults are not suitable +- [ ] Build UI with @laravel/passkeys for registration, login, confirmation, and deletion +``` + +> Use `search-docs` for passkey configuration options. For `@laravel/passkeys` frontend usage, refer to the package's README on npm. + +### Email Verification Setup + +``` +- [ ] Enable emailVerification feature in config +- [ ] Implement MustVerifyEmail interface on User model +- [ ] Set up verifyEmailView callback +- [ ] Add verified middleware to protected routes +- [ ] Test verification email flow +``` + +> Use `search-docs` for MustVerifyEmail implementation patterns. + +### Password Reset Setup + +``` +- [ ] Enable resetPasswords feature in config +- [ ] Set up requestPasswordResetLinkView callback +- [ ] Set up resetPasswordView callback +- [ ] Define password.reset named route (if views disabled) +- [ ] Test reset email and link flow +``` + +> Use `search-docs` for custom password reset flow patterns. + +### SPA Authentication Setup + +``` +- [ ] Set 'views' => false in config/fortify.php +- [ ] Install and configure Laravel Sanctum for session-based SPA authentication +- [ ] Use the 'web' guard in config/fortify.php (required for session-based authentication) +- [ ] Set up CSRF token handling +- [ ] Test XHR authentication flows +``` + +> Use `search-docs` for integration and SPA authentication patterns. + +#### Two-Factor Authentication in SPA Mode + +When `views` is set to `false`, Fortify returns JSON responses instead of redirects. + +If a user attempts to log in and two-factor authentication is enabled, the login request will return a JSON response indicating that a two-factor challenge is required: + +```json +{ + "two_factor": true +} +``` + +## Best Practices + +### Custom Authentication Logic + +Override authentication behavior using `Fortify::authenticateUsing()` for custom user retrieval or `Fortify::authenticateThrough()` to customize the authentication pipeline. Override response contracts in `AppServiceProvider` for custom redirects. + +### Registration Customization + +Modify `app/Actions/Fortify/CreateNewUser.php` to customize user creation logic, validation rules, and additional fields. + +### Rate Limiting + +Configure via `fortify.limiters.login` in config. Default configuration throttles by username + IP combination. + +## Key Endpoints + +| Feature | Method | Endpoint | +|------------------------|----------|---------------------------------------------| +| Login | POST | `/login` | +| Logout | POST | `/logout` | +| Register | POST | `/register` | +| Password Reset Request | POST | `/forgot-password` | +| Password Reset | POST | `/reset-password` | +| Email Verify Notice | GET | `/email/verify` | +| Resend Verification | POST | `/email/verification-notification` | +| Password Confirm | POST | `/user/confirm-password` | +| Enable 2FA | POST | `/user/two-factor-authentication` | +| Confirm 2FA | POST | `/user/confirmed-two-factor-authentication` | +| 2FA Challenge | POST | `/two-factor-challenge` | +| Get QR Code | GET | `/user/two-factor-qr-code` | +| Recovery Codes | GET/POST | `/user/two-factor-recovery-codes` | +| Passkey Login Options | GET | `/passkeys/login/options` | +| Passkey Login | POST | `/passkeys/login` | +| Passkey Confirm Options| GET | `/passkeys/confirm/options` | +| Passkey Confirm | POST | `/passkeys/confirm` | +| Passkey Options | GET | `/user/passkeys/options` | +| Register Passkey | POST | `/user/passkeys` | +| Delete Passkey | DELETE | `/user/passkeys/{passkey}` | diff --git a/.claude/skills/passport-development/SKILL.md b/.claude/skills/passport-development/SKILL.md new file mode 100644 index 0000000..6a083d9 --- /dev/null +++ b/.claude/skills/passport-development/SKILL.md @@ -0,0 +1,197 @@ +--- +name: passport-development +description: "Develops OAuth2 API authentication with Laravel Passport. Activates when installing or configuring Passport; setting up OAuth2 grants (authorization code, client credentials, personal access tokens, device authorization); managing OAuth clients; protecting API routes with token authentication; defining or checking token scopes; configuring SPA cookie authentication; handling token lifetimes and refresh tokens; or when the user mentions Passport, OAuth2, API tokens, bearer tokens, or API authentication. Make sure to use this skill whenever the user works with OAuth2, API tokens, or third-party API access, even if they don't explicitly mention Passport." +license: MIT +metadata: + author: laravel +--- + +# Passport OAuth2 Authentication + +## Documentation First + +**Always use `search-docs` before writing Passport code.** The documentation covers every grant type, configuration option, and edge case in detail. This skill teaches you how to navigate Passport — the docs have the implementation specifics. + +``` +search-docs(queries: ["Passport installation"], packages: ["laravel/framework@12.x"]) +``` + +The Passport docs live under the `laravel/framework` package — not `laravel/passport`. + +## When to Apply + +Activate this skill when: + +- Installing or configuring Passport +- Setting up OAuth2 authorization grants +- Creating or managing OAuth clients +- Protecting API routes with token authentication +- Defining or checking token scopes +- Configuring SPA cookie-based authentication +- Choosing between Passport and Sanctum + +## Passport vs. Sanctum + +**Passport** is a full OAuth2 server — use it when third-party applications need to consume your API and when you need OAuth2 authorization code grants, client credentials for machine-to-machine auth, or device authorization flow. + +**Sanctum** is simpler — use it when first-party SPAs, third parties, or mobile apps consume the API but you don't need the full OAuth2 grant flows. + +## Installation + +Three steps are always required: + +### 1. Install Passport + +```bash +php artisan install:api --passport +``` + +This publishes migrations, generates encryption keys, and registers routes. + +### 2. Configure the User model + +The User model needs both the `HasApiTokens` trait AND the `OAuthenticatable` interface. Missing the interface is the most common Passport setup mistake — it causes runtime errors that can be confusing to debug. + +```php +use Laravel\Passport\Contracts\OAuthenticatable; +use Laravel\Passport\HasApiTokens; + +class User extends Authenticatable implements OAuthenticatable +{ + use HasApiTokens; +} +``` + +### 3. Configure the auth guard + +The `api` guard must use the `passport` driver in `config/auth.php`. Using `token` or `sanctum` here silently breaks Passport authentication. + +```php +'guards' => [ + 'api' => [ + 'driver' => 'passport', + 'provider' => 'users', + ], +], +``` + +## Choosing a Grant Type + +Matching the right grant to the use case is the most important Passport decision. Use `search-docs` for implementation details of any grant. + +| Use Case | Grant Type | Client Flag | +|----------|-----------|-------------| +| Third-party app accessing user data | Authorization Code | (default) | +| Mobile/SPA without client secret | Authorization Code + PKCE | `--public` | +| Machine-to-machine, no user context | Client Credentials | `--client` | +| User-generated API keys | Personal Access Tokens | `--personal` | +| Smart TV, CLI, IoT devices | Device Authorization | `--device` | + +**Legacy grants** (Password, Implicit) are disabled by default and not recommended. They must be explicitly enabled with `Passport::enablePasswordGrant()` or `Passport::enableImplicitGrant()`. + +## Client Management + +Create clients with the appropriate flag for the grant type: + +```bash +php artisan passport:client # Authorization code +php artisan passport:client --public # PKCE (no secret) +php artisan passport:client --client # Client credentials +php artisan passport:client --personal # Personal access tokens +php artisan passport:client --device # Device authorization +``` + +Additional flags: `--name=`, `--redirect_uri=`, `--provider=`. + +Client secrets are hashed by default — the plain-text secret is only shown at creation time and cannot be retrieved later. + +## Protecting Routes + +Apply `auth:api` middleware. Clients send tokens via the `Authorization: Bearer ` header. + +```php +Route::get('/user', function (Request $request) { + return $request->user(); +})->middleware('auth:api'); +``` + +### Scope Enforcement + +Scope middleware must come alongside `auth:api`: + +- `CheckToken::using('scope1', 'scope2')` — requires ALL listed scopes +- `CheckTokenForAnyScope::using('scope1', 'scope2')` — requires ANY listed scope +- `EnsureClientIsResourceOwner::using('scope1')` — restricts to client credential tokens + +```php +use Laravel\Passport\Http\Middleware\CheckToken; + +Route::get('/orders', function () { + // ... +})->middleware(['auth:api', CheckToken::using('orders:read')]); +``` + +### Programmatic scope checking + +```php +if ($request->user()->tokenCan('place-orders')) { + // ... +} +``` + +Use `search-docs` for full scope middleware registration and usage patterns. + +## Key Configuration + +Configure in `AppServiceProvider::boot()`. Use `search-docs` for the full list of options. + +```php +// Token lifetimes (each is independent) +Passport::tokensExpireIn(now()->addDays(15)); +Passport::refreshTokensExpireIn(now()->addDays(30)); +Passport::personalAccessTokensExpireIn(now()->addMonths(6)); + +// Define scopes +Passport::tokensCan([ + 'place-orders' => 'Place orders', + 'check-status' => 'Check order status', +]); +``` + +## SPA Cookie Authentication + +For first-party SPAs, the `CreateFreshApiToken` middleware issues a `laravel_token` cookie containing an encrypted JWT. The SPA must include CSRF tokens — missing the `X-CSRF-TOKEN` or `X-XSRF-TOKEN` header causes 419 errors. + +Use `search-docs` for setup details — this feature has specific CSRF and cookie configuration requirements. + +## Testing + +Passport provides helpers to bypass full OAuth flows in tests: + +```php +Passport::actingAs($user, ['scope1', 'scope2']); +Passport::actingAsClient($client, ['scope1']); +``` + +## Token Maintenance + +```bash +php artisan passport:purge # Purge revoked & expired +php artisan passport:purge --revoked # Only revoked +php artisan passport:purge --expired # Only expired +``` + +Schedule `passport:purge` for regular expired token clean-up. + +## Events + +All in `Laravel\Passport\Events`: `AccessTokenCreated`, `AccessTokenRevoked`, `RefreshTokenCreated`. + +## Common Pitfalls + +- **Missing `OAuthenticatable` interface** — both the `HasApiTokens` trait and the `OAuthenticatable` interface are required on the User model. Missing the interface causes runtime errors. +- **Wrong guard driver** — the `api` guard must use `passport`, not `token` or `sanctum`. This fails silently. +- **Token lifetime confusion** — access token, refresh token, and personal access token lifetimes are all independent settings. +- **Missing CSRF for SPA cookie auth** — `CreateFreshApiToken` requires CSRF tokens. Use `Passport::ignoreCsrfToken()` only if you understand the security implications. +- **Client secrets are hashed** — the plain-text secret is only available at creation time. +- **Legacy grants are disabled** — Password and Implicit grants must be explicitly enabled and are not recommended. diff --git a/.env.example b/.env.example index 40bb13b..2acd45f 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,8 @@ -APP_NAME=Laravel +APP_NAME="3AG Accounts" APP_ENV=local APP_KEY= APP_DEBUG=true -APP_URL=http://localhost:8000 +APP_URL=http://127.0.0.1:8000 APP_LOCALE=en APP_FALLBACK_LOCALE=en @@ -63,3 +63,11 @@ AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" + +# 3AG suite application links (shown on the Accounts home page) +SALESREPORT_URL=http://127.0.0.1:8001 +PRODUCTSYNC_URL= +COMPLIANCEPLATFORM_URL= + +# Local OIDC runs over plain HTTP +OPENID_FORCE_HTTPS=false diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php new file mode 100644 index 0000000..dadf13f --- /dev/null +++ b/app/Actions/Fortify/CreateNewUser.php @@ -0,0 +1,47 @@ + $input + * + * @throws ValidationException + */ + public function create(array $input): User + { + Validator::make($input, [ + 'name' => ['required', 'string', 'max:255'], + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + Rule::unique(User::class), + ], + 'password' => $this->passwordRules(), + ])->validate(); + + $user = User::create([ + 'name' => $input['name'], + 'email' => $input['email'], + 'password' => Hash::make($input['password']), + ]); + + $user->forceFill(['email_verified_at' => now()])->save(); + + return $user; + } +} diff --git a/app/Actions/Fortify/PasswordValidationRules.php b/app/Actions/Fortify/PasswordValidationRules.php new file mode 100644 index 0000000..3678865 --- /dev/null +++ b/app/Actions/Fortify/PasswordValidationRules.php @@ -0,0 +1,19 @@ +|string> + */ + protected function passwordRules(): array + { + return ['required', 'string', Password::default(), 'confirmed']; + } +} diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php new file mode 100644 index 0000000..667651f --- /dev/null +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -0,0 +1,32 @@ + $input + * + * @throws ValidationException + */ + public function reset(User $user, array $input): void + { + Validator::make($input, [ + 'password' => $this->passwordRules(), + ])->validate(); + + $user->forceFill([ + 'password' => Hash::make($input['password']), + ])->save(); + } +} diff --git a/app/Actions/Fortify/UpdateUserPassword.php b/app/Actions/Fortify/UpdateUserPassword.php new file mode 100644 index 0000000..4a0306d --- /dev/null +++ b/app/Actions/Fortify/UpdateUserPassword.php @@ -0,0 +1,35 @@ + $input + * + * @throws ValidationException + */ + public function update(User $user, array $input): void + { + Validator::make($input, [ + 'current_password' => ['required', 'string', 'current_password:web'], + 'password' => $this->passwordRules(), + ], [ + 'current_password.current_password' => __('The provided password does not match your current password.'), + ])->validateWithBag('updatePassword'); + + $user->forceFill([ + 'password' => Hash::make($input['password']), + ])->save(); + } +} diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php new file mode 100644 index 0000000..62f58fa --- /dev/null +++ b/app/Actions/Fortify/UpdateUserProfileInformation.php @@ -0,0 +1,61 @@ + $input + * + * @throws ValidationException + */ + public function update(User $user, array $input): void + { + Validator::make($input, [ + 'name' => ['required', 'string', 'max:255'], + + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + Rule::unique('users')->ignore($user->id), + ], + ])->validateWithBag('updateProfileInformation'); + + if ($input['email'] !== $user->email && + $user instanceof MustVerifyEmail) { + $this->updateVerifiedUser($user, $input); + } else { + $user->forceFill([ + 'name' => $input['name'], + 'email' => $input['email'], + ])->save(); + } + } + + /** + * Update the given verified user's profile information. + * + * @param array $input + */ + protected function updateVerifiedUser(User $user, array $input): void + { + $user->forceFill([ + 'name' => $input['name'], + 'email' => $input['email'], + 'email_verified_at' => null, + ])->save(); + + $user->sendEmailVerificationNotification(); + } +} diff --git a/app/Entities/IdentityEntity.php b/app/Entities/IdentityEntity.php new file mode 100644 index 0000000..6274424 --- /dev/null +++ b/app/Entities/IdentityEntity.php @@ -0,0 +1,45 @@ +identifier = $identifier; + $this->user = User::findOrFail($identifier); + } + + /** + * @param string[] $scopes Optional scope filter, applied by the claim extractor + * @return array + */ + public function getClaims(array $scopes = []): array + { + return [ + 'email' => $this->user->email, + 'email_verified' => $this->user->hasVerifiedEmail(), + 'name' => $this->user->name, + 'preferred_username' => $this->user->email, + ]; + } +} diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php new file mode 100644 index 0000000..b1e24db --- /dev/null +++ b/app/Http/Controllers/HomeController.php @@ -0,0 +1,25 @@ +filter(fn (array $app): bool => filled($app['url'])) + ->values() + ->all(); + + return view('home', [ + 'user' => $request->user(), + 'apps' => $apps, + ]); + } +} diff --git a/app/Models/OAuthClient.php b/app/Models/OAuthClient.php new file mode 100644 index 0000000..03486f3 --- /dev/null +++ b/app/Models/OAuthClient.php @@ -0,0 +1,21 @@ +firstParty(); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index f6ba1d2..51cb01a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,20 +2,24 @@ namespace App\Models; -// use Illuminate\Contracts\Auth\MustVerifyEmail; use Database\Factories\UserFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Laravel\Fortify\Contracts\PasskeyUser; +use Laravel\Fortify\PasskeyAuthenticatable; +use Laravel\Fortify\TwoFactorAuthenticatable; +use Laravel\Passport\Contracts\OAuthenticatable; +use Laravel\Passport\HasApiTokens; #[Fillable(['name', 'email', 'password'])] -#[Hidden(['password', 'remember_token'])] -class User extends Authenticatable +#[Hidden(['password', 'remember_token', 'two_factor_secret', 'two_factor_recovery_codes'])] +class User extends Authenticatable implements OAuthenticatable, PasskeyUser { /** @use HasFactory */ - use HasFactory, Notifiable; + use HasApiTokens, HasFactory, Notifiable, PasskeyAuthenticatable, TwoFactorAuthenticatable; /** * Get the attributes that should be cast. @@ -27,6 +31,7 @@ protected function casts(): array return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', + 'two_factor_confirmed_at' => 'datetime', ]; } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..181d600 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,7 +2,9 @@ namespace App\Providers; +use App\Models\OAuthClient; use Illuminate\Support\ServiceProvider; +use Laravel\Passport\Passport; class AppServiceProvider extends ServiceProvider { @@ -19,6 +21,12 @@ public function register(): void */ public function boot(): void { - // + Passport::useClientModel(OAuthClient::class); + Passport::tokensCan(config('openid.passport.tokens_can')); + Passport::authorizationView('auth.oauth.authorize'); + + Passport::tokensExpireIn(now()->addDays(15)); + Passport::refreshTokensExpireIn(now()->addDays(30)); + Passport::personalAccessTokensExpireIn(now()->addMonths(6)); } } diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php new file mode 100644 index 0000000..0581b91 --- /dev/null +++ b/app/Providers/FortifyServiceProvider.php @@ -0,0 +1,64 @@ + view('auth.login')); + Fortify::registerView(fn () => view('auth.register')); + Fortify::requestPasswordResetLinkView(fn () => view('auth.forgot-password')); + Fortify::resetPasswordView(fn (Request $request) => view('auth.reset-password', ['request' => $request])); + Fortify::verifyEmailView(fn () => view('auth.verify-email')); + Fortify::twoFactorChallengeView(fn () => view('auth.two-factor-challenge')); + Fortify::confirmPasswordView(fn () => view('auth.confirm-password')); + + RateLimiter::for('login', function (Request $request) { + $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip()); + + return Limit::perMinute(5)->by($throttleKey); + }); + + RateLimiter::for('two-factor', function (Request $request) { + return Limit::perMinute(5)->by($request->session()->get('login.id')); + }); + + RateLimiter::for('passkeys', function (Request $request) { + $credentialId = $request->input('credential.id'); + + return Limit::perMinute(10)->by( + ($credentialId ?: $request->session()->getId()).'|'.$request->ip() + ); + }); + } +} diff --git a/boost.json b/boost.json index 7420140..344d310 100644 --- a/boost.json +++ b/boost.json @@ -9,8 +9,10 @@ "sail": false, "skills": [ "infer-conventions", + "fortify-development", "laravel-best-practices", "testing-best-practices", + "passport-development", "tailwindcss-development" ] } diff --git a/bootstrap/app.php b/bootstrap/app.php index 1085719..5304386 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -8,6 +8,7 @@ return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', + api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) diff --git a/bootstrap/providers.php b/bootstrap/providers.php index fc94ae6..fa436de 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -1,7 +1,11 @@ =7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.7" + }, + "time": "2025-09-16T12:23:56+00:00" + }, + { + "name": "defuse/php-encryption", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/defuse/php-encryption.git", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/defuse/php-encryption/zipball/f53396c2d34225064647a05ca76c1da9d99e5828", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "paragonie/random_compat": ">= 2", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^5|^6|^7|^8|^9|^10", + "yoast/phpunit-polyfills": "^2.0.0" + }, + "bin": [ + "bin/generate-defuse-key" + ], + "type": "library", + "autoload": { + "psr-4": { + "Defuse\\Crypto\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Hornby", + "email": "taylor@defuse.ca", + "homepage": "https://defuse.ca/" + }, + { + "name": "Scott Arciszewski", + "email": "info@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "Secure PHP Encryption Library", + "keywords": [ + "aes", + "authenticated encryption", + "cipher", + "crypto", + "cryptography", + "encrypt", + "encryption", + "openssl", + "security", + "symmetric key cryptography" + ], + "support": { + "issues": "https://github.com/defuse/php-encryption/issues", + "source": "https://github.com/defuse/php-encryption/tree/v2.4.0" + }, + "time": "2023-06-19T06:10:36+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -214,6 +386,54 @@ }, "time": "2024-07-08T12:26:09+00:00" }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" + }, { "name": "doctrine/inflector", "version": "2.1.0", @@ -512,6 +732,72 @@ ], "time": "2025-03-06T22:45:56+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/googleapis/php-jwt.git", + "reference": "9bc93bd7e3ee5bead4cd23c365ec12f3c1fb0a6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/9bc93bd7e3ee5bead4cd23c365ec12f3c1fb0a6a", + "reference": "9bc93bd7e3ee5bead4cd23c365ec12f3c1fb0a6a", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4||^8.0", + "phpfastcache/phpfastcache": "^9.2", + "phpseclib/phpseclib": "~3.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/googleapis/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.1.1" + }, + "time": "2026-09-14T17:48:47+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.4.0", @@ -1060,81 +1346,221 @@ "time": "2026-08-24T17:13:02+00:00" }, { - "name": "laravel/framework", - "version": "v13.32.0", + "name": "jeremy379/laravel-openid-connect", + "version": "3.3.0", "source": { "type": "git", - "url": "https://github.com/laravel/framework.git", - "reference": "cdd8b33c246719acdd118c705ce8c7ab5ef48a96" + "url": "https://github.com/jeremy379/laravel-openid-connect.git", + "reference": "5b996ee3eab90ce4714b347a3c4741bca27ef9d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/cdd8b33c246719acdd118c705ce8c7ab5ef48a96", - "reference": "cdd8b33c246719acdd118c705ce8c7ab5ef48a96", + "url": "https://api.github.com/repos/jeremy379/laravel-openid-connect/zipball/5b996ee3eab90ce4714b347a3c4741bca27ef9d0", + "reference": "5b996ee3eab90ce4714b347a3c4741bca27ef9d0", "shasum": "" }, "require": { - "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19", - "composer-runtime-api": "^2.2", - "doctrine/inflector": "^2.0.5", - "dragonmantank/cron-expression": "^3.4", - "egulias/email-validator": "^4.0", - "ext-ctype": "*", - "ext-filter": "*", - "ext-hash": "*", - "ext-mbstring": "*", "ext-openssl": "*", - "ext-session": "*", - "ext-tokenizer": "*", - "fruitcake/php-cors": "^1.3", - "guzzlehttp/guzzle": "^7.8.2 || ^8.0", - "guzzlehttp/promises": "^2.0.3 || ^3.0", - "guzzlehttp/psr7": "^2.9 || ^3.0", - "guzzlehttp/uri-template": "^1.0 || ^2.0", - "laravel/prompts": "^0.3.11", - "laravel/serializable-closure": "^2.0.10", - "league/commonmark": "^2.8.1", - "league/flysystem": "^3.25.1", - "league/flysystem-local": "^3.25.1", - "league/uri": "^7.5.1", - "monolog/monolog": "^3.10", - "nesbot/carbon": "^3.8.4", - "nunomaduro/termwind": "^2.0", - "php": "^8.3", - "psr/container": "^1.1.1 || ^2.0.1", - "psr/http-message": "^1.0 || ^2.0", - "psr/log": "^1.0 || ^2.0 || ^3.0", - "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", - "ramsey/uuid": "^4.7", - "symfony/console": "^7.4.0 || ^8.0.0", - "symfony/error-handler": "^7.4.0 || ^8.0.0", - "symfony/finder": "^7.4.0 || ^8.0.0", - "symfony/http-foundation": "^7.4.0 || ^8.0.0", - "symfony/http-kernel": "^7.4.0 || ^8.0.0", - "symfony/mailer": "^7.4.0 || ^8.0.0", - "symfony/mime": "^7.4.0 || ^8.0.0", - "symfony/polyfill-php84": "^1.36", - "symfony/polyfill-php85": "^1.36", - "symfony/polyfill-php86": "^1.36", - "symfony/process": "^7.4.5 || ^8.0.5", - "symfony/routing": "^7.4.0 || ^8.0.0", - "symfony/uid": "^7.4.0 || ^8.0.0", - "symfony/var-dumper": "^7.4.0 || ^8.0.0", - "tijsverkoyen/css-to-inline-styles": "^2.2.5", - "vlucas/phpdotenv": "^5.6.1", - "voku/portable-ascii": "^2.0.2" + "laravel/framework": "^12.0|^13.0", + "laravel/passport": "^13.0", + "lcobucci/jwt": "^4.0|^4.3|^5.0", + "league/oauth2-server": "^8.2.0|^9.2.0", + "nyholm/psr7": "^1.8", + "php": ">=8.2" }, - "conflict": { - "tightenco/collect": "<5.5.33" + "require-dev": { + "guzzlehttp/psr7": "^1.7|^2.7.0", + "http-interop/http-factory-guzzle": "^1.0", + "league/oauth2-client": "^2.6", + "overtrue/phplint": "^9.0", + "phpunit/phpunit": "^10.5.0", + "slevomat/coding-standard": "^6.4.1|^8.28", + "slim/slim": "4.*", + "symplify/easy-coding-standard": "^9.2|^13.0" }, - "provide": { - "psr/container-implementation": "1.1 || 2.0", - "psr/log-implementation": "1.0 || 2.0 || 3.0", - "psr/simple-cache-implementation": "1.0 || 2.0 || 3.0" + "type": "library", + "extra": { + "laravel": { + "providers": [ + "OpenIDConnect\\Laravel\\PassportServiceProvider" + ] + } }, - "replace": { - "illuminate/auth": "self.version", - "illuminate/broadcasting": "self.version", + "autoload": { + "psr-4": { + "OpenIDConnect\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jérémy Dillenbourg", + "email": "jeremy@dillenbourg.be" + }, + { + "name": "Ron van der Heijden", + "email": "r.heijden@live.nl" + } + ], + "description": "OpenID Connect support to the PHP League's OAuth2 Server. Compatible with Laravel Passport.", + "homepage": "https://github.com/jeremy379/laravel-openid-connect", + "keywords": [ + "OpenId", + "laravel", + "oauth2", + "oidc", + "openid-connect", + "passport" + ], + "support": { + "issues": "https://github.com/jeremy379/laravel-openid-connect/issues", + "source": "https://github.com/jeremy379/laravel-openid-connect/tree/3.3.0" + }, + "time": "2026-06-30T18:20:03+00:00" + }, + { + "name": "laravel/fortify", + "version": "v1.39.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/fortify.git", + "reference": "b1fc50707bbe007fd92165d8b7d460ab549b355a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/fortify/zipball/b1fc50707bbe007fd92165d8b7d460ab549b355a", + "reference": "b1fc50707bbe007fd92165d8b7d460ab549b355a", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^3.0", + "ext-json": "*", + "illuminate/console": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "laravel/passkeys": "^0.2.0", + "php": "^8.2", + "pragmarx/google2fa": "^9.0" + }, + "require-dev": { + "orchestra/testbench": "^9.15|^10.8|^11.0", + "phpstan/phpstan": "^2.2.6" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Fortify\\FortifyServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Fortify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Backend controllers and scaffolding for Laravel authentication.", + "keywords": [ + "auth", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/fortify/issues", + "source": "https://github.com/laravel/fortify" + }, + "time": "2026-08-23T07:46:41+00:00" + }, + { + "name": "laravel/framework", + "version": "v13.32.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "cdd8b33c246719acdd118c705ce8c7ab5ef48a96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/cdd8b33c246719acdd118c705ce8c7ab5ef48a96", + "reference": "cdd8b33c246719acdd118c705ce8c7ab5ef48a96", + "shasum": "" + }, + "require": { + "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.4", + "egulias/email-validator": "^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2 || ^8.0", + "guzzlehttp/promises": "^2.0.3 || ^3.0", + "guzzlehttp/psr7": "^2.9 || ^3.0", + "guzzlehttp/uri-template": "^1.0 || ^2.0", + "laravel/prompts": "^0.3.11", + "laravel/serializable-closure": "^2.0.10", + "league/commonmark": "^2.8.1", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", + "monolog/monolog": "^3.10", + "nesbot/carbon": "^3.8.4", + "nunomaduro/termwind": "^2.0", + "php": "^8.3", + "psr/container": "^1.1.1 || ^2.0.1", + "psr/http-message": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^7.4.0 || ^8.0.0", + "symfony/error-handler": "^7.4.0 || ^8.0.0", + "symfony/finder": "^7.4.0 || ^8.0.0", + "symfony/http-foundation": "^7.4.0 || ^8.0.0", + "symfony/http-kernel": "^7.4.0 || ^8.0.0", + "symfony/mailer": "^7.4.0 || ^8.0.0", + "symfony/mime": "^7.4.0 || ^8.0.0", + "symfony/polyfill-php84": "^1.36", + "symfony/polyfill-php85": "^1.36", + "symfony/polyfill-php86": "^1.36", + "symfony/process": "^7.4.5 || ^8.0.5", + "symfony/routing": "^7.4.0 || ^8.0.0", + "symfony/uid": "^7.4.0 || ^8.0.0", + "symfony/var-dumper": "^7.4.0 || ^8.0.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1 || 2.0", + "psr/log-implementation": "1.0 || 2.0 || 3.0", + "psr/simple-cache-implementation": "1.0 || 2.0 || 3.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", "illuminate/bus": "self.version", "illuminate/cache": "self.version", "illuminate/collections": "self.version", @@ -1290,6 +1716,149 @@ }, "time": "2026-09-15T14:55:30+00:00" }, + { + "name": "laravel/passkeys", + "version": "v0.2.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/passkeys-server.git", + "reference": "a76656ada41b2b4a591f075eddae5ddc67e8ab9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/passkeys-server/zipball/a76656ada41b2b4a591f075eddae5ddc67e8ab9c", + "reference": "a76656ada41b2b4a591f075eddae5ddc67e8ab9c", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^11.0|^12.0|^13.0", + "illuminate/database": "^11.0|^12.0|^13.0", + "illuminate/http": "^11.0|^12.0|^13.0", + "illuminate/routing": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "php": "^8.2", + "web-auth/webauthn-lib": "5.3.x" + }, + "require-dev": { + "laravel/pint": "^1.28.0", + "orchestra/testbench": "^9.0|^10.0|^11.0", + "pestphp/pest": "^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "rector/rector": "^2.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Passkeys\\PasskeysServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Passkeys\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Passwordless authentication using WebAuthn/passkeys for Laravel", + "homepage": "https://github.com/laravel/passkeys-server", + "keywords": [ + "Authentication", + "Passwordless", + "laravel", + "passkeys", + "webauthn" + ], + "support": { + "issues": "https://github.com/laravel/passkeys-server/issues", + "source": "https://github.com/laravel/passkeys-server" + }, + "time": "2026-05-18T16:26:00+00:00" + }, + { + "name": "laravel/passport", + "version": "v13.8.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/passport.git", + "reference": "63118b38b508d5605a0e3b5468d447bf540ea7d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/passport/zipball/63118b38b508d5605a0e3b5468d447bf540ea7d2", + "reference": "63118b38b508d5605a0e3b5468d447bf540ea7d2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "firebase/php-jwt": "^6.4|^7.0", + "illuminate/auth": "^11.35|^12.0|^13.0", + "illuminate/console": "^11.35|^12.0|^13.0", + "illuminate/container": "^11.35|^12.0|^13.0", + "illuminate/contracts": "^11.35|^12.0|^13.0", + "illuminate/cookie": "^11.35|^12.0|^13.0", + "illuminate/database": "^11.35|^12.0|^13.0", + "illuminate/encryption": "^11.35|^12.0|^13.0", + "illuminate/http": "^11.35|^12.0|^13.0", + "illuminate/support": "^11.35|^12.0|^13.0", + "league/oauth2-server": "^9.2", + "php": "^8.2", + "php-http/discovery": "^1.20", + "phpseclib/phpseclib": "^4.0", + "psr/http-factory-implementation": "*", + "symfony/console": "^7.1|^8.0", + "symfony/psr-http-message-bridge": "^7.1|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^9.15|^10.8|^11.0", + "phpstan/phpstan": "^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Passport\\PassportServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Passport\\": "src/", + "Laravel\\Passport\\Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Passport provides OAuth2 server support to Laravel.", + "keywords": [ + "laravel", + "oauth", + "passport" + ], + "support": { + "issues": "https://github.com/laravel/passport/issues", + "source": "https://github.com/laravel/passport" + }, + "time": "2026-08-28T14:24:54+00:00" + }, { "name": "laravel/prompts", "version": "v0.3.24", @@ -1480,40 +2049,113 @@ "time": "2026-03-17T14:54:13+00:00" }, { - "name": "league/commonmark", - "version": "2.10.1", + "name": "lcobucci/jwt", + "version": "5.6.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "9d489ab67a02960fd8ffe624d93f751daf95439e" + "url": "https://github.com/lcobucci/jwt.git", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/9d489ab67a02960fd8ffe624d93f751daf95439e", - "reference": "9d489ab67a02960fd8ffe624d93f751daf95439e", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", "shasum": "" }, "require": { - "ext-mbstring": "*", - "league/config": "^1.1.1", - "php": "^7.4 || ^8.0", - "psr/event-dispatcher": "^1.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/polyfill-php80": "^1.16" + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0" }, "require-dev": { - "cebe/markdown": "^1.0", - "commonmark/cmark": "0.31.1", - "commonmark/commonmark.js": "0.31.1", - "composer/package-versions-deprecated": "^1.8", - "embed/embed": "^4.4", - "erusev/parsedown": "^1.0", - "ext-json": "*", - "github/gfm": "0.29.0", - "michelf/php-markdown": "^1.4 || ^2.0", - "nyholm/psr7": "^1.5", - "phpstan/phpstan": "^2.0.0", - "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0", + "infection/infection": "^0.29", + "lcobucci/clock": "^3.2", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^11.1" + }, + "suggest": { + "lcobucci/clock": ">= 3.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/5.6.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2025-10-17T11:30:53+00:00" + }, + { + "name": "league/commonmark", + "version": "2.10.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "9d489ab67a02960fd8ffe624d93f751daf95439e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/9d489ab67a02960fd8ffe624d93f751daf95439e", + "reference": "9d489ab67a02960fd8ffe624d93f751daf95439e", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^2.0.0", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0", "scrutinizer/ocular": "^1.8.1", "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", @@ -1668,6 +2310,65 @@ ], "time": "2022-12-11T20:36:23+00:00" }, + { + "name": "league/event", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/event.git", + "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/event/zipball/ec38ff7ea10cad7d99a79ac937fbcffb9334c210", + "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210", + "shasum": "" + }, + "require": { + "php": ">=7.2.0", + "psr/event-dispatcher": "^1.0" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.45", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Event\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Event package", + "keywords": [ + "emitter", + "event", + "listener" + ], + "support": { + "issues": "https://github.com/thephpleague/event/issues", + "source": "https://github.com/thephpleague/event/tree/3.0.3" + }, + "time": "2024-09-04T16:06:53+00:00" + }, { "name": "league/flysystem", "version": "3.36.0", @@ -1856,6 +2557,103 @@ ], "time": "2026-07-09T11:49:27+00:00" }, + { + "name": "league/oauth2-server", + "version": "9.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-server.git", + "reference": "9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c", + "reference": "9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c", + "shasum": "" + }, + "require": { + "defuse/php-encryption": "^2.4", + "ext-json": "*", + "ext-openssl": "*", + "lcobucci/jwt": "^5.6", + "league/event": "^3.0", + "league/uri": "^7.8", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0", + "psr/http-message": "^2.0", + "psr/http-server-middleware": "^1.0" + }, + "replace": { + "league/oauth2server": "*", + "lncd/oauth2": "*" + }, + "require-dev": { + "laminas/laminas-diactoros": "^3.8", + "paragonie/random_compat": "^9.99.100", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.38", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0.12", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^11.5.50", + "roave/security-advisories": "dev-master", + "slevomat/coding-standard": "^8.27.1", + "squizlabs/php_codesniffer": "^4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Andy Millington", + "email": "andrew@noexceptions.io", + "homepage": "https://www.noexceptions.io", + "role": "Developer" + } + ], + "description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.", + "homepage": "https://oauth2.thephpleague.com/", + "keywords": [ + "Authentication", + "api", + "auth", + "authorisation", + "authorization", + "oauth", + "oauth 2", + "oauth 2.0", + "oauth2", + "protect", + "resource", + "secure", + "server" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-server/issues", + "source": "https://github.com/thephpleague/oauth2-server/tree/9.4.1" + }, + "funding": [ + { + "url": "https://github.com/sephster", + "type": "github" + } + ], + "time": "2026-06-25T15:24:07+00:00" + }, { "name": "league/uri", "version": "7.8.1", @@ -2551,101 +3349,110 @@ "time": "2026-02-16T23:10:27+00:00" }, { - "name": "phpoption/phpoption", - "version": "1.10.0", + "name": "nyholm/psr7", + "version": "1.8.2", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d" + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/67b192b6a42ec03944b972d6e633ddec78ad2c6d", - "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.54 || ^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.33" + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" }, "type": "library", "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - }, "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.8-dev" } }, "autoload": { "psr-4": { - "PhpOption\\": "src/PhpOption/" + "Nyholm\\Psr7\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh" + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" }, { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" } ], - "description": "Option Type for PHP", + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", "keywords": [ - "language", - "option", - "php", - "type" + "psr-17", + "psr-7" ], "support": { - "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.10.0" + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", + "url": "https://github.com/Zegnat", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", - "type": "tidelift" + "url": "https://github.com/nyholm", + "type": "github" } ], - "time": "2026-08-24T00:54:40+00:00" + "time": "2024-09-09T07:06:30+00:00" }, { - "name": "psr/clock", - "version": "1.0.0", + "name": "paragonie/constant_time_encoding", + "version": "v3.1.3", "source": { "type": "git", - "url": "https://github.com/php-fig/clock.git", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", "shasum": "" }, "require": { - "php": "^7.0 || ^8.0" + "php": "^8" + }, + "require-dev": { + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" }, "type": "library", "autoload": { "psr-4": { - "Psr\\Clock\\": "src/" + "ParagonIE\\ConstantTime\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2654,105 +3461,140 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" } ], - "description": "Common interface for reading the clock.", - "homepage": "https://github.com/php-fig/clock", + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", "keywords": [ - "clock", - "now", - "psr", - "psr-20", - "time" + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" ], "support": { - "issues": "https://github.com/php-fig/clock/issues", - "source": "https://github.com/php-fig/clock/tree/1.0.0" + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2022-11-25T14:36:26+00:00" + "time": "2025-09-24T15:06:41+00:00" }, { - "name": "psr/container", - "version": "2.0.2", + "name": "paragonie/random_compat", + "version": "v9.99.100", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", "shasum": "" }, "require": { - "php": ">=7.4.0" + "php": ">= 7" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." }, + "type": "library", "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "csprng", + "polyfill", + "pseudorandom", + "random" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" }, - "time": "2021-11-05T16:47:00+00:00" + "time": "2020-10-15T08:29:30+00:00" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "php-http/discovery", + "version": "1.20.0", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", "shasum": "" }, "require": { - "php": ">=7.2.0" + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" }, - "type": "library", + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true }, "autoload": { "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2760,49 +3602,54 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" } ], - "description": "Standard interfaces for event handling.", + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", "keywords": [ - "events", - "psr", - "psr-14" + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" ], "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" }, - "time": "2019-01-08T18:20:26+00:00" + "time": "2024-10-02T11:20:13+00:00" }, { - "name": "psr/http-client", - "version": "1.0.3", + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", "source": { "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Client\\": "src/" + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2811,50 +3658,67 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", "keywords": [ - "http", - "http-client", - "psr", - "psr-18" + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" ], "support": { - "source": "https://github.com/php-fig/http-client" + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" }, - "time": "2023-09-23T14:17:50+00:00" + "time": "2020-06-27T09:03:43+00:00" }, { - "name": "psr/http-factory", - "version": "1.1.0", + "name": "phpdocumentor/reflection-docblock", + "version": "6.0.3", "source": { "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582", "shasum": "" }, "require": { - "php": ">=7.1", - "psr/http-message": "^1.0 || ^2.0" + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2863,52 +3727,60 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { - "source": "https://github.com/php-fig/http-factory" + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3" }, - "time": "2024-04-15T12:06:14+00:00" + "time": "2026-03-18T20:49:53+00:00" }, { - "name": "psr/http-message", - "version": "2.0", + "name": "phpdocumentor/type-resolver", + "version": "2.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2917,101 +3789,132 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Mike van Riel", + "email": "me@mikevanriel.com" } ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" }, - "time": "2023-04-04T09:54:51+00:00" + "time": "2026-01-06T21:53:42+00:00" }, { - "name": "psr/log", - "version": "3.0.2", + "name": "phpoption/phpoption", + "version": "1.10.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.54 || ^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.33" }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "1.9-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "src" + "PhpOption\\": "src/PhpOption/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Option Type for PHP", "keywords": [ - "log", - "psr", - "psr-3" + "language", + "option", + "php", + "type" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.10.0" }, - "time": "2024-09-11T13:17:53+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2026-08-24T00:54:40+00:00" }, { - "name": "psr/simple-cache", - "version": "3.0.0", + "name": "phpseclib/phpseclib", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "bb7b959c8159957edae6f5084ebbac765d310e16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/bb7b959c8159957edae6f5084ebbac765d310e16", + "reference": "bb7b959c8159957edae6f5084ebbac765d310e16", "shasum": "" }, "require": { - "php": ">=8.0.0" + "paragonie/constant_time_encoding": "^2|^3", + "php": ">=8.1", + "symfony/polyfill-php82": "^1.26" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } + "require-dev": { + "brianium/paratest": "^7.22", + "ext-xml": "*", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpunit/phpunit": "^13", + "squizlabs/php_codesniffer": "^3.7", + "vimeo/psalm": "*" }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], "psr-4": { - "Psr\\SimpleCache\\": "src/" + "phpseclib4\\": "phpseclib/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3020,150 +3923,154 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + }, + { + "name": "Jack Worman", + "email": "jack.worman@gmail.com", + "homepage": "https://jackworman.com", + "role": "Developer" } ], - "description": "Common interfaces for simple caching", + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "https://phpseclib.com/", "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/4.0.1" }, - "time": "2021-10-29T13:26:27+00:00" + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2026-08-26T12:15:13+00:00" }, { - "name": "psy/psysh", - "version": "v0.12.24", + "name": "phpstan/phpdoc-parser", + "version": "2.3.5", "source": { "type": "git", - "url": "https://github.com/bobthecow/psysh.git", - "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1" + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", - "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/148cefffaf0233e4c08cc13db8a195a56dd6dfe9", + "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9", "shasum": "" }, "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "nikic/php-parser": "^5.0 || ^4.0", - "php": "^8.0 || ^7.4", - "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^8.0 || ^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" + "php": "^7.4 || ^8.0" }, "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." + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" }, - "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/" + "PHPStan\\PhpDocParser\\": [ + "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" - ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { - "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.24" + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.5" }, - "time": "2026-06-29T15:41:09+00:00" + "time": "2026-08-31T16:05:28+00:00" }, { - "name": "ramsey/collection", - "version": "2.1.1", + "name": "pragmarx/google2fa", + "version": "v9.1.0", "source": { "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "f00bc788c555adfb6765c437ff3538e59cd88af1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/f00bc788c555adfb6765c437ff3538e59cd88af1", + "reference": "f00bc788c555adfb6765c437ff3538e59cd88af1", "shasum": "" }, "require": { - "php": "^8.1" + "paragonie/constant_time_encoding": "^1.0|^2.0|^3.0", + "php": "^7.1|^8.0" }, "require-dev": { - "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.45", - "fakerphp/faker": "^1.24", - "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^2.1", - "mockery/mockery": "^1.6", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpspec/prophecy-phpunit": "^2.3", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5", - "ramsey/coding-standard": "^2.3", - "ramsey/conventional-commits": "^1.6", - "roave/security-advisories": "dev-latest" + "phpstan/phpstan": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.0|^2.0", + "phpunit/phpunit": "~9|~10|~11|~12|~13", + "psalm/plugin-phpunit": "^0.19|^0.20", + "vimeo/psalm": "^5.26|^6.13" }, "type": "library", - "extra": { - "captainhook": { - "force-install": true - }, - "ramsey/conventional-commits": { - "configFile": "conventional-commits.json" - } - }, "autoload": { "psr-4": { - "Ramsey\\Collection\\": "src/" + "PragmaRX\\Google2FA\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3172,107 +4079,976 @@ ], "authors": [ { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" } ], - "description": "A PHP library for representing and manipulating collections.", + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" + "2fa", + "Authentication", + "MFA", + "Two Factor Authentication", + "google-authenticator", + "google2fa", + "hotp", + "otp", + "rfc4226", + "rfc6238", + "totp" ], "support": { - "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.1" + "docs": "https://github.com/antonioribeiro/google2fa#readme", + "issues": "https://github.com/antonioribeiro/google2fa/issues", + "security": "https://github.com/antonioribeiro/google2fa/security/policy", + "source": "https://github.com/antonioribeiro/google2fa" }, - "time": "2025-03-22T05:38:12+00:00" + "time": "2026-08-15T13:22:01+00:00" }, { - "name": "ramsey/uuid", - "version": "4.9.4", + "name": "psr/clock", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2" + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/75d73f48d02797c2c285a7e9f348fadc0102ffe2", - "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", "shasum": "" }, "require": { - "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0", - "php": "^8.0", - "ramsey/collection": "^1.2 || ^2.0" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "captainhook/captainhook": "^5.25", - "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "ergebnis/composer-normalize": "^2.47", - "mockery/mockery": "^1.6", - "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.6", - "php-mock/php-mock-mockery": "^1.5", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpbench/phpbench": "^1.2.14", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^9.6", - "slevomat/coding-standard": "^8.18", - "squizlabs/php_codesniffer": "^3.13" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "php": "^7.0 || ^8.0" }, "type": "library", - "extra": { - "captainhook": { - "force-install": true - } - }, "autoload": { - "files": [ - "src/functions.php" - ], "psr-4": { - "Ramsey\\Uuid\\": "src/" + "Psr\\Clock\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", "keywords": [ - "guid", - "identifier", - "uuid" + "clock", + "now", + "psr", + "psr-20", + "time" ], "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.4" + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" }, - "time": "2026-09-16T11:39:30+00:00" + "time": "2022-11-25T14:36:26+00:00" }, { - "name": "symfony/clock", - "version": "v8.1.0", + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.12.24", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^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.24" + }, + "time": "2026-06-29T15:41:09+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.4", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/75d73f48d02797c2c285a7e9f348fadc0102ffe2", + "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.4" + }, + "time": "2026-09-16T11:39:30+00:00" + }, + { + "name": "spomky-labs/cbor-php", + "version": "3.4.2", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/cbor-php.git", + "reference": "8f5ea00a07ad529d20886505cdbeb2b9ac7bb2d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/8f5ea00a07ad529d20886505cdbeb2b9ac7bb2d6", + "reference": "8f5ea00a07ad529d20886505cdbeb2b9ac7bb2d6", + "shasum": "" + }, + "require": { + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17|^0.18|^0.19|^0.20|^1.0", + "ext-mbstring": "*", + "php": ">=8.0", + "symfony/polyfill-php81": "^1.32" + }, + "require-dev": { + "ext-json": "*", + "roave/security-advisories": "dev-latest", + "symfony/error-handler": "^6.4|^7.1|^8.0", + "symfony/var-dumper": "^6.4|^7.1|^8.0" + }, + "suggest": { + "ext-bcmath": "Improves the library performance when ext-gmp is missing, and is required to handle the Big Float and Decimal Fraction Tags (4 and 5)", + "ext-gmp": "Strongly recommended when decoding untrusted input: without it, converting the byte string of a Big Number Tag (2 and 3) is quadratic in its length. Also improves the library performance overall" + }, + "type": "library", + "autoload": { + "psr-4": { + "CBOR\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors" + } + ], + "description": "CBOR Encoder/Decoder for PHP", + "keywords": [ + "Concise Binary Object Representation", + "RFC7049", + "cbor" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/cbor-php/issues", + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.4.2" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-09-15T06:55:29+00:00" + }, + { + "name": "spomky-labs/pki-framework", + "version": "1.6.3", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/pki-framework.git", + "reference": "792e909d4e387adffe3c4f404451c7d57a3d2022" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/792e909d4e387adffe3c4f404451c7d57a3d2022", + "reference": "792e909d4e387adffe3c4f404451c7d57a3d2022", + "shasum": "" + }, + "require": { + "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17|^0.18|^0.19|^0.20|^1.0", + "ext-mbstring": "*", + "php": ">=8.1" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", + "ext-gmp": "*", + "ext-openssl": "*", + "infection/infection": "^0.28|^0.29|^0.31", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.3|^2.0", + "phpstan/phpstan": "^1.8|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.1|^2.0", + "phpstan/phpstan-strict-rules": "^1.3|^2.0", + "phpunit/phpunit": "^10.1|^11.0|^12.0", + "rector/rector": "^1.0|^2.0", + "roave/security-advisories": "dev-latest", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symplify/easy-coding-standard": "^12.0 || ^13.0" + }, + "suggest": { + "ext-bcmath": "For better performance (or GMP)", + "ext-gmp": "For better performance (or BCMath)", + "ext-openssl": "For OpenSSL based cyphering", + "ext-sodium": "To verify Ed25519 signatures where the OpenSSL extension has no EdDSA" + }, + "type": "library", + "autoload": { + "psr-4": { + "SpomkyLabs\\Pki\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joni Eskelinen", + "email": "jonieske@gmail.com", + "role": "Original developer" + }, + { + "name": "Florent Morselli", + "email": "florent.morselli@spomky-labs.com", + "role": "Spomky-Labs PKI Framework developer" + } + ], + "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", + "homepage": "https://github.com/spomky-labs/pki-framework", + "keywords": [ + "DER", + "Private Key", + "ac", + "algorithm identifier", + "asn.1", + "asn1", + "attribute certificate", + "certificate", + "certification request", + "cryptography", + "csr", + "decrypt", + "ec", + "encrypt", + "pem", + "pkcs", + "public key", + "rsa", + "sign", + "signature", + "verify", + "x.509", + "x.690", + "x509", + "x690" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/pki-framework/issues", + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.6.3" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-09-12T19:02:49+00:00" + }, + { + "name": "symfony/clock", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", @@ -3280,24 +5056,436 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/701ef4de9705d6c32292ebee5e8044094a09fbf6", - "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6", + "url": "https://api.github.com/repos/symfony/clock/zipball/701ef4de9705d6c32292ebee5e8044094a09fbf6", + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/console", + "version": "v8.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "29afb89f4e941f68a6e90f28e3f52ff1f6793a7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/29afb89f4e941f68a6e90f28e3f52ff1f6793a7d", + "reference": "29afb89f4e941f68a6e90f28e3f52ff1f6793a7d", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php85": "^1.32", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.4.6|^8.0.6" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", + "symfony/event-dispatcher": "<8.1" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/event-dispatcher": "^8.1", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v8.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-13T10:55:57+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "08e2905152a39cf3fd1745d83f8c483e258887d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/08e2905152a39cf3fd1745d83f8c483e258887d9", + "reference": "08e2905152a39cf3fd1745d83f8c483e258887d9", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-23T10:06:25+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", + "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", "shasum": "" }, "require": { "php": ">=8.4.1", - "psr/clock": "^1.0" + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^7.4|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:47:34+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7458da64220376b2e0dc2d8451bf43382c1ad297", + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/security-http": "<7.4", + "symfony/service-contracts": "<2.5" }, "provide": { - "psr/clock-implementation": "1.0" + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", "autoload": { - "files": [ - "Resources/now.php" - ], "psr-4": { - "Symfony\\Component\\Clock\\": "" + "Symfony\\Component\\EventDispatcher\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3309,23 +5497,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Decouples applications from the system clock", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", - "keywords": [ - "clock", - "psr20", - "time" - ], "support": { - "source": "https://github.com/symfony/clock/tree/v8.1.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.5" }, "funding": [ { @@ -3345,62 +5528,40 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { - "name": "symfony/console", - "version": "v8.1.7", + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "29afb89f4e941f68a6e90f28e3f52ff1f6793a7d" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/29afb89f4e941f68a6e90f28e3f52ff1f6793a7d", - "reference": "29afb89f4e941f68a6e90f28e3f52ff1f6793a7d", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php85": "^1.32", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.4.6|^8.0.6" - }, - "conflict": { - "symfony/dependency-injection": "<8.1", - "symfony/event-dispatcher": "<8.1" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^8.1", - "symfony/event-dispatcher": "^8.1", - "symfony/filesystem": "^7.4|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/lock": "^7.4|^8.0", - "symfony/messenger": "^7.4|^8.0", - "symfony/mime": "^7.4|^8.0", - "symfony/process": "^7.4|^8.0", - "symfony/stopwatch": "^7.4|^8.0", - "symfony/uid": "^7.4|^8.0", - "symfony/validator": "^7.4|^8.0", - "symfony/var-dumper": "^7.4|^8.0" + "php": ">=8.1", + "psr/event-dispatcher": "^1" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Contracts\\EventDispatcher\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3408,24 +5569,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", + "description": "Generic abstractions related to dispatching event", "homepage": "https://symfony.com", "keywords": [ - "cli", - "command-line", - "console", - "terminal" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/console/tree/v8.1.7" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { @@ -3445,29 +5608,32 @@ "type": "tidelift" } ], - "time": "2026-09-13T10:55:57+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { - "name": "symfony/css-selector", - "version": "v8.1.6", + "name": "symfony/finder", + "version": "v8.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "08e2905152a39cf3fd1745d83f8c483e258887d9" + "url": "https://github.com/symfony/finder.git", + "reference": "4fbe46a3eb64abf8a57f0364075b91f4a233e062" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/08e2905152a39cf3fd1745d83f8c483e258887d9", - "reference": "08e2905152a39cf3fd1745d83f8c483e258887d9", + "url": "https://api.github.com/repos/symfony/finder/zipball/4fbe46a3eb64abf8a57f0364075b91f4a233e062", + "reference": "4fbe46a3eb64abf8a57f0364075b91f4a233e062", "shasum": "" }, "require": { "php": ">=8.4.1" }, + "require-dev": { + "symfony/filesystem": "^7.4|^8.0" + }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\CssSelector\\": "" + "Symfony\\Component\\Finder\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3482,19 +5648,15 @@ "name": "Fabien Potencier", "email": "fabien@symfony.com" }, - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Converts CSS selectors to XPath expressions", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v8.1.6" + "source": "https://github.com/symfony/finder/tree/v8.1.7" }, "funding": [ { @@ -3514,38 +5676,48 @@ "type": "tidelift" } ], - "time": "2026-08-23T10:06:25+00:00" + "time": "2026-09-10T19:14:39+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.7.1", + "name": "symfony/http-foundation", + "version": "v8.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "d8fdc670ed510a69e3a9eb4f5eac89f5ed627e17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d8fdc670ed510a69e3a9eb4f5eac89f5ed627e17", + "reference": "d8fdc670ed510a69e3a9eb4f5eac89f5ed627e17", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } + "conflict": { + "doctrine/dbal": "<4.3" + }, + "require-dev": { + "doctrine/dbal": "^4.3", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0" }, + "type": "library", "autoload": { - "files": [ - "function.php" + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3554,18 +5726,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + "source": "https://github.com/symfony/http-foundation/tree/v8.1.7" }, "funding": [ { @@ -3585,45 +5757,74 @@ "type": "tidelift" } ], - "time": "2026-06-05T06:23:12+00:00" + "time": "2026-09-14T17:47:24+00:00" }, { - "name": "symfony/error-handler", - "version": "v8.1.5", + "name": "symfony/http-kernel", + "version": "v8.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/error-handler.git", - "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce" + "url": "https://github.com/symfony/http-kernel.git", + "reference": "ec7a3a5832c22cf880cf27d2fdc7ad2e94838e85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", - "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ec7a3a5832c22cf880cf27d2fdc7ad2e94838e85", + "reference": "ec7a3a5832c22cf880cf27d2fdc7ad2e94838e85", "shasum": "" }, "require": { "php": ">=8.4.1", "psr/log": "^1|^2|^3", - "symfony/polyfill-php85": "^1.32", - "symfony/var-dumper": "^7.4|^8.0" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/deprecation-contracts": "<2.5" + "symfony/dependency-injection": "<8.1", + "symfony/flex": "<2.10", + "symfony/http-client-contracts": "<2.5", + "symfony/serializer": "<7.4.15|>=8.0,<8.0.15|>=8.1,<8.1.2", + "symfony/translation-contracts": "<2.5", + "symfony/var-dumper": "<8.1", + "symfony/web-profiler-bundle": "<8.1", + "twig/twig": "<3.21" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", "symfony/console": "^7.4|^8.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", "symfony/serializer": "^7.4|^8.0", - "symfony/webpack-encore-bundle": "^1.0|^2.0" + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^8.1", + "symfony/var-exporter": "^7.4|^8.0", + "twig/twig": "^3.21|^4.0" }, - "bin": [ - "Resources/bin/patch-type-declarations" - ], "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\ErrorHandler\\": "" + "Symfony\\Component\\HttpKernel\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3643,10 +5844,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools to manage errors and ease debugging PHP code", + "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v8.1.5" + "source": "https://github.com/symfony/http-kernel/tree/v8.1.7" }, "funding": [ { @@ -3666,50 +5867,44 @@ "type": "tidelift" } ], - "time": "2026-08-21T17:47:34+00:00" + "time": "2026-09-15T07:12:52+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v8.1.5", + "name": "symfony/mailer", + "version": "v8.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297" + "url": "https://github.com/symfony/mailer.git", + "reference": "8783380ecdafa23d36fc90c5873fd44b635c6e10" }, "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7458da64220376b2e0dc2d8451bf43382c1ad297", - "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297", + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/8783380ecdafa23d36fc90c5873fd44b635c6e10", + "reference": "8783380ecdafa23d36fc90c5873fd44b635c6e10", "shasum": "" }, "require": { + "egulias/email-validator": "^2.1.10|^3|^4", "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/event-dispatcher-contracts": "^2.5|^3" + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/security-http": "<7.4", - "symfony/service-contracts": "<2.5" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" + "symfony/http-client-contracts": "<2.5" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/error-handler": "^7.4|^8.0", - "symfony/expression-language": "^7.4|^8.0", - "symfony/framework-bundle": "^7.4|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^7.4|^8.0" + "symfony/console": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" + "Symfony\\Component\\Mailer\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3729,10 +5924,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.5" + "source": "https://github.com/symfony/mailer/tree/v8.1.7" }, "funding": [ { @@ -3752,40 +5947,50 @@ "type": "tidelift" } ], - "time": "2026-08-21T17:47:34+00:00" + "time": "2026-09-15T06:01:24+00:00" }, { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.7.1", + "name": "symfony/mime", + "version": "v8.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" + "url": "https://github.com/symfony/mime.git", + "reference": "773ac57f20e2795bdadb65b5b1b5f4850d498283" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", - "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "url": "https://api.github.com/repos/symfony/mime/zipball/773ac57f20e2795bdadb65b5b1b5f4850d498283", + "reference": "773ac57f20e2795bdadb65b5b1b5f4850d498283", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" + "php": ">=8.4.1", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/serializer": "^7.4.17|^8.1.5" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3793,26 +5998,22 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to dispatching event", + "description": "Allows manipulating MIME messages", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "mime", + "mime-type" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" + "source": "https://github.com/symfony/mime/tree/v8.1.7" }, "funding": [ { @@ -3832,36 +6033,45 @@ "type": "tidelift" } ], - "time": "2026-06-05T06:23:12+00:00" + "time": "2026-09-04T11:02:17+00:00" }, { - "name": "symfony/finder", - "version": "v8.1.7", + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "4fbe46a3eb64abf8a57f0364075b91f4a233e062" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4fbe46a3eb64abf8a57f0364075b91f4a233e062", - "reference": "4fbe46a3eb64abf8a57f0364075b91f4a233e062", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { - "php": ">=8.4.1" + "php": ">=7.2" }, - "require-dev": { - "symfony/filesystem": "^7.4|^8.0" + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3869,18 +6079,24 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], "support": { - "source": "https://github.com/symfony/finder/tree/v8.1.7" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -3900,49 +6116,42 @@ "type": "tidelift" } ], - "time": "2026-09-10T19:14:39+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/http-foundation", - "version": "v8.1.7", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.41.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "d8fdc670ed510a69e3a9eb4f5eac89f5ed627e17" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d8fdc670ed510a69e3a9eb4f5eac89f5ed627e17", - "reference": "d8fdc670ed510a69e3a9eb4f5eac89f5ed627e17", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "^1.1" - }, - "conflict": { - "doctrine/dbal": "<4.3" + "php": ">=7.2" }, - "require-dev": { - "doctrine/dbal": "^4.3", - "predis/predis": "^1.1|^2.0", - "symfony/cache": "^7.4|^8.0", - "symfony/clock": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/expression-language": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/mime": "^7.4|^8.0", - "symfony/rate-limiter": "^7.4|^8.0" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3950,18 +6159,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Defines an object-oriented layer for the HTTP specification", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/http-foundation/tree/v8.1.7" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { @@ -3981,78 +6198,43 @@ "type": "tidelift" } ], - "time": "2026-09-14T17:47:24+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { - "name": "symfony/http-kernel", - "version": "v8.1.7", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-kernel.git", - "reference": "ec7a3a5832c22cf880cf27d2fdc7ad2e94838e85" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ec7a3a5832c22cf880cf27d2fdc7ad2e94838e85", - "reference": "ec7a3a5832c22cf880cf27d2fdc7ad2e94838e85", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^7.4|^8.0", - "symfony/event-dispatcher": "^7.4|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/dependency-injection": "<8.1", - "symfony/flex": "<2.10", - "symfony/http-client-contracts": "<2.5", - "symfony/serializer": "<7.4.15|>=8.0,<8.0.15|>=8.1,<8.1.2", - "symfony/translation-contracts": "<2.5", - "symfony/var-dumper": "<8.1", - "symfony/web-profiler-bundle": "<8.1", - "twig/twig": "<3.21" + "name": "symfony/polyfill-intl-idn", + "version": "v1.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9" }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/51b5ff5ba85452b31ec6f55490b08148612339d9", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9", + "shasum": "" }, - "require-dev": { - "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^7.4|^8.0", - "symfony/clock": "^7.4|^8.0", - "symfony/config": "^7.4|^8.0", - "symfony/console": "^7.4|^8.0", - "symfony/css-selector": "^7.4|^8.0", - "symfony/dependency-injection": "^8.1", - "symfony/dom-crawler": "^7.4|^8.0", - "symfony/expression-language": "^7.4|^8.0", - "symfony/finder": "^7.4|^8.0", - "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^7.4|^8.0", - "symfony/property-access": "^7.4|^8.0", - "symfony/rate-limiter": "^7.4|^8.0", - "symfony/routing": "^7.4|^8.0", - "symfony/serializer": "^7.4|^8.0", - "symfony/stopwatch": "^7.4|^8.0", - "symfony/translation": "^7.4|^8.0", - "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^7.4|^8.0", - "symfony/validator": "^7.4|^8.0", - "symfony/var-dumper": "^8.1", - "symfony/var-exporter": "^7.4|^8.0", - "twig/twig": "^3.21|^4.0" + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\HttpKernel\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4060,18 +6242,30 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a structured process for converting a Request into a Response", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/http-kernel/tree/v8.1.7" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.42.0" }, "funding": [ { @@ -4091,47 +6285,44 @@ "type": "tidelift" } ], - "time": "2026-09-15T07:12:52+00:00" + "time": "2026-08-24T10:51:20+00:00" }, { - "name": "symfony/mailer", - "version": "v8.1.7", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.42.0", "source": { "type": "git", - "url": "https://github.com/symfony/mailer.git", - "reference": "8783380ecdafa23d36fc90c5873fd44b635c6e10" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/8783380ecdafa23d36fc90c5873fd44b635c6e10", - "reference": "8783380ecdafa23d36fc90c5873fd44b635c6e10", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", "shasum": "" }, "require": { - "egulias/email-validator": "^2.1.10|^3|^4", - "php": ">=8.4.1", - "psr/event-dispatcher": "^1", - "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^7.4|^8.0", - "symfony/mime": "^7.4|^8.0", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/http-client-contracts": "<2.5" + "php": ">=7.2" }, - "require-dev": { - "symfony/console": "^7.4|^8.0", - "symfony/http-client": "^7.4|^8.0", - "symfony/messenger": "^7.4|^8.0", - "symfony/twig-bridge": "^7.4|^8.0" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Mailer\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4140,18 +6331,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Helps sending emails", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/mailer/tree/v8.1.7" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" }, "funding": [ { @@ -4171,50 +6370,46 @@ "type": "tidelift" } ], - "time": "2026-09-15T06:01:24+00:00" + "time": "2026-08-07T06:33:24+00:00" }, { - "name": "symfony/mime", - "version": "v8.1.7", + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", "source": { "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "773ac57f20e2795bdadb65b5b1b5f4850d498283" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/773ac57f20e2795bdadb65b5b1b5f4850d498283", - "reference": "773ac57f20e2795bdadb65b5b1b5f4850d498283", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { - "php": ">=8.4.1", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" + "ext-iconv": "*", + "php": ">=7.2" }, - "conflict": { - "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<5.2|>=7", - "phpdocumentor/type-resolver": "<1.5.1" + "provide": { + "ext-mbstring": "*" }, - "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1|^4", - "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^5.2|^6.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/process": "^7.4|^8.0", - "symfony/property-access": "^7.4|^8.0", - "symfony/property-info": "^7.4|^8.0", - "symfony/serializer": "^7.4.17|^8.1.5" + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Mime\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4222,22 +6417,25 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Allows manipulating MIME messages", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ - "mime", - "mime-type" + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/mime/tree/v8.1.7" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -4257,31 +6455,25 @@ "type": "tidelift" } ], - "time": "2026-09-04T11:02:17+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { - "name": "symfony/polyfill-ctype", + "name": "symfony/polyfill-php80", "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", "shasum": "" }, "require": { "php": ">=7.2" }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -4294,8 +6486,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4303,24 +6498,28 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "ctype", "polyfill", - "portable" + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" }, "funding": [ { @@ -4343,25 +6542,22 @@ "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.41.0", + "name": "symfony/polyfill-php81", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "shasum": "" }, "require": { "php": ">=7.2" }, - "suggest": { - "ext-intl": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -4374,8 +6570,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4391,18 +6590,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "grapheme", - "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" }, "funding": [ { @@ -4422,28 +6619,24 @@ "type": "tidelift" } ], - "time": "2026-07-28T08:25:59+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { - "name": "symfony/polyfill-intl-idn", - "version": "v1.42.0", + "name": "symfony/polyfill-php82", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9" + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/51b5ff5ba85452b31ec6f55490b08148612339d9", - "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", "shasum": "" }, "require": { - "php": ">=7.2", - "symfony/polyfill-intl-normalizer": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" + "php": ">=7.2" }, "type": "library", "extra": { @@ -4457,8 +6650,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - } + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4466,30 +6662,24 @@ ], "authors": [ { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "idn", - "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.42.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1" }, "funding": [ { @@ -4509,28 +6699,25 @@ "type": "tidelift" } ], - "time": "2026-08-24T10:51:20+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.42.0", + "name": "symfony/polyfill-php84", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", - "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { "php": ">=7.2" }, - "suggest": { - "ext-intl": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -4543,7 +6730,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + "Symfony\\Polyfill\\Php84\\": "" }, "classmap": [ "Resources/stubs" @@ -4563,18 +6750,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "intl", - "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" }, "funding": [ { @@ -4594,32 +6779,25 @@ "type": "tidelift" } ], - "time": "2026-08-07T06:33:24+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.38.2", + "name": "symfony/polyfill-php85", + "version": "v1.41.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", "shasum": "" }, "require": { - "ext-iconv": "*", "php": ">=7.2" }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -4632,8 +6810,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4649,17 +6830,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" }, "funding": [ { @@ -4679,20 +6859,20 @@ "type": "tidelift" } ], - "time": "2026-05-27T06:59:30+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.37.0", + "name": "symfony/polyfill-php86", + "version": "v1.41.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + "url": "https://github.com/symfony/polyfill-php86.git", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", - "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e", "shasum": "" }, "require": { @@ -4710,7 +6890,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" + "Symfony\\Polyfill\\Php86\\": "" }, "classmap": [ "Resources/stubs" @@ -4721,10 +6901,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -4734,7 +6910,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -4743,7 +6919,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php86/tree/v1.41.0" }, "funding": [ { @@ -4763,25 +6939,31 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2026-07-02T13:42:24+00:00" }, { - "name": "symfony/polyfill-php82", - "version": "v1.38.1", + "name": "symfony/polyfill-uuid", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php82.git", - "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b" + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", - "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", "shasum": "" }, "require": { "php": ">=7.2" }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, "type": "library", "extra": { "thanks": { @@ -4794,11 +6976,8 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php82\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Polyfill\\Uuid\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4806,24 +6985,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "description": "Symfony polyfill for uuid functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "polyfill", "portable", - "shim" + "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" }, "funding": [ { @@ -4843,41 +7022,32 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:45:58+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/polyfill-php84", - "version": "v1.38.1", + "name": "symfony/process", + "version": "v8.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" + "url": "https://github.com/symfony/process.git", + "reference": "10823b09358e690df4ff943e24e8492bb1019fc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", - "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "url": "https://api.github.com/repos/symfony/process/zipball/10823b09358e690df4ff943e24e8492bb1019fc3", + "reference": "10823b09358e690df4ff943e24e8492bb1019fc3", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.4.1" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" + "Symfony\\Component\\Process\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4886,24 +7056,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" + "source": "https://github.com/symfony/process/tree/v8.1.7" }, "funding": [ { @@ -4923,41 +7087,37 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-09-02T12:40:29+00:00" }, { - "name": "symfony/polyfill-php85", - "version": "v1.41.0", + "name": "symfony/property-access", + "version": "v8.1.4", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" + "url": "https://github.com/symfony/property-access.git", + "reference": "1a41232c678972b93ce499a504e19ea09dfcd0b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", - "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", + "url": "https://api.github.com/repos/symfony/property-access/zipball/1a41232c678972b93ce499a504e19ea09dfcd0b2", + "reference": "1a41232c678972b93ce499a504e19ea09dfcd0b2", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.4.1", + "symfony/property-info": "^7.4.4|^8.0.4" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } + "require-dev": { + "symfony/cache": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0" }, + "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php85\\": "" + "Symfony\\Component\\PropertyAccess\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4966,24 +7126,29 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "description": "Provides functions to read and write from/to an object or array using a simple string notation", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" + "source": "https://github.com/symfony/property-access/tree/v8.1.4" }, "funding": [ { @@ -5003,41 +7168,45 @@ "type": "tidelift" } ], - "time": "2026-07-01T12:47:55+00:00" + "time": "2026-07-30T12:40:56+00:00" }, { - "name": "symfony/polyfill-php86", - "version": "v1.41.0", + "name": "symfony/property-info", + "version": "v8.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php86.git", - "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e" + "url": "https://github.com/symfony/property-info.git", + "reference": "b42ee98197831d33788c33492cfc35ba62d1f701" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/6bc356ed3d8dbfeea8f0de235e34d670704e880e", - "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "url": "https://api.github.com/repos/symfony/property-info/zipball/b42ee98197831d33788c33492cfc35ba62d1f701", + "reference": "b42ee98197831d33788c33492cfc35ba62d1f701", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.4.1", + "symfony/string": "^7.4|^8.0", + "symfony/type-info": "^7.4.7|^8.0.7" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1" }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" + }, + "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php86\\": "" + "Symfony\\Component\\PropertyInfo\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5046,24 +7215,26 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions", + "description": "Extracts information about PHP class' properties using metadata of popular sources", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" ], "support": { - "source": "https://github.com/symfony/polyfill-php86/tree/v1.41.0" + "source": "https://github.com/symfony/property-info/tree/v8.1.7" }, "funding": [ { @@ -5083,45 +7254,49 @@ "type": "tidelift" } ], - "time": "2026-07-02T13:42:24+00:00" + "time": "2026-09-04T10:14:04+00:00" }, { - "name": "symfony/polyfill-uuid", - "version": "v1.37.0", + "name": "symfony/psr-http-message-bridge", + "version": "v8.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "67fd34de15ded1763aa1e330fe345f080a94022c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", - "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/67fd34de15ded1763aa1e330fe345f080a94022c", + "reference": "67fd34de15ded1763aa1e330fe345f080a94022c", "shasum": "" }, "require": { - "php": ">=7.2" - }, - "provide": { - "ext-uuid": "*" + "php": ">=8.4.1", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^7.4|^8.0" }, - "suggest": { - "ext-uuid": "For best performance" + "conflict": { + "php-http/discovery": "<1.15" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0" }, + "type": "symfony-bridge", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Uuid\\": "" - } + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5129,24 +7304,24 @@ ], "authors": [ { - "name": "Grégoire Pineau", - "email": "lyrixx@lyrixx.info" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for uuid functions", + "description": "PSR HTTP message bridge", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "uuid" + "http", + "http-message", + "psr-17", + "psr-7" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v8.1.0" }, "funding": [ { @@ -5166,29 +7341,38 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { - "name": "symfony/process", - "version": "v8.1.7", + "name": "symfony/routing", + "version": "v8.1.6", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "10823b09358e690df4ff943e24e8492bb1019fc3" + "url": "https://github.com/symfony/routing.git", + "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/10823b09358e690df4ff943e24e8492bb1019fc3", - "reference": "10823b09358e690df4ff943e24e8492bb1019fc3", + "url": "https://api.github.com/repos/symfony/routing/zipball/3c188091b6b4fa2e4bc83a135caede12deb8576c", + "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c", "shasum": "" }, "require": { - "php": ">=8.4.1" + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Component\\Routing\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -5208,10 +7392,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Executes commands in sub-processes", + "description": "Maps an HTTP request to a set of configuration variables", "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], "support": { - "source": "https://github.com/symfony/process/tree/v8.1.7" + "source": "https://github.com/symfony/routing/tree/v8.1.6" }, "funding": [ { @@ -5231,38 +7421,63 @@ "type": "tidelift" } ], - "time": "2026-09-02T12:40:29+00:00" + "time": "2026-08-17T13:18:34+00:00" }, { - "name": "symfony/routing", - "version": "v8.1.6", + "name": "symfony/serializer", + "version": "v8.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c" + "url": "https://github.com/symfony/serializer.git", + "reference": "9a88015b4bb1a2bc5a3bbdf6ca3025444ba67321" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3c188091b6b4fa2e4bc83a135caede12deb8576c", - "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c", + "url": "https://api.github.com/repos/symfony/serializer/zipball/9a88015b4bb1a2bc5a3bbdf6ca3025444ba67321", + "reference": "9a88015b4bb1a2bc5a3bbdf6ca3025444ba67321", "shasum": "" }, "require": { "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/property-access": "<8.1", + "symfony/property-info": "<7.4.15", + "symfony/type-info": "<7.4" }, "require-dev": { - "psr/log": "^1|^2|^3", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^7.4|^8.0", "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", "symfony/dependency-injection": "^7.4|^8.0", - "symfony/expression-language": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/property-access": "^8.1", + "symfony/property-info": "^7.4.15|~8.0.15|^8.1.2", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0", "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Routing\\": "" + "Symfony\\Component\\Serializer\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -5282,16 +7497,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Maps an HTTP request to a set of configuration variables", + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", - "keywords": [ - "router", - "routing", - "uri", - "url" - ], "support": { - "source": "https://github.com/symfony/routing/tree/v8.1.6" + "source": "https://github.com/symfony/serializer/tree/v8.1.7" }, "funding": [ { @@ -5311,7 +7520,7 @@ "type": "tidelift" } ], - "time": "2026-08-17T13:18:34+00:00" + "time": "2026-09-08T13:39:13+00:00" }, { "name": "symfony/service-contracts", @@ -5597,25 +7806,105 @@ "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, - "require": { - "php": ">=8.1" + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/type-info", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "ceb48db5b38d6a48640c414be0c69d53980ae5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/ceb48db5b38d6a48640c414be0c69d53980ae5c5", + "reference": "ceb48db5b38d6a48640c414be0c69d53980ae5c5", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Contracts\\Translation\\": "" + "Symfony\\Component\\TypeInfo\\": "" }, "exclude-from-classmap": [ - "/Test/" + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5624,26 +7913,28 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to translation", + "description": "Extracts PHP types information.", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "PHPStan", + "phpdoc", + "symfony", + "type" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" + "source": "https://github.com/symfony/type-info/tree/v8.1.5" }, "funding": [ { @@ -5663,7 +7954,7 @@ "type": "tidelift" } ], - "time": "2026-06-05T06:23:12+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/uid", @@ -6042,6 +8333,233 @@ } ], "time": "2026-04-26T05:33:54+00:00" + }, + { + "name": "web-auth/cose-lib", + "version": "4.8.2", + "source": { + "type": "git", + "url": "https://github.com/web-auth/cose-lib.git", + "reference": "8849e8bf043a2d42d0bec5bda5db2469ad376148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/8849e8bf043a2d42d0bec5bda5db2469ad376148", + "reference": "8849e8bf043a2d42d0bec5bda5db2469ad376148", + "shasum": "" + }, + "require": { + "brick/math": "^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0", + "ext-json": "*", + "ext-openssl": "*", + "php": ">=8.1", + "spomky-labs/pki-framework": "^1.0" + }, + "conflict": { + "spomky-labs/cbor-php": "<3.4.0" + }, + "require-dev": { + "spomky-labs/cbor-php": "^3.4" + }, + "suggest": { + "ext-bcmath": "Recommended: without GMP or BCMath, signing with RSASSA-PSS (PS256/PS384/PS512) blinds its private exponentiation in pure PHP", + "ext-gmp": "Recommended: without GMP or BCMath, signing with RSASSA-PSS (PS256/PS384/PS512) blinds its private exponentiation in pure PHP", + "ext-sodium": "Required by the EdDSA/Ed25519 signature algorithms (-8, -19, -260, -261) and to recompute an OKP public key from its private key", + "spomky-labs/cbor-php": "Required by the RFC 9052 header reader and cryptographic structures. 3.4.0 or later: it ships the six COSE message classes (CBOR\\Tag\\CoseSign1Tag and its siblings) that replace the deprecated Cose\\...Tag classes, and its decoder is what enforces the RFC 9052 label uniqueness and nesting bounds this library relies on" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cose\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/cose/contributors" + } + ], + "description": "CBOR Object Signing and Encryption (COSE) For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "COSE", + "RFC8152" + ], + "support": { + "issues": "https://github.com/web-auth/cose-lib/issues", + "source": "https://github.com/web-auth/cose-lib/tree/4.8.2" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-09-15T17:30:33+00:00" + }, + { + "name": "web-auth/webauthn-lib", + "version": "5.3.9", + "source": { + "type": "git", + "url": "https://github.com/web-auth/webauthn-lib.git", + "reference": "727e378fb7a36c26be5c911e4a0120c146741ce7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/727e378fb7a36c26be5c911e4a0120c146741ce7", + "reference": "727e378fb7a36c26be5c911e4a0120c146741ce7", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "paragonie/constant_time_encoding": "^2.6|^3.0", + "php": ">=8.2", + "phpdocumentor/reflection-docblock": "^5.3|^6.0", + "psr/clock": "^1.0", + "psr/event-dispatcher": "^1.0", + "psr/log": "^1.0|^2.0|^3.0", + "spomky-labs/cbor-php": "^3.4", + "spomky-labs/pki-framework": "^1.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^3.2", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "web-auth/cose-lib": "^4.8" + }, + "suggest": { + "psr/log-implementation": "Recommended to receive logs from the library", + "symfony/event-dispatcher": "Recommended to use dispatched events", + "web-token/jwt-library": "Mandatory for fetching Metadata Statement from distant sources" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/web-auth/webauthn-framework", + "name": "web-auth/webauthn-framework" + } + }, + "autoload": { + "psr-4": { + "Webauthn\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/webauthn-library/contributors" + } + ], + "description": "FIDO2/Webauthn Support For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "FIDO2", + "fido", + "webauthn" + ], + "support": { + "source": "https://github.com/web-auth/webauthn-lib/tree/5.3.9" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-09-10T21:10:45+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, + "branch-alias": { + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.4.1" + }, + "time": "2026-06-15T15:31:57+00:00" } ], "packages-dev": [ @@ -6248,74 +8766,26 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Anton Medvedev", - "email": "anton@medv.io" - } - ], - "description": "Deployment Tool", - "homepage": "https://deployer.org", - "support": { - "docs": "https://deployer.org/docs", - "issues": "https://github.com/deployphp/deployer/issues", - "source": "https://github.com/deployphp/deployer" - }, - "funding": [ - { - "url": "https://github.com/sponsors/antonmedv", - "type": "github" - } - ], - "time": "2025-02-19T16:45:27+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "1.1.6", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", - "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "phpunit/phpunit": "<=7.5 || >=14" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^12 || ^14", - "phpstan/phpstan": "1.4.10 || 2.1.30", - "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", - "psr/log": "^1 || ^2 || ^3" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", + "authors": [ + { + "name": "Anton Medvedev", + "email": "anton@medv.io" + } + ], + "description": "Deployment Tool", + "homepage": "https://deployer.org", "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + "docs": "https://deployer.org/docs", + "issues": "https://github.com/deployphp/deployer/issues", + "source": "https://github.com/deployphp/deployer" }, - "time": "2026-02-07T07:09:04+00:00" + "funding": [ + { + "url": "https://github.com/sponsors/antonmedv", + "type": "github" + } + ], + "time": "2025-02-19T16:45:27+00:00" }, { "name": "fakerphp/faker", @@ -7793,396 +10263,173 @@ "reference": "3dedf9ea6e2876b5b31f7733d3eacbd86f4653b9" }, "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin-profanity/zipball/3dedf9ea6e2876b5b31f7733d3eacbd86f4653b9", - "reference": "3dedf9ea6e2876b5b31f7733d3eacbd86f4653b9", - "shasum": "" - }, - "require": { - "pestphp/pest-plugin": "^5.0.0", - "php": "^8.4" - }, - "conflict": { - "pestphp/pest": "<5.0.0" - }, - "require-dev": { - "faissaloux/pest-plugin-inside": "^1.11", - "pestphp/pest": "^5.0.0", - "pestphp/pest-dev-tools": "^5.0.0" - }, - "type": "library", - "extra": { - "pest": { - "plugins": [ - "Pest\\Profanity\\Plugin" - ] - } - }, - "autoload": { - "psr-4": { - "Pest\\Profanity\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The Pest Profanity Plugin", - "keywords": [ - "framework", - "pest", - "php", - "plugin", - "profanity", - "test", - "testing", - "unit" - ], - "support": { - "source": "https://github.com/pestphp/pest-plugin-profanity/tree/v5.0.0" - }, - "time": "2026-07-21T07:48:56+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "6.0.3", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582", - "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582", + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-profanity/zipball/3dedf9ea6e2876b5b31f7733d3eacbd86f4653b9", + "reference": "3dedf9ea6e2876b5b31f7733d3eacbd86f4653b9", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.1", - "ext-filter": "*", - "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^2.0", - "phpstan/phpdoc-parser": "^2.0", - "webmozart/assert": "^1.9.1 || ^2" + "pestphp/pest-plugin": "^5.0.0", + "php": "^8.4" + }, + "conflict": { + "pestphp/pest": "<5.0.0" }, "require-dev": { - "mockery/mockery": "~1.3.5 || ~1.6.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-webmozart-assert": "^1.2", - "phpunit/phpunit": "^9.5", - "psalm/phar": "^5.26", - "shipmonk/dead-code-detector": "^0.5.1" + "faissaloux/pest-plugin-inside": "^1.11", + "pestphp/pest": "^5.0.0", + "pestphp/pest-dev-tools": "^5.0.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.x-dev" + "pest": { + "plugins": [ + "Pest\\Profanity\\Plugin" + ] } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": "src" + "Pest\\Profanity\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } + "description": "The Pest Profanity Plugin", + "keywords": [ + "framework", + "pest", + "php", + "plugin", + "profanity", + "test", + "testing", + "unit" ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3" + "source": "https://github.com/pestphp/pest-plugin-profanity/tree/v5.0.0" }, - "time": "2026-03-18T20:49:53+00:00" + "time": "2026-07-21T07:48:56+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "2.0.0", + "name": "phar-io/manifest", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", - "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "psalm/phar": "^4" + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.x-dev", - "dev-2.x": "2.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" } ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2026-01-06T21:53:42+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { - "name": "phpstan/phpdoc-parser", - "version": "2.3.5", + "name": "phar-io/version", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9" + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/148cefffaf0233e4c08cc13db8a195a56dd6dfe9", - "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0", - "nikic/php-parser": "^5.3.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6", - "symfony/process": "^5.2" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.5" + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2026-08-31T16:05:28+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { "name": "phpunit/php-code-coverage", @@ -10001,72 +12248,6 @@ } ], "time": "2025-12-08T11:19:18+00:00" - }, - { - "name": "webmozart/assert", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", - "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-date": "*", - "ext-filter": "*", - "php": "^8.2" - }, - "suggest": { - "ext-intl": "", - "ext-simplexml": "", - "ext-spl": "" - }, - "type": "library", - "extra": { - "psalm": { - "pluginClass": "Webmozart\\Assert\\PsalmPlugin" - }, - "branch-alias": { - "dev-master": "2.0-dev", - "dev-feature/2-0": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - }, - { - "name": "Woody Gilk", - "email": "woody.gilk@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.4.1" - }, - "time": "2026-06-15T15:31:57+00:00" } ], "aliases": [], diff --git a/config/auth.php b/config/auth.php index d7568ff..24d8311 100644 --- a/config/auth.php +++ b/config/auth.php @@ -42,6 +42,11 @@ 'driver' => 'session', 'provider' => 'users', ], + + 'api' => [ + 'driver' => 'passport', + 'provider' => 'users', + ], ], /* diff --git a/config/fortify.php b/config/fortify.php new file mode 100644 index 0000000..3c76c68 --- /dev/null +++ b/config/fortify.php @@ -0,0 +1,180 @@ + 'web', + + /* + |-------------------------------------------------------------------------- + | Fortify Password Broker + |-------------------------------------------------------------------------- + | + | Here you may specify which password broker Fortify can use when a user + | is resetting their password. This configured value should match one + | of your password brokers setup in your "auth" configuration file. + | + */ + + 'passwords' => 'users', + + /* + |-------------------------------------------------------------------------- + | Username / Email + |-------------------------------------------------------------------------- + | + | This value defines which model attribute should be considered as your + | application's "username" field. Typically, this might be the email + | address of the users but you are free to change this value here. + | + | Out of the box, Fortify expects forgot password and reset password + | requests to have a field named 'email'. If the application uses + | another name for the field you may define it below as needed. + | + */ + + 'username' => 'email', + + 'email' => 'email', + + /* + |-------------------------------------------------------------------------- + | Lowercase Usernames + |-------------------------------------------------------------------------- + | + | This value defines whether usernames should be lowercased before saving + | them in the database, as some database system string fields are case + | sensitive. You may disable this for your application if necessary. + | + */ + + 'lowercase_usernames' => true, + + /* + |-------------------------------------------------------------------------- + | Home Path + |-------------------------------------------------------------------------- + | + | Here you may configure the path where users will get redirected during + | authentication or password reset when the operations are successful + | and the user is authenticated. You are free to change this value. + | + */ + + 'home' => '/home', + + /* + |-------------------------------------------------------------------------- + | Fortify Routes Prefix / Subdomain + |-------------------------------------------------------------------------- + | + | Here you may specify which prefix Fortify will assign to all the routes + | that it registers with the application. If necessary, you may change + | subdomain under which all of the Fortify routes will be available. + | + */ + + 'prefix' => '', + + 'domain' => null, + + /* + |-------------------------------------------------------------------------- + | Fortify Routes Middleware + |-------------------------------------------------------------------------- + | + | Here you may specify which middleware Fortify will assign to the routes + | that it registers with the application. If necessary, you may change + | these middleware but typically this provided default is preferred. + | + */ + + 'middleware' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Rate Limiting + |-------------------------------------------------------------------------- + | + | By default, Fortify will throttle logins to five requests per minute for + | every email and IP address combination. However, if you would like to + | specify a custom rate limiter to call then you may specify it here. + | + */ + + 'limiters' => [ + 'login' => 'login', + 'two-factor' => 'two-factor', + 'passkeys' => 'passkeys', + ], + + /* + |-------------------------------------------------------------------------- + | Register View Routes + |-------------------------------------------------------------------------- + | + | Here you may specify if the routes returning views should be disabled as + | you may not need them when building your own application. This may be + | especially true if you're writing a custom single-page application. + | + */ + + 'views' => true, + + /* + |-------------------------------------------------------------------------- + | Passkeys + |-------------------------------------------------------------------------- + | + | These settings configure Fortify's passkey (WebAuthn) support. Passkeys + | allow users to sign in without needing to remember credentials since + | they use public-key cryptography - making them immune to breaches. + | + */ + + 'passkeys' => [ + 'relying_party_id' => parse_url(config('app.url'), PHP_URL_HOST), + 'allowed_origins' => [config('app.url')], + 'timeout' => 60000, + ], + + /* + |-------------------------------------------------------------------------- + | Features + |-------------------------------------------------------------------------- + | + | Some of the Fortify features are optional. You may disable the features + | by removing them from this array. You're free to only remove some of + | these features or you can even remove all of these if you need to. + | + */ + + 'features' => [ + Features::registration(), + Features::resetPasswords(), + // Features::emailVerification(), + Features::updateProfileInformation(), + Features::updatePasswords(), + Features::twoFactorAuthentication([ + 'confirm' => true, + 'confirmPassword' => true, + // 'window' => 0, + ]), + Features::passkeys([ + 'confirmPassword' => true, + ]), + ], + +]; diff --git a/config/openid.php b/config/openid.php new file mode 100644 index 0000000..781d0a4 --- /dev/null +++ b/config/openid.php @@ -0,0 +1,105 @@ + [ + + /** + * Place your Passport and OpenID Connect scopes here. + * To receive an `id_token, you should at least provide the openid scope. + */ + 'tokens_can' => [ + 'openid' => 'Enable OpenID Connect', + 'profile' => 'Information about your profile', + 'email' => 'Information about your email address', + 'phone' => 'Information about your phone numbers', + 'address' => 'Information about your address', + // 'login' => 'See your login information', + ], + ], + + /** + * Place your custom claim sets here. + */ + 'custom_claim_sets' => [ + // 'login' => [ + // 'last-login', + // ], + // 'company' => [ + // 'company_name', + // 'company_address', + // 'company_phone', + // 'company_email', + // ], + ], + + /** + * You can override the repositories below. + */ + 'repositories' => [ + 'identity' => IdentityRepository::class, + ], + + 'routes' => [ + /** + * When set to true, this package will expose the OpenID Connect Discovery endpoint. + * - /.well-known/openid-configuration + */ + 'discovery' => true, + /** + * When set to true, this package will expose the JSON Web Key Set endpoint. + */ + 'jwks' => true, + /** + * Optional URL to change the JWKS path to align with your custom Passport routes. + * Defaults to /oauth/jwks + */ + 'jwks_url' => '/oauth/jwks', + /** + * When set to true, this package will expose the UserInfo endpoint at /oauth/userinfo. + * The endpoint is protected by Passport's auth:api guard and returns claims + * for the authenticated user filtered by the access token's granted scopes. + */ + 'userinfo' => true, + ], + + /** + * Settings for the discovery endpoint + */ + 'discovery' => [ + /** + * Hide scopes that aren't from the OpenID Core spec from the Discovery, + * default = false (all scopes are listed) + */ + 'hide_scopes' => false, + ], + + /** + * The signer to be used + */ + 'signer' => Sha256::class, + + /** + * Optional associative array that will be used to set headers on the JWT + */ + 'token_headers' => [], + + /** + * By default, microseconds are included. + */ + 'use_microseconds' => true, + + /** + * Value for the issuedBy params. By default: laravel to get the scheme and host from the $_SERVER variable. + * Options: laravel (use Request to extract scheme and host), server (use $_SERVER to detect) + * or another string that will be used as-is + */ + 'issuedBy' => 'laravel', + + /** + * By default, https is enforce. You can disable it here. + */ + 'forceHttps' => env('OPENID_FORCE_HTTPS', false), +]; diff --git a/config/passport.php b/config/passport.php new file mode 100644 index 0000000..aed4358 --- /dev/null +++ b/config/passport.php @@ -0,0 +1,48 @@ + 'web', + + 'middleware' => [], + + /* + |-------------------------------------------------------------------------- + | Encryption Keys + |-------------------------------------------------------------------------- + | + | Passport uses encryption keys while generating secure access tokens for + | your application. By default, the keys are stored as local files but + | can be set via environment variables when that is more convenient. + | + */ + + 'private_key' => env('PASSPORT_PRIVATE_KEY'), + + 'public_key' => env('PASSPORT_PUBLIC_KEY'), + + /* + |-------------------------------------------------------------------------- + | Passport Database Connection + |-------------------------------------------------------------------------- + | + | By default, Passport's models will utilize your application's default + | database connection. If you wish to use a different connection you + | may specify the configured name of the database connection here. + | + */ + + 'connection' => env('PASSPORT_CONNECTION'), + +]; diff --git a/config/suite.php b/config/suite.php new file mode 100644 index 0000000..b6c9c9a --- /dev/null +++ b/config/suite.php @@ -0,0 +1,34 @@ + [ + [ + 'name' => 'SalesReport', + 'description' => 'Sales performance reporting and dashboards.', + 'url' => env('SALESREPORT_URL'), + ], + [ + 'name' => 'ProductSync', + 'description' => 'Catalogue and inventory synchronisation.', + 'url' => env('PRODUCTSYNC_URL'), + ], + [ + 'name' => 'Compliance Platform', + 'description' => 'Regulatory checks and audit trails.', + 'url' => env('COMPLIANCEPLATFORM_URL'), + ], + ], + +]; diff --git a/database/migrations/2026_09_19_184556_add_two_factor_columns_to_users_table.php b/database/migrations/2026_09_19_184556_add_two_factor_columns_to_users_table.php new file mode 100644 index 0000000..45739ef --- /dev/null +++ b/database/migrations/2026_09_19_184556_add_two_factor_columns_to_users_table.php @@ -0,0 +1,42 @@ +text('two_factor_secret') + ->after('password') + ->nullable(); + + $table->text('two_factor_recovery_codes') + ->after('two_factor_secret') + ->nullable(); + + $table->timestamp('two_factor_confirmed_at') + ->after('two_factor_recovery_codes') + ->nullable(); + }); + } + + /** + * 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', + ]); + }); + } +}; diff --git a/database/migrations/2026_09_19_184557_create_passkeys_table.php b/database/migrations/2026_09_19_184557_create_passkeys_table.php new file mode 100644 index 0000000..a0b9e7d --- /dev/null +++ b/database/migrations/2026_09_19_184557_create_passkeys_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignIdFor(Passkeys::userModel(), 'user_id')->constrained()->cascadeOnDelete(); + $table->string('name'); + $table->string('credential_id')->unique(); + $table->json('credential'); + $table->timestamp('last_used_at')->nullable(); + $table->timestamps(); + + $table->index('user_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('passkeys'); + } +}; diff --git a/database/migrations/2026_09_19_184603_create_oauth_auth_codes_table.php b/database/migrations/2026_09_19_184603_create_oauth_auth_codes_table.php new file mode 100644 index 0000000..c700b50 --- /dev/null +++ b/database/migrations/2026_09_19_184603_create_oauth_auth_codes_table.php @@ -0,0 +1,39 @@ +char('id', 80)->primary(); + $table->foreignId('user_id')->index(); + $table->foreignUuid('client_id'); + $table->text('scopes')->nullable(); + $table->boolean('revoked'); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_auth_codes'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/database/migrations/2026_09_19_184604_create_oauth_access_tokens_table.php b/database/migrations/2026_09_19_184604_create_oauth_access_tokens_table.php new file mode 100644 index 0000000..3e50f7f --- /dev/null +++ b/database/migrations/2026_09_19_184604_create_oauth_access_tokens_table.php @@ -0,0 +1,41 @@ +char('id', 80)->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->foreignUuid('client_id'); + $table->string('name')->nullable(); + $table->text('scopes')->nullable(); + $table->boolean('revoked'); + $table->timestamps(); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_access_tokens'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/database/migrations/2026_09_19_184605_create_oauth_refresh_tokens_table.php b/database/migrations/2026_09_19_184605_create_oauth_refresh_tokens_table.php new file mode 100644 index 0000000..afb3c55 --- /dev/null +++ b/database/migrations/2026_09_19_184605_create_oauth_refresh_tokens_table.php @@ -0,0 +1,37 @@ +char('id', 80)->primary(); + $table->char('access_token_id', 80)->index(); + $table->boolean('revoked'); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_refresh_tokens'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/database/migrations/2026_09_19_184606_create_oauth_clients_table.php b/database/migrations/2026_09_19_184606_create_oauth_clients_table.php new file mode 100644 index 0000000..9794dc8 --- /dev/null +++ b/database/migrations/2026_09_19_184606_create_oauth_clients_table.php @@ -0,0 +1,42 @@ +uuid('id')->primary(); + $table->nullableMorphs('owner'); + $table->string('name'); + $table->string('secret')->nullable(); + $table->string('provider')->nullable(); + $table->text('redirect_uris'); + $table->text('grant_types'); + $table->boolean('revoked'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_clients'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/database/migrations/2026_09_19_184607_create_oauth_device_codes_table.php b/database/migrations/2026_09_19_184607_create_oauth_device_codes_table.php new file mode 100644 index 0000000..ea07831 --- /dev/null +++ b/database/migrations/2026_09_19_184607_create_oauth_device_codes_table.php @@ -0,0 +1,42 @@ +char('id', 80)->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->foreignUuid('client_id')->index(); + $table->char('user_code', 8)->unique(); + $table->text('scopes'); + $table->boolean('revoked'); + $table->dateTime('user_approved_at')->nullable(); + $table->dateTime('last_polled_at')->nullable(); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_device_codes'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/database/seeders/OidcClientSeeder.php b/database/seeders/OidcClientSeeder.php new file mode 100644 index 0000000..97412a6 --- /dev/null +++ b/database/seeders/OidcClientSeeder.php @@ -0,0 +1,103 @@ +client($clients); + $user = $this->testUser(); + + $credentials = [ + 'issuer' => config('app.url'), + 'discovery_url' => rtrim((string) config('app.url'), '/').'/.well-known/openid-configuration', + 'client_id' => (string) $client->getKey(), + 'client_secret' => $client->plainSecret, + 'redirect_uri' => self::REDIRECT_URI, + 'scopes' => 'openid profile email', + 'test_user' => [ + 'email' => $user->email, + 'password' => self::TEST_USER_PASSWORD, + ], + ]; + + $json = json_encode($credentials, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL; + + File::put(storage_path('app/local-oidc.json'), $json); + File::put(storage_path('app/oidc-salesreport.json'), $json); + + $this->command?->table(['Key', 'Value'], [ + ['client_id', $credentials['client_id']], + ['client_secret', $credentials['client_secret']], + ['redirect_uri', $credentials['redirect_uri']], + ['test user', $credentials['test_user']['email'].' / '.self::TEST_USER_PASSWORD], + ]); + + $this->command?->info('Credentials written to storage/app/local-oidc.json'); + } + + /** + * Create the SalesReport authorization code client, or realign an existing one. + * + * Stored secrets are hashed, so re-seeding issues a fresh secret that the client + * application has to pick up. The plain-text value only exists on this request. + */ + private function client(ClientRepository $clients): Client + { + $existing = Passport::client()->newQuery()->where('name', self::CLIENT_NAME)->first(); + + if ($existing === null) { + return $clients->createAuthorizationCodeGrantClient( + name: self::CLIENT_NAME, + redirectUris: [self::REDIRECT_URI], + confidential: true, + ); + } + + $existing->forceFill([ + 'redirect_uris' => [self::REDIRECT_URI], + 'grant_types' => ['authorization_code', 'refresh_token'], + 'revoked' => false, + ])->save(); + + $clients->regenerateSecret($existing); + + return $existing; + } + + /** + * Create a pre-verified local test user with a known password. + */ + private function testUser(): User + { + $user = User::firstOrNew(['email' => self::TEST_USER_EMAIL]); + + $user->forceFill([ + 'name' => 'Test User', + 'password' => self::TEST_USER_PASSWORD, + 'email_verified_at' => now(), + ])->save(); + + return $user; + } +} diff --git a/resources/views/auth/confirm-password.blade.php b/resources/views/auth/confirm-password.blade.php new file mode 100644 index 0000000..df2a0bb --- /dev/null +++ b/resources/views/auth/confirm-password.blade.php @@ -0,0 +1,19 @@ +@extends('layouts.app') + +@section('title', 'Confirm password') + +@section('content') + +
+ @csrf + +
+ Password + + +
+ + Confirm +
+
+@endsection diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php new file mode 100644 index 0000000..28542e2 --- /dev/null +++ b/resources/views/auth/forgot-password.blade.php @@ -0,0 +1,25 @@ +@extends('layouts.app') + +@section('title', 'Forgot password') + +@section('content') + + + +
+ @csrf + +
+ Email address + + +
+ + Email password reset link +
+ + + Back to sign in + +
+@endsection diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php new file mode 100644 index 0000000..7353ac9 --- /dev/null +++ b/resources/views/auth/login.blade.php @@ -0,0 +1,47 @@ +@extends('layouts.app') + +@section('title', 'Sign in') + +@section('content') + + + +
+ @csrf + +
+ Email address + + +
+ +
+
+ Password + + @if (Route::has('password.request')) + + Forgot password? + + @endif +
+ + +
+ + + + Sign in +
+ + + @if (Route::has('register')) + Don't have an account? + Create one + @endif + +
+@endsection diff --git a/resources/views/auth/oauth/authorize.blade.php b/resources/views/auth/oauth/authorize.blade.php new file mode 100644 index 0000000..48d7eff --- /dev/null +++ b/resources/views/auth/oauth/authorize.blade.php @@ -0,0 +1,46 @@ +@extends('layouts.app') + +@section('title', 'Authorization request') + +@section('content') + +

+ {{ $client->name }} + is requesting permission to access your 3AG Accounts profile. +

+ + @if (count($scopes) > 0) +
+

This will allow it to:

+ +
    + @foreach ($scopes as $scope) +
  • + + {{ $scope->description }} +
  • + @endforeach +
+
+ @endif + +
+
+ @csrf + + Approve +
+ +
+ @csrf + @method('DELETE') + + Deny +
+
+ + + Signed in as {{ $user->email }}. + +
+@endsection diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php new file mode 100644 index 0000000..3e8e009 --- /dev/null +++ b/resources/views/auth/register.blade.php @@ -0,0 +1,42 @@ +@extends('layouts.app') + +@section('title', 'Create account') + +@section('content') + +
+ @csrf + +
+ Name + + +
+ +
+ Email address + + +
+ +
+ Password + + +
+ +
+ Confirm password + + +
+ + Create account +
+ + + Already have an account? + Sign in + +
+@endsection diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php new file mode 100644 index 0000000..7b33854 --- /dev/null +++ b/resources/views/auth/reset-password.blade.php @@ -0,0 +1,32 @@ +@extends('layouts.app') + +@section('title', 'Reset password') + +@section('content') + +
+ @csrf + + +
+ Email address + + +
+ +
+ New password + + +
+ +
+ Confirm new password + + +
+ + Reset password +
+
+@endsection diff --git a/resources/views/auth/two-factor-challenge.blade.php b/resources/views/auth/two-factor-challenge.blade.php new file mode 100644 index 0000000..330562d --- /dev/null +++ b/resources/views/auth/two-factor-challenge.blade.php @@ -0,0 +1,31 @@ +@extends('layouts.app') + +@section('title', 'Two-factor authentication') + +@section('content') + +
+ @csrf + +
+ Authentication code + + +
+ +
+ + Lost your device? Use a recovery code + + +
+ Recovery code + + +
+
+ + Continue +
+
+@endsection diff --git a/resources/views/auth/verify-email.blade.php b/resources/views/auth/verify-email.blade.php new file mode 100644 index 0000000..dab6a21 --- /dev/null +++ b/resources/views/auth/verify-email.blade.php @@ -0,0 +1,23 @@ +@extends('layouts.app') + +@section('title', 'Verify email') + +@section('content') + + + +
+ @if (Route::has('verification.send')) +
+ @csrf + Resend verification email +
+ @endif + +
+ @csrf + Sign out +
+
+
+@endsection diff --git a/resources/views/components/auth-card.blade.php b/resources/views/components/auth-card.blade.php new file mode 100644 index 0000000..eb3f157 --- /dev/null +++ b/resources/views/components/auth-card.blade.php @@ -0,0 +1,24 @@ +@props([ + 'title', + 'description' => null, +]) + +
+
+

{{ $title }}

+ + @if ($description) +

{{ $description }}

+ @endif + +
+ {{ $slot }} +
+
+ + @isset($footer) +

+ {{ $footer }} +

+ @endisset +
diff --git a/resources/views/components/input-error.blade.php b/resources/views/components/input-error.blade.php new file mode 100644 index 0000000..7f4b03d --- /dev/null +++ b/resources/views/components/input-error.blade.php @@ -0,0 +1,5 @@ +@props(['for']) + +@error($for) +

merge(['class' => 'mt-2 text-sm text-red-600 dark:text-red-400']) }}>{{ $message }}

+@enderror diff --git a/resources/views/components/input-label.blade.php b/resources/views/components/input-label.blade.php new file mode 100644 index 0000000..3493e8c --- /dev/null +++ b/resources/views/components/input-label.blade.php @@ -0,0 +1,5 @@ +@props(['for']) + + diff --git a/resources/views/components/primary-button.blade.php b/resources/views/components/primary-button.blade.php new file mode 100644 index 0000000..57faf65 --- /dev/null +++ b/resources/views/components/primary-button.blade.php @@ -0,0 +1,3 @@ + diff --git a/resources/views/components/secondary-button.blade.php b/resources/views/components/secondary-button.blade.php new file mode 100644 index 0000000..c52dc6b --- /dev/null +++ b/resources/views/components/secondary-button.blade.php @@ -0,0 +1,3 @@ + diff --git a/resources/views/components/session-status.blade.php b/resources/views/components/session-status.blade.php new file mode 100644 index 0000000..860e37a --- /dev/null +++ b/resources/views/components/session-status.blade.php @@ -0,0 +1,7 @@ +@props(['status' => null]) + +@if ($status) +
merge(['class' => 'mb-6 rounded-md border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800 dark:border-emerald-900 dark:bg-emerald-950 dark:text-emerald-200']) }}> + {{ $status }} +
+@endif diff --git a/resources/views/components/text-input.blade.php b/resources/views/components/text-input.blade.php new file mode 100644 index 0000000..11b6afc --- /dev/null +++ b/resources/views/components/text-input.blade.php @@ -0,0 +1,6 @@ +@props(['type' => 'text']) + +merge(['class' => 'mt-2 block w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm transition placeholder:text-neutral-400 focus:border-neutral-900 focus:outline-none focus:ring-1 focus:ring-neutral-900 dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-100 dark:focus:border-neutral-300 dark:focus:ring-neutral-300']) }} +> diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php new file mode 100644 index 0000000..b2d5412 --- /dev/null +++ b/resources/views/home.blade.php @@ -0,0 +1,33 @@ +@extends('layouts.app') + +@section('title', 'Your 3AG apps') + +@section('content') +
+

Your 3AG apps

+

+ Signed in as {{ $user->name }}. Choose an application to continue — you will not be asked to sign in again. +

+ + @if (count($apps) > 0) + + @else +
+

+ No applications are configured yet. Set SALESREPORT_URL, PRODUCTSYNC_URL or COMPLIANCEPLATFORM_URL in your environment file. +

+
+ @endif +
+@endsection diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php new file mode 100644 index 0000000..f5740b1 --- /dev/null +++ b/resources/views/layouts/app.blade.php @@ -0,0 +1,44 @@ + + + + + + + + @hasSection('title')@yield('title') · @endif{{ config('app.name') }} + + @vite(['resources/css/app.css', 'resources/js/app.js']) + + +
+
+ + 3AG + {{ config('app.name') }} + + + @auth +
+ +
+ @csrf + +
+
+ @endauth +
+
+ +
+ @yield('content') +
+ +
+

+ {{ config('app.name') }} — single sign-on for the 3AG suite. +

+
+ + diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..f35f6f8 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,8 @@ +user(); +})->middleware('auth:api'); diff --git a/routes/web.php b/routes/web.php index 86a06c5..cebfabe 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,7 +1,10 @@ auth()->check() ? redirect()->route('home') : redirect()->route('login')); + +Route::middleware('auth')->group(function () { + Route::get('/home', HomeController::class)->name('home'); }); diff --git a/tests/Feature/Entities/IdentityEntityTest.php b/tests/Feature/Entities/IdentityEntityTest.php new file mode 100644 index 0000000..c9febba --- /dev/null +++ b/tests/Feature/Entities/IdentityEntityTest.php @@ -0,0 +1,34 @@ +create([ + 'name' => 'Ada Lovelace', + 'email' => 'ada@3ag.local', + ]); + + $entity = new IdentityEntity; + $entity->setIdentifier($user->getKey()); + + expect($entity->getIdentifier())->toBe((string) $user->getKey()); + expect($entity->getClaims())->toBe([ + 'email' => 'ada@3ag.local', + 'email_verified' => true, + 'name' => 'Ada Lovelace', + 'preferred_username' => 'ada@3ag.local', + ]); +}); + +it('reports an unverified email address as unverified', function () { + $user = User::factory()->unverified()->create(); + + $entity = new IdentityEntity; + $entity->setIdentifier($user->getKey()); + + expect($entity->getClaims()['email_verified'])->toBeFalse(); +}); diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php index 8fdc86b..0e10a87 100644 --- a/tests/Feature/ExampleTest.php +++ b/tests/Feature/ExampleTest.php @@ -1,7 +1,16 @@ get('/'); +use App\Models\User; +use Illuminate\Foundation\Testing\RefreshDatabase; - $response->assertStatus(200); +uses(RefreshDatabase::class); + +test('the root path sends a guest to the login page', function () { + $this->get('/')->assertRedirect(route('login')); +}); + +test('the root path sends a signed-in user to their applications', function () { + $this->actingAs(User::factory()->create()) + ->get('/') + ->assertRedirect(route('home')); }); diff --git a/tests/Feature/Http/Controllers/HomeControllerTest.php b/tests/Feature/Http/Controllers/HomeControllerTest.php new file mode 100644 index 0000000..93eb1d0 --- /dev/null +++ b/tests/Feature/Http/Controllers/HomeControllerTest.php @@ -0,0 +1,34 @@ +get(route('home'))->assertRedirect(route('login')); +}); + +it('lists only the applications that have a configured url', function () { + config()->set('suite.apps', [ + ['name' => 'SalesReport', 'description' => 'Sales reporting.', 'url' => 'http://127.0.0.1:8001'], + ['name' => 'ProductSync', 'description' => 'Catalogue sync.', 'url' => null], + ]); + + $response = $this->actingAs(User::factory()->create())->get(route('home')); + + $response->assertOk(); + $response->assertSee('SalesReport'); + $response->assertSee('http://127.0.0.1:8001', escape: false); + $response->assertDontSee('ProductSync'); +}); + +it('explains how to configure applications when none are set', function () { + config()->set('suite.apps', [ + ['name' => 'SalesReport', 'description' => 'Sales reporting.', 'url' => null], + ]); + + $this->actingAs(User::factory()->create()) + ->get(route('home')) + ->assertSee('No applications are configured yet.'); +}); diff --git a/tests/Feature/Models/OAuthClientTest.php b/tests/Feature/Models/OAuthClientTest.php new file mode 100644 index 0000000..5ba8227 --- /dev/null +++ b/tests/Feature/Models/OAuthClientTest.php @@ -0,0 +1,87 @@ +redirectUri = 'http://127.0.0.1:8001/auth/accounts/callback'; +}); + +function authorizeUrl(string $clientId, string $redirectUri, array $extra = []): string +{ + return '/oauth/authorize?'.http_build_query([ + 'client_id' => $clientId, + 'redirect_uri' => $redirectUri, + 'response_type' => 'code', + 'scope' => 'openid profile email', + 'state' => 'state-value', + ...$extra, + ]); +} + +it('redirects a guest to the login page', function () { + $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( + name: 'SalesReport', + redirectUris: [$this->redirectUri], + ); + + $this->get(authorizeUrl($client->getKey(), $this->redirectUri)) + ->assertRedirect(route('login')); +}); + +it('issues an authorization code without a consent screen for a first-party client', function () { + $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( + name: 'SalesReport', + redirectUris: [$this->redirectUri], + ); + + $response = $this->actingAs(User::factory()->create()) + ->get(authorizeUrl($client->getKey(), $this->redirectUri)); + + $response->assertRedirectContains($this->redirectUri); + $response->assertRedirectContains('code='); + $response->assertRedirectContains('state=state-value'); +}); + +it('renders the consent screen for a third-party client', function () { + $owner = User::factory()->create(); + + $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( + name: 'Third Party App', + redirectUris: [$this->redirectUri], + user: $owner, + ); + + $response = $this->actingAs(User::factory()->create()) + ->get(authorizeUrl($client->getKey(), $this->redirectUri, ['nonce' => 'nonce-value'])); + + $response->assertOk(); + $response->assertViewIs('auth.oauth.authorize'); + $response->assertSee('Third Party App'); + $response->assertSee('Approve'); + $response->assertSee('Deny'); + $response->assertSee(route('passport.authorizations.approve').'?nonce=nonce-value', escape: false); +}); + +it('issues an authorization code when a third-party consent is approved', function () { + $owner = User::factory()->create(); + + $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( + name: 'Third Party App', + redirectUris: [$this->redirectUri], + user: $owner, + ); + + $this->actingAs(User::factory()->create()) + ->get(authorizeUrl($client->getKey(), $this->redirectUri)); + + $response = $this->post(route('passport.authorizations.approve'), [ + 'auth_token' => session('authToken'), + ]); + + $response->assertRedirectContains($this->redirectUri); + $response->assertRedirectContains('code='); +}); From 8e99eb06e64667afddecb0108f2b2d182d9e05c4 Mon Sep 17 00:00:00 2001 From: Sourov Biswas Date: Sun, 20 Sep 2026 02:05:50 +0600 Subject: [PATCH 2/2] Show a continue-as prompt before suite apps sign users in. Always present the authorize screen so users can confirm the account or switch before continuing into a first-party app. Co-authored-by: Cursor --- .../Auth/SwitchOAuthAccountController.php | 62 +++++++++++++ app/Models/OAuthClient.php | 9 +- .../views/auth/oauth/authorize.blade.php | 72 +++++++++----- routes/web.php | 4 + tests/Feature/Models/OAuthClientTest.php | 93 ++++++++++++++++++- 5 files changed, 210 insertions(+), 30 deletions(-) create mode 100644 app/Http/Controllers/Auth/SwitchOAuthAccountController.php diff --git a/app/Http/Controllers/Auth/SwitchOAuthAccountController.php b/app/Http/Controllers/Auth/SwitchOAuthAccountController.php new file mode 100644 index 0000000..f3b82db --- /dev/null +++ b/app/Http/Controllers/Auth/SwitchOAuthAccountController.php @@ -0,0 +1,62 @@ +validatedAuthorizeReturnUrl($request); + + Auth::guard('web')->logout(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return redirect()->to($returnTo); + } + + /** + * Only allow returning to this app's OAuth authorize endpoint. + */ + private function validatedAuthorizeReturnUrl(Request $request): string + { + $validator = Validator::make($request->all(), [ + 'return' => ['required', 'string', 'url'], + ]); + + if ($validator->fails()) { + throw ValidationException::withMessages($validator->errors()->toArray()); + } + + /** @var string $returnTo */ + $returnTo = $validator->validated()['return']; + $parts = parse_url($returnTo); + + $host = $parts['host'] ?? null; + $path = $parts['path'] ?? ''; + $scheme = $parts['scheme'] ?? null; + + if ($host !== $request->getHost() + || ($scheme !== null && $scheme !== $request->getScheme()) + || $path !== '/oauth/authorize' + ) { + throw ValidationException::withMessages([ + 'return' => __('The return URL must be this app\'s OAuth authorize endpoint.'), + ]); + } + + return $returnTo; + } +} diff --git a/app/Models/OAuthClient.php b/app/Models/OAuthClient.php index 03486f3..125b434 100644 --- a/app/Models/OAuthClient.php +++ b/app/Models/OAuthClient.php @@ -9,13 +9,16 @@ class OAuthClient extends Client { /** - * First-party clients belong to the 3AG suite itself, so signing in to Accounts is - * consent enough and the user is never shown the authorization prompt for them. + * Always show the authorization prompt so the user can confirm which + * account continues into the requesting app, or switch accounts first. + * + * Suite apps also send prompt=consent on interactive login so this stays + * true even when the user has previously granted the same scopes. * * @param Scope[] $scopes */ public function skipsAuthorization(Authenticatable $user, array $scopes): bool { - return $this->firstParty(); + return false; } } diff --git a/resources/views/auth/oauth/authorize.blade.php b/resources/views/auth/oauth/authorize.blade.php index 48d7eff..a853b8c 100644 --- a/resources/views/auth/oauth/authorize.blade.php +++ b/resources/views/auth/oauth/authorize.blade.php @@ -1,42 +1,64 @@ @extends('layouts.app') -@section('title', 'Authorization request') +@section('title', $client->firstParty() ? 'Continue' : 'Authorization request') @section('content') - -

- {{ $client->name }} - is requesting permission to access your 3AG Accounts profile. -

- - @if (count($scopes) > 0) -
-

This will allow it to:

- -
    - @foreach ($scopes as $scope) -
  • - - {{ $scope->description }} -
  • - @endforeach -
-
+ + @if ($client->firstParty()) +

+ You will be signed in to + {{ $client->name }} + as + {{ $user->name }} + ({{ $user->email }}). +

+ @else +

+ {{ $client->name }} + is requesting permission to access your 3AG Accounts profile. +

+ + @if (count($scopes) > 0) +
+

This will allow it to:

+ +
    + @foreach ($scopes as $scope) +
  • + + {{ $scope->description }} +
  • + @endforeach +
+
+ @endif @endif
@csrf - Approve + + {{ $client->firstParty() ? 'Continue' : 'Approve' }} +
-
+ @csrf - @method('DELETE') - - Deny + + + Use a different account +
+ + @unless ($client->firstParty()) +
+ @csrf + @method('DELETE') + + Deny +
+ @endunless
diff --git a/routes/web.php b/routes/web.php index cebfabe..c75f51a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,6 @@ group(function () { Route::get('/home', HomeController::class)->name('home'); + + Route::post('/oauth/switch-account', SwitchOAuthAccountController::class) + ->name('oauth.switch-account'); }); diff --git a/tests/Feature/Models/OAuthClientTest.php b/tests/Feature/Models/OAuthClientTest.php index 5ba8227..9309aa2 100644 --- a/tests/Feature/Models/OAuthClientTest.php +++ b/tests/Feature/Models/OAuthClientTest.php @@ -32,20 +32,108 @@ function authorizeUrl(string $clientId, string $redirectUri, array $extra = []): ->assertRedirect(route('login')); }); -it('issues an authorization code without a consent screen for a first-party client', function () { +it('shows a continue-as prompt for a first-party client', function () { $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( name: 'SalesReport', redirectUris: [$this->redirectUri], ); - $response = $this->actingAs(User::factory()->create()) + $user = User::factory()->create([ + 'name' => 'Jamie Example', + 'email' => 'jamie@example.com', + ]); + + $response = $this->actingAs($user) + ->get(authorizeUrl($client->getKey(), $this->redirectUri)); + + $response->assertOk(); + $response->assertViewIs('auth.oauth.authorize'); + $response->assertSee('Continue to SalesReport'); + $response->assertSee('Jamie Example'); + $response->assertSee('jamie@example.com'); + $response->assertSee('Continue'); + $response->assertSee('Use a different account'); + $response->assertDontSee('>Deny<', false); +}); + +it('still shows the continue-as prompt when prompt=consent is sent', function () { + $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( + name: 'SalesReport', + redirectUris: [$this->redirectUri], + ); + + $user = User::factory()->create([ + 'name' => 'Jamie Example', + 'email' => 'jamie@example.com', + ]); + + $this->actingAs($user) ->get(authorizeUrl($client->getKey(), $this->redirectUri)); + $this->post(route('passport.authorizations.approve'), [ + 'auth_token' => session('authToken'), + ])->assertRedirectContains($this->redirectUri); + + $response = $this->actingAs($user) + ->get(authorizeUrl($client->getKey(), $this->redirectUri, ['prompt' => 'consent'])); + + $response->assertOk(); + $response->assertViewIs('auth.oauth.authorize'); + $response->assertSee('Continue to SalesReport'); +}); + +it('issues an authorization code when the continue-as prompt is approved', function () { + $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( + name: 'SalesReport', + redirectUris: [$this->redirectUri], + ); + + $this->actingAs(User::factory()->create()) + ->get(authorizeUrl($client->getKey(), $this->redirectUri)); + + $response = $this->post(route('passport.authorizations.approve'), [ + 'auth_token' => session('authToken'), + ]); + $response->assertRedirectContains($this->redirectUri); $response->assertRedirectContains('code='); $response->assertRedirectContains('state=state-value'); }); +it('signs out and returns to authorize when switching accounts', function () { + $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( + name: 'SalesReport', + redirectUris: [$this->redirectUri], + ); + + $authorize = authorizeUrl($client->getKey(), $this->redirectUri, ['prompt' => 'consent']); + $user = User::factory()->create(); + + $this->actingAs($user)->get($authorize)->assertOk(); + + $response = $this->actingAs($user)->post(route('oauth.switch-account'), [ + 'return' => url($authorize), + ]); + + $response->assertRedirect($authorize); + $this->assertGuest(); + + $this->get($authorize)->assertRedirect(route('login')); +}); + +it('rejects switch-account returns that are not the authorize endpoint', function () { + $user = User::factory()->create(); + + $this->actingAs($user) + ->from(route('home')) + ->post(route('oauth.switch-account'), [ + 'return' => 'https://evil.example/phish', + ]) + ->assertSessionHasErrors('return'); + + $this->assertAuthenticatedAs($user); +}); + it('renders the consent screen for a third-party client', function () { $owner = User::factory()->create(); @@ -63,6 +151,7 @@ function authorizeUrl(string $clientId, string $redirectUri, array $extra = []): $response->assertSee('Third Party App'); $response->assertSee('Approve'); $response->assertSee('Deny'); + $response->assertSee('Use a different account'); $response->assertSee(route('passport.authorizations.approve').'?nonce=nonce-value', escape: false); });