From 57232a052a52182f47709ab959b31d452246aa61 Mon Sep 17 00:00:00 2001 From: ildyria Date: Fri, 28 Nov 2025 22:07:09 +0100 Subject: [PATCH 1/3] update to support Pro level --- config/verify.php | 12 +++-- docs/README.md | 6 +-- phpstan.neon | 1 + src/Contract/Status.php | 5 +- src/Contract/VerifyInterface.php | 13 +++-- src/Exceptions/BaseVerifyException.php | 2 +- .../SupporterOnlyOperationException.php | 5 +- src/Http/Middleware/VerifyProStatus.php | 45 ++++++++++++++++ src/Validators/ValidatePro.php | 42 +++++++++++++++ src/Validators/ValidateSignature.php | 4 +- ...ValidateHash.php => ValidateSupporter.php} | 2 +- src/Verify.php | 53 ++++++++++++------- tests/Constants.php | 3 ++ tests/Verify/Validators/ValidateProTest.php | 26 +++++++++ ...HashTest.php => ValidateSupporterTest.php} | 8 +-- tests/Verify/VerifyTest.php | 50 ++++++++++++----- 16 files changed, 225 insertions(+), 52 deletions(-) create mode 100644 src/Http/Middleware/VerifyProStatus.php create mode 100644 src/Validators/ValidatePro.php rename src/Validators/{ValidateHash.php => ValidateSupporter.php} (94%) create mode 100644 tests/Verify/Validators/ValidateProTest.php rename tests/Verify/Validators/{ValidateHashTest.php => ValidateSupporterTest.php} (69%) diff --git a/config/verify.php b/config/verify.php index 4461a5e..c7a2d19 100644 --- a/config/verify.php +++ b/config/verify.php @@ -1,17 +1,21 @@ [ - ValidateHash::class => 'e2511ed0f1adc865c7c8b40bec19d656323d81f6', - ValidateSignature::class => '1bae28471b402e73ddad2ea871b15835954822c3', - Verify::class => '6dd9c193b7505dff9d4ba0f19f0e2b3a3171d83a', + ValidateSupporter::class => '882f8890242f77ee8dd8b84c6511c82e32858c12', + ValidatePro::class => '676b945ac8649a099ced12bfbab87b11c5c0daad', + ValidateSignature::class => '5a8a855d4b59c44c298daa66801c79f2aba20492', + Verify::class => 'e5a8ebb4878c0fd3387ec0cb2f28d9caa463e5bb', VerifySupporterStatus::class => '6358c45ed0414c1e2697e0881238659fa6221bed', + VerifyProStatus::class => '212e6ada794587ee8e2b81cf76e243d134a7e823', VerifyServiceProvider::class => '927a8f3c811fc82cb8a0ac2667c06e7d292c3633', ], ]; diff --git a/docs/README.md b/docs/README.md index b1509ae..127e60c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,7 +34,7 @@ The license status is represented by the `Status` enum, which defines three leve - `FREE_EDITION` - Basic features, no license required - `SUPPORTER_EDITION` - Standard supporter features -- `PLUS_EDITION` - Premium features with extended capabilities +- `PRO_EDITION` - Premium features with extended capabilities #### 2. Validation Mechanism @@ -55,7 +55,7 @@ Two concrete validators are provided: - Simple validation mechanism for standard supporters 2. **ValidateSignature** - Uses cryptographic signatures for validation - - Returns `PLUS_EDITION` status when valid + - Returns `PRO_EDITION` status when valid - More secure mechanism for premium users - Uses asymmetric cryptography (ECDSA) for verification @@ -102,7 +102,7 @@ if ($verify->is_plus()) { $verify->authorize(); // Will throw exception if not a plus user -$verify->authorize(Status::PLUS_EDITION); +$verify->authorize(Status::PRO_EDITION); ``` ### Conditional Execution diff --git a/phpstan.neon b/phpstan.neon index 1e5ba0a..b55f92a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,6 +3,7 @@ includes: - vendor/lychee-org/phpstan-lychee/phpstan.neon parameters: + level: 9 treatPhpDocTypesAsCertain: false paths: - src diff --git a/src/Contract/Status.php b/src/Contract/Status.php index 99d2742..75fc359 100644 --- a/src/Contract/Status.php +++ b/src/Contract/Status.php @@ -9,5 +9,6 @@ enum Status: string { case FREE_EDITION = 'free'; case SUPPORTER_EDITION = 'se'; - case PLUS_EDITION = 'plus'; -} \ No newline at end of file + case PRO_EDITION = 'pro'; + case SIGNATURE_EDITION = 'signature'; +} diff --git a/src/Contract/VerifyInterface.php b/src/Contract/VerifyInterface.php index b3af797..949cd81 100644 --- a/src/Contract/VerifyInterface.php +++ b/src/Contract/VerifyInterface.php @@ -23,18 +23,25 @@ public function get_status(): Status; public function check(Status $required_status = Status::SUPPORTER_EDITION): bool; /** - * Returns true if the user is a supporter (or plus registered user). + * Returns true if the user is a supporter (or pro/signature user). * * @return bool */ public function is_supporter(): bool; /** - * Return true of the user is a plus registered user. + * Return true of the user is a pro user (or signature user). * * @return bool */ - public function is_plus(): bool; + public function is_pro(): bool; + + /** + * Return true if the user is a signature user + * + * @return bool + */ + public function is_signature(): bool; /** * Authorize the operation if the installation is verified. diff --git a/src/Exceptions/BaseVerifyException.php b/src/Exceptions/BaseVerifyException.php index 40e6061..da743e1 100644 --- a/src/Exceptions/BaseVerifyException.php +++ b/src/Exceptions/BaseVerifyException.php @@ -60,4 +60,4 @@ protected function __construct(int $httpStatusCode, string $message, ?\Throwable } parent::__construct($httpStatusCode, $message, $previous, [], $code ?? 0); } -} \ No newline at end of file +} diff --git a/src/Exceptions/SupporterOnlyOperationException.php b/src/Exceptions/SupporterOnlyOperationException.php index f059d37..5c5ac2f 100644 --- a/src/Exceptions/SupporterOnlyOperationException.php +++ b/src/Exceptions/SupporterOnlyOperationException.php @@ -12,9 +12,10 @@ final class SupporterOnlyOperationException extends BaseVerifyException public function __construct(Status $status = Status::SUPPORTER_EDITION) { $users = match ($status) { - Status::PLUS_EDITION => 'plus users', + Status::SIGNATURE_EDITION => 'signature users', + Status::PRO_EDITION => 'pro users', default => 'supporters', }; parent::__construct(402, sprintf('This operation is reserved to the %s of LycheeOrg.', $users), null); } -} \ No newline at end of file +} diff --git a/src/Http/Middleware/VerifyProStatus.php b/src/Http/Middleware/VerifyProStatus.php new file mode 100644 index 0000000..60b8f4c --- /dev/null +++ b/src/Http/Middleware/VerifyProStatus.php @@ -0,0 +1,45 @@ +verify = $verify ?? new Verify(); + } + + /** + * Handle an incoming request. + * + * @param Request $request the incoming request to serve + * @param \Closure $next the next operation to be applied to the + * request + * + * @return mixed + * + * @throws VerifyException + */ + public function handle(Request $request, \Closure $next, string $required_status): mixed + { + $required_status = Status::tryFrom($required_status) ?? Status::PRO_EDITION; + + if ($this->verify->check($required_status)) { + return $next($request); + } + + throw new SupporterOnlyOperationException(Status::PRO_EDITION); + } +} diff --git a/src/Validators/ValidatePro.php b/src/Validators/ValidatePro.php new file mode 100644 index 0000000..1e80fad --- /dev/null +++ b/src/Validators/ValidatePro.php @@ -0,0 +1,42 @@ +hash = $hash ?? '$2y$10$Bo9bqC34tQr.hEJ1qZZNBO.dkJRoEiLeZpXxlsYaSlaKi/dRyCyea'; + } + + /** + * Validate whether the static license key provided matches with the hash. + */ + public function validate(string $verifiable, string $license): bool + { + if ($license === '') { + return false; + } + + return Hash::check($license, $this->hash); + } + + /** + * If the hash passes, we grant the user the supporter edition. + * + * @return Status::PRO_EDITION + */ + public function grant(): Status + { + return Status::PRO_EDITION; + } +} diff --git a/src/Validators/ValidateSignature.php b/src/Validators/ValidateSignature.php index b6d7726..778619e 100644 --- a/src/Validators/ValidateSignature.php +++ b/src/Validators/ValidateSignature.php @@ -51,10 +51,10 @@ public function validate(string $verifiable, string $license): bool /** * If the signature passes, we grant the user the plus edition. * - * @return Status::PLUS_EDITION + * @return Status::SIGNATURE_EDITION */ public function grant(): Status { - return Status::PLUS_EDITION; + return Status::SIGNATURE_EDITION; } } diff --git a/src/Validators/ValidateHash.php b/src/Validators/ValidateSupporter.php similarity index 94% rename from src/Validators/ValidateHash.php rename to src/Validators/ValidateSupporter.php index 9a91b66..a5dcc5c 100644 --- a/src/Validators/ValidateHash.php +++ b/src/Validators/ValidateSupporter.php @@ -9,7 +9,7 @@ /** * This is the validator for supporters. */ -class ValidateHash implements ValidatorInterface +class ValidateSupporter implements ValidatorInterface { private string $hash; diff --git a/src/Verify.php b/src/Verify.php index 87fddaf..cb1c040 100755 --- a/src/Verify.php +++ b/src/Verify.php @@ -6,8 +6,10 @@ use LycheeVerify\Contract\Status; use LycheeVerify\Contract\VerifyInterface; use LycheeVerify\Exceptions\SupporterOnlyOperationException; -use LycheeVerify\Validators\ValidateHash; +use LycheeVerify\Validators\ValidatePro; use LycheeVerify\Validators\ValidateSignature; +use LycheeVerify\Validators\ValidateSupporter; + use function Safe\json_encode; use function Safe\preg_replace; @@ -16,18 +18,21 @@ class Verify implements VerifyInterface private string $config_email; private string $license_key; private ValidateSignature $validateSignature; - private ValidateHash $validateHash; + private ValidateSupporter $validateSupporter; + private ValidatePro $validatePro; public function __construct( #[\SensitiveParameter] ?string $config_email = null, #[\SensitiveParameter] ?string $license_key = null, #[\SensitiveParameter] ?string $public_key = null, - #[\SensitiveParameter] ?string $hash = null, + #[\SensitiveParameter] ?string $hash_supporter = null, + #[\SensitiveParameter] ?string $hash_pro = null, ) { $this->config_email = $config_email ?? DB::table('configs')->where('key', 'email')->first()?->value ?? ''; // @phpstan-ignore-line $this->license_key = $license_key ?? DB::table('configs')->where('key', 'license_key')->first()?->value ?? ''; // @phpstan-ignore-line $this->validateSignature = new ValidateSignature($public_key); - $this->validateHash = new ValidateHash($hash); + $this->validatePro = new ValidatePro($hash_pro); + $this->validateSupporter = new ValidateSupporter($hash_supporter); } /** @@ -39,11 +44,15 @@ public function get_status(): Status { $base = json_encode(['url' => config('app.url'), 'email' => $this->config_email]); - if ($this->validateHash->validate($base, $this->license_key)) { - return $this->validateHash->grant(); + if ($this->validateSupporter->validate($base, $this->license_key)) { + return $this->validateSupporter->grant(); + } + + if ($this->validatePro->validate($base, $this->license_key)) { + return $this->validatePro->grant(); } - if ($this->config_email !== '' && $this->validateSignature->validate($base, $this->license_key)) { + if ($this->config_email !== '' && $this->validateSignature->validate($base, $this->license_key)) { return $this->validateSignature->grant(); } @@ -64,15 +73,12 @@ public function check(Status $required_status = Status::SUPPORTER_EDITION): bool } $status = $this->get_status(); - if ($status === Status::PLUS_EDITION) { - return true; - } - - if ($status === Status::SUPPORTER_EDITION && $required_status === Status::SUPPORTER_EDITION) { - return true; - } - - return false; + return match ($status) { + Status::SIGNATURE_EDITION => true, + Status::PRO_EDITION => in_array($required_status, [Status::PRO_EDITION, Status::SUPPORTER_EDITION], true), + Status::SUPPORTER_EDITION => in_array($required_status, [Status::SUPPORTER_EDITION], true), + default => false, + }; } /** @@ -90,11 +96,21 @@ public function is_supporter(): bool * * @return bool */ - public function is_plus(): bool + public function is_pro(): bool { - return $this->check(Status::PLUS_EDITION); + return $this->check(Status::PRO_EDITION); } + /** + * Return true if the user is a signature user + * + * @return bool + */ + public function is_signature(): bool + { + return $this->check(Status::SIGNATURE_EDITION); + } + /** * Authorize the operation if the installation is verified. * Otherwise throw an exception. @@ -137,6 +153,7 @@ public function when(mixed $valIfTrue, mixed $valIfFalse, Status $required_statu */ public function validate(): bool { + /** @var array|null $checks */ $checks = config('verify.validation'); if ($checks === null || count($checks) === 0) { return false; diff --git a/tests/Constants.php b/tests/Constants.php index 60e5c99..9c97330 100644 --- a/tests/Constants.php +++ b/tests/Constants.php @@ -10,6 +10,9 @@ class Constants public const HASH = '$2y$10$TBBCJeXOa10Y5WwLU.yeQ.4fQS/BujBknIvISyhlPzp.LU9jWIH2W'; public const HASH_KEY = 'BSIM4-MEVVQ-HVQKG-Q6VNT-KP3EZ'; + public const HASH2 = '$2y$10$QZe2zo7.0ymBbHTfg7j2YOLjVQV7St5.mHDaMUro0Kx2Ca/c0ZZZ2'; + public const HASH2_KEY = 'OFVZ4-ZRIID-UIDXT-D6IAG-XC3RM'; + public const EMAIL_TEST = 'test@example.com'; public const LICENSE_JSON = 'MEUCIQCSxj5Oe1JoAgrR0lfa4UZksnjPBZjqhuJmRPupu1YJvwIgIRAcwM3r89tLYuO9DOcmOzOHLTX4hPw0FVKwZG8M/2I='; diff --git a/tests/Verify/Validators/ValidateProTest.php b/tests/Verify/Validators/ValidateProTest.php new file mode 100644 index 0000000..675e43f --- /dev/null +++ b/tests/Verify/Validators/ValidateProTest.php @@ -0,0 +1,26 @@ +validate($verifiable, $signature)); + } + + public function testInvalidHash(): void + { + $crypto = new ValidatePro(Constants::HASH2); + $signature = 'random stuff'; + $verifiable = ''; + self::assertFalse($crypto->validate($verifiable, $signature)); + } +} diff --git a/tests/Verify/Validators/ValidateHashTest.php b/tests/Verify/Validators/ValidateSupporterTest.php similarity index 69% rename from tests/Verify/Validators/ValidateHashTest.php rename to tests/Verify/Validators/ValidateSupporterTest.php index 7861bf5..4a08bf4 100644 --- a/tests/Verify/Validators/ValidateHashTest.php +++ b/tests/Verify/Validators/ValidateSupporterTest.php @@ -4,13 +4,13 @@ use LycheeVerify\Tests\Constants; use LycheeVerify\Tests\TestCase; -use LycheeVerify\Validators\ValidateHash; +use LycheeVerify\Validators\ValidateSupporter; -class ValidateHashTest extends TestCase +class ValidateSupporterTest extends TestCase { public function testValidHash(): void { - $crypto = new ValidateHash(Constants::HASH); + $crypto = new ValidateSupporter(Constants::HASH); $signature = Constants::HASH_KEY; $verifiable = ''; self::assertTrue($crypto->validate($verifiable, $signature)); @@ -18,7 +18,7 @@ public function testValidHash(): void public function testInvalidHash(): void { - $crypto = new ValidateHash(Constants::HASH); + $crypto = new ValidateSupporter(Constants::HASH); $signature = 'random stuff'; $verifiable = ''; self::assertFalse($crypto->validate($verifiable, $signature)); diff --git a/tests/Verify/VerifyTest.php b/tests/Verify/VerifyTest.php index 2255812..3bda9a0 100644 --- a/tests/Verify/VerifyTest.php +++ b/tests/Verify/VerifyTest.php @@ -19,46 +19,71 @@ public function testVerifyDefault(): void self::assertFalse($verify->check()); self::assertTrue($verify->check(Status::FREE_EDITION)); self::assertFalse($verify->check(Status::SUPPORTER_EDITION)); - self::assertFalse($verify->check(Status::PLUS_EDITION)); + self::assertFalse($verify->check(Status::PRO_EDITION)); + self::assertFalse($verify->check(Status::SIGNATURE_EDITION)); self::assertFalse($verify->is_supporter()); // User is not recognised as supporter. - self::assertFalse($verify->is_plus()); // User is not recognised as plus. + self::assertFalse($verify->is_pro()); // User is not recognised as pro. $this->assertThrows(fn () => $verify->authorize(Status::SUPPORTER_EDITION), SupporterOnlyOperationException::class, 'supporters'); - $this->assertThrows(fn () => $verify->authorize(Status::PLUS_EDITION), SupporterOnlyOperationException::class, 'plus'); + $this->assertThrows(fn () => $verify->authorize(Status::PRO_EDITION), SupporterOnlyOperationException::class, 'pro'); } - public function testVerifySupporterDefault(): void + public function testVerifySupporter(): void { $verify = new Verify( license_key: Constants::HASH_KEY, - hash: Constants::HASH, + hash_supporter: Constants::HASH, + hash_pro: Constants::HASH2, ); self::assertEquals($verify->get_status(), Status::SUPPORTER_EDITION); self::assertTrue($verify->check()); self::assertTrue($verify->check(Status::FREE_EDITION)); self::assertTrue($verify->check(Status::SUPPORTER_EDITION)); - self::assertFalse($verify->check(Status::PLUS_EDITION)); + self::assertFalse($verify->check(Status::PRO_EDITION)); + self::assertFalse($verify->check(Status::SIGNATURE_EDITION)); self::assertTrue($verify->is_supporter()); // User is recognised as supporter. - self::assertFalse($verify->is_plus()); // User is not recognised as plus. + self::assertFalse($verify->is_pro()); // User is not recognised as pro. + self::assertFalse($verify->is_signature()); // User is not recognised as signature. - $this->assertThrows(fn () => $verify->authorize(Status::PLUS_EDITION), SupporterOnlyOperationException::class, 'plus'); + $this->assertThrows(fn () => $verify->authorize(Status::PRO_EDITION), SupporterOnlyOperationException::class, 'pro'); } - public function testVerifyPlus(): void + public function testVerifyPro(): void + { + $verify = new Verify( + license_key: Constants::HASH2_KEY, + hash_supporter: Constants::HASH, + hash_pro: Constants::HASH2, + ); + self::assertEquals($verify->get_status(), Status::PRO_EDITION); + + self::assertTrue($verify->check()); + self::assertTrue($verify->check(Status::FREE_EDITION)); + self::assertTrue($verify->check(Status::SUPPORTER_EDITION)); + self::assertTrue($verify->check(Status::PRO_EDITION)); + self::assertFalse($verify->check(Status::SIGNATURE_EDITION)); + self::assertTrue($verify->is_supporter()); // User is recognised as supporter. + self::assertTrue($verify->is_pro()); // User is recognised as pro. + self::assertFalse($verify->is_signature()); // User is not recognised as signature. + } + + public function testVerifySignature(): void { $verify = new Verify( config_email: Constants::EMAIL_TEST, license_key: Constants::LICENSE_JSON, public_key: Constants::PUBLIC_EC_KEY, ); - self::assertEquals($verify->get_status(), Status::PLUS_EDITION); + self::assertEquals($verify->get_status(), Status::SIGNATURE_EDITION); self::assertTrue($verify->check()); self::assertTrue($verify->check(Status::FREE_EDITION)); self::assertTrue($verify->check(Status::SUPPORTER_EDITION)); - self::assertTrue($verify->check(Status::PLUS_EDITION)); + self::assertTrue($verify->check(Status::PRO_EDITION)); + self::assertTrue($verify->check(Status::SIGNATURE_EDITION)); self::assertTrue($verify->is_supporter()); // user is recognised as supporter. - self::assertTrue($verify->is_plus()); // user is recognised as plus. + self::assertTrue($verify->is_pro()); // user is recognised as pro. + self::assertTrue($verify->is_signature()); // User is not recognised as signature. } public function testVerifyValidate(): void @@ -66,6 +91,7 @@ public function testVerifyValidate(): void $verify = new Verify(); // Check config before executing validation + /** @var array $checks */ $checks = config('verify.validation'); foreach ($checks as $class => $value) { $file = (new \ReflectionClass($class))->getFileName(); From 4cf9e9bd67564c9c62ce9db0e4d44502a2c6d574 Mon Sep 17 00:00:00 2001 From: ildyria Date: Fri, 12 Dec 2025 22:49:08 +0100 Subject: [PATCH 2/3] keys rotated --- config/verify.php | 6 +-- generate-key.php | 35 ++++++++++++++ src/Console/Commands/CheckKeyCommand.php | 61 ++++++++++++++++++++++++ src/Validators/ValidatePro.php | 2 +- src/Validators/ValidateSupporter.php | 2 +- src/VerifyServiceProvider.php | 8 +++- 6 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 generate-key.php create mode 100644 src/Console/Commands/CheckKeyCommand.php diff --git a/config/verify.php b/config/verify.php index c7a2d19..94821d0 100644 --- a/config/verify.php +++ b/config/verify.php @@ -10,12 +10,12 @@ return [ 'validation' => [ - ValidateSupporter::class => '882f8890242f77ee8dd8b84c6511c82e32858c12', - ValidatePro::class => '676b945ac8649a099ced12bfbab87b11c5c0daad', + ValidateSupporter::class => 'ef1a42701af6dc36e052556a0ee1c762394f9428', + ValidatePro::class => '482b48f1a026684b6c1754e45ca180ffc52483ff', ValidateSignature::class => '5a8a855d4b59c44c298daa66801c79f2aba20492', Verify::class => 'e5a8ebb4878c0fd3387ec0cb2f28d9caa463e5bb', VerifySupporterStatus::class => '6358c45ed0414c1e2697e0881238659fa6221bed', VerifyProStatus::class => '212e6ada794587ee8e2b81cf76e243d134a7e823', - VerifyServiceProvider::class => '927a8f3c811fc82cb8a0ac2667c06e7d292c3633', + VerifyServiceProvider::class => '923b63b15d25e69b95ed1d5ec1c82ba57f1a7d74', ], ]; diff --git a/generate-key.php b/generate-key.php new file mode 100644 index 0000000..f8dfff3 --- /dev/null +++ b/generate-key.php @@ -0,0 +1,35 @@ +argument('key'); + + $validateSupporter = new ValidateSupporter(); + $validatePro = new ValidatePro(); + + // We use a dummy verifiable string for checking + $verifiable = json_encode(['url' => '', 'email' => '']); + + // Check Pro edition first (highest tier) + if ($validatePro->validate($verifiable, $key)) { + $this->info('Key: ' . $key); + $this->info('Sponsorship Level: ' . Status::PRO_EDITION->value . ' (Pro Edition)'); + return Command::SUCCESS; + } + + // Check Supporter edition + if ($validateSupporter->validate($verifiable, $key)) { + $this->info('Key: ' . $key); + $this->info('Sponsorship Level: ' . Status::SUPPORTER_EDITION->value . ' (Supporter Edition)'); + return Command::SUCCESS; + } + + // Key doesn't match any known tier + $this->warn('Key: ' . $key); + $this->warn('Sponsorship Level: ' . Status::FREE_EDITION->value . ' (No valid sponsorship found)'); + + return Command::FAILURE; + } +} diff --git a/src/Validators/ValidatePro.php b/src/Validators/ValidatePro.php index 1e80fad..8c33a65 100644 --- a/src/Validators/ValidatePro.php +++ b/src/Validators/ValidatePro.php @@ -15,7 +15,7 @@ class ValidatePro implements ValidatorInterface public function __construct(#[\SensitiveParameter] ?string $hash = null) { - $this->hash = $hash ?? '$2y$10$Bo9bqC34tQr.hEJ1qZZNBO.dkJRoEiLeZpXxlsYaSlaKi/dRyCyea'; + $this->hash = $hash ?? '$2y$12$HCan1i4raFBRltqPSy2imeDAIf7UQxSQTJpA3Jv0cJ/fplNc4mEta'; } /** diff --git a/src/Validators/ValidateSupporter.php b/src/Validators/ValidateSupporter.php index a5dcc5c..4a76d96 100644 --- a/src/Validators/ValidateSupporter.php +++ b/src/Validators/ValidateSupporter.php @@ -15,7 +15,7 @@ class ValidateSupporter implements ValidatorInterface public function __construct(#[\SensitiveParameter] ?string $hash = null) { - $this->hash = $hash ?? '$2y$10$Bo9bqC34tQr.hEJ1qZZNBO.dkJRoEiLeZpXxlsYaSlaKi/dRyCyea'; + $this->hash = $hash ?? '$2y$12$x58lfmOIxyKh3kzZyWJibuDPnDXO7er6xmDqoUZ3PNrFJHF9DaHpC'; } /** diff --git a/src/VerifyServiceProvider.php b/src/VerifyServiceProvider.php index c05a183..15e812a 100644 --- a/src/VerifyServiceProvider.php +++ b/src/VerifyServiceProvider.php @@ -3,6 +3,7 @@ namespace LycheeVerify; use Illuminate\Support\ServiceProvider; +use LycheeVerify\Console\Commands\CheckKeyCommand; class VerifyServiceProvider extends ServiceProvider { @@ -17,7 +18,7 @@ class VerifyServiceProvider extends ServiceProvider */ public function register() { - $this->mergeConfigFrom(static::CONFIG, 'verify'); + $this->mergeConfigFrom(self::CONFIG, 'verify'); $this->app->bind('verify', function () { return new \LycheeVerify\Verify(); // Replace with your actual instantiation logic. @@ -26,5 +27,10 @@ public function register() public function boot(): void { + if ($this->app->runningInConsole()) { + $this->commands([ + CheckKeyCommand::class, + ]); + } } } From e2a5a828faec257967806168620bb900fcf02440 Mon Sep 17 00:00:00 2001 From: ildyria Date: Fri, 12 Dec 2025 22:53:13 +0100 Subject: [PATCH 3/3] fixed --- config/verify.php | 2 +- src/Console/Commands/CheckKeyCommand.php | 109 ++++++++++-------- src/Contract/Status.php | 2 +- src/Contract/VerifyInterface.php | 12 +- .../SupporterOnlyOperationException.php | 2 +- src/Verify.php | 40 +++---- tests/Constants.php | 2 +- tests/Verify/VerifyTest.php | 20 ++-- 8 files changed, 101 insertions(+), 88 deletions(-) diff --git a/config/verify.php b/config/verify.php index 94821d0..ccc8af3 100644 --- a/config/verify.php +++ b/config/verify.php @@ -13,7 +13,7 @@ ValidateSupporter::class => 'ef1a42701af6dc36e052556a0ee1c762394f9428', ValidatePro::class => '482b48f1a026684b6c1754e45ca180ffc52483ff', ValidateSignature::class => '5a8a855d4b59c44c298daa66801c79f2aba20492', - Verify::class => 'e5a8ebb4878c0fd3387ec0cb2f28d9caa463e5bb', + Verify::class => 'ffd01909f5189bc7bae21266e356f83c898ccd37', VerifySupporterStatus::class => '6358c45ed0414c1e2697e0881238659fa6221bed', VerifyProStatus::class => '212e6ada794587ee8e2b81cf76e243d134a7e823', VerifyServiceProvider::class => '923b63b15d25e69b95ed1d5ec1c82ba57f1a7d74', diff --git a/src/Console/Commands/CheckKeyCommand.php b/src/Console/Commands/CheckKeyCommand.php index 1dd0808..fc5718d 100644 --- a/src/Console/Commands/CheckKeyCommand.php +++ b/src/Console/Commands/CheckKeyCommand.php @@ -6,56 +6,69 @@ use LycheeVerify\Contract\Status; use LycheeVerify\Validators\ValidatePro; use LycheeVerify\Validators\ValidateSupporter; - use function Safe\json_encode; class CheckKeyCommand extends Command { - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'verify:check-key {key : The license key to check}'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Check the sponsorship level for a given license key'; - - /** - * Execute the console command. - */ - public function handle(): int - { - $key = $this->argument('key'); - - $validateSupporter = new ValidateSupporter(); - $validatePro = new ValidatePro(); - - // We use a dummy verifiable string for checking - $verifiable = json_encode(['url' => '', 'email' => '']); - - // Check Pro edition first (highest tier) - if ($validatePro->validate($verifiable, $key)) { - $this->info('Key: ' . $key); - $this->info('Sponsorship Level: ' . Status::PRO_EDITION->value . ' (Pro Edition)'); - return Command::SUCCESS; - } - - // Check Supporter edition - if ($validateSupporter->validate($verifiable, $key)) { - $this->info('Key: ' . $key); - $this->info('Sponsorship Level: ' . Status::SUPPORTER_EDITION->value . ' (Supporter Edition)'); - return Command::SUCCESS; - } - - // Key doesn't match any known tier - $this->warn('Key: ' . $key); - $this->warn('Sponsorship Level: ' . Status::FREE_EDITION->value . ' (No valid sponsorship found)'); - - return Command::FAILURE; - } + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'verify:check-key {key : The license key to check}'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Check the sponsorship level for a given license key'; + + /** + * Execute the console command. + */ + public function handle(): int + { + if (!$this->hasArgument('key')) { + $this->error('No key provided. Please provide a license key to check.'); + + return Command::FAILURE; + } + + if (!is_string($this->argument('key'))) { + $this->error('Invalid key format. The key must be a string.'); + + return Command::FAILURE; + } + + $key = $this->argument('key'); + + $validateSupporter = new ValidateSupporter(); + $validatePro = new ValidatePro(); + + // We use a dummy verifiable string for checking + $verifiable = json_encode(['url' => '', 'email' => '']); + + // Check Pro edition first (highest tier) + if ($validatePro->validate($verifiable, $key)) { + $this->info('Key: ' . $key); + $this->info('Sponsorship Level: ' . Status::PRO_EDITION->value . ' (Pro Edition)'); + + return Command::SUCCESS; + } + + // Check Supporter edition + if ($validateSupporter->validate($verifiable, $key)) { + $this->info('Key: ' . $key); + $this->info('Sponsorship Level: ' . Status::SUPPORTER_EDITION->value . ' (Supporter Edition)'); + + return Command::SUCCESS; + } + + // Key doesn't match any known tier + $this->warn('Key: ' . $key); + $this->warn('Sponsorship Level: ' . Status::FREE_EDITION->value . ' (No valid sponsorship found)'); + + return Command::FAILURE; + } } diff --git a/src/Contract/Status.php b/src/Contract/Status.php index 75fc359..270b574 100644 --- a/src/Contract/Status.php +++ b/src/Contract/Status.php @@ -10,5 +10,5 @@ enum Status: string case FREE_EDITION = 'free'; case SUPPORTER_EDITION = 'se'; case PRO_EDITION = 'pro'; - case SIGNATURE_EDITION = 'signature'; + case SIGNATURE_EDITION = 'signature'; } diff --git a/src/Contract/VerifyInterface.php b/src/Contract/VerifyInterface.php index 949cd81..5b6f910 100644 --- a/src/Contract/VerifyInterface.php +++ b/src/Contract/VerifyInterface.php @@ -36,12 +36,12 @@ public function is_supporter(): bool; */ public function is_pro(): bool; - /** - * Return true if the user is a signature user - * - * @return bool - */ - public function is_signature(): bool; + /** + * Return true if the user is a signature user. + * + * @return bool + */ + public function is_signature(): bool; /** * Authorize the operation if the installation is verified. diff --git a/src/Exceptions/SupporterOnlyOperationException.php b/src/Exceptions/SupporterOnlyOperationException.php index 5c5ac2f..cf905a1 100644 --- a/src/Exceptions/SupporterOnlyOperationException.php +++ b/src/Exceptions/SupporterOnlyOperationException.php @@ -12,7 +12,7 @@ final class SupporterOnlyOperationException extends BaseVerifyException public function __construct(Status $status = Status::SUPPORTER_EDITION) { $users = match ($status) { - Status::SIGNATURE_EDITION => 'signature users', + Status::SIGNATURE_EDITION => 'signature users', Status::PRO_EDITION => 'pro users', default => 'supporters', }; diff --git a/src/Verify.php b/src/Verify.php index cb1c040..df358e7 100755 --- a/src/Verify.php +++ b/src/Verify.php @@ -9,7 +9,6 @@ use LycheeVerify\Validators\ValidatePro; use LycheeVerify\Validators\ValidateSignature; use LycheeVerify\Validators\ValidateSupporter; - use function Safe\json_encode; use function Safe\preg_replace; @@ -18,7 +17,7 @@ class Verify implements VerifyInterface private string $config_email; private string $license_key; private ValidateSignature $validateSignature; - private ValidateSupporter $validateSupporter; + private ValidateSupporter $validateSupporter; private ValidatePro $validatePro; public function __construct( @@ -32,7 +31,7 @@ public function __construct( $this->license_key = $license_key ?? DB::table('configs')->where('key', 'license_key')->first()?->value ?? ''; // @phpstan-ignore-line $this->validateSignature = new ValidateSignature($public_key); $this->validatePro = new ValidatePro($hash_pro); - $this->validateSupporter = new ValidateSupporter($hash_supporter); + $this->validateSupporter = new ValidateSupporter($hash_supporter); } /** @@ -52,7 +51,7 @@ public function get_status(): Status return $this->validatePro->grant(); } - if ($this->config_email !== '' && $this->validateSignature->validate($base, $this->license_key)) { + if ($this->config_email !== '' && $this->validateSignature->validate($base, $this->license_key)) { return $this->validateSignature->grant(); } @@ -73,12 +72,13 @@ public function check(Status $required_status = Status::SUPPORTER_EDITION): bool } $status = $this->get_status(); - return match ($status) { - Status::SIGNATURE_EDITION => true, - Status::PRO_EDITION => in_array($required_status, [Status::PRO_EDITION, Status::SUPPORTER_EDITION], true), - Status::SUPPORTER_EDITION => in_array($required_status, [Status::SUPPORTER_EDITION], true), - default => false, - }; + + return match ($status) { + Status::SIGNATURE_EDITION => true, + Status::PRO_EDITION => in_array($required_status, [Status::PRO_EDITION, Status::SUPPORTER_EDITION], true), + Status::SUPPORTER_EDITION => in_array($required_status, [Status::SUPPORTER_EDITION], true), + default => false, + }; } /** @@ -101,15 +101,15 @@ public function is_pro(): bool return $this->check(Status::PRO_EDITION); } - /** - * Return true if the user is a signature user - * - * @return bool - */ - public function is_signature(): bool - { - return $this->check(Status::SIGNATURE_EDITION); - } + /** + * Return true if the user is a signature user. + * + * @return bool + */ + public function is_signature(): bool + { + return $this->check(Status::SIGNATURE_EDITION); + } /** * Authorize the operation if the installation is verified. @@ -153,7 +153,7 @@ public function when(mixed $valIfTrue, mixed $valIfFalse, Status $required_statu */ public function validate(): bool { - /** @var array|null $checks */ + /** @var array|null $checks */ $checks = config('verify.validation'); if ($checks === null || count($checks) === 0) { return false; diff --git a/tests/Constants.php b/tests/Constants.php index 9c97330..60e721a 100644 --- a/tests/Constants.php +++ b/tests/Constants.php @@ -10,7 +10,7 @@ class Constants public const HASH = '$2y$10$TBBCJeXOa10Y5WwLU.yeQ.4fQS/BujBknIvISyhlPzp.LU9jWIH2W'; public const HASH_KEY = 'BSIM4-MEVVQ-HVQKG-Q6VNT-KP3EZ'; - public const HASH2 = '$2y$10$QZe2zo7.0ymBbHTfg7j2YOLjVQV7St5.mHDaMUro0Kx2Ca/c0ZZZ2'; + public const HASH2 = '$2y$10$QZe2zo7.0ymBbHTfg7j2YOLjVQV7St5.mHDaMUro0Kx2Ca/c0ZZZ2'; public const HASH2_KEY = 'OFVZ4-ZRIID-UIDXT-D6IAG-XC3RM'; public const EMAIL_TEST = 'test@example.com'; diff --git a/tests/Verify/VerifyTest.php b/tests/Verify/VerifyTest.php index 3bda9a0..ab4509d 100644 --- a/tests/Verify/VerifyTest.php +++ b/tests/Verify/VerifyTest.php @@ -20,7 +20,7 @@ public function testVerifyDefault(): void self::assertTrue($verify->check(Status::FREE_EDITION)); self::assertFalse($verify->check(Status::SUPPORTER_EDITION)); self::assertFalse($verify->check(Status::PRO_EDITION)); - self::assertFalse($verify->check(Status::SIGNATURE_EDITION)); + self::assertFalse($verify->check(Status::SIGNATURE_EDITION)); self::assertFalse($verify->is_supporter()); // User is not recognised as supporter. self::assertFalse($verify->is_pro()); // User is not recognised as pro. @@ -33,7 +33,7 @@ public function testVerifySupporter(): void $verify = new Verify( license_key: Constants::HASH_KEY, hash_supporter: Constants::HASH, - hash_pro: Constants::HASH2, + hash_pro: Constants::HASH2, ); self::assertEquals($verify->get_status(), Status::SUPPORTER_EDITION); @@ -41,20 +41,20 @@ public function testVerifySupporter(): void self::assertTrue($verify->check(Status::FREE_EDITION)); self::assertTrue($verify->check(Status::SUPPORTER_EDITION)); self::assertFalse($verify->check(Status::PRO_EDITION)); - self::assertFalse($verify->check(Status::SIGNATURE_EDITION)); + self::assertFalse($verify->check(Status::SIGNATURE_EDITION)); self::assertTrue($verify->is_supporter()); // User is recognised as supporter. self::assertFalse($verify->is_pro()); // User is not recognised as pro. - self::assertFalse($verify->is_signature()); // User is not recognised as signature. + self::assertFalse($verify->is_signature()); // User is not recognised as signature. $this->assertThrows(fn () => $verify->authorize(Status::PRO_EDITION), SupporterOnlyOperationException::class, 'pro'); } - public function testVerifyPro(): void + public function testVerifyPro(): void { $verify = new Verify( license_key: Constants::HASH2_KEY, hash_supporter: Constants::HASH, - hash_pro: Constants::HASH2, + hash_pro: Constants::HASH2, ); self::assertEquals($verify->get_status(), Status::PRO_EDITION); @@ -62,10 +62,10 @@ public function testVerifyPro(): void self::assertTrue($verify->check(Status::FREE_EDITION)); self::assertTrue($verify->check(Status::SUPPORTER_EDITION)); self::assertTrue($verify->check(Status::PRO_EDITION)); - self::assertFalse($verify->check(Status::SIGNATURE_EDITION)); + self::assertFalse($verify->check(Status::SIGNATURE_EDITION)); self::assertTrue($verify->is_supporter()); // User is recognised as supporter. self::assertTrue($verify->is_pro()); // User is recognised as pro. - self::assertFalse($verify->is_signature()); // User is not recognised as signature. + self::assertFalse($verify->is_signature()); // User is not recognised as signature. } public function testVerifySignature(): void @@ -83,7 +83,7 @@ public function testVerifySignature(): void self::assertTrue($verify->check(Status::SIGNATURE_EDITION)); self::assertTrue($verify->is_supporter()); // user is recognised as supporter. self::assertTrue($verify->is_pro()); // user is recognised as pro. - self::assertTrue($verify->is_signature()); // User is not recognised as signature. + self::assertTrue($verify->is_signature()); // User is not recognised as signature. } public function testVerifyValidate(): void @@ -91,7 +91,7 @@ public function testVerifyValidate(): void $verify = new Verify(); // Check config before executing validation - /** @var array $checks */ + /** @var array $checks */ $checks = config('verify.validation'); foreach ($checks as $class => $value) { $file = (new \ReflectionClass($class))->getFileName();