Skip to content

Commit fe0fa68

Browse files
authored
Update to support Pro level (#4)
1 parent 5be8590 commit fe0fa68

19 files changed

+341
-53
lines changed

config/verify.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
<?php
22

3+
use LycheeVerify\Http\Middleware\VerifyProStatus;
34
use LycheeVerify\Http\Middleware\VerifySupporterStatus;
4-
use LycheeVerify\Validators\ValidateHash;
5+
use LycheeVerify\Validators\ValidatePro;
56
use LycheeVerify\Validators\ValidateSignature;
7+
use LycheeVerify\Validators\ValidateSupporter;
68
use LycheeVerify\Verify;
79
use LycheeVerify\VerifyServiceProvider;
810

911
return [
1012
'validation' => [
11-
ValidateHash::class => 'e2511ed0f1adc865c7c8b40bec19d656323d81f6',
12-
ValidateSignature::class => '1bae28471b402e73ddad2ea871b15835954822c3',
13-
Verify::class => '6dd9c193b7505dff9d4ba0f19f0e2b3a3171d83a',
13+
ValidateSupporter::class => 'ef1a42701af6dc36e052556a0ee1c762394f9428',
14+
ValidatePro::class => '482b48f1a026684b6c1754e45ca180ffc52483ff',
15+
ValidateSignature::class => '5a8a855d4b59c44c298daa66801c79f2aba20492',
16+
Verify::class => 'ffd01909f5189bc7bae21266e356f83c898ccd37',
1417
VerifySupporterStatus::class => '6358c45ed0414c1e2697e0881238659fa6221bed',
15-
VerifyServiceProvider::class => '927a8f3c811fc82cb8a0ac2667c06e7d292c3633',
18+
VerifyProStatus::class => '212e6ada794587ee8e2b81cf76e243d134a7e823',
19+
VerifyServiceProvider::class => '923b63b15d25e69b95ed1d5ec1c82ba57f1a7d74',
1620
],
1721
];

docs/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The license status is represented by the `Status` enum, which defines three leve
3434

3535
- `FREE_EDITION` - Basic features, no license required
3636
- `SUPPORTER_EDITION` - Standard supporter features
37-
- `PLUS_EDITION` - Premium features with extended capabilities
37+
- `PRO_EDITION` - Premium features with extended capabilities
3838

3939
#### 2. Validation Mechanism
4040

@@ -55,7 +55,7 @@ Two concrete validators are provided:
5555
- Simple validation mechanism for standard supporters
5656

5757
2. **ValidateSignature** - Uses cryptographic signatures for validation
58-
- Returns `PLUS_EDITION` status when valid
58+
- Returns `PRO_EDITION` status when valid
5959
- More secure mechanism for premium users
6060
- Uses asymmetric cryptography (ECDSA) for verification
6161

@@ -102,7 +102,7 @@ if ($verify->is_plus()) {
102102
$verify->authorize();
103103

104104
// Will throw exception if not a plus user
105-
$verify->authorize(Status::PLUS_EDITION);
105+
$verify->authorize(Status::PRO_EDITION);
106106
```
107107

108108
### Conditional Execution

generate-key.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/**
4+
* Generate a cryptographically secure key in the format:
5+
* XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
6+
*
7+
* Each segment contains 5 uppercase alphanumeric characters (A-Z, 0-9)
8+
*/
9+
function generateSecureKey(): string
10+
{
11+
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
12+
$segments = [];
13+
14+
// Generate 5 segments
15+
for ($i = 0; $i < 5; $i++) {
16+
$segment = '';
17+
18+
// Generate 5 characters per segment
19+
for ($j = 0; $j < 5; $j++) {
20+
$randomIndex = random_int(0, strlen($characters) - 1);
21+
$segment .= $characters[$randomIndex];
22+
}
23+
24+
$segments[] = $segment;
25+
}
26+
27+
return implode('-', $segments);
28+
}
29+
30+
// Generate and display the key
31+
$key = generateSecureKey();
32+
$hash = password_hash($key, PASSWORD_BCRYPT);
33+
34+
echo "Generated key: " . $key . PHP_EOL;
35+
echo "Bcrypt hash: " . $hash . PHP_EOL;

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ includes:
33
- vendor/lychee-org/phpstan-lychee/phpstan.neon
44

55
parameters:
6+
level: 9
67
treatPhpDocTypesAsCertain: false
78
paths:
89
- src
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace LycheeVerify\Console\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use LycheeVerify\Contract\Status;
7+
use LycheeVerify\Validators\ValidatePro;
8+
use LycheeVerify\Validators\ValidateSupporter;
9+
use function Safe\json_encode;
10+
11+
class CheckKeyCommand extends Command
12+
{
13+
/**
14+
* The name and signature of the console command.
15+
*
16+
* @var string
17+
*/
18+
protected $signature = 'verify:check-key {key : The license key to check}';
19+
20+
/**
21+
* The console command description.
22+
*
23+
* @var string
24+
*/
25+
protected $description = 'Check the sponsorship level for a given license key';
26+
27+
/**
28+
* Execute the console command.
29+
*/
30+
public function handle(): int
31+
{
32+
if (!$this->hasArgument('key')) {
33+
$this->error('No key provided. Please provide a license key to check.');
34+
35+
return Command::FAILURE;
36+
}
37+
38+
if (!is_string($this->argument('key'))) {
39+
$this->error('Invalid key format. The key must be a string.');
40+
41+
return Command::FAILURE;
42+
}
43+
44+
$key = $this->argument('key');
45+
46+
$validateSupporter = new ValidateSupporter();
47+
$validatePro = new ValidatePro();
48+
49+
// We use a dummy verifiable string for checking
50+
$verifiable = json_encode(['url' => '', 'email' => '']);
51+
52+
// Check Pro edition first (highest tier)
53+
if ($validatePro->validate($verifiable, $key)) {
54+
$this->info('Key: ' . $key);
55+
$this->info('Sponsorship Level: ' . Status::PRO_EDITION->value . ' (Pro Edition)');
56+
57+
return Command::SUCCESS;
58+
}
59+
60+
// Check Supporter edition
61+
if ($validateSupporter->validate($verifiable, $key)) {
62+
$this->info('Key: ' . $key);
63+
$this->info('Sponsorship Level: ' . Status::SUPPORTER_EDITION->value . ' (Supporter Edition)');
64+
65+
return Command::SUCCESS;
66+
}
67+
68+
// Key doesn't match any known tier
69+
$this->warn('Key: ' . $key);
70+
$this->warn('Sponsorship Level: ' . Status::FREE_EDITION->value . ' (No valid sponsorship found)');
71+
72+
return Command::FAILURE;
73+
}
74+
}

src/Contract/Status.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ enum Status: string
99
{
1010
case FREE_EDITION = 'free';
1111
case SUPPORTER_EDITION = 'se';
12-
case PLUS_EDITION = 'plus';
13-
}
12+
case PRO_EDITION = 'pro';
13+
case SIGNATURE_EDITION = 'signature';
14+
}

src/Contract/VerifyInterface.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,25 @@ public function get_status(): Status;
2323
public function check(Status $required_status = Status::SUPPORTER_EDITION): bool;
2424

2525
/**
26-
* Returns true if the user is a supporter (or plus registered user).
26+
* Returns true if the user is a supporter (or pro/signature user).
2727
*
2828
* @return bool
2929
*/
3030
public function is_supporter(): bool;
3131

3232
/**
33-
* Return true of the user is a plus registered user.
33+
* Return true of the user is a pro user (or signature user).
3434
*
3535
* @return bool
3636
*/
37-
public function is_plus(): bool;
37+
public function is_pro(): bool;
38+
39+
/**
40+
* Return true if the user is a signature user.
41+
*
42+
* @return bool
43+
*/
44+
public function is_signature(): bool;
3845

3946
/**
4047
* Authorize the operation if the installation is verified.

src/Exceptions/BaseVerifyException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,4 @@ protected function __construct(int $httpStatusCode, string $message, ?\Throwable
6060
}
6161
parent::__construct($httpStatusCode, $message, $previous, [], $code ?? 0);
6262
}
63-
}
63+
}

src/Exceptions/SupporterOnlyOperationException.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ final class SupporterOnlyOperationException extends BaseVerifyException
1212
public function __construct(Status $status = Status::SUPPORTER_EDITION)
1313
{
1414
$users = match ($status) {
15-
Status::PLUS_EDITION => 'plus users',
15+
Status::SIGNATURE_EDITION => 'signature users',
16+
Status::PRO_EDITION => 'pro users',
1617
default => 'supporters',
1718
};
1819
parent::__construct(402, sprintf('This operation is reserved to the %s of LycheeOrg.', $users), null);
1920
}
20-
}
21+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace LycheeVerify\Http\Middleware;
4+
5+
use Illuminate\Http\Request;
6+
use LycheeVerify\Contract\Status;
7+
use LycheeVerify\Contract\VerifyException;
8+
use LycheeVerify\Exceptions\SupporterOnlyOperationException;
9+
use LycheeVerify\Verify;
10+
11+
/**
12+
* This class checks whether the use on a supporter installation.
13+
* If it is not, then the request is aborted.
14+
*/
15+
class VerifyProStatus
16+
{
17+
private Verify $verify;
18+
19+
public function __construct(?Verify $verify)
20+
{
21+
$this->verify = $verify ?? new Verify();
22+
}
23+
24+
/**
25+
* Handle an incoming request.
26+
*
27+
* @param Request $request the incoming request to serve
28+
* @param \Closure $next the next operation to be applied to the
29+
* request
30+
*
31+
* @return mixed
32+
*
33+
* @throws VerifyException
34+
*/
35+
public function handle(Request $request, \Closure $next, string $required_status): mixed
36+
{
37+
$required_status = Status::tryFrom($required_status) ?? Status::PRO_EDITION;
38+
39+
if ($this->verify->check($required_status)) {
40+
return $next($request);
41+
}
42+
43+
throw new SupporterOnlyOperationException(Status::PRO_EDITION);
44+
}
45+
}

0 commit comments

Comments
 (0)