composer update + update jwt (#1587)

This commit is contained in:
Charles 2025-08-11 16:57:59 -04:00 committed by GitHub
parent 27a8423f55
commit b03d2cf919
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 111 additions and 167 deletions

View File

@ -2,16 +2,16 @@
namespace App\Services\Nodes;
use App\Extensions\Lcobucci\JWT\Encoding\TimestampDates;
use Carbon\CarbonImmutable;
use DateTimeImmutable;
use Illuminate\Support\Str;
use App\Models\Node;
use App\Models\User;
use Lcobucci\JWT\Token\Plain;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use App\Extensions\Lcobucci\JWT\Encoding\TimestampDates;
use Lcobucci\JWT\UnencryptedToken;
class NodeJWTService
{
@ -64,7 +64,7 @@ class NodeJWTService
/**
* Generate a new JWT for a given node.
*/
public function handle(Node $node, ?string $identifiedBy, string $algo = 'md5'): Plain
public function handle(Node $node, ?string $identifiedBy, string $algo = 'sha256'): UnencryptedToken
{
$identifier = hash($algo, $identifiedBy);
$config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($node->daemon_token));
@ -80,7 +80,9 @@ class NodeJWTService
$builder = $builder->expiresAt($this->expiresAt);
if (!empty($this->subject)) {
$builder = $builder->relatedTo($this->subject)->withHeader('sub', $this->subject);
$builder = $builder
->relatedTo($this->subject)
->withHeader('sub', $this->subject);
}
foreach ($this->claims as $key => $value) {
@ -88,14 +90,7 @@ class NodeJWTService
}
if (!is_null($this->user)) {
$builder = $builder
->withClaim('user_uuid', $this->user->uuid)
// The "user_id" claim is deprecated and should not be referenced — it remains
// here solely to ensure older versions of daemon are unaffected when the Panel
// is updated.
//
// This claim will be removed in Panel@1.11 or later.
->withClaim('user_id', $this->user->id);
$builder = $builder->withClaim('user_uuid', $this->user->uuid);
}
return $builder

View File

@ -10,7 +10,7 @@ use App\Services\Nodes\NodeJWTService;
use Carbon\CarbonImmutable;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Support\Facades\Http;
use Lcobucci\JWT\Token\Plain;
use Lcobucci\JWT\UnencryptedToken;
class TransferServerService
{
@ -22,7 +22,7 @@ class TransferServerService
private NodeJWTService $nodeJWTService,
) {}
private function notify(ServerTransfer $transfer, Plain $token): void
private function notify(ServerTransfer $transfer, UnencryptedToken $token): void
{
Http::daemon($transfer->oldNode)->post("/api/servers/{$transfer->server->uuid}/transfer", [
'url' => $transfer->newNode->getConnectionAddress() . '/api/transfers',

View File

@ -23,7 +23,7 @@
"laravel/socialite": "^5.21",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"lcobucci/jwt": "~4.3.0",
"lcobucci/jwt": "^5.5",
"league/flysystem-aws-s3-v3": "^3.29",
"league/flysystem-memory": "^3.29",
"phpseclib/phpseclib": "~3.0.18",

192
composer.lock generated
View File

@ -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": "7138d3f3e583251a87fcbf5157c43315",
"content-hash": "e5d9f294519edc6e4cca937c579709f8",
"packages": [
{
"name": "abdelhamiderrahmouni/filament-monaco-editor",
@ -1020,16 +1020,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.352.4",
"version": "3.352.5",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "d3ce2a85687d55cd67d52682306227bc6aa836d6"
"reference": "e226dcc96c0a1165d9c8248ec637d1006b883609"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d3ce2a85687d55cd67d52682306227bc6aa836d6",
"reference": "d3ce2a85687d55cd67d52682306227bc6aa836d6",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e226dcc96c0a1165d9c8248ec637d1006b883609",
"reference": "e226dcc96c0a1165d9c8248ec637d1006b883609",
"shasum": ""
},
"require": {
@ -1111,9 +1111,9 @@
"support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.352.4"
"source": "https://github.com/aws/aws-sdk-php/tree/3.352.5"
},
"time": "2025-08-07T18:15:55+00:00"
"time": "2025-08-08T18:09:38+00:00"
},
{
"name": "blade-ui-kit/blade-heroicons",
@ -2258,33 +2258,32 @@
},
{
"name": "doctrine/inflector",
"version": "2.0.10",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/inflector.git",
"reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc"
"reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc",
"reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc",
"url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b",
"reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^11.0",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.1",
"phpstan/phpstan-strict-rules": "^1.3",
"phpunit/phpunit": "^8.5 || ^9.5",
"vimeo/psalm": "^4.25 || ^5.4"
"doctrine/coding-standard": "^12.0 || ^13.0",
"phpstan/phpstan": "^1.12 || ^2.0",
"phpstan/phpstan-phpunit": "^1.4 || ^2.0",
"phpstan/phpstan-strict-rules": "^1.6 || ^2.0",
"phpunit/phpunit": "^8.5 || ^12.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Inflector\\": "lib/Doctrine/Inflector"
"Doctrine\\Inflector\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -2329,7 +2328,7 @@
],
"support": {
"issues": "https://github.com/doctrine/inflector/issues",
"source": "https://github.com/doctrine/inflector/tree/2.0.10"
"source": "https://github.com/doctrine/inflector/tree/2.1.0"
},
"funding": [
{
@ -2345,7 +2344,7 @@
"type": "tidelift"
}
],
"time": "2024-02-18T20:23:39+00:00"
"time": "2025-08-10T19:31:58+00:00"
},
{
"name": "doctrine/lexer",
@ -4373,105 +4372,40 @@
},
"time": "2025-01-28T15:15:29+00:00"
},
{
"name": "lcobucci/clock",
"version": "3.3.1",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/clock.git",
"reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lcobucci/clock/zipball/db3713a61addfffd615b79bf0bc22f0ccc61b86b",
"reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b",
"shasum": ""
},
"require": {
"php": "~8.2.0 || ~8.3.0 || ~8.4.0",
"psr/clock": "^1.0"
},
"provide": {
"psr/clock-implementation": "1.0"
},
"require-dev": {
"infection/infection": "^0.29",
"lcobucci/coding-standard": "^11.1.0",
"phpstan/extension-installer": "^1.3.1",
"phpstan/phpstan": "^1.10.25",
"phpstan/phpstan-deprecation-rules": "^1.1.3",
"phpstan/phpstan-phpunit": "^1.3.13",
"phpstan/phpstan-strict-rules": "^1.5.1",
"phpunit/phpunit": "^11.3.6"
},
"type": "library",
"autoload": {
"psr-4": {
"Lcobucci\\Clock\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Luís Cobucci",
"email": "lcobucci@gmail.com"
}
],
"description": "Yet another clock abstraction",
"support": {
"issues": "https://github.com/lcobucci/clock/issues",
"source": "https://github.com/lcobucci/clock/tree/3.3.1"
},
"funding": [
{
"url": "https://github.com/lcobucci",
"type": "github"
},
{
"url": "https://www.patreon.com/lcobucci",
"type": "patreon"
}
],
"time": "2024-09-24T20:45:14+00:00"
},
{
"name": "lcobucci/jwt",
"version": "4.3.0",
"version": "5.5.0",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
"reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4"
"reference": "a835af59b030d3f2967725697cf88300f579088e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/4d7de2fe0d51a96418c0d04004986e410e87f6b4",
"reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/a835af59b030d3f2967725697cf88300f579088e",
"reference": "a835af59b030d3f2967725697cf88300f579088e",
"shasum": ""
},
"require": {
"ext-hash": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-sodium": "*",
"lcobucci/clock": "^2.0 || ^3.0",
"php": "^7.4 || ^8.0"
"php": "~8.2.0 || ~8.3.0 || ~8.4.0",
"psr/clock": "^1.0"
},
"require-dev": {
"infection/infection": "^0.21",
"lcobucci/coding-standard": "^6.0",
"mikey179/vfsstream": "^1.6.7",
"infection/infection": "^0.29",
"lcobucci/clock": "^3.2",
"lcobucci/coding-standard": "^11.0",
"phpbench/phpbench": "^1.2",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/php-invoker": "^3.1",
"phpunit/phpunit": "^9.5"
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.10.7",
"phpstan/phpstan-deprecation-rules": "^1.1.3",
"phpstan/phpstan-phpunit": "^1.3.10",
"phpstan/phpstan-strict-rules": "^1.5.0",
"phpunit/phpunit": "^11.1"
},
"suggest": {
"lcobucci/clock": ">= 3.2"
},
"type": "library",
"autoload": {
@ -4497,7 +4431,7 @@
],
"support": {
"issues": "https://github.com/lcobucci/jwt/issues",
"source": "https://github.com/lcobucci/jwt/tree/4.3.0"
"source": "https://github.com/lcobucci/jwt/tree/5.5.0"
},
"funding": [
{
@ -4509,7 +4443,7 @@
"type": "patreon"
}
],
"time": "2023-01-02T13:28:00+00:00"
"time": "2025-01-26T21:29:45+00:00"
},
{
"name": "league/commonmark",
@ -14666,16 +14600,16 @@
},
{
"name": "sebastian/comparator",
"version": "6.3.1",
"version": "6.3.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959"
"reference": "85c77556683e6eee4323e4c5468641ca0237e2e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959",
"reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8",
"reference": "85c77556683e6eee4323e4c5468641ca0237e2e8",
"shasum": ""
},
"require": {
@ -14734,15 +14668,27 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1"
"source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
"type": "tidelift"
}
],
"time": "2025-03-07T06:57:01+00:00"
"time": "2025-08-10T08:07:46+00:00"
},
{
"name": "sebastian/complexity",
@ -15323,16 +15269,16 @@
},
{
"name": "sebastian/type",
"version": "5.1.2",
"version": "5.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e"
"reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e",
"reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449",
"reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449",
"shasum": ""
},
"require": {
@ -15368,15 +15314,27 @@
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"security": "https://github.com/sebastianbergmann/type/security/policy",
"source": "https://github.com/sebastianbergmann/type/tree/5.1.2"
"source": "https://github.com/sebastianbergmann/type/tree/5.1.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/type",
"type": "tidelift"
}
],
"time": "2025-03-18T13:35:50+00:00"
"time": "2025-08-09T06:55:48+00:00"
},
{
"name": "sebastian/version",

View File

@ -8,15 +8,12 @@ use Lcobucci\JWT\Configuration;
use App\Models\Permission;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\UnencryptedToken;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase;
class WebsocketControllerTest extends ClientApiIntegrationTestCase
{
/**
* Test that a subuser attempting to connect to the websocket receives an error if they
* do not explicitly have the permission.
*/
public function test_subuser_without_websocket_permission_receives_error(): void
{
[$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_RESTART]);
@ -59,41 +56,34 @@ class WebsocketControllerTest extends ClientApiIntegrationTestCase
$response->assertJsonStructure(['data' => ['token', 'socket']]);
$connection = $response->json('data.socket');
$this->assertStringStartsWith('wss://', $connection, 'Failed asserting that websocket connection address has expected "wss://" prefix.');
$this->assertStringEndsWith("/api/servers/$server->uuid/ws", $connection, 'Failed asserting that websocket connection address uses expected Daemon endpoint.');
$this->assertStringStartsWith('wss://', $connection);
$this->assertStringEndsWith("/api/servers/$server->uuid/ws", $connection);
$key = InMemory::plainText($server->node->daemon_token);
$config = Configuration::forSymmetricSigner(new Sha256(), $key);
$config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->daemon_token));
$config->setValidationConstraints(new SignedWith(new Sha256(), $key));
/** @var \Lcobucci\JWT\Token\Plain $token */
$token = $config->parser()->parse($response->json('data.token'));
$this->assertInstanceOf(UnencryptedToken::class, $token);
$constraints = [new SignedWith(new Sha256(), $key)];
$this->assertTrue(
$config->validator()->validate($token, ...$config->validationConstraints()),
$config->validator()->validate($token, ...$constraints),
'Failed to validate that the JWT data returned was signed using the Node\'s secret key.'
);
// The way we generate times for the JWT will truncate the microseconds from the
// time, but CarbonImmutable::now() will include them, thus causing test failures.
//
// This little chunk of logic just strips those out by generating a new CarbonImmutable
// instance from the current timestamp, which is how the JWT works. We also need to
// switch to UTC here for consistency.
$expect = CarbonImmutable::createFromTimestamp(CarbonImmutable::now()->getTimestamp())->timezone('UTC');
$expect = CarbonImmutable::createFromTimestamp(CarbonImmutable::now()->getTimestamp())->timezone('UTC')->setMicroseconds(0);
// Check that the claims are generated correctly.
$this->assertTrue($token->hasBeenIssuedBy(config('app.url')));
$this->assertTrue($token->isPermittedFor($server->node->getConnectionAddress()));
$this->assertEquals($expect, $token->claims()->get('iat'));
$this->assertEquals($expect->subMinutes(5), $token->claims()->get('nbf'));
$this->assertEquals($expect->addMinutes(10), $token->claims()->get('exp'));
$this->assertSame($user->id, $token->claims()->get('user_id'));
$this->assertSame($server->uuid, $token->claims()->get('server_uuid'));
$this->assertSame(['*'], $token->claims()->get('permissions'));
$claims = $token->claims();
$this->assertSame(config('app.url'), $claims->get('iss'));
$this->assertSame($server->node->getConnectionAddress(), $claims->get('aud')[0] ?? null);
$this->assertEquals($expect, CarbonImmutable::instance($claims->get('iat'))->setMicroseconds(0));
$this->assertEquals($expect->subMinutes(5), CarbonImmutable::instance($claims->get('nbf'))->setMicroseconds(0));
$this->assertEquals($expect->addMinutes(10), CarbonImmutable::instance($claims->get('exp'))->setMicroseconds(0));
$this->assertSame($user->uuid, $claims->get('user_uuid'));
$this->assertSame($server->uuid, $claims->get('server_uuid'));
$this->assertSame(['*'], $claims->get('permissions'));
}
/**
* Test that the subuser's permissions are passed along correctly in the generated JWT.
*/
public function test_jwt_is_configured_correctly_for_server_subuser(): void
{
$permissions = [Permission::ACTION_WEBSOCKET_CONNECT, Permission::ACTION_CONTROL_CONSOLE];
@ -107,17 +97,18 @@ class WebsocketControllerTest extends ClientApiIntegrationTestCase
$response->assertOk();
$response->assertJsonStructure(['data' => ['token', 'socket']]);
$config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->daemon_token));
$config->setValidationConstraints(new SignedWith(new Sha256(), $key));
/** @var \Lcobucci\JWT\Token\Plain $token */
$token = $config->parser()->parse($response->json('data.token'));
$key = InMemory::plainText($server->node->daemon_token);
$config = Configuration::forSymmetricSigner(new Sha256(), $key);
$token = $config->parser()->parse($response->json('data.token'));
$this->assertInstanceOf(UnencryptedToken::class, $token);
$constraints = [new SignedWith(new Sha256(), $key)];
$this->assertTrue(
$config->validator()->validate($token, ...$config->validationConstraints()),
$config->validator()->validate($token, ...$constraints),
'Failed to validate that the JWT data returned was signed using the Node\'s secret key.'
);
// Check that the claims are generated correctly.
$this->assertSame($permissions, $token->claims()->get('permissions'));
}
}