diff --git a/app/Http/Controllers/Api/Client/SecurityKeyController.php b/app/Http/Controllers/Api/Client/SecurityKeyController.php new file mode 100644 index 000000000..ea72d9a8b --- /dev/null +++ b/app/Http/Controllers/Api/Client/SecurityKeyController.php @@ -0,0 +1,105 @@ +fractal->collection($request->user()->securityKeys) + ->transformWith(SecurityKeyTransformer::class) + ->toArray(); + } + + /** + * Returns the data necessary for creating a new hardware security key for the + * user. + * + * @throws \Webauthn\Exception\InvalidDataException + */ + public function create(Request $request): JsonResponse + { + $tokenId = Str::random(64); + $credentials = $this->createPublicKeyCredentialService->handle($request->user()); + + // TODO: session + $this->cache->put( + "register-security-key:$tokenId", + serialize($credentials), + CarbonImmutable::now()->addMinutes(10) + ); + + return new JsonResponse([ + 'data' => [ + 'token_id' => $tokenId, + 'credentials' => $credentials->jsonSerialize(), + ], + ]); + } + + /** + * Stores a new key for a user account. + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Throwable + */ + public function store(RegisterSecurityKeyRequest $request): array + { + $credentials = unserialize( + $this->cache->pull("register-security-key:{$request->input('token_id')}", serialize(null)) + ); + + if ( + !is_object($credentials) || + !$credentials instanceof PublicKeyCredentialCreationOptions || + $credentials->getUser()->getId() !== $request->user()->uuid + ) { + throw new DisplayException('Could not register security key: invalid data present in session, please try again.'); + } + + $key = $this->storeSecurityKeyService + ->setRequest(SecurityKey::getPsrRequestFactory($request)) + ->setKeyName($request->input('name')) + ->handle($request->user(), $request->input('registration'), $credentials); + + return $this->fractal->item($key) + ->transformWith(SecurityKeyTransformer::class) + ->toArray(); + } + + /** + * Removes a WebAuthn key from a user's account. + */ + public function delete(Request $request, string $securityKey): JsonResponse + { + $request->user()->securityKeys()->where('uuid', $securityKey)->delete(); + + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Auth/AbstractLoginController.php b/app/Http/Controllers/Auth/AbstractLoginController.php index c76a5afc2..0974093d1 100644 --- a/app/Http/Controllers/Auth/AbstractLoginController.php +++ b/app/Http/Controllers/Auth/AbstractLoginController.php @@ -8,7 +8,6 @@ use Illuminate\Auth\AuthManager; use Illuminate\Http\JsonResponse; use Illuminate\Auth\Events\Failed; use Illuminate\Container\Container; -use Illuminate\Support\Facades\Event; use Pterodactyl\Events\Auth\DirectLogin; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; @@ -37,7 +36,7 @@ abstract class AbstractLoginController extends Controller protected string $redirectTo = '/'; /** - * LoginController constructor. + * AbstractLoginController constructor. */ public function __construct() { @@ -60,7 +59,7 @@ abstract class AbstractLoginController extends Controller $this->getField($request->input('user')) => $request->input('user'), ]); - if ($request->route()->named('auth.login-checkpoint')) { + if ($request->route()->named('auth.checkpoint') || $request->route()->named('auth.checkpoint.key')) { throw new DisplayException($message ?? trans('auth.two_factor.checkpoint_failed')); } @@ -79,14 +78,13 @@ abstract class AbstractLoginController extends Controller $this->auth->guard()->login($user, true); - Event::dispatch(new DirectLogin($user, true)); + event(new DirectLogin($user, true)); return new JsonResponse([ - 'data' => [ - 'complete' => true, - 'intended' => $this->redirectPath(), - 'user' => $user->toReactObject(), - ], + 'complete' => true, + 'methods' => [], + 'intended' => $this->redirectPath(), + 'user' => $user->toReactObject(), ]); } @@ -103,6 +101,6 @@ abstract class AbstractLoginController extends Controller */ protected function fireFailedLoginEvent(Authenticatable $user = null, array $credentials = []) { - Event::dispatch(new Failed('auth', $user, $credentials)); + event(new Failed('auth', $user, $credentials)); } } diff --git a/app/Http/Controllers/Auth/LoginCheckpointController.php b/app/Http/Controllers/Auth/LoginCheckpointController.php index 82580edc0..27763085e 100644 --- a/app/Http/Controllers/Auth/LoginCheckpointController.php +++ b/app/Http/Controllers/Auth/LoginCheckpointController.php @@ -4,15 +4,18 @@ namespace Pterodactyl\Http\Controllers\Auth; use Carbon\CarbonImmutable; use Carbon\CarbonInterface; +use Illuminate\Http\Request; use Pterodactyl\Models\User; use Illuminate\Http\JsonResponse; use PragmaRX\Google2FA\Google2FA; -use Illuminate\Support\Facades\Event; +use Pterodactyl\Models\SecurityKey; use Illuminate\Contracts\Encryption\Encrypter; +use Webauthn\PublicKeyCredentialRequestOptions; use Illuminate\Database\Eloquent\ModelNotFoundException; -use Pterodactyl\Events\Auth\ProvidedAuthenticationToken; use Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest; use Illuminate\Contracts\Validation\Factory as ValidationFactory; +use Pterodactyl\Repositories\SecurityKeys\WebauthnServerRepository; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; class LoginCheckpointController extends AbstractLoginController { @@ -24,6 +27,7 @@ class LoginCheckpointController extends AbstractLoginController public function __construct( private Encrypter $encrypter, private Google2FA $google2FA, + private WebauthnServerRepository $webauthnServerRepository, private ValidationFactory $validation ) { parent::__construct(); @@ -34,13 +38,80 @@ class LoginCheckpointController extends AbstractLoginController * token. Once a user has reached this stage it is assumed that they have already * provided a valid username and password. * + * @return \Illuminate\Http\JsonResponse|void + * * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException - * @throws \Exception * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Exception */ - public function __invoke(LoginCheckpointRequest $request): JsonResponse + public function token(LoginCheckpointRequest $request) + { + $user = $this->extractUserFromRequest($request); + + // Recovery tokens go through a slightly different pathway for usage. + if (!is_null($recoveryToken = $request->input('recovery_token'))) { + if ($this->isValidRecoveryToken($user, $recoveryToken)) { + return $this->sendLoginResponse($user, $request); + } + } else { + if (!$user->use_totp) { + $this->sendFailedLoginResponse($request, $user); + } + + $decrypted = $this->encrypter->decrypt($user->totp_secret); + + if ($this->google2FA->verifyKey($decrypted, $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { + return $this->sendLoginResponse($user, $request); + } + } + + $this->sendFailedLoginResponse($request, $user, !empty($recoveryToken) ? 'The recovery token provided is not valid.' : null); + } + + /** + * Authenticates a login request using a security key for a user. + * + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function key(Request $request): JsonResponse + { + $options = $request->session()->get(SecurityKey::PK_SESSION_NAME); + if (!$options instanceof PublicKeyCredentialRequestOptions) { + throw new BadRequestHttpException('No security keys configured in session.'); + } + + $user = $this->extractUserFromRequest($request); + + try { + $source = $this->webauthnServerRepository->loadAndCheckAssertionResponse( + $user, + // TODO: we may have to `json_encode` this so it will be decoded properly. + $request->input('data'), + $options, + SecurityKey::getPsrRequestFactory($request) + ); + } catch (\Exception|\Throwable $e) { + throw $e; + } + + if (hash_equals($user->uuid, $source->getUserHandle())) { + return $this->sendLoginResponse($user, $request); + } + + throw new BadRequestHttpException('An unexpected error was encountered while validating that security key.'); + } + + /** + * Extracts the user from the session data using the provided confirmation token. + * + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + */ + protected function extractUserFromRequest(Request $request): User { if ($this->hasTooManyLoginAttempts($request)) { $this->sendLockoutResponse($request); @@ -62,24 +133,7 @@ class LoginCheckpointController extends AbstractLoginController $this->sendFailedLoginResponse($request, null, self::TOKEN_EXPIRED_MESSAGE); } - // Recovery tokens go through a slightly different pathway for usage. - if (!is_null($recoveryToken = $request->input('recovery_token'))) { - if ($this->isValidRecoveryToken($user, $recoveryToken)) { - Event::dispatch(new ProvidedAuthenticationToken($user, true)); - - return $this->sendLoginResponse($user, $request); - } - } else { - $decrypted = $this->encrypter->decrypt($user->totp_secret); - - if ($this->google2FA->verifyKey($decrypted, $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { - Event::dispatch(new ProvidedAuthenticationToken($user)); - - return $this->sendLoginResponse($user, $request); - } - } - - $this->sendFailedLoginResponse($request, $user, !empty($recoveryToken) ? 'The recovery token provided is not valid.' : null); + return $user; } /** @@ -101,14 +155,19 @@ class LoginCheckpointController extends AbstractLoginController return false; } + protected function hasValidSessionData(array $data): bool + { + return static::isValidSessionData($this->validation, $data); + } + /** * Determines if the data provided from the session is valid or not. This * will return false if the data is invalid, or if more time has passed than * was configured when the session was written. */ - protected function hasValidSessionData(array $data): bool + protected static function isValidSessionData(ValidationFactory $validation, array $data): bool { - $validator = $this->validation->make($data, [ + $validator = $validation->make($data, [ 'user_id' => 'required|integer|min:1', 'token_value' => 'required|string', 'expires_at' => 'required', diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 49a17378d..9b7d7c7a4 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -9,10 +9,23 @@ use Pterodactyl\Models\User; use Illuminate\Http\JsonResponse; use Pterodactyl\Facades\Activity; use Illuminate\Contracts\View\View; +use Pterodactyl\Models\SecurityKey; use Illuminate\Database\Eloquent\ModelNotFoundException; +use Pterodactyl\Repositories\SecurityKeys\WebauthnServerRepository; class LoginController extends AbstractLoginController { + private const METHOD_TOTP = 'totp'; + private const METHOD_WEBAUTHN = 'webauthn'; + + /** + * LoginController constructor. + */ + public function __construct(protected WebauthnServerRepository $webauthnServerRepository) + { + parent::__construct(); + } + /** * Handle all incoming requests for the authentication routes and render the * base authentication view component. React will take over at this point and @@ -28,6 +41,7 @@ class LoginController extends AbstractLoginController * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Illuminate\Validation\ValidationException + * @throws \Webauthn\Exception\InvalidDataException */ public function login(Request $request): JsonResponse { @@ -53,7 +67,9 @@ class LoginController extends AbstractLoginController $this->sendFailedLoginResponse($request, $user); } - if (!$user->use_totp) { + // Return early if the user does not have 2FA enabled, otherwise we will require them + // to complete a secondary challenge before they can log in. + if (!$user->has2FAEnabled()) { return $this->sendLoginResponse($user, $request); } @@ -65,11 +81,23 @@ class LoginController extends AbstractLoginController 'expires_at' => CarbonImmutable::now()->addMinutes(5), ]); - return new JsonResponse([ - 'data' => [ - 'complete' => false, - 'confirmation_token' => $token, - ], - ]); + $response = [ + 'complete' => false, + 'methods' => array_values(array_filter([ + $user->use_totp ? self::METHOD_TOTP : null, + $user->securityKeys->isNotEmpty() ? self::METHOD_WEBAUTHN : null, + ])), + 'confirm_token' => $token, + ]; + + if ($user->securityKeys->isNotEmpty()) { + $key = $this->webauthnServerRepository->generatePublicKeyCredentialRequestOptions($user); + + $request->session()->put(SecurityKey::PK_SESSION_NAME, $key); + + $request['webauthn'] = ['public_key' => $key]; + } + + return new JsonResponse($response); } } diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php index a6110edb1..9bc6f0ac8 100644 --- a/app/Http/Middleware/RequireTwoFactorAuthentication.php +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -47,9 +47,7 @@ class RequireTwoFactorAuthentication // send them right through, nothing else needs to be checked. // // If the level is set as admin and the user is not an admin, pass them through as well. - if ($level === self::LEVEL_NONE || $user->use_totp) { - return $next($request); - } elseif ($level === self::LEVEL_ADMIN && !$user->root_admin) { + if ($level === self::LEVEL_NONE || $user->has2FAEnabled() || ($level === self::LEVEL_ADMIN && !$user->root_admin)) { return $next($request); } diff --git a/app/Http/Requests/Api/Client/Account/RegisterSecurityKeyRequest.php b/app/Http/Requests/Api/Client/Account/RegisterSecurityKeyRequest.php new file mode 100644 index 000000000..959a8951d --- /dev/null +++ b/app/Http/Requests/Api/Client/Account/RegisterSecurityKeyRequest.php @@ -0,0 +1,21 @@ + ['string', 'required'], + 'token_id' => ['required', 'string'], + 'registration' => ['required', 'array'], + 'registration.id' => ['required', 'string'], + 'registration.type' => ['required', 'in:public-key'], + 'registration.response.attestationObject' => ['required', 'string'], + 'registration.response.clientDataJSON' => ['required', 'string'], + ]; + } +} diff --git a/app/Http/Requests/Api/Client/ClientApiRequest.php b/app/Http/Requests/Api/Client/ClientApiRequest.php index 5ae1680a4..8b703f059 100644 --- a/app/Http/Requests/Api/Client/ClientApiRequest.php +++ b/app/Http/Requests/Api/Client/ClientApiRequest.php @@ -23,7 +23,7 @@ class ClientApiRequest extends ApplicationApiRequest return $this->user()->can($this->permission(), $server); } - // If there is no server available on the reqest, trigger a failure since + // If there is no server available on the request, trigger a failure since // we expect there to be one at this point. return false; } diff --git a/app/Models/SecurityKey.php b/app/Models/SecurityKey.php new file mode 100644 index 000000000..e19c9f11f --- /dev/null +++ b/app/Models/SecurityKey.php @@ -0,0 +1,125 @@ +|null $other_ui + * + * @property \Carbon\CarbonImmutable $created_at + * @property \Carbon\CarbonImmutable $updated_at + */ +class SecurityKey extends Model +{ + use HasFactory; + + public const RESOURCE_NAME = 'security_key'; + public const PK_SESSION_NAME = 'security_key_pk_request'; + + protected $casts = [ + 'user_id' => 'int', + 'transports' => 'array', + 'other_ui' => 'array', + ]; + + protected $guarded = [ + 'uuid', + 'user_id', + ]; + + public function publicKey(): Attribute + { + return new Attribute( + get: fn (string $value) => base64_decode($value), + set: fn (string $value) => base64_encode($value), + ); + } + + public function publicKeyId(): Attribute + { + return new Attribute( + get: fn (string $value) => base64_decode($value), + set: fn (string $value) => base64_encode($value), + ); + } + + public function aaguid(): Attribute + { + return Attribute::make( + get: fn (string|null $value): AbstractUid => is_null($value) ? new NilUuid() : Uuid::fromString($value), + set: fn (AbstractUid|null $value): string|null => (is_null($value) || $value instanceof NilUuid) ? null : $value->__toString(), + ); + } + + public function trustPath(): Attribute + { + return new Attribute( + get: fn (mixed $value) => is_null($value) ? null : TrustPathLoader::loadTrustPath(json_decode($value, true)), + set: fn (TrustPath|null $value) => json_encode($value), + ); + } + + public function getPublicKeyCredentialDescriptor(): PublicKeyCredentialDescriptor + { + return new PublicKeyCredentialDescriptor($this->type, $this->public_key_id, $this->transports); + } + + public function getPublicKeyCredentialSource(): PublicKeyCredentialSource + { + return new PublicKeyCredentialSource( + $this->public_key_id, + $this->type, + $this->transports, + $this->attestation_type, + $this->trust_path, + $this->aaguid, + $this->public_key, + $this->user_handle, + $this->counter + ); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * Returns a PSR17 Request factory to be used by different Webauthn tooling. + */ + public static function getPsrRequestFactory(Request $request): ServerRequestInterface + { + $factory = new Psr17Factory(); + + $httpFactory = new PsrHttpFactory($factory, $factory, $factory, $factory); + + return $httpFactory->createRequest($request); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 6ffe07091..d4036752c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -9,6 +9,7 @@ use Illuminate\Validation\Rules\In; use Illuminate\Auth\Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Builder; +use Webauthn\PublicKeyCredentialUserEntity; use Pterodactyl\Models\Traits\HasAccessTokens; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Database\Eloquent\Casts\Attribute; @@ -51,6 +52,8 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; * @property int|null $notifications_count * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\RecoveryToken[] $recoveryTokens * @property int|null $recovery_tokens_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\SecurityKey[] $securityKeys + * @property int|null $security_keys_count * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers * @property int|null $servers_count * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\UserSSHKey[] $sshKeys @@ -276,6 +279,11 @@ class User extends Model implements return $this->hasMany(UserSSHKey::class); } + public function securityKeys(): HasMany + { + return $this->hasMany(SecurityKey::class); + } + /** * Returns all the servers that a user can access by way of being the owner of the * server, or because they are assigned as a subuser for that server. @@ -290,4 +298,17 @@ class User extends Model implements }) ->groupBy('servers.id'); } + + public function toPublicKeyCredentialEntity(): PublicKeyCredentialUserEntity + { + return PublicKeyCredentialUserEntity::create($this->username, $this->uuid, $this->email); + } + + /** + * Returns true if the user has two-factor authentication enabled. + */ + public function has2FAEnabled(): bool + { + return $this->use_totp || $this->securityKeys->isNotEmpty(); + } } diff --git a/app/Repositories/SecurityKeys/PublicKeyCredentialSourceRepository.php b/app/Repositories/SecurityKeys/PublicKeyCredentialSourceRepository.php new file mode 100644 index 000000000..c2076d53a --- /dev/null +++ b/app/Repositories/SecurityKeys/PublicKeyCredentialSourceRepository.php @@ -0,0 +1,68 @@ +user = $user; + } + + /** + * Find a single hardware security token for a user by using the credential ID. + */ + public function findOneByCredentialId(string $id): ?PublicKeyCredentialSource + { + /** @var \Pterodactyl\Models\SecurityKey $key */ + $key = $this->user->securityKeys() + ->where('public_key_id', base64_encode($id)) + ->first(); + + return optional($key)->getPublicKeyCredentialSource(); + } + + /** + * Find all the hardware tokens that exist for the user using the given + * entity handle. + */ + public function findAllForUserEntity(PublicKeyCredentialUserEntity $entity): array + { + $results = $this->user->securityKeys() + ->where('user_handle', $entity->getId()) + ->get(); + + return $results->map(function (SecurityKey $key) { + return $key->getPublicKeyCredentialSource(); + })->values()->toArray(); + } + + /** + * Save a credential to the database and link it with the user. + * + * @throws \Throwable + */ + public function saveCredentialSource(PublicKeyCredentialSource $source): void + { + // no-op — we handle creation of the keys in StoreSecurityKeyService + // + // If you put logic in here it is triggered on each login. + } + + /** + * Returns a new instance of the repository with the provided user attached. + */ + public static function factory(User $user): self + { + return Container::getInstance()->make(static::class, ['user' => $user]); + } +} diff --git a/app/Repositories/SecurityKeys/WebauthnServerRepository.php b/app/Repositories/SecurityKeys/WebauthnServerRepository.php new file mode 100644 index 000000000..935cd2cda --- /dev/null +++ b/app/Repositories/SecurityKeys/WebauthnServerRepository.php @@ -0,0 +1,161 @@ +publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository; + + $this->rpEntity = new PublicKeyCredentialRpEntity(config('app.name'), trim($url, '/')); + $this->credentialLoader = new PublicKeyCredentialLoader(new AttestationObjectLoader(new AttestationStatementSupportManager())); + $this->assertionValidator = new AuthenticatorAssertionResponseValidator( + $this->publicKeyCredentialSourceRepository, + null, + ExtensionOutputCheckerHandler::create(), + AlgorithmManager::create(), + ); + $this->attestationValidator = new AuthenticatorAttestationResponseValidator( + new AttestationStatementSupportManager(), + $this->publicKeyCredentialSourceRepository, + null, + new ExtensionOutputCheckerHandler(), + ); + } + + /** + * @throws \Webauthn\Exception\InvalidDataException + */ + public function getPublicKeyCredentialCreationOptions(User $user): PublicKeyCredentialCreationOptions + { + $excluded = $user->securityKeys->map(function (SecurityKey $key) { + return $key->getPublicKeyCredentialDescriptor(); + })->values()->toArray(); + + $challenge = Str::random(16); + + return (new PublicKeyCredentialCreationOptions( + $this->rpEntity, + $user->toPublicKeyCredentialEntity(), + $challenge, + [ + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES256), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES256K), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES384), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES512), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_RS256), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_RS384), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_RS512), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_PS256), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_PS384), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_PS512), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ED256), + PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ED512), + ], + )) + ->setTimeout(30_000) + ->excludeCredentials(...$excluded) + ->setAttestation(PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE) + ->setAuthenticatorSelection(AuthenticatorSelectionCriteria::create()); + } + + /** + * @throws \Webauthn\Exception\InvalidDataException + */ + public function generatePublicKeyCredentialRequestOptions(User $user): PublicKeyCredentialRequestOptions + { + $allowedCredentials = $user->securityKeys->map(function (SecurityKey $key) { + return $key->getPublicKeyCredentialDescriptor(); + })->values()->toArray(); + + return (new PublicKeyCredentialRequestOptions(Str::random(32))) + ->allowCredentials(...$allowedCredentials) + ->setUserVerification(PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED); + } + + /** + * @throws \Throwable + * @throws \JsonException + */ + public function loadAndCheckAssertionResponse( + User $user, + array $data, + PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, + ServerRequestInterface $request + ): PublicKeyCredentialSource { + $credential = $this->credentialLoader->loadArray($data); + + $authenticatorAssertionResponse = $credential->getResponse(); + if (!$authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) { + // TODO + throw new \Exception(''); + } + + return $this->assertionValidator->check( + $credential->getRawId(), + $authenticatorAssertionResponse, + $publicKeyCredentialRequestOptions, + $request, + null, // TODO: use handle? +// $user->toPublicKeyCredentialEntity() + ); + } + + /** + * Register a new security key for a user. + * + * @throws \Throwable + * @throws \JsonException + */ + public function loadAndCheckAttestationResponse( + User $user, + array $data, + PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, + ServerRequestInterface $request + ): PublicKeyCredentialSource { + $credential = $this->credentialLoader->loadArray($data); + + $authenticatorAttestationResponse = $credential->getResponse(); + if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) { + // TODO + throw new \Exception(''); + } + + return $this->attestationValidator->check( + $authenticatorAttestationResponse, + $publicKeyCredentialCreationOptions, + $request, + ); + } +} diff --git a/app/Services/Databases/DeployServerDatabaseService.php b/app/Services/Databases/DeployServerDatabaseService.php index ec678e40a..f10473bf1 100644 --- a/app/Services/Databases/DeployServerDatabaseService.php +++ b/app/Services/Databases/DeployServerDatabaseService.php @@ -38,7 +38,9 @@ class DeployServerDatabaseService throw new NoSuitableDatabaseHostException(); } - $databaseHostId = $hosts->random()->id; + /** @var \Pterodactyl\Models\DatabaseHost $databaseHost */ + $databaseHost = $hosts->random(); + $databaseHostId = $databaseHost->id; } return $this->managementService->create($server, [ diff --git a/app/Services/Users/SecurityKeys/CreatePublicKeyCredentialService.php b/app/Services/Users/SecurityKeys/CreatePublicKeyCredentialService.php new file mode 100644 index 000000000..2fbf7668e --- /dev/null +++ b/app/Services/Users/SecurityKeys/CreatePublicKeyCredentialService.php @@ -0,0 +1,25 @@ +webauthnServerRepository = $webauthnServerRepository; + } + + /** + * @throws \Webauthn\Exception\InvalidDataException + */ + public function handle(User $user): PublicKeyCredentialCreationOptions + { + return $this->webauthnServerRepository->getPublicKeyCredentialCreationOptions($user); + } +} diff --git a/app/Services/Users/SecurityKeys/StoreSecurityKeyService.php b/app/Services/Users/SecurityKeys/StoreSecurityKeyService.php new file mode 100644 index 000000000..c7fc74038 --- /dev/null +++ b/app/Services/Users/SecurityKeys/StoreSecurityKeyService.php @@ -0,0 +1,79 @@ +request = $request; + + return $this; + } + + /** + * Sets the security key's name. If not provided a random string will be used. + */ + public function setKeyName(?string $name): self + { + $this->keyName = $name; + + return $this; + } + + /** + * Validates and stores a new hardware security key on a user's account. + * + * @throws \Throwable + */ + public function handle(User $user, array $registration, PublicKeyCredentialCreationOptions $options): SecurityKey + { + Assert::notNull($this->request, 'A request interface must be set on the service before it can be called.'); + + $source = $this->webauthnServerRepository->loadAndCheckAttestationResponse($user, $registration, $options, $this->request); + + // Unfortunately this repository interface doesn't define a response — it is explicitly + // void — so we need to just query the database immediately after this to pull the information + // we just stored to return to the caller. + /** @var \Pterodactyl\Models\SecurityKey $key */ + $key = $user->securityKeys()->make()->forceFill([ + 'uuid' => Uuid::uuid4(), + 'name' => $this->keyName ?? 'Security Key (' . Str::random() . ')', + 'public_key_id' => $source->getPublicKeyCredentialId(), + 'public_key' => $source->getCredentialPublicKey(), + 'aaguid' => $source->getAaguid(), + 'type' => $source->getType(), + 'transports' => $source->getTransports(), + 'attestation_type' => $source->getAttestationType(), + 'trust_path' => $source->getTrustPath(), + 'user_handle' => $user->uuid, + 'counter' => $source->getCounter(), + 'other_ui' => $source->getOtherUI(), + ]); + + $key->saveOrFail(); + + return $key; + } +} diff --git a/app/Transformers/Api/Client/SecurityKeyTransformer.php b/app/Transformers/Api/Client/SecurityKeyTransformer.php new file mode 100644 index 000000000..48ee6ffc2 --- /dev/null +++ b/app/Transformers/Api/Client/SecurityKeyTransformer.php @@ -0,0 +1,26 @@ + $model->uuid, + 'name' => $model->name, + 'type' => $model->type, + 'public_key_id' => base64_encode($model->public_key_id), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), + ]; + } +} diff --git a/composer.json b/composer.json index cf82119e7..c0881ddf0 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } ], "require": { - "php": "^8.0.2 || ^8.1 || ^8.2", + "php": "^8.1 || ^8.2", "ext-json": "*", "ext-mbstring": "*", "ext-pdo": "*", @@ -38,6 +38,7 @@ "league/flysystem-aws-s3-v3": "~3.10", "league/flysystem-memory": "~3.10", "matriphe/iso-639": "~1.2", + "nyholm/psr7": "~1.5", "phpseclib/phpseclib": "~3.0", "pragmarx/google2fa": "~8.0", "predis/predis": "~2.0", @@ -49,7 +50,9 @@ "symfony/http-client": "~6.0", "symfony/mailgun-mailer": "~6.0", "symfony/postmark-mailer": "~6.0", + "symfony/psr-http-message-bridge": "~2.1", "symfony/yaml": "~6.0", + "web-auth/webauthn-lib": "~4.3", "webmozart/assert": "~1.11" }, "require-dev": { @@ -101,7 +104,7 @@ "preferred-install": "dist", "sort-packages": true, "platform": { - "php": "8.0.2" + "php": "8.1.0" } } } diff --git a/composer.lock b/composer.lock index 4ec57b684..22b2a84df 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a6235c87dc288858ed2c6e8413ab868a", + "content-hash": "b026679e1f288c2f9e67f84d1bba6d66", "packages": [ { "name": "aws/aws-crt-php", @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.253.2", + "version": "3.257.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "0f0e24bfae22edcdd62bcaedaff9610f8a328952" + "reference": "2511f952db0717407df0c4220068c010ccaa2de2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0f0e24bfae22edcdd62bcaedaff9610f8a328952", - "reference": "0f0e24bfae22edcdd62bcaedaff9610f8a328952", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2511f952db0717407df0c4220068c010ccaa2de2", + "reference": "2511f952db0717407df0c4220068c010ccaa2de2", "shasum": "" }, "require": { @@ -146,9 +146,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.253.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.257.2" }, - "time": "2022-12-14T19:25:13+00:00" + "time": "2023-01-17T19:19:40+00:00" }, { "name": "brick/math", @@ -376,16 +376,16 @@ }, { "name": "doctrine/dbal", - "version": "3.5.1", + "version": "3.5.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5" + "reference": "88fa7e5189fd5ec6682477044264dc0ed4e3aa1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/f38ee8aaca2d58ee88653cb34a6a3880c23f38a5", - "reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/88fa7e5189fd5ec6682477044264dc0ed4e3aa1e", + "reference": "88fa7e5189fd5ec6682477044264dc0ed4e3aa1e", "shasum": "" }, "require": { @@ -398,16 +398,16 @@ "psr/log": "^1|^2|^3" }, "require-dev": { - "doctrine/coding-standard": "10.0.0", - "jetbrains/phpstorm-stubs": "2022.2", - "phpstan/phpstan": "1.8.10", + "doctrine/coding-standard": "11.0.0", + "jetbrains/phpstorm-stubs": "2022.3", + "phpstan/phpstan": "1.9.4", "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "9.5.25", - "psalm/plugin-phpunit": "0.17.0", + "phpunit/phpunit": "9.5.27", + "psalm/plugin-phpunit": "0.18.4", "squizlabs/php_codesniffer": "3.7.1", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4|^5.4|^6.0", - "vimeo/psalm": "4.29.0" + "vimeo/psalm": "4.30.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -467,7 +467,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.5.1" + "source": "https://github.com/doctrine/dbal/tree/3.5.3" }, "funding": [ { @@ -483,7 +483,7 @@ "type": "tidelift" } ], - "time": "2022-10-24T07:26:18+00:00" + "time": "2023-01-12T10:21:44+00:00" }, { "name": "doctrine/deprecations", @@ -530,30 +530,29 @@ }, { "name": "doctrine/event-manager", - "version": "1.2.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520" + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/95aa4cb529f1e96576f3fda9f5705ada4056a520", - "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3 || ^1", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "conflict": { "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.8", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.24" + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.28" }, "type": "library", "autoload": { @@ -602,7 +601,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.2.0" + "source": "https://github.com/doctrine/event-manager/tree/2.0.0" }, "funding": [ { @@ -618,7 +617,7 @@ "type": "tidelift" } ], - "time": "2022-10-12T20:51:15+00:00" + "time": "2022-10-12T20:59:15+00:00" }, { "name": "doctrine/inflector", @@ -713,31 +712,33 @@ }, { "name": "doctrine/lexer", - "version": "1.2.3", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.0", "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9.0", + "doctrine/coding-standard": "^9 || ^10", "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Doctrine\\Common\\Lexer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -769,7 +770,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" + "source": "https://github.com/doctrine/lexer/tree/2.1.0" }, "funding": [ { @@ -785,7 +786,7 @@ "type": "tidelift" } ], - "time": "2022-02-28T11:07:21+00:00" + "time": "2022-12-14T08:49:07+00:00" }, { "name": "dragonmantank/cron-expression", @@ -850,25 +851,24 @@ }, { "name": "egulias/email-validator", - "version": "3.2.1", + "version": "3.2.5", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715" + "reference": "b531a2311709443320c786feb4519cfaf94af796" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/f88dcf4b14af14a98ad96b14b2b317969eab6715", - "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b531a2311709443320c786feb4519cfaf94af796", + "reference": "b531a2311709443320c786feb4519cfaf94af796", "shasum": "" }, "require": { - "doctrine/lexer": "^1.2", + "doctrine/lexer": "^1.2|^2", "php": ">=7.2", "symfony/polyfill-intl-idn": "^1.15" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^8.5.8|^9.3.3", "vimeo/psalm": "^4" }, @@ -906,7 +906,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/3.2.1" + "source": "https://github.com/egulias/EmailValidator/tree/3.2.5" }, "funding": [ { @@ -914,7 +914,83 @@ "type": "github" } ], - "time": "2022-06-18T20:57:19+00:00" + "time": "2023-01-02T17:26:14+00:00" + }, + { + "name": "fgrosse/phpasn1", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/fgrosse/PHPASN1.git", + "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b", + "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "~2.0", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "suggest": { + "ext-bcmath": "BCmath is the fallback extension for big integer calculations", + "ext-curl": "For loading OID information from the web if they have not bee defined statically", + "ext-gmp": "GMP is the preferred extension for big integer calculations", + "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "FG\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Friedrich Große", + "email": "friedrich.grosse@gmail.com", + "homepage": "https://github.com/FGrosse", + "role": "Author" + }, + { + "name": "All contributors", + "homepage": "https://github.com/FGrosse/PHPASN1/contributors" + } + ], + "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", + "homepage": "https://github.com/FGrosse/PHPASN1", + "keywords": [ + "DER", + "asn.1", + "asn1", + "ber", + "binary", + "decoding", + "encoding", + "x.509", + "x.690", + "x509", + "x690" + ], + "support": { + "issues": "https://github.com/fgrosse/PHPASN1/issues", + "source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0" + }, + "abandoned": true, + "time": "2022-12-19T11:08:26+00:00" }, { "name": "fruitcake/php-cors", @@ -1513,27 +1589,28 @@ }, { "name": "laravel/framework", - "version": "v9.43.0", + "version": "v9.48.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "011f2e1d49a11c22519a7899b46ddf3bc5b0f40b" + "reference": "c78ae7aeb0cbcb1a205050d3592247ba07f5b711" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/011f2e1d49a11c22519a7899b46ddf3bc5b0f40b", - "reference": "011f2e1d49a11c22519a7899b46ddf3bc5b0f40b", + "url": "https://api.github.com/repos/laravel/framework/zipball/c78ae7aeb0cbcb1a205050d3592247ba07f5b711", + "reference": "c78ae7aeb0cbcb1a205050d3592247ba07f5b711", "shasum": "" }, "require": { + "brick/math": "^0.10.2", "doctrine/inflector": "^2.0", "dragonmantank/cron-expression": "^3.3.2", - "egulias/email-validator": "^3.2.1", + "egulias/email-validator": "^3.2.1|^4.0", "ext-mbstring": "*", "ext-openssl": "*", "fruitcake/php-cors": "^1.2", "laravel/serializable-closure": "^1.2.2", - "league/commonmark": "^2.2", + "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", "monolog/monolog": "^2.0", "nesbot/carbon": "^2.62.1", @@ -1542,7 +1619,7 @@ "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "ramsey/uuid": "^4.2.2", + "ramsey/uuid": "^4.7", "symfony/console": "^6.0.9", "symfony/error-handler": "^6.0", "symfony/finder": "^6.0", @@ -1603,7 +1680,7 @@ "ably/ably-php": "^1.0", "aws/aws-sdk-php": "^3.235.5", "doctrine/dbal": "^2.13.3|^3.1.4", - "fakerphp/faker": "^1.9.2", + "fakerphp/faker": "^1.21", "guzzlehttp/guzzle": "^7.5", "league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-ftp": "^3.0", @@ -1611,12 +1688,14 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^7.11", + "orchestra/testbench-core": "^7.16", "pda/pheanstalk": "^4.0", + "phpstan/phpdoc-parser": "^1.15", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^9.5.8", "predis/predis": "^1.1.9|^2.0.2", - "symfony/cache": "^6.0" + "symfony/cache": "^6.0", + "symfony/http-client": "^6.0" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", @@ -1695,24 +1774,24 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-12-06T14:26:07+00:00" + "time": "2023-01-17T15:06:19+00:00" }, { "name": "laravel/helpers", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/laravel/helpers.git", - "reference": "c28b0ccd799d58564c41a62395ac9511a1e72931" + "reference": "4dd0f9436d3911611622a6ced8329a1710576f60" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/helpers/zipball/c28b0ccd799d58564c41a62395ac9511a1e72931", - "reference": "c28b0ccd799d58564c41a62395ac9511a1e72931", + "url": "https://api.github.com/repos/laravel/helpers/zipball/4dd0f9436d3911611622a6ced8329a1710576f60", + "reference": "4dd0f9436d3911611622a6ced8329a1710576f60", "shasum": "" }, "require": { - "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0", + "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0", "php": "^7.1.3|^8.0" }, "require-dev": { @@ -1749,35 +1828,35 @@ "laravel" ], "support": { - "source": "https://github.com/laravel/helpers/tree/v1.5.0" + "source": "https://github.com/laravel/helpers/tree/v1.6.0" }, - "time": "2022-01-12T15:58:51+00:00" + "time": "2023-01-09T14:48:11+00:00" }, { "name": "laravel/sanctum", - "version": "v3.0.1", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "b71e80a3a8e8029e2ec8c1aa814b999609ce16dc" + "reference": "d09d69bac55708fcd4a3b305d760e673d888baf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/b71e80a3a8e8029e2ec8c1aa814b999609ce16dc", - "reference": "b71e80a3a8e8029e2ec8c1aa814b999609ce16dc", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/d09d69bac55708fcd4a3b305d760e673d888baf9", + "reference": "d09d69bac55708fcd4a3b305d760e673d888baf9", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^9.21", - "illuminate/contracts": "^9.21", - "illuminate/database": "^9.21", - "illuminate/support": "^9.21", + "illuminate/console": "^9.21|^10.0", + "illuminate/contracts": "^9.21|^10.0", + "illuminate/database": "^9.21|^10.0", + "illuminate/support": "^9.21|^10.0", "php": "^8.0.2" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^7.0", + "orchestra/testbench": "^7.0|^8.0", "phpunit/phpunit": "^9.3" }, "type": "library", @@ -1816,7 +1895,7 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2022-07-29T21:33:30+00:00" + "time": "2023-01-13T15:41:49+00:00" }, { "name": "laravel/serializable-closure", @@ -1880,22 +1959,22 @@ }, { "name": "laravel/tinker", - "version": "v2.7.3", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "5062061b4924af3392225dd482ca7b4d85d8b8ef" + "reference": "74d0b287cc4ae65d15c368dd697aae71d62a73ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/5062061b4924af3392225dd482ca7b4d85d8b8ef", - "reference": "5062061b4924af3392225dd482ca7b4d85d8b8ef", + "url": "https://api.github.com/repos/laravel/tinker/zipball/74d0b287cc4ae65d15c368dd697aae71d62a73ad", + "reference": "74d0b287cc4ae65d15c368dd697aae71d62a73ad", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0|^9.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0", + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", "php": "^7.2.5|^8.0", "psy/psysh": "^0.10.4|^0.11.1", "symfony/var-dumper": "^4.3.4|^5.0|^6.0" @@ -1905,7 +1984,7 @@ "phpunit/phpunit": "^8.5.8|^9.3.3" }, "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0)." + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0)." }, "type": "library", "extra": { @@ -1942,33 +2021,33 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.7.3" + "source": "https://github.com/laravel/tinker/tree/v2.8.0" }, - "time": "2022-11-09T15:11:38+00:00" + "time": "2023-01-10T18:03:30+00:00" }, { "name": "laravel/ui", - "version": "v4.1.1", + "version": "v4.2.0", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "ac94e596ffd39c63cfa41f5407b765b07df97483" + "reference": "810adddcf4e2538b0d1ed470c3a5220ffe761370" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/ac94e596ffd39c63cfa41f5407b765b07df97483", - "reference": "ac94e596ffd39c63cfa41f5407b765b07df97483", + "url": "https://api.github.com/repos/laravel/ui/zipball/810adddcf4e2538b0d1ed470c3a5220ffe761370", + "reference": "810adddcf4e2538b0d1ed470c3a5220ffe761370", "shasum": "" }, "require": { - "illuminate/console": "^9.21", - "illuminate/filesystem": "^9.21", - "illuminate/support": "^9.21", - "illuminate/validation": "^9.21", + "illuminate/console": "^9.21|^10.0", + "illuminate/filesystem": "^9.21|^10.0", + "illuminate/support": "^9.21|^10.0", + "illuminate/validation": "^9.21|^10.0", "php": "^8.0" }, "require-dev": { - "orchestra/testbench": "^7.0" + "orchestra/testbench": "^7.0|^8.0" }, "type": "library", "extra": { @@ -2003,37 +2082,40 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v4.1.1" + "source": "https://github.com/laravel/ui/tree/v4.2.0" }, - "time": "2022-12-05T15:09:21+00:00" + "time": "2023-01-10T12:13:29+00:00" }, { "name": "lcobucci/clock", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/lcobucci/clock.git", - "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3" + "reference": "c7aadcd6fd97ed9e199114269c0be3f335e38876" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/fb533e093fd61321bfcbac08b131ce805fe183d3", - "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/c7aadcd6fd97ed9e199114269c0be3f335e38876", + "reference": "c7aadcd6fd97ed9e199114269c0be3f335e38876", "shasum": "" }, "require": { - "php": "^8.0", - "stella-maris/clock": "^0.1.4" + "php": "~8.1.0 || ~8.2.0", + "stella-maris/clock": "^0.1.7" + }, + "provide": { + "psr/clock-implementation": "1.0" }, "require-dev": { "infection/infection": "^0.26", - "lcobucci/coding-standard": "^8.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^9.5" + "lcobucci/coding-standard": "^9.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-deprecation-rules": "^1.1.1", + "phpstan/phpstan-phpunit": "^1.3.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^9.5.27" }, "type": "library", "autoload": { @@ -2054,7 +2136,7 @@ "description": "Yet another clock abstraction", "support": { "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/2.2.0" + "source": "https://github.com/lcobucci/clock/tree/2.3.0" }, "funding": [ { @@ -2066,20 +2148,20 @@ "type": "patreon" } ], - "time": "2022-04-19T19:34:17+00:00" + "time": "2022-12-19T14:38:11+00:00" }, { "name": "lcobucci/jwt", - "version": "4.2.1", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "72ac6d807ee51a70ad376ee03a2387e8646e10f3" + "reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/72ac6d807ee51a70ad376ee03a2387e8646e10f3", - "reference": "72ac6d807ee51a70ad376ee03a2387e8646e10f3", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/4d7de2fe0d51a96418c0d04004986e410e87f6b4", + "reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4", "shasum": "" }, "require": { @@ -2088,7 +2170,7 @@ "ext-mbstring": "*", "ext-openssl": "*", "ext-sodium": "*", - "lcobucci/clock": "^2.0", + "lcobucci/clock": "^2.0 || ^3.0", "php": "^7.4 || ^8.0" }, "require-dev": { @@ -2128,7 +2210,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/4.2.1" + "source": "https://github.com/lcobucci/jwt/tree/4.3.0" }, "funding": [ { @@ -2140,7 +2222,7 @@ "type": "patreon" } ], - "time": "2022-08-19T23:14:07+00:00" + "time": "2023-01-02T13:28:00+00:00" }, { "name": "league/commonmark", @@ -2332,16 +2414,16 @@ }, { "name": "league/flysystem", - "version": "3.11.0", + "version": "3.12.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "7e423e5dd240a60adfab9bde058d7668863b7731" + "reference": "b934123c1f11ada6363d057d691e3065fa6d6d49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7e423e5dd240a60adfab9bde058d7668863b7731", - "reference": "7e423e5dd240a60adfab9bde058d7668863b7731", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/b934123c1f11ada6363d057d691e3065fa6d6d49", + "reference": "b934123c1f11ada6363d057d691e3065fa6d6d49", "shasum": "" }, "require": { @@ -2358,7 +2440,7 @@ "require-dev": { "async-aws/s3": "^1.5", "async-aws/simple-s3": "^1.1", - "aws/aws-sdk-php": "^3.198.1", + "aws/aws-sdk-php": "^3.220.0", "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", @@ -2403,7 +2485,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.11.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.12.1" }, "funding": [ { @@ -2419,24 +2501,24 @@ "type": "tidelift" } ], - "time": "2022-12-02T14:39:57+00:00" + "time": "2023-01-06T16:34:48+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.10.3", + "version": "3.12.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "f593bf91f94f2adf4f71513d29f1dfa693f2f640" + "reference": "ea100348d497585687e4ad487bf150b0d766b46d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/f593bf91f94f2adf4f71513d29f1dfa693f2f640", - "reference": "f593bf91f94f2adf4f71513d29f1dfa693f2f640", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/ea100348d497585687e4ad487bf150b0d766b46d", + "reference": "ea100348d497585687e4ad487bf150b0d766b46d", "shasum": "" }, "require": { - "aws/aws-sdk-php": "^3.132.4", + "aws/aws-sdk-php": "^3.220.0", "league/flysystem": "^3.10.0", "league/mime-type-detection": "^1.0.0", "php": "^8.0.2" @@ -2473,7 +2555,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.10.3" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.12.1" }, "funding": [ { @@ -2489,7 +2571,7 @@ "type": "tidelift" } ], - "time": "2022-10-26T18:15:09+00:00" + "time": "2023-01-06T15:19:01+00:00" }, { "name": "league/flysystem-memory", @@ -2893,16 +2975,16 @@ }, { "name": "nesbot/carbon", - "version": "2.64.0", + "version": "2.65.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "889546413c97de2d05063b8cb7b193c2531ea211" + "reference": "09acf64155c16dc6f580f36569ae89344e9734a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/889546413c97de2d05063b8cb7b193c2531ea211", - "reference": "889546413c97de2d05063b8cb7b193c2531ea211", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/09acf64155c16dc6f580f36569ae89344e9734a3", + "reference": "09acf64155c16dc6f580f36569ae89344e9734a3", "shasum": "" }, "require": { @@ -2991,7 +3073,7 @@ "type": "tidelift" } ], - "time": "2022-11-26T17:36:00+00:00" + "time": "2023-01-06T15:55:01+00:00" }, { "name": "nette/schema", @@ -3142,16 +3224,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.2", + "version": "v4.15.3", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc" + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", "shasum": "" }, "require": { @@ -3192,22 +3274,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" }, - "time": "2022-11-12T15:38:23+00:00" + "time": "2023-01-16T22:05:37+00:00" }, { "name": "nunomaduro/termwind", - "version": "v1.14.2", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "9a8218511eb1a0965629ff820dda25985440aefc" + "reference": "594ab862396c16ead000de0c3c38f4a5cbe1938d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/9a8218511eb1a0965629ff820dda25985440aefc", - "reference": "9a8218511eb1a0965629ff820dda25985440aefc", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/594ab862396c16ead000de0c3c38f4a5cbe1938d", + "reference": "594ab862396c16ead000de0c3c38f4a5cbe1938d", "shasum": "" }, "require": { @@ -3264,7 +3346,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v1.14.2" + "source": "https://github.com/nunomaduro/termwind/tree/v1.15.0" }, "funding": [ { @@ -3280,7 +3362,84 @@ "type": "github" } ], - "time": "2022-10-28T22:51:32+00:00" + "time": "2022-12-20T19:00:15+00:00" + }, + { + "name": "nyholm/psr7", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "f734364e38a876a23be4d906a2a089e1315be18a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/f734364e38a876a23be4d906a2a089e1315be18a", + "reference": "f734364e38a876a23be4d906a2a089e1315be18a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || 8.5 || 9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.5.1" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2022-06-22T07:13:36+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -3399,6 +3558,60 @@ }, "time": "2020-10-15T08:29:30+00:00" }, + { + "name": "php-http/message-factory", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "support": { + "issues": "https://github.com/php-http/message-factory/issues", + "source": "https://github.com/php-http/message-factory/tree/master" + }, + "time": "2015-12-19T14:08:53+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.0", @@ -3476,16 +3689,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.17", + "version": "3.0.18", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "dbc2307d5c69aeb22db136c52e91130d7f2ca761" + "reference": "f28693d38ba21bb0d9f0c411ee5dae2b178201da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/dbc2307d5c69aeb22db136c52e91130d7f2ca761", - "reference": "dbc2307d5c69aeb22db136c52e91130d7f2ca761", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/f28693d38ba21bb0d9f0c411ee5dae2b178201da", + "reference": "f28693d38ba21bb0d9f0c411ee5dae2b178201da", "shasum": "" }, "require": { @@ -3566,7 +3779,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.17" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.18" }, "funding": [ { @@ -3582,7 +3795,7 @@ "type": "tidelift" } ], - "time": "2022-10-24T10:51:50+00:00" + "time": "2022-12-17T18:26:50+00:00" }, { "name": "pragmarx/google2fa", @@ -3638,16 +3851,16 @@ }, { "name": "predis/predis", - "version": "v2.0.3", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "ff59f745815150c65ed388f7d64e7660fe961771" + "reference": "c5b60884e89630f9518a7919f0566db438f0fc9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/ff59f745815150c65ed388f7d64e7660fe961771", - "reference": "ff59f745815150c65ed388f7d64e7660fe961771", + "url": "https://api.github.com/repos/predis/predis/zipball/c5b60884e89630f9518a7919f0566db438f0fc9a", + "reference": "c5b60884e89630f9518a7919f0566db438f0fc9a", "shasum": "" }, "require": { @@ -3657,8 +3870,7 @@ "phpunit/phpunit": "^8.0 || ~9.4.4" }, "suggest": { - "ext-curl": "Allows access to Webdis when paired with phpiredis", - "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + "ext-curl": "Allows access to Webdis when paired with phpiredis" }, "type": "library", "extra": { @@ -3676,16 +3888,16 @@ "MIT" ], "authors": [ - { - "name": "Daniele Alessandri", - "email": "suppakilla@gmail.com", - "homepage": "http://clorophilla.net", - "role": "Creator & Maintainer" - }, { "name": "Till Krüss", "homepage": "https://till.im", "role": "Maintainer" + }, + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net", + "role": "Creator" } ], "description": "A flexible and feature-complete Redis client for PHP.", @@ -3697,7 +3909,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v2.0.3" + "source": "https://github.com/predis/predis/tree/v2.1.1" }, "funding": [ { @@ -3705,7 +3917,7 @@ "type": "github" } ], - "time": "2022-10-11T16:52:29+00:00" + "time": "2023-01-17T20:57:35+00:00" }, { "name": "psr/cache", @@ -4170,16 +4382,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.9", + "version": "v0.11.10", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "1acec99d6684a54ff92f8b548a4e41b566963778" + "reference": "e9eadffbed9c9deb5426fd107faae0452bf20a36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/1acec99d6684a54ff92f8b548a4e41b566963778", - "reference": "1acec99d6684a54ff92f8b548a4e41b566963778", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e9eadffbed9c9deb5426fd107faae0452bf20a36", + "reference": "e9eadffbed9c9deb5426fd107faae0452bf20a36", "shasum": "" }, "require": { @@ -4240,9 +4452,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.9" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.10" }, - "time": "2022-11-06T15:29:46+00:00" + "time": "2022-12-23T17:47:18+00:00" }, { "name": "ralouphie/getallheaders", @@ -4290,42 +4502,52 @@ }, { "name": "ramsey/collection", - "version": "1.2.2", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", - "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", "shasum": "" }, "require": { - "php": "^7.3 || ^8", - "symfony/polyfill-php81": "^1.23" + "php": "^8.1" }, "require-dev": { - "captainhook/captainhook": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "ergebnis/composer-normalize": "^2.6", - "fakerphp/faker": "^1.5", - "hamcrest/hamcrest-php": "^2", - "jangregor/phpstan-prophecy": "^0.8", - "mockery/mockery": "^1.3", + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1", - "phpstan/phpstan": "^0.12.32", - "phpstan/phpstan-mockery": "^0.12.5", - "phpstan/phpstan-phpunit": "^0.12.11", - "phpunit/phpunit": "^8.5 || ^9", - "psy/psysh": "^0.10.4", - "slevomat/coding-standard": "^6.3", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.4" + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" }, "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, "autoload": { "psr-4": { "Ramsey\\Collection\\": "src/" @@ -4353,7 +4575,7 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.2.2" + "source": "https://github.com/ramsey/collection/tree/2.0.0" }, "funding": [ { @@ -4365,27 +4587,27 @@ "type": "tidelift" } ], - "time": "2021-10-10T03:01:02+00:00" + "time": "2022-12-31T21:50:55+00:00" }, { "name": "ramsey/uuid", - "version": "4.6.0", + "version": "4.7.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f" + "reference": "433b2014e3979047db08a17a205f410ba3869cf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/ad63bc700e7d021039e30ce464eba384c4a1d40f", - "reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/433b2014e3979047db08a17a205f410ba3869cf2", + "reference": "433b2014e3979047db08a17a205f410ba3869cf2", "shasum": "" }, "require": { "brick/math": "^0.8.8 || ^0.9 || ^0.10", "ext-json": "*", "php": "^8.0", - "ramsey/collection": "^1.0" + "ramsey/collection": "^1.2 || ^2.0" }, "replace": { "rhumsaa/uuid": "self.version" @@ -4445,7 +4667,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.6.0" + "source": "https://github.com/ramsey/uuid/tree/4.7.3" }, "funding": [ { @@ -4457,29 +4679,29 @@ "type": "tidelift" } ], - "time": "2022-11-05T23:03:38+00:00" + "time": "2023-01-12T18:13:24+00:00" }, { "name": "s1lentium/iptools", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/S1lentium/IPTools.git", - "reference": "f6f8ab6132ca7443bd7cced1681f5066d725fd5f" + "reference": "88be1aaaab3c50fc131ebe778e246215ff006d8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/S1lentium/IPTools/zipball/f6f8ab6132ca7443bd7cced1681f5066d725fd5f", - "reference": "f6f8ab6132ca7443bd7cced1681f5066d725fd5f", + "url": "https://api.github.com/repos/S1lentium/IPTools/zipball/88be1aaaab3c50fc131ebe778e246215ff006d8e", + "reference": "88be1aaaab3c50fc131ebe778e246215ff006d8e", "shasum": "" }, "require": { "ext-bcmath": "*", - "php": ">=5.4.0" + "php": "^8.0" }, "require-dev": { - "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "~1.0" + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^9.0" }, "type": "library", "autoload": { @@ -4510,9 +4732,9 @@ ], "support": { "issues": "https://github.com/S1lentium/IPTools/issues", - "source": "https://github.com/S1lentium/IPTools/tree/master" + "source": "https://github.com/S1lentium/IPTools/tree/v1.2.0" }, - "time": "2018-09-19T06:15:53+00:00" + "time": "2022-08-17T14:28:59+00:00" }, { "name": "spatie/fractalistic", @@ -4656,25 +4878,25 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.13.7", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "4af8e608184471b5568af6265ebb0ca0025c131a" + "reference": "9964e65c318c30577ca1b91469f739d2b381359b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/4af8e608184471b5568af6265ebb0ca0025c131a", - "reference": "4af8e608184471b5568af6265ebb0ca0025c131a", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/9964e65c318c30577ca1b91469f739d2b381359b", + "reference": "9964e65c318c30577ca1b91469f739d2b381359b", "shasum": "" }, "require": { - "illuminate/contracts": "^9.28", + "illuminate/contracts": "^9.28|^10.0", "php": "^8.0" }, "require-dev": { "mockery/mockery": "^1.5", - "orchestra/testbench": "^7.7", + "orchestra/testbench": "^7.7|^8.0", "pestphp/pest": "^1.22", "phpunit/phpunit": "^9.5.24", "spatie/pest-plugin-test-time": "^1.1" @@ -4704,7 +4926,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.13.7" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.14.0" }, "funding": [ { @@ -4712,7 +4934,7 @@ "type": "github" } ], - "time": "2022-11-15T09:10:09+00:00" + "time": "2023-01-10T14:09:55+00:00" }, { "name": "spatie/laravel-query-builder", @@ -4786,6 +5008,190 @@ ], "time": "2022-12-02T21:28:40+00:00" }, + { + "name": "spomky-labs/cbor-php", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/cbor-php.git", + "reference": "de06c1be866bade5270f4cf5ef3b3746ba3f26eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/de06c1be866bade5270f4cf5ef3b3746ba3f26eb", + "reference": "de06c1be866bade5270f4cf5ef3b3746ba3f26eb", + "shasum": "" + }, + "require": { + "brick/math": "^0.9|^0.10", + "ext-mbstring": "*", + "php": ">=8.0" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "ext-json": "*", + "infection/infection": "^0.26", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13", + "roave/security-advisories": "dev-latest", + "symfony/var-dumper": "^6.0", + "symplify/easy-coding-standard": "^11.0" + }, + "suggest": { + "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags", + "ext-gmp": "GMP or BCMath extensions will drastically improve the library performance" + }, + "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/v3.0.1" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2022-06-26T07:20:40+00:00" + }, + { + "name": "spomky-labs/pki-framework", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/pki-framework.git", + "reference": "969f3554c49ba7ed6fa603576f1f205e7d66a00c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/969f3554c49ba7ed6fa603576f1f205e7d66a00c", + "reference": "969f3554c49ba7ed6fa603576f1f205e7d66a00c", + "shasum": "" + }, + "require": { + "brick/math": "^0.10", + "ext-mbstring": "*", + "php": ">=8.1" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "ext-gmp": "*", + "ext-openssl": "*", + "infection/infection": "^0.26", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "rector/rector": "0.15.0", + "roave/security-advisories": "dev-latest", + "symfony/phpunit-bridge": "^6.1", + "symplify/easy-coding-standard": "^11.1", + "thecodingmachine/phpstan-safe-rule": "^1.2" + }, + "suggest": { + "ext-bcmath": "For better performance (or GMP)", + "ext-gmp": "For better performance (or BCMath)", + "ext-openssl": "For OpenSSL based cyphering" + }, + "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" + } + ], + "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.0.1" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2022-12-14T21:48:24+00:00" + }, { "name": "staudenmeir/belongs-to-through", "version": "v2.12.1", @@ -4889,20 +5295,21 @@ }, { "name": "symfony/console", - "version": "v6.0.16", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "be294423f337dda97c810733138c0caec1bb0575" + "reference": "0f579613e771dba2dbb8211c382342a641f5da06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/be294423f337dda97c810733138c0caec1bb0575", - "reference": "be294423f337dda97c810733138c0caec1bb0575", + "url": "https://api.github.com/repos/symfony/console/zipball/0f579613e771dba2dbb8211c382342a641f5da06", + "reference": "0f579613e771dba2dbb8211c382342a641f5da06", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^1.1|^2|^3", "symfony/string": "^5.4|^6.0" @@ -4964,7 +5371,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.0.16" + "source": "https://github.com/symfony/console/tree/v6.2.3" }, "funding": [ { @@ -4980,24 +5387,24 @@ "type": "tidelift" } ], - "time": "2022-11-25T18:58:46+00:00" + "time": "2022-12-28T14:26:22+00:00" }, { "name": "symfony/css-selector", - "version": "v6.0.11", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ab2746acddc4f03a7234c8441822ac5d5c63efe9" + "reference": "ab1df4ba3ded7b724766ba3a6e0eca0418e74f80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab2746acddc4f03a7234c8441822ac5d5c63efe9", - "reference": "ab2746acddc4f03a7234c8441822ac5d5c63efe9", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab1df4ba3ded7b724766ba3a6e0eca0418e74f80", + "reference": "ab1df4ba3ded7b724766ba3a6e0eca0418e74f80", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, "type": "library", "autoload": { @@ -5029,7 +5436,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.0.11" + "source": "https://github.com/symfony/css-selector/tree/v6.2.3" }, "funding": [ { @@ -5045,29 +5452,29 @@ "type": "tidelift" } ], - "time": "2022-06-27T17:10:44+00:00" + "time": "2022-12-28T14:26:22+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.0.2", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -5096,7 +5503,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" }, "funding": [ { @@ -5112,24 +5519,24 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/error-handler", - "version": "v6.0.15", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "f000c166cb3ee32c4c822831a79260a135cd59b5" + "reference": "0926124c95d220499e2baf0fb465772af3a4eddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/f000c166cb3ee32c4c822831a79260a135cd59b5", - "reference": "f000c166cb3ee32c4c822831a79260a135cd59b5", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/0926124c95d220499e2baf0fb465772af3a4eddb", + "reference": "0926124c95d220499e2baf0fb465772af3a4eddb", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "psr/log": "^1|^2|^3", "symfony/var-dumper": "^5.4|^6.0" }, @@ -5167,7 +5574,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.0.15" + "source": "https://github.com/symfony/error-handler/tree/v6.2.3" }, "funding": [ { @@ -5183,24 +5590,24 @@ "type": "tidelift" } ], - "time": "2022-10-28T16:22:58+00:00" + "time": "2022-12-19T14:33:49+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.0.9", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "5c85b58422865d42c6eb46f7693339056db098a8" + "reference": "3ffeb31139b49bf6ef0bc09d1db95eac053388d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5c85b58422865d42c6eb46f7693339056db098a8", - "reference": "5c85b58422865d42c6eb46f7693339056db098a8", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3ffeb31139b49bf6ef0bc09d1db95eac053388d1", + "reference": "3ffeb31139b49bf6ef0bc09d1db95eac053388d1", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/event-dispatcher-contracts": "^2|^3" }, "conflict": { @@ -5250,7 +5657,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.0.9" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.2" }, "funding": [ { @@ -5266,24 +5673,24 @@ "type": "tidelift" } ], - "time": "2022-05-05T16:45:52+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.0.2", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" + "reference": "0782b0b52a737a05b4383d0df35a474303cabdae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0782b0b52a737a05b4383d0df35a474303cabdae", + "reference": "0782b0b52a737a05b4383d0df35a474303cabdae", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, "suggest": { @@ -5292,7 +5699,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -5329,7 +5736,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.0" }, "funding": [ { @@ -5345,24 +5752,27 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/finder", - "version": "v6.0.11", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "09cb683ba5720385ea6966e5e06be2a34f2568b1" + "reference": "81eefbddfde282ee33b437ba5e13d7753211ae8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/09cb683ba5720385ea6966e5e06be2a34f2568b1", - "reference": "09cb683ba5720385ea6966e5e06be2a34f2568b1", + "url": "https://api.github.com/repos/symfony/finder/zipball/81eefbddfde282ee33b437ba5e13d7753211ae8e", + "reference": "81eefbddfde282ee33b437ba5e13d7753211ae8e", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" }, "type": "library", "autoload": { @@ -5390,7 +5800,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.0.11" + "source": "https://github.com/symfony/finder/tree/v6.2.3" }, "funding": [ { @@ -5406,25 +5816,26 @@ "type": "tidelift" } ], - "time": "2022-07-29T07:39:48+00:00" + "time": "2022-12-22T17:55:15+00:00" }, { "name": "symfony/http-client", - "version": "v6.0.16", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "5db1221826d5f841f443d434358d5f82c862c5a9" + "reference": "7054ad466f836309aef511789b9c697bc986d8ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/5db1221826d5f841f443d434358d5f82c862c5a9", - "reference": "5db1221826d5f841f443d434358d5f82c862c5a9", + "url": "https://api.github.com/repos/symfony/http-client/zipball/7054ad466f836309aef511789b9c697bc986d8ce", + "reference": "7054ad466f836309aef511789b9c697bc986d8ce", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/http-client-contracts": "^3", "symfony/service-contracts": "^1.0|^2|^3" }, @@ -5474,7 +5885,7 @@ "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-client/tree/v6.0.16" + "source": "https://github.com/symfony/http-client/tree/v6.2.2" }, "funding": [ { @@ -5490,24 +5901,24 @@ "type": "tidelift" } ], - "time": "2022-11-14T10:09:52+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.0.2", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "4184b9b63af1edaf35b6a7974c6f1f9f33294129" + "reference": "c5f587eb445224ddfeb05b5ee703476742d730bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4184b9b63af1edaf35b6a7974c6f1f9f33294129", - "reference": "4184b9b63af1edaf35b6a7974c6f1f9f33294129", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/c5f587eb445224ddfeb05b5ee703476742d730bf", + "reference": "c5f587eb445224ddfeb05b5ee703476742d730bf", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, "suggest": { "symfony/http-client-implementation": "" @@ -5515,7 +5926,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -5525,7 +5936,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\HttpClient\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5552,7 +5966,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.2.0" }, "funding": [ { @@ -5568,27 +5982,30 @@ "type": "tidelift" } ], - "time": "2022-04-12T16:11:42+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.0.16", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "86eec2c66d00a2dd03d84352cd10b12df73101ec" + "reference": "ddf4dd35de1623e7c02013523e6c2137b67b636f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/86eec2c66d00a2dd03d84352cd10b12df73101ec", - "reference": "86eec2c66d00a2dd03d84352cd10b12df73101ec", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ddf4dd35de1623e7c02013523e6c2137b67b636f", + "reference": "ddf4dd35de1623e7c02013523e6c2137b67b636f", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.1" }, + "conflict": { + "symfony/cache": "<6.2" + }, "require-dev": { "predis/predis": "~1.0", "symfony/cache": "^5.4|^6.0", @@ -5627,7 +6044,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.0.16" + "source": "https://github.com/symfony/http-foundation/tree/v6.2.2" }, "funding": [ { @@ -5643,26 +6060,27 @@ "type": "tidelift" } ], - "time": "2022-11-07T08:07:05+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.0.16", + "version": "v6.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "8ba1344821807ad51f230f0d01e0fa8f366e4abb" + "reference": "74f2e638ec3fa0315443bd85fab7fc8066b77f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8ba1344821807ad51f230f0d01e0fa8f366e4abb", - "reference": "8ba1344821807ad51f230f0d01e0fa8f366e4abb", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/74f2e638ec3fa0315443bd85fab7fc8066b77f83", + "reference": "74f2e638ec3fa0315443bd85fab7fc8066b77f83", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/error-handler": "^5.4|^6.0", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/error-handler": "^6.1", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", "symfony/polyfill-ctype": "^1.8" @@ -5670,9 +6088,9 @@ "conflict": { "symfony/browser-kit": "<5.4", "symfony/cache": "<5.4", - "symfony/config": "<5.4", + "symfony/config": "<6.1", "symfony/console": "<5.4", - "symfony/dependency-injection": "<5.4", + "symfony/dependency-injection": "<6.2", "symfony/doctrine-bridge": "<5.4", "symfony/form": "<5.4", "symfony/http-client": "<5.4", @@ -5689,10 +6107,10 @@ "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", "symfony/browser-kit": "^5.4|^6.0", - "symfony/config": "^5.4|^6.0", + "symfony/config": "^6.1", "symfony/console": "^5.4|^6.0", "symfony/css-selector": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", + "symfony/dependency-injection": "^6.2", "symfony/dom-crawler": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", @@ -5702,6 +6120,7 @@ "symfony/stopwatch": "^5.4|^6.0", "symfony/translation": "^5.4|^6.0", "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/uid": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, "suggest": { @@ -5736,7 +6155,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.0.16" + "source": "https://github.com/symfony/http-kernel/tree/v6.2.4" }, "funding": [ { @@ -5752,37 +6171,42 @@ "type": "tidelift" } ], - "time": "2022-11-28T18:15:44+00:00" + "time": "2022-12-29T19:05:08+00:00" }, { "name": "symfony/mailer", - "version": "v6.0.16", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "aa47b34ab09fa03106d9e156632e4c6176b962da" + "reference": "b355ad81f1d2987c47dcd3b04d5dce669e1e62e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/aa47b34ab09fa03106d9e156632e4c6176b962da", - "reference": "aa47b34ab09fa03106d9e156632e4c6176b962da", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b355ad81f1d2987c47dcd3b04d5dce669e1e62e6", + "reference": "b355ad81f1d2987c47dcd3b04d5dce669e1e62e6", "shasum": "" }, "require": { "egulias/email-validator": "^2.1.10|^3", - "php": ">=8.0.2", + "php": ">=8.1", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", + "symfony/mime": "^6.2", "symfony/service-contracts": "^1.1|^2|^3" }, "conflict": { - "symfony/http-kernel": "<5.4" + "symfony/http-kernel": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", + "symfony/twig-bridge": "<6.2.1" }, "require-dev": { + "symfony/console": "^5.4|^6.0", "symfony/http-client-contracts": "^1.1|^2|^3", - "symfony/messenger": "^5.4|^6.0" + "symfony/messenger": "^6.2", + "symfony/twig-bridge": "^6.2" }, "type": "library", "autoload": { @@ -5810,7 +6234,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.0.16" + "source": "https://github.com/symfony/mailer/tree/v6.2.2" }, "funding": [ { @@ -5826,24 +6250,24 @@ "type": "tidelift" } ], - "time": "2022-11-04T07:39:59+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "symfony/mailgun-mailer", - "version": "v6.0.7", + "version": "v6.2.0", "source": { "type": "git", "url": "https://github.com/symfony/mailgun-mailer.git", - "reference": "f0d032c26683b26f4bc26864e09b1e08fa55226e" + "reference": "c5364fbcf5581ba9eae569db12b380b9255ce238" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/f0d032c26683b26f4bc26864e09b1e08fa55226e", - "reference": "f0d032c26683b26f4bc26864e09b1e08fa55226e", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/c5364fbcf5581ba9eae569db12b380b9255ce238", + "reference": "c5364fbcf5581ba9eae569db12b380b9255ce238", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/mailer": "^5.4|^6.0" }, "require-dev": { @@ -5875,7 +6299,7 @@ "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailgun-mailer/tree/v6.0.7" + "source": "https://github.com/symfony/mailgun-mailer/tree/v6.2.0" }, "funding": [ { @@ -5891,24 +6315,24 @@ "type": "tidelift" } ], - "time": "2022-03-24T17:11:42+00:00" + "time": "2022-10-09T08:55:40+00:00" }, { "name": "symfony/mime", - "version": "v6.0.16", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ad9878bede5707cdf5ff7f5c86d82a921bbbfe1c" + "reference": "8c98bf40406e791043890a163f6f6599b9cfa1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ad9878bede5707cdf5ff7f5c86d82a921bbbfe1c", - "reference": "ad9878bede5707cdf5ff7f5c86d82a921bbbfe1c", + "url": "https://api.github.com/repos/symfony/mime/zipball/8c98bf40406e791043890a163f6f6599b9cfa1ed", + "reference": "8c98bf40406e791043890a163f6f6599b9cfa1ed", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -5917,15 +6341,16 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/mailer": "<5.4", - "symfony/serializer": "<5.4.14|>=6.0,<6.0.14|>=6.1,<6.1.6" + "symfony/serializer": "<6.2" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1", + "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", "symfony/property-info": "^5.4|^6.0", - "symfony/serializer": "^5.4.14|~6.0.14|^6.1.6" + "symfony/serializer": "^6.2" }, "type": "library", "autoload": { @@ -5957,7 +6382,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.0.16" + "source": "https://github.com/symfony/mime/tree/v6.2.2" }, "funding": [ { @@ -5973,7 +6398,7 @@ "type": "tidelift" } ], - "time": "2022-11-28T12:25:56+00:00" + "time": "2022-12-14T16:38:10+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6551,85 +6976,6 @@ ], "time": "2022-11-03T14:55:06+00:00" }, - { - "name": "symfony/polyfill-php81", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "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": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, { "name": "symfony/polyfill-uuid", "version": "v1.27.0", @@ -6714,20 +7060,20 @@ }, { "name": "symfony/postmark-mailer", - "version": "v6.0.7", + "version": "v6.2.0", "source": { "type": "git", "url": "https://github.com/symfony/postmark-mailer.git", - "reference": "8405569233efb0140e55eb6236c9e55693f058ff" + "reference": "5f38d688df43bea507bf0dfe0d0ca6f99221b708" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/postmark-mailer/zipball/8405569233efb0140e55eb6236c9e55693f058ff", - "reference": "8405569233efb0140e55eb6236c9e55693f058ff", + "url": "https://api.github.com/repos/symfony/postmark-mailer/zipball/5f38d688df43bea507bf0dfe0d0ca6f99221b708", + "reference": "5f38d688df43bea507bf0dfe0d0ca6f99221b708", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/mailer": "^5.4|^6.0" }, "require-dev": { @@ -6759,7 +7105,7 @@ "description": "Symfony Postmark Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/postmark-mailer/tree/v6.0.7" + "source": "https://github.com/symfony/postmark-mailer/tree/v6.2.0" }, "funding": [ { @@ -6775,24 +7121,24 @@ "type": "tidelift" } ], - "time": "2022-03-24T17:11:42+00:00" + "time": "2022-04-01T07:15:35+00:00" }, { "name": "symfony/process", - "version": "v6.0.11", + "version": "v6.2.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "44270a08ccb664143dede554ff1c00aaa2247a43" + "reference": "ba6e55359f8f755fe996c58a81e00eaa67a35877" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/44270a08ccb664143dede554ff1c00aaa2247a43", - "reference": "44270a08ccb664143dede554ff1c00aaa2247a43", + "url": "https://api.github.com/repos/symfony/process/zipball/ba6e55359f8f755fe996c58a81e00eaa67a35877", + "reference": "ba6e55359f8f755fe996c58a81e00eaa67a35877", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, "type": "library", "autoload": { @@ -6820,7 +7166,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.0.11" + "source": "https://github.com/symfony/process/tree/v6.2.0" }, "funding": [ { @@ -6836,35 +7182,123 @@ "type": "tidelift" } ], - "time": "2022-06-27T17:10:44+00:00" + "time": "2022-11-02T09:08:04+00:00" }, { - "name": "symfony/routing", - "version": "v6.0.15", + "name": "symfony/psr-http-message-bridge", + "version": "v2.1.4", "source": { "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "3b7384fad32c6d0e1919b5bd18a69fbcfc383508" + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "a125b93ef378c492e274f217874906fb9babdebb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3b7384fad32c6d0e1919b5bd18a69fbcfc383508", - "reference": "3b7384fad32c6d0e1919b5bd18a69fbcfc383508", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/a125b93ef378c492e274f217874906fb9babdebb", + "reference": "a125b93ef378c492e274f217874906fb9babdebb", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=7.1", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^4.4 || ^5.0 || ^6.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "psr/log": "^1.1 || ^2 || ^3", + "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0", + "symfony/config": "^4.4 || ^5.0 || ^6.0", + "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0", + "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", + "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0", + "symfony/phpunit-bridge": "^5.4@dev || ^6.0" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-main": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "http://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/symfony/psr-http-message-bridge/issues", + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-28T22:46:34+00:00" + }, + { + "name": "symfony/routing", + "version": "v6.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "35fec764f3e2c8c08fb340d275c84bc78ca7e0c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/35fec764f3e2c8c08fb340d275c84bc78ca7e0c9", + "reference": "35fec764f3e2c8c08fb340d275c84bc78ca7e0c9", + "shasum": "" + }, + "require": { + "php": ">=8.1" }, "conflict": { "doctrine/annotations": "<1.12", - "symfony/config": "<5.4", + "symfony/config": "<6.2", "symfony/dependency-injection": "<5.4", "symfony/yaml": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.12", + "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", + "symfony/config": "^6.2", "symfony/dependency-injection": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", @@ -6908,7 +7342,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.0.15" + "source": "https://github.com/symfony/routing/tree/v6.2.3" }, "funding": [ { @@ -6924,24 +7358,24 @@ "type": "tidelift" } ], - "time": "2022-10-18T13:11:57+00:00" + "time": "2022-12-20T16:41:15+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.0.2", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66" + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d78d39c1599bd1188b8e26bb341da52c3c6d8a66", - "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "psr/container": "^2.0" }, "conflict": { @@ -6953,7 +7387,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -6963,7 +7397,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6990,7 +7427,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/service-contracts/tree/v3.2.0" }, "funding": [ { @@ -7006,24 +7443,24 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:17:58+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/string", - "version": "v6.0.15", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "51ac0fa0ccf132a00519b87c97e8f775fa14e771" + "reference": "863219fd713fa41cbcd285a79723f94672faff4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/51ac0fa0ccf132a00519b87c97e8f775fa14e771", - "reference": "51ac0fa0ccf132a00519b87c97e8f775fa14e771", + "url": "https://api.github.com/repos/symfony/string/zipball/863219fd713fa41cbcd285a79723f94672faff4d", + "reference": "863219fd713fa41cbcd285a79723f94672faff4d", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -7035,6 +7472,7 @@ "require-dev": { "symfony/error-handler": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0", + "symfony/intl": "^6.2", "symfony/translation-contracts": "^2.0|^3.0", "symfony/var-exporter": "^5.4|^6.0" }, @@ -7075,7 +7513,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.0.15" + "source": "https://github.com/symfony/string/tree/v6.2.2" }, "funding": [ { @@ -7091,24 +7529,24 @@ "type": "tidelift" } ], - "time": "2022-10-10T09:34:08+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "symfony/translation", - "version": "v6.0.14", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "6f99eb179aee4652c0a7cd7c11f2a870d904330c" + "reference": "a2a15404ef4c15d92c205718eb828b225a144379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/6f99eb179aee4652c0a7cd7c11f2a870d904330c", - "reference": "6f99eb179aee4652c0a7cd7c11f2a870d904330c", + "url": "https://api.github.com/repos/symfony/translation/zipball/a2a15404ef4c15d92c205718eb828b225a144379", + "reference": "a2a15404ef4c15d92c205718eb828b225a144379", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/polyfill-mbstring": "~1.0", "symfony/translation-contracts": "^2.3|^3.0" }, @@ -7124,6 +7562,7 @@ "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { + "nikic/php-parser": "^4.13", "psr/log": "^1|^2|^3", "symfony/config": "^5.4|^6.0", "symfony/console": "^5.4|^6.0", @@ -7133,10 +7572,12 @@ "symfony/http-kernel": "^5.4|^6.0", "symfony/intl": "^5.4|^6.0", "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0", "symfony/service-contracts": "^1.1.2|^2|^3", "symfony/yaml": "^5.4|^6.0" }, "suggest": { + "nikic/php-parser": "To use PhpAstExtractor", "psr/log-implementation": "To use logging capability in translator", "symfony/config": "", "symfony/yaml": "" @@ -7170,7 +7611,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.0.14" + "source": "https://github.com/symfony/translation/tree/v6.2.3" }, "funding": [ { @@ -7186,24 +7627,24 @@ "type": "tidelift" } ], - "time": "2022-10-07T08:02:12+00:00" + "time": "2022-12-23T14:11:11+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.0.2", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "acbfbb274e730e5a0236f619b6168d9dedb3e282" + "reference": "68cce71402305a015f8c1589bfada1280dc64fe7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/acbfbb274e730e5a0236f619b6168d9dedb3e282", - "reference": "acbfbb274e730e5a0236f619b6168d9dedb3e282", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/68cce71402305a015f8c1589bfada1280dc64fe7", + "reference": "68cce71402305a015f8c1589bfada1280dc64fe7", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, "suggest": { "symfony/translation-implementation": "" @@ -7211,7 +7652,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -7221,7 +7662,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\Translation\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7248,7 +7692,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/translation-contracts/tree/v3.2.0" }, "funding": [ { @@ -7264,24 +7708,24 @@ "type": "tidelift" } ], - "time": "2022-06-27T17:10:44+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/uid", - "version": "v6.0.13", + "version": "v6.2.0", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "db426b27173f5e2d8b960dd10fa8ce19ea9ca5f3" + "reference": "4f9f537e57261519808a7ce1d941490736522bbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/db426b27173f5e2d8b960dd10fa8ce19ea9ca5f3", - "reference": "db426b27173f5e2d8b960dd10fa8ce19ea9ca5f3", + "url": "https://api.github.com/repos/symfony/uid/zipball/4f9f537e57261519808a7ce1d941490736522bbc", + "reference": "4f9f537e57261519808a7ce1d941490736522bbc", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/polyfill-uuid": "^1.15" }, "require-dev": { @@ -7322,7 +7766,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.0.13" + "source": "https://github.com/symfony/uid/tree/v6.2.0" }, "funding": [ { @@ -7338,24 +7782,24 @@ "type": "tidelift" } ], - "time": "2022-09-09T09:33:56+00:00" + "time": "2022-10-09T08:55:40+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.0.14", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "72af925ddd41ca0372d166d004bc38a00c4608cc" + "reference": "fdbadd4803bc3c96ef89238c9c9e2ebe424ec2e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/72af925ddd41ca0372d166d004bc38a00c4608cc", - "reference": "72af925ddd41ca0372d166d004bc38a00c4608cc", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fdbadd4803bc3c96ef89238c9c9e2ebe424ec2e0", + "reference": "fdbadd4803bc3c96ef89238c9c9e2ebe424ec2e0", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { @@ -7410,7 +7854,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.14" + "source": "https://github.com/symfony/var-dumper/tree/v6.2.3" }, "funding": [ { @@ -7426,24 +7870,24 @@ "type": "tidelift" } ], - "time": "2022-10-07T08:02:12+00:00" + "time": "2022-12-22T17:55:15+00:00" }, { "name": "symfony/yaml", - "version": "v6.0.16", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "eb85bd1b0b297e976f3ada52ad239ef80b4dbd0b" + "reference": "6ed8243aa5f2cb5a57009f826b5e7fb3c4200cf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/eb85bd1b0b297e976f3ada52ad239ef80b4dbd0b", - "reference": "eb85bd1b0b297e976f3ada52ad239ef80b4dbd0b", + "url": "https://api.github.com/repos/symfony/yaml/zipball/6ed8243aa5f2cb5a57009f826b5e7fb3c4200cf3", + "reference": "6ed8243aa5f2cb5a57009f826b5e7fb3c4200cf3", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -7484,7 +7928,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.0.16" + "source": "https://github.com/symfony/yaml/tree/v6.2.2" }, "funding": [ { @@ -7500,20 +7944,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T18:58:46+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.5", + "version": "2.2.6", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "4348a3a06651827a27d989ad1d13efec6bb49b19" + "reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/4348a3a06651827a27d989ad1d13efec6bb49b19", - "reference": "4348a3a06651827a27d989ad1d13efec6bb49b19", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/c42125b83a4fa63b187fdf29f9c93cb7733da30c", + "reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c", "shasum": "" }, "require": { @@ -7551,9 +7995,9 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.5" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.6" }, - "time": "2022-09-12T13:28:28+00:00" + "time": "2023-01-03T09:29:04+00:00" }, { "name": "vlucas/phpdotenv", @@ -7713,6 +8157,241 @@ ], "time": "2022-03-08T17:03:00+00:00" }, + { + "name": "web-auth/cose-lib", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/web-auth/cose-lib.git", + "reference": "fb53f1030e483235312957612806d69d846d66dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/fb53f1030e483235312957612806d69d846d66dd", + "reference": "fb53f1030e483235312957612806d69d846d66dd", + "shasum": "" + }, + "require": { + "brick/math": "^0.9|^0.10", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "php": ">=8.1", + "spomky-labs/pki-framework": "^1.0" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "infection/infection": "^0.26.12", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^1.7", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "^9.5", + "qossmic/deptrac-shim": "^1.0", + "rector/rector": "^0.15", + "symfony/phpunit-bridge": "^6.1", + "symplify/easy-coding-standard": "^11.0" + }, + "suggest": { + "ext-bcmath": "For better performance, please install either GMP (recommended) or BCMath extension", + "ext-gmp": "For better performance, please install either GMP (recommended) or BCMath extension" + }, + "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.1.0" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2022-12-11T15:04:50+00:00" + }, + { + "name": "web-auth/metadata-service", + "version": "4.4.1", + "source": { + "type": "git", + "url": "https://github.com/web-auth/webauthn-metadata-service.git", + "reference": "bd2a01cd94b98c42bbde47ec37b1a308256a041d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/bd2a01cd94b98c42bbde47ec37b1a308256a041d", + "reference": "bd2a01cd94b98c42bbde47ec37b1a308256a041d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "lcobucci/clock": "^2.2", + "paragonie/constant_time_encoding": "^2.6", + "php": ">=8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/log": "^1.0|^2.0|^3.0", + "spomky-labs/pki-framework": "^1.0" + }, + "suggest": { + "psr/log-implementation": "Recommended to receive logs from the library", + "web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources", + "web-token/jwt-signature-algorithm-ecdsa": "Mandatory for fetching Metadata Statement from distant sources" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webauthn\\MetadataService\\": "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/metadata-service/contributors" + } + ], + "description": "Metadata Service for FIDO2/Webauthn", + "homepage": "https://github.com/web-auth", + "keywords": [ + "FIDO2", + "fido", + "webauthn" + ], + "support": { + "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.4.1" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2022-11-07T21:45:07+00:00" + }, + { + "name": "web-auth/webauthn-lib", + "version": "4.4.1", + "source": { + "type": "git", + "url": "https://github.com/web-auth/webauthn-lib.git", + "reference": "2cc0aabe6f93c4d680dd507490fc5699841c7490" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/2cc0aabe6f93c4d680dd507490fc5699841c7490", + "reference": "2cc0aabe6f93c4d680dd507490fc5699841c7490", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "fgrosse/phpasn1": "^2.4", + "paragonie/constant_time_encoding": "^2.6", + "php": ">=8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/log": "^1.0|^2.0|^3.0", + "spomky-labs/cbor-php": "^3.0", + "symfony/uid": "^6.1", + "web-auth/cose-lib": "^4.0.12", + "web-auth/metadata-service": "self.version" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.1" + }, + "suggest": { + "psr/log-implementation": "Recommended to receive logs from the library", + "symfony/event-dispatcher": "Recommended to use dispatched events", + "web-token/jwt-key-mgmt": "Mandatory for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support" + }, + "type": "library", + "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/4.4.1" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2022-11-07T21:45:07+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", @@ -8139,16 +8818,16 @@ }, { "name": "doctrine/annotations", - "version": "1.14.1", + "version": "1.14.2", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "9e034d7a70032d422169f27d8759e8d84abb4f51" + "reference": "ad785217c1e9555a7d6c6c8c9f406395a5e2882b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/9e034d7a70032d422169f27d8759e8d84abb4f51", - "reference": "9e034d7a70032d422169f27d8759e8d84abb4f51", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/ad785217c1e9555a7d6c6c8c9f406395a5e2882b", + "reference": "ad785217c1e9555a7d6c6c8c9f406395a5e2882b", "shasum": "" }, "require": { @@ -8209,36 +8888,36 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.14.1" + "source": "https://github.com/doctrine/annotations/tree/1.14.2" }, - "time": "2022-12-12T12:46:12+00:00" + "time": "2022-12-15T06:48:22+00:00" }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -8265,7 +8944,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -8281,7 +8960,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "fakerphp/faker", @@ -8424,16 +9103,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.13.0", + "version": "v3.13.2", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "a6232229a8309e8811dc751c28b91cb34b2943e1" + "reference": "3952f08a81bd3b1b15e11c3de0b6bf037faa8496" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a6232229a8309e8811dc751c28b91cb34b2943e1", - "reference": "a6232229a8309e8811dc751c28b91cb34b2943e1", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/3952f08a81bd3b1b15e11c3de0b6bf037faa8496", + "reference": "3952f08a81bd3b1b15e11c3de0b6bf037faa8496", "shasum": "" }, "require": { @@ -8501,7 +9180,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.13.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.13.2" }, "funding": [ { @@ -8509,7 +9188,7 @@ "type": "github" } ], - "time": "2022-10-31T19:28:50+00:00" + "time": "2023-01-02T23:53:50+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -8632,22 +9311,22 @@ }, { "name": "laravel/sail", - "version": "v1.16.4", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "72412b14d6f4e73b71b5f3068bdb064184fbb001" + "reference": "a64f78a4ab86c04a4c5de39bea20a8d36ad48a22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/72412b14d6f4e73b71b5f3068bdb064184fbb001", - "reference": "72412b14d6f4e73b71b5f3068bdb064184fbb001", + "url": "https://api.github.com/repos/laravel/sail/zipball/a64f78a4ab86c04a4c5de39bea20a8d36ad48a22", + "reference": "a64f78a4ab86c04a4c5de39bea20a8d36ad48a22", "shasum": "" }, "require": { - "illuminate/console": "^8.0|^9.0", - "illuminate/contracts": "^8.0|^9.0", - "illuminate/support": "^8.0|^9.0", + "illuminate/console": "^8.0|^9.0|^10.0", + "illuminate/contracts": "^8.0|^9.0|^10.0", + "illuminate/support": "^8.0|^9.0|^10.0", "php": "^7.3|^8.0" }, "bin": [ @@ -8688,7 +9367,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2022-12-12T16:47:37+00:00" + "time": "2023-01-11T14:35:04+00:00" }, { "name": "mockery/mockery", @@ -8823,16 +9502,16 @@ }, { "name": "nunomaduro/collision", - "version": "v6.3.1", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "0f6349c3ed5dd28467087b08fb59384bb458a22b" + "reference": "f05978827b9343cba381ca05b8c7deee346b6015" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/0f6349c3ed5dd28467087b08fb59384bb458a22b", - "reference": "0f6349c3ed5dd28467087b08fb59384bb458a22b", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f05978827b9343cba381ca05b8c7deee346b6015", + "reference": "f05978827b9343cba381ca05b8c7deee346b6015", "shasum": "" }, "require": { @@ -8907,40 +9586,39 @@ "type": "patreon" } ], - "time": "2022-09-29T12:29:49+00:00" + "time": "2023-01-03T12:54:54+00:00" }, { "name": "nunomaduro/larastan", - "version": "2.2.9", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/larastan.git", - "reference": "333e7915b984ce6606175749430081a372ead37e" + "reference": "14f631348ead3e245651606931863b4f218d1f78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/333e7915b984ce6606175749430081a372ead37e", - "reference": "333e7915b984ce6606175749430081a372ead37e", + "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/14f631348ead3e245651606931863b4f218d1f78", + "reference": "14f631348ead3e245651606931863b4f218d1f78", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^9", - "illuminate/container": "^9", - "illuminate/contracts": "^9", - "illuminate/database": "^9", - "illuminate/http": "^9", - "illuminate/pipeline": "^9", - "illuminate/support": "^9", - "mockery/mockery": "^1.4.4", + "illuminate/console": "^9.47.0 || ^10.0.0", + "illuminate/container": "^9.47.0 || ^10.0.0", + "illuminate/contracts": "^9.47.0 || ^10.0.0", + "illuminate/database": "^9.47.0 || ^10.0.0", + "illuminate/http": "^9.47.0 || ^10.0.0", + "illuminate/pipeline": "^9.47.0 || ^10.0.0", + "illuminate/support": "^9.47.0 || ^10.0.0", "php": "^8.0.2", - "phpmyadmin/sql-parser": "^5.5", - "phpstan/phpstan": "^1.9.0" + "phpmyadmin/sql-parser": "^5.6.0", + "phpstan/phpstan": "^1.9.8" }, "require-dev": { - "nikic/php-parser": "^4.13.2", - "orchestra/testbench": "^7.0.0", - "phpunit/phpunit": "^9.5.11" + "nikic/php-parser": "^4.15.2", + "orchestra/testbench": "^7.19.0|^8.0.0", + "phpunit/phpunit": "^9.5.27" }, "suggest": { "orchestra/testbench": "Using Larastan for analysing a package needs Testbench" @@ -8984,7 +9662,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/larastan/issues", - "source": "https://github.com/nunomaduro/larastan/tree/2.2.9" + "source": "https://github.com/nunomaduro/larastan/tree/2.4.0" }, "funding": [ { @@ -9004,7 +9682,7 @@ "type": "patreon" } ], - "time": "2022-11-04T14:58:00+00:00" + "time": "2023-01-11T11:57:44+00:00" }, { "name": "phar-io/manifest", @@ -9426,21 +10104,22 @@ }, { "name": "phpmyadmin/sql-parser", - "version": "5.5.0", + "version": "5.6.0", "source": { "type": "git", "url": "https://github.com/phpmyadmin/sql-parser.git", - "reference": "8ab99cd0007d880f49f5aa1807033dbfa21b1cb5" + "reference": "63f2f77847586864a661ef009ae687dbdda0a9f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/8ab99cd0007d880f49f5aa1807033dbfa21b1cb5", - "reference": "8ab99cd0007d880f49f5aa1807033dbfa21b1cb5", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/63f2f77847586864a661ef009ae687dbdda0a9f1", + "reference": "63f2f77847586864a661ef009ae687dbdda0a9f1", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", - "symfony/polyfill-mbstring": "^1.3" + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.16" }, "conflict": { "phpmyadmin/motranslator": "<3.0" @@ -9489,26 +10168,38 @@ "analysis", "lexer", "parser", - "sql" + "query linter", + "sql", + "sql lexer", + "sql linter", + "sql parser", + "sql syntax highlighter", + "sql tokenizer" ], "support": { "issues": "https://github.com/phpmyadmin/sql-parser/issues", "source": "https://github.com/phpmyadmin/sql-parser" }, - "time": "2021-12-09T04:31:52+00:00" + "funding": [ + { + "url": "https://www.phpmyadmin.net/donate/", + "type": "other" + } + ], + "time": "2023-01-02T05:36:07+00:00" }, { "name": "phpstan/phpstan", - "version": "1.9.3", + "version": "1.9.12", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "709999b91448d4f2bb07daffffedc889b33e461c" + "reference": "44a338ff0d5572c13fd77dfd91addb96e48c29f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/709999b91448d4f2bb07daffffedc889b33e461c", - "reference": "709999b91448d4f2bb07daffffedc889b33e461c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/44a338ff0d5572c13fd77dfd91addb96e48c29f8", + "reference": "44a338ff0d5572c13fd77dfd91addb96e48c29f8", "shasum": "" }, "require": { @@ -9538,7 +10229,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.3" + "source": "https://github.com/phpstan/phpstan/tree/1.9.12" }, "funding": [ { @@ -9554,20 +10245,20 @@ "type": "tidelift" } ], - "time": "2022-12-13T10:28:10+00:00" + "time": "2023-01-17T10:44:04+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.21", + "version": "9.2.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "3f893e19712bb0c8bc86665d1562e9fd509c4ef0" + "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/3f893e19712bb0c8bc86665d1562e9fd509c4ef0", - "reference": "3f893e19712bb0c8bc86665d1562e9fd509c4ef0", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", + "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", "shasum": "" }, "require": { @@ -9623,7 +10314,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.21" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23" }, "funding": [ { @@ -9631,7 +10322,7 @@ "type": "github" } ], - "time": "2022-12-14T13:26:54+00:00" + "time": "2022-12-28T12:41:10+00:00" }, { "name": "phpunit/php-file-iterator", @@ -9876,20 +10567,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.27", + "version": "9.5.28", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38" + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38", - "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -9958,7 +10649,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.27" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" }, "funding": [ { @@ -9974,7 +10665,7 @@ "type": "tidelift" } ], - "time": "2022-12-09T07:31:23+00:00" + "time": "2023-01-14T12:32:24+00:00" }, { "name": "sebastian/cli-parser", @@ -11004,16 +11695,16 @@ }, { "name": "spatie/flare-client-php", - "version": "1.3.1", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "ebb9ae0509b75e02f128b39537eb9a3ef5ce18e8" + "reference": "609903bd154ba3d71f5e23a91c3b431fa8f71868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/ebb9ae0509b75e02f128b39537eb9a3ef5ce18e8", - "reference": "ebb9ae0509b75e02f128b39537eb9a3ef5ce18e8", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/609903bd154ba3d71f5e23a91c3b431fa8f71868", + "reference": "609903bd154ba3d71f5e23a91c3b431fa8f71868", "shasum": "" }, "require": { @@ -11061,7 +11752,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.3.1" + "source": "https://github.com/spatie/flare-client-php/tree/1.3.2" }, "funding": [ { @@ -11069,7 +11760,7 @@ "type": "github" } ], - "time": "2022-11-16T08:30:20+00:00" + "time": "2022-12-26T14:36:46+00:00" }, { "name": "spatie/ignition", @@ -11148,16 +11839,16 @@ }, { "name": "spatie/laravel-ignition", - "version": "1.6.2", + "version": "1.6.4", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "d6e1e1ad93abe280abf41c33f8ea7647dfc0c233" + "reference": "1a2b4bd3d48c72526c0ba417687e5c56b5cf49bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/d6e1e1ad93abe280abf41c33f8ea7647dfc0c233", - "reference": "d6e1e1ad93abe280abf41c33f8ea7647dfc0c233", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/1a2b4bd3d48c72526c0ba417687e5c56b5cf49bc", + "reference": "1a2b4bd3d48c72526c0ba417687e5c56b5cf49bc", "shasum": "" }, "require": { @@ -11234,24 +11925,24 @@ "type": "github" } ], - "time": "2022-12-08T15:31:38+00:00" + "time": "2023-01-03T19:28:04+00:00" }, { "name": "symfony/filesystem", - "version": "v6.0.13", + "version": "v6.2.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "3adca49133bd055ebe6011ed1e012be3c908af79" + "reference": "50b2523c874605cf3d4acf7a9e2b30b6a440a016" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/3adca49133bd055ebe6011ed1e012be3c908af79", - "reference": "3adca49133bd055ebe6011ed1e012be3c908af79", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/50b2523c874605cf3d4acf7a9e2b30b6a440a016", + "reference": "50b2523c874605cf3d4acf7a9e2b30b6a440a016", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, @@ -11281,7 +11972,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.0.13" + "source": "https://github.com/symfony/filesystem/tree/v6.2.0" }, "funding": [ { @@ -11297,24 +11988,24 @@ "type": "tidelift" } ], - "time": "2022-09-21T20:25:27+00:00" + "time": "2022-11-20T13:01:27+00:00" }, { "name": "symfony/options-resolver", - "version": "v6.0.3", + "version": "v6.2.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "51f7006670febe4cbcbae177cbffe93ff833250d" + "reference": "d28f02acde71ff75e957082cd36e973df395f626" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/51f7006670febe4cbcbae177cbffe93ff833250d", - "reference": "51f7006670febe4cbcbae177cbffe93ff833250d", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d28f02acde71ff75e957082cd36e973df395f626", + "reference": "d28f02acde71ff75e957082cd36e973df395f626", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/deprecation-contracts": "^2.1|^3" }, "type": "library", @@ -11348,7 +12039,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.0.3" + "source": "https://github.com/symfony/options-resolver/tree/v6.2.0" }, "funding": [ { @@ -11364,24 +12055,103 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2022-11-02T09:08:04+00:00" }, { - "name": "symfony/stopwatch", - "version": "v6.0.13", + "name": "symfony/polyfill-php81", + "version": "v1.27.0", "source": { "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "7554fde6848af5ef1178f8ccbdbdb8ae1092c70a" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/7554fde6848af5ef1178f8ccbdbdb8ae1092c70a", - "reference": "7554fde6848af5ef1178f8ccbdbdb8ae1092c70a", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", + "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v6.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "266636bb8f3fbdccc302491df7b3a1b9a8c238a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/266636bb8f3fbdccc302491df7b3a1b9a8c238a7", + "reference": "266636bb8f3fbdccc302491df7b3a1b9a8c238a7", + "shasum": "" + }, + "require": { + "php": ">=8.1", "symfony/service-contracts": "^1|^2|^3" }, "type": "library", @@ -11410,7 +12180,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.0.13" + "source": "https://github.com/symfony/stopwatch/tree/v6.2.0" }, "funding": [ { @@ -11426,7 +12196,7 @@ "type": "tidelift" } ], - "time": "2022-09-28T15:52:47+00:00" + "time": "2022-09-28T16:00:52+00:00" }, { "name": "theseer/tokenizer", @@ -11485,7 +12255,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.0.2 || ^8.1 || ^8.2", + "php": "^8.1 || ^8.2", "ext-json": "*", "ext-mbstring": "*", "ext-pdo": "*", @@ -11495,7 +12265,7 @@ }, "platform-dev": [], "platform-overrides": { - "php": "8.0.2" + "php": "8.1.0" }, "plugin-api-version": "2.3.0" } diff --git a/database/Factories/SecurityKeyFactory.php b/database/Factories/SecurityKeyFactory.php new file mode 100644 index 000000000..6938b6727 --- /dev/null +++ b/database/Factories/SecurityKeyFactory.php @@ -0,0 +1,34 @@ + Uuid::uuid4()->toString(), + 'name' => $this->faker->word, + 'type' => 'public-key', + 'transports' => [], + 'attestation_type' => 'none', + 'trust_path' => new EmptyTrustPath(), + 'counter' => 0, + ]; + } +} diff --git a/database/migrations/2021_08_07_170141_create_security_keys_table.php b/database/migrations/2021_08_07_170141_create_security_keys_table.php new file mode 100644 index 000000000..71622b638 --- /dev/null +++ b/database/migrations/2021_08_07_170141_create_security_keys_table.php @@ -0,0 +1,42 @@ +id(); + $table->char('uuid', 36)->unique(); + $table->unsignedInteger('user_id'); + $table->string('name'); + $table->text('public_key_id'); + $table->text('public_key'); + $table->char('aaguid', 36)->nullable(); + $table->string('type'); + $table->json('transports'); + $table->string('attestation_type'); + $table->json('trust_path'); + $table->text('user_handle'); + $table->unsignedInteger('counter'); + $table->json('other_ui')->nullable(); + $table->timestamps(); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('security_keys'); + } +} diff --git a/resources/scripts/api/account/security-keys.ts b/resources/scripts/api/account/security-keys.ts new file mode 100644 index 000000000..2541729ed --- /dev/null +++ b/resources/scripts/api/account/security-keys.ts @@ -0,0 +1,95 @@ +import type { AxiosError } from 'axios'; +import type { SWRConfiguration } from 'swr'; +import useSWR from 'swr'; + +import type { SecurityKey } from '@definitions/user'; +import { Transformers } from '@definitions/user'; +import { LoginResponse } from '@/api/auth/login'; +import type { FractalResponseList } from '@/api/http'; +import http from '@/api/http'; +import { decodeBase64 } from '@/lib/base64'; +import { decodeBuffer, encodeBuffer } from '@/lib/buffer'; +import { useUserSWRKey } from '@/plugins/useSWRKey'; + +function decodeSecurityKeyCredentials(credentials: PublicKeyCredentialDescriptor[]) { + return credentials.map(c => ({ + id: decodeBuffer(decodeBase64(c.id.toString())), + type: c.type, + transports: c.transports, + })); +} + +function useSecurityKeys(config?: SWRConfiguration) { + const key = useUserSWRKey(['account', 'security-keys']); + + return useSWR( + key, + async (): Promise => { + const { data } = await http.get('/api/client/account/security-keys'); + + return (data as FractalResponseList).data.map(datum => Transformers.toSecurityKey(datum.attributes)); + }, + { revalidateOnMount: false, ...(config ?? {}) }, + ); +} + +async function deleteSecurityKey(uuid: string): Promise { + await http.delete(`/api/client/account/security-keys/${uuid}`); +} + +async function registerCredentialForAccount( + name: string, + tokenId: string, + credential: PublicKeyCredential, +): Promise { + const { data } = await http.post('/api/client/account/security-keys/register', { + name, + token_id: tokenId, + registration: { + id: credential.id, + type: credential.type, + rawId: encodeBuffer(credential.rawId), + response: { + attestationObject: encodeBuffer( + (credential.response as AuthenticatorAttestationResponse).attestationObject, + ), + clientDataJSON: encodeBuffer(credential.response.clientDataJSON), + }, + }, + }); + + return Transformers.toSecurityKey(data.attributes); +} + +async function registerSecurityKey(name: string): Promise { + const { data } = await http.get('/api/client/account/security-keys/register'); + + const publicKey = data.data.credentials; + publicKey.challenge = decodeBuffer(decodeBase64(publicKey.challenge)); + publicKey.user.id = decodeBuffer(publicKey.user.id); + + if (publicKey.excludeCredentials) { + publicKey.excludeCredentials = decodeSecurityKeyCredentials(publicKey.excludeCredentials); + } + + const credentials = await navigator.credentials.create({ publicKey }); + if (!credentials || credentials.type !== 'public-key') { + throw new Error( + `Unexpected type returned by navigator.credentials.create(): expected "public-key", got "${credentials?.type}"`, + ); + } + + return await registerCredentialForAccount(name, data.data.token_id, credentials as PublicKeyCredential); +} + +// eslint-disable-next-line camelcase +async function authenticateSecurityKey(data: { confirmation_token: string; data: string }): Promise { + const response = await http.post('/auth/login/checkpoint/key', data); + + return { + complete: response.data.complete, + intended: response.data.data?.intended || null, + }; +} + +export { useSecurityKeys, deleteSecurityKey, registerSecurityKey, authenticateSecurityKey }; diff --git a/resources/scripts/api/definitions/user/models.d.ts b/resources/scripts/api/definitions/user/models.d.ts index a5b40ca67..3d85b83d2 100644 --- a/resources/scripts/api/definitions/user/models.d.ts +++ b/resources/scripts/api/definitions/user/models.d.ts @@ -1,24 +1,6 @@ import { Model, UUID } from '@/api/definitions'; import { SubuserPermission } from '@/state/server/subusers'; -interface User extends Model { - uuid: string; - username: string; - email: string; - image: string; - twoFactorEnabled: boolean; - createdAt: Date; - permissions: SubuserPermission[]; - can(permission: SubuserPermission): boolean; -} - -interface SSHKey extends Model { - name: string; - publicKey: string; - fingerprint: string; - createdAt: Date; -} - interface ActivityLog extends Model<'actor'> { id: string; batch: UUID | null; @@ -33,3 +15,30 @@ interface ActivityLog extends Model<'actor'> { actor: User | null; }; } + +interface User extends Model { + uuid: string; + username: string; + email: string; + image: string; + twoFactorEnabled: boolean; + createdAt: Date; + permissions: SubuserPermission[]; + can(permission: SubuserPermission): boolean; +} + +interface SecurityKey extends Model { + uuid: UUID; + name: string; + type: 'public-key'; + publicKeyId: string; + createdAt: Date; + updatedAt: Date; +} + +interface SSHKey extends Model { + name: string; + publicKey: string; + fingerprint: string; + createdAt: Date; +} diff --git a/resources/scripts/api/definitions/user/transformers.ts b/resources/scripts/api/definitions/user/transformers.ts index 1fa62d3f9..6a04af939 100644 --- a/resources/scripts/api/definitions/user/transformers.ts +++ b/resources/scripts/api/definitions/user/transformers.ts @@ -3,6 +3,36 @@ import { FractalResponseData } from '@/api/http'; import { transform } from '@definitions/helpers'; export default class Transformers { + static toActivityLog = ({ attributes }: FractalResponseData): Models.ActivityLog => { + const { actor } = attributes.relationships || {}; + + return { + id: attributes.id, + batch: attributes.batch, + event: attributes.event, + ip: attributes.ip, + isApi: attributes.is_api, + description: attributes.description, + properties: attributes.properties, + hasAdditionalMetadata: attributes.has_additional_metadata ?? false, + timestamp: new Date(attributes.timestamp), + relationships: { + actor: transform(actor as FractalResponseData, this.toUser, null), + }, + }; + }; + + static toSecurityKey(data: Record): Models.SecurityKey { + return { + uuid: data.uuid, + name: data.name, + type: data.type, + publicKeyId: data.public_key_id, + createdAt: new Date(data.created_at), + updatedAt: new Date(data.updated_at), + }; + } + static toSSHKey = (data: Record): Models.SSHKey => { return { name: data.name, @@ -26,25 +56,6 @@ export default class Transformers { }, }; }; - - static toActivityLog = ({ attributes }: FractalResponseData): Models.ActivityLog => { - const { actor } = attributes.relationships || {}; - - return { - id: attributes.id, - batch: attributes.batch, - event: attributes.event, - ip: attributes.ip, - isApi: attributes.is_api, - description: attributes.description, - properties: attributes.properties, - hasAdditionalMetadata: attributes.has_additional_metadata ?? false, - timestamp: new Date(attributes.timestamp), - relationships: { - actor: transform(actor as FractalResponseData, this.toUser, null), - }, - }; - }; } export class MetaTransformers {} diff --git a/resources/scripts/lib/base64.spec.ts b/resources/scripts/lib/base64.spec.ts new file mode 100644 index 000000000..5f4299eda --- /dev/null +++ b/resources/scripts/lib/base64.spec.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from 'vitest'; + +import { decodeBase64 } from '@/lib/base64'; + +describe('@/lib/base64.ts', function () { + describe('decodeBase64()', function () { + it.each([ + ['', ''], + ['', ''], + ])('should decode "%s" to "%s"', function (input, output) { + expect(decodeBase64(input)).toBe(output); + }); + }); +}); diff --git a/resources/scripts/lib/base64.ts b/resources/scripts/lib/base64.ts new file mode 100644 index 000000000..a9da15330 --- /dev/null +++ b/resources/scripts/lib/base64.ts @@ -0,0 +1,16 @@ +function decodeBase64(input: string): string { + input = input.replace(/-/g, '+').replace(/_/g, '/'); + + const pad = input.length % 4; + if (pad) { + if (pad === 1) { + throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding'); + } + + input += new Array(5 - pad).join('='); + } + + return input; +} + +export { decodeBase64 }; diff --git a/resources/scripts/lib/buffer.spec.ts b/resources/scripts/lib/buffer.spec.ts new file mode 100644 index 000000000..9a6e6b24a --- /dev/null +++ b/resources/scripts/lib/buffer.spec.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from 'vitest'; + +import { decodeBuffer, encodeBuffer } from '@/lib/buffer'; + +describe('@/lib/buffer.ts', function () { + describe('decodeBuffer()', function () { + it.each([ + ['', ''], + ['', ''], + ])('should decode "%s" to "%s"', function (input, output) { + expect(decodeBuffer(input)).toBe(output); + }); + }); + + describe('encodeBuffer()', function () { + it.each([ + [new Uint8Array(0), ''], + [new Uint8Array(0), ''], + ])('should encode "%s" to "%s"', function (input, output) { + expect(encodeBuffer(input)).toBe(output); + }); + }); +}); diff --git a/resources/scripts/lib/buffer.ts b/resources/scripts/lib/buffer.ts new file mode 100644 index 000000000..5c8593513 --- /dev/null +++ b/resources/scripts/lib/buffer.ts @@ -0,0 +1,9 @@ +function decodeBuffer(value: string): ArrayBuffer { + return Uint8Array.from(window.atob(value), c => c.charCodeAt(0)); +} + +function encodeBuffer(value: ArrayBuffer): string { + return btoa(String.fromCharCode(...new Uint8Array(value))); +} + +export { decodeBuffer, encodeBuffer }; diff --git a/routes/api-client.php b/routes/api-client.php index 64ceecf3e..14118dd9d 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -36,6 +36,11 @@ Route::prefix('/account')->middleware(AccountSubject::class)->group(function () Route::post('/api-keys', [Client\ApiKeyController::class, 'store']); Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']); + Route::get('/security-keys', [Client\SecurityKeyController::class, 'index'])->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::get('/security-keys/register', [Client\SecurityKeyController::class, 'create'])->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::post('/security-keys/register', [Client\SecurityKeyController::class, 'store'])->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::delete('/security-keys/{securityKey}', [Client\SecurityKeyController::class, 'delete'])->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::prefix('/ssh-keys')->group(function () { Route::get('/', [Client\SSHKeyController::class, 'index']); Route::post('/', [Client\SSHKeyController::class, 'store']); diff --git a/routes/auth.php b/routes/auth.php index 36039f3a2..f54bcab4d 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -25,7 +25,8 @@ Route::get('/password/reset/{token}', [Auth\LoginController::class, 'index'])->n Route::middleware(['throttle:authentication'])->group(function () { // Login endpoints. Route::post('/login', [Auth\LoginController::class, 'login'])->middleware('recaptcha'); - Route::post('/login/checkpoint', Auth\LoginCheckpointController::class)->name('auth.login-checkpoint'); + Route::post('/login/checkpoint', [Auth\LoginCheckpointController::class, 'token'])->name('auth.checkpoint'); + Route::post('/login/checkpoint/key', [Auth\LoginCheckpointController::class, 'key'])->name('auth.checkpoint.key'); // Forgot password route. A post to this endpoint will trigger an // email to be sent containing a reset token. @@ -46,5 +47,5 @@ Route::post('/logout', [Auth\LoginController::class, 'logout']) ->middleware('auth') ->name('auth.logout'); -// Catch any other combinations of routes and pass them off to the React component. +// Catch any other combinations of routes and pass them off to the React frontend. Route::fallback([Auth\LoginController::class, 'index']); diff --git a/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php b/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php new file mode 100644 index 000000000..3b600e6e3 --- /dev/null +++ b/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php @@ -0,0 +1,338 @@ +set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_NONE); + + $user = $this->generateRequestUserModel(['use_totp' => false]); + + $this->assertFalse($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isEmpty()); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testNoRequirementUserWithTotp2fa() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_NONE); + + $user = $this->generateRequestUserModel(['use_totp' => true]); + + $this->assertTrue($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isEmpty()); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testNoRequirementUserWithSecurityKey2fa() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_NONE); + + /** @var \Pterodactyl\Models\User $user */ + $user = User::factory() + ->make(['use_totp' => false]) + ->setRelation('securityKeys', SecurityKey::factory()->count(1)->make()); + $this->setRequestUserModel($user); + + $this->assertFalse($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isNotEmpty()); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testNoRequirementGuestUser() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_NONE); + + $this->setRequestUserModel(); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/auth/login'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn('auth.login'); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAllRequirementUserWithout2fa() + { + $this->expectException(TwoFactorAuthRequiredException::class); + + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ALL); + + $user = $this->generateRequestUserModel(['use_totp' => false]); + + $this->assertFalse($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isEmpty()); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAllRequirementUserWithTotp2fa() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ALL); + + $user = $this->generateRequestUserModel(['use_totp' => true]); + + $this->assertTrue($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isEmpty()); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAllRequirementUserWithSecurityKey2fa() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ALL); + + /** @var \Pterodactyl\Models\User $user */ + $user = User::factory() + ->make(['use_totp' => false]) + ->setRelation('securityKeys', SecurityKey::factory()->count(1)->make()); + $this->setRequestUserModel($user); + + $this->assertFalse($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isNotEmpty()); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAllRequirementGuestUser() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ALL); + + $this->setRequestUserModel(); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/auth/login'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn('auth.login'); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAdminRequirementUserWithout2fa() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); + + $user = $this->generateRequestUserModel(['use_totp' => false]); + + $this->assertFalse($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isEmpty()); + $this->assertFalse($user->root_admin); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAdminRequirementAdminUserWithout2fa() + { + $this->expectException(TwoFactorAuthRequiredException::class); + + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); + + $user = $this->generateRequestUserModel(['use_totp' => false, 'root_admin' => true]); + + $this->assertFalse($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isEmpty()); + $this->assertTrue($user->root_admin); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAdminRequirementUserWithTotp2fa() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); + + $user = $this->generateRequestUserModel(['use_totp' => true]); + + $this->assertTrue($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isEmpty()); + $this->assertFalse($user->root_admin); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAdminRequirementAdminUserWithTotp2fa() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); + + $user = $this->generateRequestUserModel(['use_totp' => true, 'root_admin' => true]); + + $this->assertTrue($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isEmpty()); + $this->assertTrue($user->root_admin); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAdminRequirementUserWithSecurityKey2fa() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); + + /** @var \Pterodactyl\Models\User $user */ + $user = User::factory() + ->make(['use_totp' => false]) + ->setRelation('securityKeys', SecurityKey::factory()->count(1)->make()); + $this->setRequestUserModel($user); + + $this->assertFalse($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertFalse($user->root_admin); + $this->assertTrue($user->securityKeys->isNotEmpty()); + $this->assertNotEmpty($user->securityKeys); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAdminRequirementAdminUserWithSecurityKey2fa() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); + + /** @var \Pterodactyl\Models\User $user */ + $user = User::factory() + ->make(['use_totp' => false, 'root_admin' => true]) + ->setRelation('securityKeys', SecurityKey::factory()->count(1)->make()); + $this->setRequestUserModel($user); + + $this->assertFalse($user->use_totp); + $this->assertEmpty($user->totp_secret); + $this->assertEmpty($user->totp_authenticated_at); + $this->assertTrue($user->securityKeys->isNotEmpty()); + $this->assertTrue($user->root_admin); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } + + public function testAdminRequirementGuestUser() + { + // Disable the 2FA requirement + config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); + + $this->setRequestUserModel(); + + $this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/auth/login'); + $this->request->shouldReceive('route->getName')->withNoArgs()->andReturn('auth.login'); + $this->request->shouldReceive('isJson')->withNoArgs()->andReturn(true); + + /** @var \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication $controller */ + $middleware = $this->app->make(RequireTwoFactorAuthentication::class); + $middleware->handle($this->request, $this->getClosureAssertions()); + } +}