From f0be3016f9c4333f2c21679c32ea8aa2e23d2229 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Sun, 4 Feb 2024 16:31:15 -0800 Subject: [PATCH 01/18] Import from backend prototype --- composer.json | 13 ++-- src/ApiError.php | 11 +++ src/AttachResponse.php | 14 ++++ src/AuthResponse.php | 16 +++++ src/Client.php | 153 +++++++++++++++++++++++++++++++++++++++++ src/Example.php | 23 ------- src/User.php | 18 +++++ 7 files changed, 218 insertions(+), 30 deletions(-) create mode 100644 src/ApiError.php create mode 100644 src/AttachResponse.php create mode 100644 src/AuthResponse.php create mode 100644 src/Client.php delete mode 100644 src/Example.php create mode 100644 src/User.php diff --git a/composer.json b/composer.json index cf33e55..c8bebb6 100644 --- a/composer.json +++ b/composer.json @@ -1,13 +1,12 @@ { - "name": "your/library", - "description": "Description of your library", + "name": "snapauth/sdk", + "description": "SnapAuth SDK", "keywords": [], "type": "library", - "license": "MIT", "authors": [ { - "name": "Your Name", - "email": "you@example.com" + "name": "Eric Stern", + "email": "eric@snapauth.app" } ], "config": { @@ -16,12 +15,12 @@ }, "autoload": { "psr-4": { - "Your\\Library\\": "src" + "SnapAuth\\": "src" } }, "autoload-dev": { "psr-4": { - "Your\\Library\\": "tests" + "SnapAuth\\": "tests" } }, "require": { diff --git a/src/ApiError.php b/src/ApiError.php new file mode 100644 index 0000000..6ddd4ba --- /dev/null +++ b/src/ApiError.php @@ -0,0 +1,11 @@ +user = new User($data['user']); + } +} diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000..3904685 --- /dev/null +++ b/src/Client.php @@ -0,0 +1,153 @@ +makeApiCall( + route: '/auth/verify', + data: [ + 'token' => $authToken, + ], + type: AuthResponse::class, + ); + } + + /** + * @param array{ + * handle: string, + * id?: string, + * } $user + */ + public function attachRegistration(string $regToken, array $user): AttachResponse + { + return $this->makeApiCall( + route: '/registration/attach', + data: [ + 'token' => $regToken, + 'user' => $user, + ], + type: AttachResponse::class + ); + } + + /** + * @template T of object + * @param mixed[] $data + * @param class-string $type + * @return T + */ + private function makeApiCall(string $route, array $data, string $type): object + { + // TODO: PSR-xx + $json = json_encode($data, JSON_THROW_ON_ERROR); + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => sprintf('%s%s', $this->apiHost, $route), + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => $json, + // CURLOPT_VERBOSE => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Authorization: Basic ' . base64_encode(':' . $this->secretKey), + 'Accept: application/json', + 'Content-Type: application/json', + 'Content-Length: ' . strlen($json), + sprintf( + 'User-Agent: php-sdk/%s curl/%s php/%s', + self::VERSION, + curl_version()['version'] ?? 'unknown', + PHP_VERSION, + ), + ], + ]); + + try { + $response = curl_exec($ch); + $errno = curl_errno($ch); + $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + + if ($response === false || $errno !== CURLE_OK) { + $this->error(); + } + + if ($code >= 300) { + $this->error(); + } + // Handle non-200s, non-JSON (severe upstream error) + assert(is_string($response)); + $decoded = json_decode($response, true, flags: JSON_THROW_ON_ERROR); + assert(is_array($decoded)); + return new $type($decoded['result']); + } catch (JsonException) { + $this->error(); + } finally { + curl_close($ch); + } + } + + /** + * TODO: specific error info! + */ + private function error(): never + { + throw new ApiError(); + // TODO: also make this more specific + } +} diff --git a/src/Example.php b/src/Example.php deleted file mode 100644 index f29e087..0000000 --- a/src/Example.php +++ /dev/null @@ -1,23 +0,0 @@ -message = $message; - } -} diff --git a/src/User.php b/src/User.php new file mode 100644 index 0000000..aa2ffc8 --- /dev/null +++ b/src/User.php @@ -0,0 +1,18 @@ +id = $data['id']; + $this->handle = $data['handle']; + } +} From bed21aecb5413447d4a922f1d8efae7ca83ea581 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Sun, 4 Feb 2024 16:34:03 -0800 Subject: [PATCH 02/18] add ext-curl to requirements --- .github/workflows/test.yml | 1 + composer.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 425c724..1697fec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,7 @@ jobs: - '8.0' - '8.1' - '8.2' + - '8.3' steps: - name: Check out code diff --git a/composer.json b/composer.json index c8bebb6..5e64ff9 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ } }, "require": { - "php": "^7.4 || ^8.0" + "php": "^7.4 || ^8.0", + "ext-curl": "*" }, "require-dev": { "maglnet/composer-require-checker": "^2.0 || ^3.0 || ^4.0", From 8b81d8a90b1e7ef06718a6b2e559878da6a6b969 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 12:01:13 -0700 Subject: [PATCH 03/18] current versions only --- .github/workflows/test.yml | 2 -- composer.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1697fec..05ed73b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,8 +20,6 @@ jobs: - 'high' - 'low' php: - - '7.4' - - '8.0' - '8.1' - '8.2' - '8.3' diff --git a/composer.json b/composer.json index 5e64ff9..f040142 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ } }, "require": { - "php": "^7.4 || ^8.0", + "php": "^8.1", "ext-curl": "*" }, "require-dev": { From 6c0e2c740c21a4b0aadaba2fd977b304ccf4e9eb Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 13:36:48 -0700 Subject: [PATCH 04/18] readme --- README.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7ad0dd8..f1272cb 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,96 @@ -# php-library-template -Repository template for PHP libraries. Sets up composer, CI with Github Actions, and more. +# SnapAuth PHP SDK -## Git -- Configures `.gitignore` for common excludes in a PHP library +## Installation -## Composer -- Placeholders for library name, description, and PSR-4 autoloading -- Scripts for testing -- Requires current version of PHP -- Includes testing tools (configured) as dev dependencies +```bash +composer require snapauth/sdk +``` -## Testing and CI -CI is configured using Github Actions. +## Setup -- PHPUnit `^9.3` with default configuration (`src`/`tests`). -- The tests workflow uses a build matrix to test against multiple versions of PHP, and with high and low Composer dependencies installed -- PHPStan with strict ruleset, max level, and the PHPUnit extension -- PHP Code Sniffer configured with PSR-12 +Get your _secret_ key from the [dashboard](https://dashboard.snapauth.app). +Provide it to the SnapAuth\Client class: + +```php +use SnapAuth\Client; + +$yourSecret = getenv('SNAPAUTH_SECRET_KEY'); +$snapAuth = new Client(secretKey: $yourSecret); +``` + +> [!TIP] +> Secret keys are specific to an environment and domain. +> We HIGHLY RECOMMEND using environment variables or another external storage mechanism. +> Avoid committing them to version control. + +## Usage + +### Registration + +Once you obtain a registration token from your frontend, use the Client to complete the process and attach it to the user: + +```php +$token = 'value_from_frontend'; // $_POST['snapauth_token'] or similar +$userInfo = [ + 'id' => 'your_user_id', + 'handle' => 'your_user_handle', +]; +$snapAuth->attachRegistration($token, $userInfo); +``` + +Registration returns an `AttachResponse` object, which contains a credential identifier. +You may store this information at your end, but it's not necessary in most cases. + +This activates the passkey and associates it with the user. +`$userInfo` will be provided back to you during authentication, so you know who is signing in. + +`id` should be some sort of _stable_ identifer, like a database primary key. + +`handle` can be anything you want, or omitted entirely. +It's a convenience during _client_ authentication so you don't need to look up the user id again. +This would commonly be the value a user provides to sign in, such as a username or email. + +Both must be strings, and can be up to 255 characters long. +Lookups during authentication are **case-insensitive**. + +> [!TIP] +> We strongly ENCOURAGE you to obfuscate any possibly sensitive information, such as email addresses. +> You can accomplish this by hashing the value. +> Be aware that to use the handle during authentication, you will want to replicate the obfuscation procedure on your frontend. + +### Authentication + +Like registration, you will need to obtain a token from your frontend provided by the client SDK. + +Use the `verifyAuthToken` method to get information about the authentication process, in the form of an `AuthResponse` object. + +```php +$token = 'value_from_frontend'; // $_POST['snapauth_token'] or similar +$authInfo = $snapAuth->verifyAuthToken($token); + +// Specific to your application: +$authenticatedUserId = $authInfo->user->id; + +// Laravel: +use Illuminate\Support\Facades\Auth; +Auth::loginUsingId($authenticatedUserId); +``` + +## Error Handling + +The SnapAuth SDK is written in a fail-secure manner, and will throw an exception if you're not on the successful path. +This helps ensure that your integration is easy and reliable. + +You may choose to locally wrap API calls in a `try/catch` block, or let a general application-wide error handler deal with any exceptions. + +All SnapAuth exceptions are an `instanceof \SnapAuth\ApiError`. + +## Compatibility + +We follow semantic versioning, and limit backwards-incompatible changes to major versions (the X in X.Y.Z) only. + +The SnapAuth SDK is maintained for all versions of PHP with [current security support](https://www.php.net/supported-versions.php). +Since Composer will platform-detect your currently-installed version of PHP, dropping support for older versions is _not_ considered a backwards compatibility break. + +Anything marked as `@internal` or any `protected` or `private` method is not considered in scope for backwards-compatibility guarantees. +Similarly, all methods should be treated as ones that may throw an exception, and as such new types of exceptions are not considered a BC break either. From 01680db4ff17965b87d0932e5ae372174c428c8d Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 13:36:59 -0700 Subject: [PATCH 05/18] typo --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 3904685..03bd2f6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -36,7 +36,7 @@ * internal use, forcing a completely dogfooded experience. * * TODO: make testable, presumably via PSR-18 - * TODO: make an inteface so the enture client can be mocked by developers + * TODO: make an interface so the entire client can be mocked by developers */ class Client { From 150a5af6b6e82479bbc1462f70ff1980cad71211 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 13:39:58 -0700 Subject: [PATCH 06/18] Switch to 8.1 readonly syntax --- src/AttachResponse.php | 2 +- src/AuthResponse.php | 4 ++-- src/User.php | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/AttachResponse.php b/src/AttachResponse.php index 1f77d8e..51bc5b0 100644 --- a/src/AttachResponse.php +++ b/src/AttachResponse.php @@ -4,7 +4,7 @@ namespace SnapAuth; -readonly class AttachResponse +class AttachResponse { // @phpstan-ignore-next-line public function __construct(array $data) diff --git a/src/AuthResponse.php b/src/AuthResponse.php index d539ae8..79ded32 100644 --- a/src/AuthResponse.php +++ b/src/AuthResponse.php @@ -4,9 +4,9 @@ namespace SnapAuth; -readonly class AuthResponse +class AuthResponse { - public User $user; + public readonly User $user; // @phpstan-ignore-next-line public function __construct(array $data) diff --git a/src/User.php b/src/User.php index aa2ffc8..ea5f150 100644 --- a/src/User.php +++ b/src/User.php @@ -4,10 +4,10 @@ namespace SnapAuth; -readonly class User +class User { - public string $id; - public string $handle; + public readonly string $id; + public readonly string $handle; // @phpstan-ignore-next-line public function __construct(array $data) From 36e1cf730eda402e3f12392d26918cc0f1a02a28 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 13:43:07 -0700 Subject: [PATCH 07/18] start on 0.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 03bd2f6..4d2b563 100644 --- a/src/Client.php +++ b/src/Client.php @@ -42,7 +42,7 @@ class Client { private const DEFAULT_API_HOST = 'https://api.snapauth.app'; - private const VERSION = '0.0.1'; + private const VERSION = '0.1.0'; public function __construct( #[SensitiveParameter] private string $secretKey, From 40dfa990ef1f0d2ff8888e4cc748c475e2b56665 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 13:43:41 -0700 Subject: [PATCH 08/18] remove placeholders --- src/.gitkeep | 0 tests/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/.gitkeep delete mode 100644 tests/.gitkeep diff --git a/src/.gitkeep b/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 From a41f4f2b780431eceaff58e1285b834a455be093 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 13:46:12 -0700 Subject: [PATCH 09/18] Test constructor API --- tests/ClientTest.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/ClientTest.php diff --git a/tests/ClientTest.php b/tests/ClientTest.php new file mode 100644 index 0000000..1c42f68 --- /dev/null +++ b/tests/ClientTest.php @@ -0,0 +1,20 @@ + Date: Mon, 1 Apr 2024 14:11:27 -0700 Subject: [PATCH 10/18] Add explicit SDK version same as FE header --- src/Client.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Client.php b/src/Client.php index 4d2b563..8fb3722 100644 --- a/src/Client.php +++ b/src/Client.php @@ -115,6 +115,7 @@ private function makeApiCall(string $route, array $data, string $type): object curl_version()['version'] ?? 'unknown', PHP_VERSION, ), + sprintf('X-SDK-Version: php/%s', self::VERSION), ], ]); From 29251d2a8fede0cc4c1989cca6168736328296e5 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 14:13:42 -0700 Subject: [PATCH 11/18] Use runtime version --- src/Client.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Client.php b/src/Client.php index 8fb3722..7b0c1ed 100644 --- a/src/Client.php +++ b/src/Client.php @@ -4,6 +4,7 @@ namespace SnapAuth; +use Composer\InstalledVersions; use JsonException; use SensitiveParameter; @@ -42,8 +43,6 @@ class Client { private const DEFAULT_API_HOST = 'https://api.snapauth.app'; - private const VERSION = '0.1.0'; - public function __construct( #[SensitiveParameter] private string $secretKey, private string $apiHost = self::DEFAULT_API_HOST, @@ -111,11 +110,11 @@ private function makeApiCall(string $route, array $data, string $type): object 'Content-Length: ' . strlen($json), sprintf( 'User-Agent: php-sdk/%s curl/%s php/%s', - self::VERSION, + InstalledVersions::getVersion('snapauth/sdk'), curl_version()['version'] ?? 'unknown', PHP_VERSION, ), - sprintf('X-SDK-Version: php/%s', self::VERSION), + sprintf('X-SDK-Version: php/%s', InstalledVersions::getVersion('snapauth/sdk')), ], ]); From 3a25255c34179381bfed11e64875ff36333016ec Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 14:29:18 -0700 Subject: [PATCH 12/18] Fix header --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 7b0c1ed..72d5ee6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -114,7 +114,7 @@ private function makeApiCall(string $route, array $data, string $type): object curl_version()['version'] ?? 'unknown', PHP_VERSION, ), - sprintf('X-SDK-Version: php/%s', InstalledVersions::getVersion('snapauth/sdk')), + sprintf('X-SDK: php/%s', InstalledVersions::getVersion('snapauth/sdk')), ], ]); From 627704c001434eba49194bb8c16b63cb8df58a24 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 15:00:31 -0700 Subject: [PATCH 13/18] Validate secrets and hide them from debuginfo --- src/Client.php | 11 +++++++++++ tests/ClientTest.php | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/Client.php b/src/Client.php index 72d5ee6..d9efc0d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -47,6 +47,9 @@ public function __construct( #[SensitiveParameter] private string $secretKey, private string $apiHost = self::DEFAULT_API_HOST, ) { + if (!str_starts_with($secretKey, 'secret_')) { + throw new ApiError('Invalid secret key. Please verify you copied the full value from the SnapAuth dashboard.'); + } } /** @@ -150,4 +153,12 @@ private function error(): never throw new ApiError(); // TODO: also make this more specific } + + public function __debugInfo(): array + { + return [ + 'apiHost' => $this->apiHost, + 'secretKey' => substr($this->secretKey, 0, 9) . '***' . substr($this->secretKey, -2), + ]; + } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 1c42f68..91a524b 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -15,6 +15,20 @@ class ClientTest extends TestCase public function testConstructApi(): void { $client = new Client(secretKey: 'secret_abc_123'); + // @phpstan-ignore-next-line BC enforcement check self::assertInstanceOf(Client::class, $client); } + + public function testSecretKeyValidation(): void + { + self::expectException(ApiError::class); + new Client(secretKey: 'not_a_secret'); + } + + public function testKeyIsRedactedInDebugInfo(): void + { + $client = new Client(secretKey: 'secret_abc_123'); + $result = print_r($client, true); + self::assertStringNotContainsString('secret_abc_123', $result); + } } From d40847c7d1aa1d6e43d6221503fed36a86777103 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 15:05:39 -0700 Subject: [PATCH 14/18] docs --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f1272cb..1e96a08 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # SnapAuth PHP SDK +This is the official PHP SDK for [SnapAuth](https://www.snapauth.app). + +## Documentation + +Full API and usage docs are available [at the official site](https://docs.snapauth.app/server.html#introduction). + ## Installation ```bash @@ -9,7 +15,7 @@ composer require snapauth/sdk ## Setup Get your _secret_ key from the [dashboard](https://dashboard.snapauth.app). -Provide it to the SnapAuth\Client class: +Provide it to the `SnapAuth\Client` class: ```php use SnapAuth\Client; @@ -21,13 +27,13 @@ $snapAuth = new Client(secretKey: $yourSecret); > [!TIP] > Secret keys are specific to an environment and domain. > We HIGHLY RECOMMEND using environment variables or another external storage mechanism. -> Avoid committing them to version control. +> Avoid committing them to version control, as this can more easily lead to compromise. ## Usage ### Registration -Once you obtain a registration token from your frontend, use the Client to complete the process and attach it to the user: +Once you obtain a registration token from your frontend, use the `Client` to complete the process and attach it to the user: ```php $token = 'value_from_frontend'; // $_POST['snapauth_token'] or similar @@ -38,8 +44,10 @@ $userInfo = [ $snapAuth->attachRegistration($token, $userInfo); ``` + This activates the passkey and associates it with the user. `$userInfo` will be provided back to you during authentication, so you know who is signing in. @@ -63,6 +71,7 @@ Lookups during authentication are **case-insensitive**. Like registration, you will need to obtain a token from your frontend provided by the client SDK. Use the `verifyAuthToken` method to get information about the authentication process, in the form of an `AuthResponse` object. +This object contains the previously-registered User `id` and `handle`. ```php $token = 'value_from_frontend'; // $_POST['snapauth_token'] or similar From 8438dbe852a41822a1296f3e877f59281737f434 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 15:14:49 -0700 Subject: [PATCH 15/18] clarify bc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e96a08..39a1ff6 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ All SnapAuth exceptions are an `instanceof \SnapAuth\ApiError`. We follow semantic versioning, and limit backwards-incompatible changes to major versions (the X in X.Y.Z) only. The SnapAuth SDK is maintained for all versions of PHP with [current security support](https://www.php.net/supported-versions.php). -Since Composer will platform-detect your currently-installed version of PHP, dropping support for older versions is _not_ considered a backwards compatibility break. +Since Composer will platform-detect your currently-installed version of PHP, dropping support for older versions is _not_ considered a backwards compatibility break (but you may be unable to install newer versions until updating to a supported version of PHP). Anything marked as `@internal` or any `protected` or `private` method is not considered in scope for backwards-compatibility guarantees. Similarly, all methods should be treated as ones that may throw an exception, and as such new types of exceptions are not considered a BC break either. From e4b02e981af19e7d134300b5f9a3e3356ac3a4b7 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 15:15:34 -0700 Subject: [PATCH 16/18] lint --- src/Client.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index d9efc0d..68b83bc 100644 --- a/src/Client.php +++ b/src/Client.php @@ -48,7 +48,9 @@ public function __construct( private string $apiHost = self::DEFAULT_API_HOST, ) { if (!str_starts_with($secretKey, 'secret_')) { - throw new ApiError('Invalid secret key. Please verify you copied the full value from the SnapAuth dashboard.'); + throw new ApiError( + 'Invalid secret key. Please verify you copied the full value from the SnapAuth dashboard.', + ); } } From 8f40f53fd865824e8f5686da8b3010012f4b0556 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 15:19:07 -0700 Subject: [PATCH 17/18] List composer runtime api as a dependency --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f040142..0da26de 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,8 @@ }, "require": { "php": "^8.1", - "ext-curl": "*" + "ext-curl": "*", + "composer-runtime-api": "^2.2" }, "require-dev": { "maglnet/composer-require-checker": "^2.0 || ^3.0 || ^4.0", From 727094fc3ff42213bb463df2e6739a525cc9b62c Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 1 Apr 2024 15:19:40 -0700 Subject: [PATCH 18/18] test on 8.4' --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 05ed73b..bdc4b75 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,6 +23,7 @@ jobs: - '8.1' - '8.2' - '8.3' + - '8.4-dev' steps: - name: Check out code