Merge branch 'main' into filament-v4

This commit is contained in:
notCharles 2025-08-11 17:18:11 -04:00
commit 582b9bc0ac
32 changed files with 418 additions and 390 deletions

View File

@ -63,7 +63,7 @@ FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS fin
WORKDIR /var/www/html
# Install additional required libraries
RUN apk update && apk add --no-cache \
RUN apk add --no-cache \
caddy ca-certificates supervisor supercronic
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
@ -93,10 +93,11 @@ COPY docker/Caddyfile /etc/caddy/Caddyfile
# Add Laravel scheduler to crontab
COPY docker/crontab /etc/supercronic/crontab
COPY docker/entrypoint.sh ./docker/entrypoint.sh
COPY docker/entrypoint.sh /entrypoint.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost/up || exit 1
CMD /bin/ash /healthcheck.sh
EXPOSE 80 443
@ -104,5 +105,5 @@ VOLUME /pelican-data
USER www-data
ENTRYPOINT [ "/bin/ash", "docker/entrypoint.sh" ]
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]

View File

@ -67,8 +67,8 @@ FROM --platform=$TARGETOS/$TARGETARCH base AS final
WORKDIR /var/www/html
# Install additional required libraries
RUN apk update && apk add --no-cache \
caddy ca-certificates supervisor supercronic
RUN apk add --no-cache \
caddy ca-certificates supervisor supercronic coreutils
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
@ -97,10 +97,11 @@ COPY docker/Caddyfile /etc/caddy/Caddyfile
# Add Laravel scheduler to crontab
COPY docker/crontab /etc/supercronic/crontab
COPY docker/entrypoint.sh ./docker/entrypoint.sh
COPY docker/entrypoint.sh /entrypoint.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost/up || exit 1
CMD /bin/ash /healthcheck.sh
EXPOSE 80 443
@ -108,5 +109,5 @@ VOLUME /pelican-data
USER www-data
ENTRYPOINT [ "/bin/ash", "docker/entrypoint.sh" ]
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]

View File

@ -32,4 +32,6 @@ interface OAuthSchemaInterface
public function getHexColor(): ?string;
public function isEnabled(): bool;
public function shouldCreateMissingUsers(): bool;
}

View File

@ -4,6 +4,8 @@ namespace App\Extensions\OAuth\Schemas;
use App\Extensions\OAuth\OAuthSchemaInterface;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Schemas\Components\Component;
use Illuminate\Support\Str;
@ -53,6 +55,17 @@ abstract class OAuthSchema implements OAuthSchemaInterface
->revealable()
->autocomplete(false)
->default(env("OAUTH_{$id}_CLIENT_SECRET")),
Toggle::make("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS")
->label(trans('admin/setting.oauth.create_missing_users'))
->columnSpanFull()
->inline(false)
->onIcon('tabler-check')
->offIcon('tabler-x')
->onColor('success')
->offColor('danger')
->formatStateUsing(fn ($state): bool => (bool) $state)
->afterStateUpdated(fn ($state, Set $set) => $set("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", (bool) $state))
->default(env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS")),
];
}
@ -96,4 +109,11 @@ abstract class OAuthSchema implements OAuthSchemaInterface
return env("OAUTH_{$id}_ENABLED", false);
}
public function shouldCreateMissingUsers(): bool
{
$id = Str::upper($this->getId());
return env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", false);
}
}

View File

@ -2,8 +2,6 @@
namespace App\Http\Controllers\Api\Remote;
use DateTimeInterface;
use Exception;
use App\Models\Node;
use Carbon\Carbon;
use Illuminate\Support\Str;

View File

@ -2,7 +2,8 @@
namespace App\Http\Controllers\Api\Remote\Servers;
use Illuminate\Http\Request;
use App\Enums\ContainerStatus;
use App\Http\Requests\Api\Remote\ServerRequest;
use App\Models\Server;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
@ -12,11 +13,11 @@ class ServerContainersController extends Controller
/**
* Updates the server container's status on the Panel
*/
public function status(Server $server, Request $request): JsonResponse
public function status(ServerRequest $request, Server $server): JsonResponse
{
$status = fluent($request->json()->all())->get('data.new_state');
$status = ContainerStatus::tryFrom($request->json('data.new_state')) ?? ContainerStatus::Missing;
cache()->put("servers.$server->uuid.container.status", $status, now()->addHour());
cache()->put("servers.$server->uuid.status", $status, now()->addHour());
return new JsonResponse([]);
}

View File

@ -6,6 +6,7 @@ use App\Models\Node;
use Throwable;
use App\Models\ActivityLog;
use App\Enums\ServerState;
use App\Http\Requests\Api\Remote\ServerRequest;
use App\Models\Backup;
use Illuminate\Http\Request;
use App\Models\Server;
@ -32,7 +33,7 @@ class ServerDetailsController extends Controller
* Returns details about the server that allows daemon to self-recover and ensure
* that the state of the server matches the Panel at all times.
*/
public function __invoke(Server $server): JsonResponse
public function __invoke(ServerRequest $request, Server $server): JsonResponse
{
return new JsonResponse([
'settings' => $this->configurationStructureService->handle($server),

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\Remote\Servers;
use App\Exceptions\Model\DataValidationException;
use App\Enums\ServerState;
use App\Http\Requests\Api\Remote\ServerRequest;
use Illuminate\Http\Response;
use App\Models\Server;
use Illuminate\Http\JsonResponse;
@ -16,14 +17,12 @@ class ServerInstallController extends Controller
/**
* Returns installation information for a server.
*/
public function index(Server $server): JsonResponse
public function index(ServerRequest $request, Server $server): JsonResponse
{
$egg = $server->egg;
return new JsonResponse([
'container_image' => $egg->copy_script_container,
'entrypoint' => $egg->copy_script_entry,
'script' => $egg->copy_script_install,
'container_image' => $server->egg->copy_script_container,
'entrypoint' => $server->egg->copy_script_entry,
'script' => $server->egg->copy_script_install,
]);
}

View File

@ -2,13 +2,12 @@
namespace App\Http\Controllers\Api\Remote\Servers;
use Throwable;
use App\Http\Requests\Api\Remote\ServerRequest;
use App\Models\Server;
use App\Repositories\Daemon\DaemonServerRepository;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use App\Models\Allocation;
use App\Models\ServerTransfer;
use Illuminate\Database\ConnectionInterface;
use App\Http\Controllers\Controller;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
@ -29,14 +28,23 @@ class ServerTransferController extends Controller
*
* @throws Throwable
*/
public function failure(Server $server): JsonResponse
public function failure(ServerRequest $request, Server $server): JsonResponse
{
$transfer = $server->transfer;
if (is_null($transfer)) {
throw new ConflictHttpException('Server is not being transferred.');
}
return $this->processFailedTransfer($transfer);
$this->connection->transaction(function () use ($transfer) {
$transfer->forceFill(['successful' => false])->saveOrFail();
if ($transfer->new_allocation || $transfer->new_additional_allocations) {
$allocations = array_merge([$transfer->new_allocation], $transfer->new_additional_allocations);
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
}
});
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
/**
@ -44,16 +52,17 @@ class ServerTransferController extends Controller
*
* @throws Throwable
*/
public function success(Server $server): JsonResponse
public function success(ServerRequest $request, Server $server): JsonResponse
{
$transfer = $server->transfer;
if (is_null($transfer)) {
throw new ConflictHttpException('Server is not being transferred.');
}
$data = [];
/** @var Server $server */
$server = $this->connection->transaction(function () use ($server, $transfer, $data) {
$server = $this->connection->transaction(function () use ($server, $transfer) {
$data = [];
if ($transfer->old_allocation || $transfer->old_additional_allocations) {
$allocations = array_merge([$transfer->old_allocation], $transfer->old_additional_allocations);
// Remove the old allocations for the server and re-assign the server to the new
@ -61,6 +70,7 @@ class ServerTransferController extends Controller
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
$data['allocation_id'] = $transfer->new_allocation;
}
$data['node_id'] = $transfer->new_node;
$server->update($data);
@ -83,24 +93,4 @@ class ServerTransferController extends Controller
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
/**
* Release all the reserved allocations for this transfer and mark it as failed in
* the database.
*
* @throws Throwable
*/
protected function processFailedTransfer(ServerTransfer $transfer): JsonResponse
{
$this->connection->transaction(function () use (&$transfer) {
$transfer->forceFill(['successful' => false])->saveOrFail();
if ($transfer->new_allocation || $transfer->new_additional_allocations) {
$allocations = array_merge([$transfer->new_allocation], $transfer->new_additional_allocations);
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
}
});
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
}

View File

@ -6,8 +6,8 @@ use App\Extensions\OAuth\OAuthService;
use App\Filament\Pages\Auth\EditProfile;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\Users\UserCreationService;
use App\Services\Users\UserUpdateService;
use Exception;
use Filament\Notifications\Notification;
use Illuminate\Auth\AuthManager;
use Illuminate\Http\RedirectResponse;
@ -18,8 +18,9 @@ class OAuthController extends Controller
{
public function __construct(
private readonly AuthManager $auth,
private UserCreationService $userCreation,
private readonly UserUpdateService $updateService,
private readonly OAuthService $oauthService
private readonly OAuthService $oauthService,
) {}
/**
@ -40,8 +41,10 @@ class OAuthController extends Controller
*/
public function callback(Request $request, string $driver): RedirectResponse
{
// Driver is disabled - redirect to normal login
if (!$this->oauthService->get($driver)?->isEnabled()) {
$driver = $this->oauthService->get($driver);
// Unknown driver or driver is disabled - redirect to normal login
if (!$driver || !$driver->isEnabled()) {
return redirect()->route('auth.login');
}
@ -59,33 +62,57 @@ class OAuthController extends Controller
return redirect()->route('auth.login');
}
$oauthUser = Socialite::driver($driver)->user();
$oauthUser = Socialite::driver($driver->getId())->user();
// User is already logged in and wants to link a new OAuth Provider
if ($request->user()) {
$oauth = $request->user()->oauth;
$oauth[$driver] = $oauthUser->getId();
$oauth[$driver->getId()] = $oauthUser->getId();
$this->updateService->handle($request->user(), ['oauth' => $oauth]);
return redirect(EditProfile::getUrl(['tab' => '-oauth-tab'], panel: 'app'));
}
try {
$user = User::query()->whereJsonContains('oauth->'. $driver, $oauthUser->getId())->firstOrFail();
$user = User::whereJsonContains('oauth->'. $driver->getId(), $oauthUser->getId())->first();
$this->auth->guard()->login($user, true);
} catch (Exception) {
// No user found - redirect to normal login
Notification::make()
->title('No linked User found')
->danger()
->persistent()
->send();
if (!$user) {
// No user found and auto creation is disabled - redirect to normal login
if (!$driver->shouldCreateMissingUsers()) {
Notification::make()
->title('No linked User found')
->danger()
->persistent()
->send();
return redirect()->route('auth.login');
return redirect()->route('auth.login');
}
$username = $oauthUser->getNickname();
$email = $oauthUser->getEmail();
// Incomplete data, can't create user - redirect to normal login
if (!$email) {
Notification::make()
->title('No linked User found')
->danger()
->persistent()
->send();
return redirect()->route('auth.login');
}
$user = $this->userCreation->handle([
'username' => $username,
'email' => $email,
'oauth' => [
$driver->getId() => $oauthUser->getId(),
],
]);
}
$this->auth->guard()->login($user, true);
return redirect('/');
}
}

View File

@ -2,15 +2,8 @@
namespace App\Http\Requests\Api\Remote;
use Illuminate\Foundation\Http\FormRequest;
class InstallationDataRequest extends FormRequest
class InstallationDataRequest extends ServerRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, string|string[]>
*/

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Requests\Api\Remote;
use App\Models\Node;
use App\Models\Server;
use Illuminate\Foundation\Http\FormRequest;
class ServerRequest extends FormRequest
{
public function authorize(): bool
{
/** @var Node $node */
$node = $this->attributes->get('node');
/** @var ?Server $server */
$server = $this->route()->parameter('server');
return $server && $server->node_id === $node->id;
}
}

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

@ -44,15 +44,7 @@ class ServerCreationService
* as possible given the input data. For example, if an allocation_id is passed with
* no node_id the node_is will be picked from the allocation.
*
* @param array{
* node_id?: int,
* oom_killer?: bool,
* oom_disabled?: bool,
* egg_id?: int,
* image?: ?string,
* startup?: ?string,
* start_on_completion?: ?bool,
* } $data
* @param array<mixed, mixed> $data
*
* @throws Throwable
* @throws DisplayException
@ -99,6 +91,8 @@ class ServerCreationService
if (empty($data['node_id'])) {
$data['node_id'] = $nodes->first();
}
} else {
$data['node_id'] = Allocation::find($data['allocation_id'])?->node_id;
}
Assert::false(empty($data['node_id']), 'Expected a non-empty node_id in server creation data.');

View File

@ -11,7 +11,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
{
@ -23,7 +23,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

@ -6,7 +6,6 @@ use App\Exceptions\Model\DataValidationException;
use Throwable;
use App\Events\Server\SubUserAdded;
use App\Models\User;
use Illuminate\Support\Str;
use App\Models\Server;
use App\Models\Subuser;
use Illuminate\Database\ConnectionInterface;
@ -42,14 +41,8 @@ class SubuserCreationService
return $this->connection->transaction(function () use ($server, $email, $permissions) {
$user = User::withoutGlobalScopes()->where('email', $email)->first();
if (!$user) {
// Just cap the username generated at 64 characters at most and then append a random string
// to the end to make it "unique"...
[$beforeDomain] = explode('@', $email, 1);
$username = substr(preg_replace('/([^\w.-]+)/', '', $beforeDomain), 0, 64) . Str::random(3);
$user = $this->userCreationService->handle([
'email' => $email,
'username' => $username,
'root_admin' => false,
]);
} else {

View File

@ -5,6 +5,7 @@ namespace App\Services\Users;
use Exception;
use App\Exceptions\Model\DataValidationException;
use App\Models\Role;
use Illuminate\Support\Str;
use Ramsey\Uuid\Uuid;
use App\Models\User;
use Illuminate\Contracts\Hashing\Hasher;
@ -44,6 +45,16 @@ class UserCreationService
$isRootAdmin = array_key_exists('root_admin', $data) && $data['root_admin'];
unset($data['root_admin']);
if (empty($data['username'])) {
$data['username'] = str($data['email'])->before('@')->toString() . Str::random(3);
}
$data['username'] = str($data['username'])
->replace(['.', '-'], '')
->ascii()
->substr(0, 64)
->toString();
/** @var User $user */
$user = User::query()->forceCreate(array_merge($data, [
'uuid' => Uuid::uuid4()->toString(),

View File

@ -45,6 +45,7 @@ services:
<<: [*panel-environment, *mail-environment]
XDG_DATA_HOME: /pelican-data
# SKIP_CADDY: true # enable when not using caddy.
TRUSTED_PROXIES:
volumes:
pelican-data:

View File

@ -13,15 +13,15 @@
"calebporzio/sushi": "^2.5",
"dedoc/scramble": "^0.12.10",
"doctrine/dbal": "~3.6.0",
"filament/filament": "4.0.0-beta22",
"filament/filament": "4.0.0-beta25",
"guzzlehttp/guzzle": "^7.9",
"laravel/framework": "^12.21",
"laravel/framework": "^12.22",
"laravel/helpers": "^1.7",
"laravel/sanctum": "^4.1",
"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",

433
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": "6bf532c77a50b48ff70400ca649c8de9",
"content-hash": "be2f0e0bdd59e886fd0a64dda84fa95d",
"packages": [
{
"name": "amphp/amp",
@ -936,16 +936,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.351.3",
"version": "3.352.6",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "7c58f4a8acd2230daad1ef23bceb9972e62bdf94"
"reference": "06d8e8c85f91e957f48480ce41e9c8a6d9fa253f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7c58f4a8acd2230daad1ef23bceb9972e62bdf94",
"reference": "7c58f4a8acd2230daad1ef23bceb9972e62bdf94",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/06d8e8c85f91e957f48480ce41e9c8a6d9fa253f",
"reference": "06d8e8c85f91e957f48480ce41e9c8a6d9fa253f",
"shasum": ""
},
"require": {
@ -1027,9 +1027,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.351.3"
"source": "https://github.com/aws/aws-sdk-php/tree/3.352.6"
},
"time": "2025-07-21T18:04:02+00:00"
"time": "2025-08-11T18:04:59+00:00"
},
{
"name": "blade-ui-kit/blade-heroicons",
@ -1674,16 +1674,16 @@
},
{
"name": "dedoc/scramble",
"version": "v0.12.23",
"version": "v0.12.28",
"source": {
"type": "git",
"url": "https://github.com/dedoc/scramble.git",
"reference": "5b650167c81c59138e844c2ae550c14dc1a249d0"
"reference": "f06a98d1fd6678544428df7077d73194e2d28de3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dedoc/scramble/zipball/5b650167c81c59138e844c2ae550c14dc1a249d0",
"reference": "5b650167c81c59138e844c2ae550c14dc1a249d0",
"url": "https://api.github.com/repos/dedoc/scramble/zipball/f06a98d1fd6678544428df7077d73194e2d28de3",
"reference": "f06a98d1fd6678544428df7077d73194e2d28de3",
"shasum": ""
},
"require": {
@ -1742,7 +1742,7 @@
],
"support": {
"issues": "https://github.com/dedoc/scramble/issues",
"source": "https://github.com/dedoc/scramble/tree/v0.12.23"
"source": "https://github.com/dedoc/scramble/tree/v0.12.28"
},
"funding": [
{
@ -1750,7 +1750,7 @@
"type": "github"
}
],
"time": "2025-06-15T09:04:49+00:00"
"time": "2025-08-04T12:20:10+00:00"
},
{
"name": "dflydev/dot-access-data",
@ -2174,33 +2174,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/",
@ -2245,7 +2244,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": [
{
@ -2261,7 +2260,7 @@
"type": "tidelift"
}
],
"time": "2024-02-18T20:23:39+00:00"
"time": "2025-08-10T19:31:58+00:00"
},
{
"name": "doctrine/lexer",
@ -2474,16 +2473,16 @@
},
{
"name": "filament/actions",
"version": "v4.0.0-beta22",
"version": "v4.0.0-beta25",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/actions.git",
"reference": "97fec740f57241c2537c8ddf6a8c22ad34cf96de"
"reference": "8b8eef43e31f86681d7c8f1a8d8400afa9172442"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/actions/zipball/97fec740f57241c2537c8ddf6a8c22ad34cf96de",
"reference": "97fec740f57241c2537c8ddf6a8c22ad34cf96de",
"url": "https://api.github.com/repos/filamentphp/actions/zipball/8b8eef43e31f86681d7c8f1a8d8400afa9172442",
"reference": "8b8eef43e31f86681d7c8f1a8d8400afa9172442",
"shasum": ""
},
"require": {
@ -2519,20 +2518,20 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-08-05T09:46:29+00:00"
"time": "2025-08-11T19:29:26+00:00"
},
{
"name": "filament/filament",
"version": "v4.0.0-beta22",
"version": "v4.0.0-beta25",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/panels.git",
"reference": "ada74566525dbcf2e9dcae03f493ac5948216fb7"
"reference": "dc8306c984d0d52b0b8365b5e66bb7c43b81784b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/panels/zipball/ada74566525dbcf2e9dcae03f493ac5948216fb7",
"reference": "ada74566525dbcf2e9dcae03f493ac5948216fb7",
"url": "https://api.github.com/repos/filamentphp/panels/zipball/dc8306c984d0d52b0b8365b5e66bb7c43b81784b",
"reference": "dc8306c984d0d52b0b8365b5e66bb7c43b81784b",
"shasum": ""
},
"require": {
@ -2576,20 +2575,20 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-08-04T10:36:20+00:00"
"time": "2025-08-11T19:28:59+00:00"
},
{
"name": "filament/forms",
"version": "v4.0.0-beta22",
"version": "v4.0.0-beta25",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/forms.git",
"reference": "c1b3e830850d276669a0a8eeb2a43bf8d1b4d330"
"reference": "cb06cbd7855a178fd9c1db88ad5f3ecad7d993a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/forms/zipball/c1b3e830850d276669a0a8eeb2a43bf8d1b4d330",
"reference": "c1b3e830850d276669a0a8eeb2a43bf8d1b4d330",
"url": "https://api.github.com/repos/filamentphp/forms/zipball/cb06cbd7855a178fd9c1db88ad5f3ecad7d993a1",
"reference": "cb06cbd7855a178fd9c1db88ad5f3ecad7d993a1",
"shasum": ""
},
"require": {
@ -2626,20 +2625,20 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-08-04T10:36:15+00:00"
"time": "2025-08-11T19:29:11+00:00"
},
{
"name": "filament/infolists",
"version": "v4.0.0-beta22",
"version": "v4.0.0-beta25",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/infolists.git",
"reference": "0371df3b6423c9bd85a49d947cb1836eb2f2324a"
"reference": "749e080d195fc94dcaa3b1bc1545e0e4b95d4703"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/0371df3b6423c9bd85a49d947cb1836eb2f2324a",
"reference": "0371df3b6423c9bd85a49d947cb1836eb2f2324a",
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/749e080d195fc94dcaa3b1bc1545e0e4b95d4703",
"reference": "749e080d195fc94dcaa3b1bc1545e0e4b95d4703",
"shasum": ""
},
"require": {
@ -2671,11 +2670,11 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-08-05T09:46:25+00:00"
"time": "2025-08-11T19:29:15+00:00"
},
{
"name": "filament/notifications",
"version": "v4.0.0-beta22",
"version": "v4.0.0-beta25",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/notifications.git",
@ -2722,16 +2721,16 @@
},
{
"name": "filament/schemas",
"version": "v4.0.0-beta22",
"version": "v4.0.0-beta25",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/schemas.git",
"reference": "b4175cca16f6f09f0c409f6dfc0abb446dc31675"
"reference": "407e656aab02840781161648ea4a3f4c96de6570"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/schemas/zipball/b4175cca16f6f09f0c409f6dfc0abb446dc31675",
"reference": "b4175cca16f6f09f0c409f6dfc0abb446dc31675",
"url": "https://api.github.com/repos/filamentphp/schemas/zipball/407e656aab02840781161648ea4a3f4c96de6570",
"reference": "407e656aab02840781161648ea4a3f4c96de6570",
"shasum": ""
},
"require": {
@ -2763,20 +2762,20 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-08-05T09:46:35+00:00"
"time": "2025-08-11T19:28:52+00:00"
},
{
"name": "filament/support",
"version": "v4.0.0-beta22",
"version": "v4.0.0-beta25",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/support.git",
"reference": "89463a9f6bebc72cb91f41634dae9275664d2687"
"reference": "1c6ed2b3697600f5213bee6760e0f709a746a968"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/support/zipball/89463a9f6bebc72cb91f41634dae9275664d2687",
"reference": "89463a9f6bebc72cb91f41634dae9275664d2687",
"url": "https://api.github.com/repos/filamentphp/support/zipball/1c6ed2b3697600f5213bee6760e0f709a746a968",
"reference": "1c6ed2b3697600f5213bee6760e0f709a746a968",
"shasum": ""
},
"require": {
@ -2821,20 +2820,20 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-08-04T10:36:21+00:00"
"time": "2025-08-10T20:27:33+00:00"
},
{
"name": "filament/tables",
"version": "v4.0.0-beta22",
"version": "v4.0.0-beta25",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/tables.git",
"reference": "9b2441ee99e6e95d28df394588827a5bbb71c7fe"
"reference": "a2d45bc8741044a0409e8e9fd81abcc3999e24ef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/tables/zipball/9b2441ee99e6e95d28df394588827a5bbb71c7fe",
"reference": "9b2441ee99e6e95d28df394588827a5bbb71c7fe",
"url": "https://api.github.com/repos/filamentphp/tables/zipball/a2d45bc8741044a0409e8e9fd81abcc3999e24ef",
"reference": "a2d45bc8741044a0409e8e9fd81abcc3999e24ef",
"shasum": ""
},
"require": {
@ -2866,20 +2865,20 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-08-05T09:46:36+00:00"
"time": "2025-08-11T19:29:31+00:00"
},
{
"name": "filament/widgets",
"version": "v4.0.0-beta22",
"version": "v4.0.0-beta25",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/widgets.git",
"reference": "03bb26c9c072f4f26dcd0c9b2b0bd8232e91abcc"
"reference": "ec65855e6b572900eefbb514608fb2ce92abb8d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/widgets/zipball/03bb26c9c072f4f26dcd0c9b2b0bd8232e91abcc",
"reference": "03bb26c9c072f4f26dcd0c9b2b0bd8232e91abcc",
"url": "https://api.github.com/repos/filamentphp/widgets/zipball/ec65855e6b572900eefbb514608fb2ce92abb8d0",
"reference": "ec65855e6b572900eefbb514608fb2ce92abb8d0",
"shasum": ""
},
"require": {
@ -2910,7 +2909,7 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-08-05T09:46:27+00:00"
"time": "2025-08-10T20:27:56+00:00"
},
{
"name": "firebase/php-jwt",
@ -3642,16 +3641,16 @@
},
{
"name": "laravel/framework",
"version": "v12.21.0",
"version": "v12.22.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b"
"reference": "d33ee45184126f32f593d4b809a846ed88a1dc43"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
"reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
"url": "https://api.github.com/repos/laravel/framework/zipball/d33ee45184126f32f593d4b809a846ed88a1dc43",
"reference": "d33ee45184126f32f593d4b809a846ed88a1dc43",
"shasum": ""
},
"require": {
@ -3853,7 +3852,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-07-22T15:41:55+00:00"
"time": "2025-08-08T13:58:03+00:00"
},
{
"name": "laravel/helpers",
@ -4098,16 +4097,16 @@
},
{
"name": "laravel/socialite",
"version": "v5.22.0",
"version": "v5.23.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
"reference": "99d0fe750a7c68e5b60d8b1850de2554f3ea4072"
"reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/socialite/zipball/99d0fe750a7c68e5b60d8b1850de2554f3ea4072",
"reference": "99d0fe750a7c68e5b60d8b1850de2554f3ea4072",
"url": "https://api.github.com/repos/laravel/socialite/zipball/e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5",
"reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5",
"shasum": ""
},
"require": {
@ -4166,7 +4165,7 @@
"issues": "https://github.com/laravel/socialite/issues",
"source": "https://github.com/laravel/socialite"
},
"time": "2025-07-08T22:07:57+00:00"
"time": "2025-07-23T14:16:08+00:00"
},
{
"name": "laravel/tinker",
@ -4297,105 +4296,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": {
@ -4421,7 +4355,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": [
{
@ -4433,7 +4367,7 @@
"type": "patreon"
}
],
"time": "2023-01-02T13:28:00+00:00"
"time": "2025-01-26T21:29:45+00:00"
},
{
"name": "league/commonmark",
@ -5722,16 +5656,16 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.13.3",
"version": "1.13.4",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
"reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"shasum": ""
},
"require": {
@ -5770,7 +5704,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
},
"funding": [
{
@ -5778,7 +5712,7 @@
"type": "tidelift"
}
],
"time": "2025-07-05T12:25:42+00:00"
"time": "2025-08-01T08:46:24+00:00"
},
{
"name": "nesbot/carbon",
@ -6025,12 +5959,12 @@
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "34f1ba0f5576d7433a7c60fad946ec0667d44620"
"reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/34f1ba0f5576d7433a7c60fad946ec0667d44620",
"reference": "34f1ba0f5576d7433a7c60fad946ec0667d44620",
"url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede",
"reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede",
"shasum": ""
},
"require": {
@ -6106,20 +6040,20 @@
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v4.0.8"
},
"time": "2025-08-01T02:17:40+00:00"
"time": "2025-08-06T21:43:34+00:00"
},
{
"name": "nikic/php-parser",
"version": "v5.5.0",
"version": "v5.6.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
"reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56",
"reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56",
"shasum": ""
},
"require": {
@ -6162,9 +6096,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0"
},
"time": "2025-05-31T08:24:38+00:00"
"time": "2025-07-27T20:03:57+00:00"
},
{
"name": "nunomaduro/termwind",
@ -7586,16 +7520,16 @@
},
{
"name": "psy/psysh",
"version": "v0.12.9",
"version": "v0.12.10",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
"reference": "1b801844becfe648985372cb4b12ad6840245ace"
"reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/1b801844becfe648985372cb4b12ad6840245ace",
"reference": "1b801844becfe648985372cb4b12ad6840245ace",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/6e80abe6f2257121f1eb9a4c55bf29d921025b22",
"reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22",
"shasum": ""
},
"require": {
@ -7645,12 +7579,11 @@
"authors": [
{
"name": "Justin Hileman",
"email": "justin@justinhileman.info",
"homepage": "http://justinhileman.com"
"email": "justin@justinhileman.info"
}
],
"description": "An interactive shell for modern PHP.",
"homepage": "http://psysh.org",
"homepage": "https://psysh.org",
"keywords": [
"REPL",
"console",
@ -7659,9 +7592,9 @@
],
"support": {
"issues": "https://github.com/bobthecow/psysh/issues",
"source": "https://github.com/bobthecow/psysh/tree/v0.12.9"
"source": "https://github.com/bobthecow/psysh/tree/v0.12.10"
},
"time": "2025-06-23T02:35:06+00:00"
"time": "2025-08-04T12:39:37+00:00"
},
{
"name": "ralouphie/getallheaders",
@ -8790,16 +8723,16 @@
},
{
"name": "spatie/laravel-health",
"version": "1.34.4",
"version": "1.34.5",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-health.git",
"reference": "ac04fb0b82b4c89ab88c18897f9eda4e559d624b"
"reference": "8487a3a43551f3d24e73546362f93da2684a0a3a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-health/zipball/ac04fb0b82b4c89ab88c18897f9eda4e559d624b",
"reference": "ac04fb0b82b4c89ab88c18897f9eda4e559d624b",
"url": "https://api.github.com/repos/spatie/laravel-health/zipball/8487a3a43551f3d24e73546362f93da2684a0a3a",
"reference": "8487a3a43551f3d24e73546362f93da2684a0a3a",
"shasum": ""
},
"require": {
@ -8871,7 +8804,7 @@
"spatie"
],
"support": {
"source": "https://github.com/spatie/laravel-health/tree/1.34.4"
"source": "https://github.com/spatie/laravel-health/tree/1.34.5"
},
"funding": [
{
@ -8879,7 +8812,7 @@
"type": "github"
}
],
"time": "2025-07-22T08:06:42+00:00"
"time": "2025-07-25T07:00:21+00:00"
},
{
"name": "spatie/laravel-package-tools",
@ -8944,16 +8877,16 @@
},
{
"name": "spatie/laravel-permission",
"version": "6.20.0",
"version": "6.21.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-permission.git",
"reference": "31c05679102c73f3b0d05790d2400182745a5615"
"reference": "6a118e8855dfffcd90403aab77bbf35a03db51b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-permission/zipball/31c05679102c73f3b0d05790d2400182745a5615",
"reference": "31c05679102c73f3b0d05790d2400182745a5615",
"url": "https://api.github.com/repos/spatie/laravel-permission/zipball/6a118e8855dfffcd90403aab77bbf35a03db51b3",
"reference": "6a118e8855dfffcd90403aab77bbf35a03db51b3",
"shasum": ""
},
"require": {
@ -9015,7 +8948,7 @@
],
"support": {
"issues": "https://github.com/spatie/laravel-permission/issues",
"source": "https://github.com/spatie/laravel-permission/tree/6.20.0"
"source": "https://github.com/spatie/laravel-permission/tree/6.21.0"
},
"funding": [
{
@ -9023,20 +8956,20 @@
"type": "github"
}
],
"time": "2025-06-05T07:33:07+00:00"
"time": "2025-07-23T16:08:05+00:00"
},
{
"name": "spatie/laravel-query-builder",
"version": "6.3.3",
"version": "6.3.5",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-query-builder.git",
"reference": "0d80323d2b2ffc410f06bf73c2e3a6ad763e4b8d"
"reference": "ee3c98235616f88c11e75d3df5ea48dc7b20dd93"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/0d80323d2b2ffc410f06bf73c2e3a6ad763e4b8d",
"reference": "0d80323d2b2ffc410f06bf73c2e3a6ad763e4b8d",
"url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/ee3c98235616f88c11e75d3df5ea48dc7b20dd93",
"reference": "ee3c98235616f88c11e75d3df5ea48dc7b20dd93",
"shasum": ""
},
"require": {
@ -9097,7 +9030,7 @@
"type": "custom"
}
],
"time": "2025-07-14T08:31:42+00:00"
"time": "2025-08-04T07:36:33+00:00"
},
{
"name": "spatie/php-structure-discoverer",
@ -10051,16 +9984,16 @@
},
{
"name": "symfony/http-client",
"version": "v7.3.1",
"version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "4403d87a2c16f33345dca93407a8714ee8c05a64"
"reference": "1c064a0c67749923483216b081066642751cc2c7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/4403d87a2c16f33345dca93407a8714ee8c05a64",
"reference": "4403d87a2c16f33345dca93407a8714ee8c05a64",
"url": "https://api.github.com/repos/symfony/http-client/zipball/1c064a0c67749923483216b081066642751cc2c7",
"reference": "1c064a0c67749923483216b081066642751cc2c7",
"shasum": ""
},
"require": {
@ -10126,7 +10059,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.3.1"
"source": "https://github.com/symfony/http-client/tree/v7.3.2"
},
"funding": [
{
@ -10137,12 +10070,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-06-28T07:58:39+00:00"
"time": "2025-07-15T11:36:08+00:00"
},
{
"name": "symfony/http-client-contracts",
@ -12032,16 +11969,16 @@
},
{
"name": "symfony/yaml",
"version": "v7.3.1",
"version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "0c3555045a46ab3cd4cc5a69d161225195230edb"
"reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb",
"reference": "0c3555045a46ab3cd4cc5a69d161225195230edb",
"url": "https://api.github.com/repos/symfony/yaml/zipball/b8d7d868da9eb0919e99c8830431ea087d6aae30",
"reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30",
"shasum": ""
},
"require": {
@ -12084,7 +12021,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v7.3.1"
"source": "https://github.com/symfony/yaml/tree/v7.3.2"
},
"funding": [
{
@ -12095,12 +12032,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-06-03T06:57:57+00:00"
"time": "2025-07-10T08:47:49+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@ -12961,16 +12902,16 @@
},
{
"name": "filp/whoops",
"version": "2.18.3",
"version": "2.18.4",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "59a123a3d459c5a23055802237cb317f609867e5"
"reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/59a123a3d459c5a23055802237cb317f609867e5",
"reference": "59a123a3d459c5a23055802237cb317f609867e5",
"url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d",
"reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d",
"shasum": ""
},
"require": {
@ -13020,7 +12961,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.18.3"
"source": "https://github.com/filp/whoops/tree/2.18.4"
},
"funding": [
{
@ -13028,7 +12969,7 @@
"type": "github"
}
],
"time": "2025-06-16T00:02:10+00:00"
"time": "2025-08-08T12:00:00+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@ -14903,16 +14844,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": {
@ -14971,15 +14912,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",
@ -15560,16 +15513,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": {
@ -15605,15 +15558,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

@ -1,12 +1,17 @@
{
admin off
email {$ADMIN_EMAIL}
servers {
## docs https://caddyserver.com/docs/caddyfile/options#trusted-proxies
{$CADDY_TRUSTED_PROXIES}
{$CADDY_STRICT_PROXIES}
}
admin off
auto_https off
email {$ADMIN_EMAIL}
}
{$APP_URL} {
root * /var/www/html/public
encode gzip
root * /var/www/html/public
encode gzip
php_fastcgi 127.0.0.1:9000
file_server
php_fastcgi 127.0.0.1:9000
}

View File

@ -1,5 +1,4 @@
#!/bin/ash -e
## check for .env file or symlink and generate app keys if missing
if [ -f /var/www/html/.env ]; then
echo "external vars exist."
@ -23,6 +22,8 @@ else
echo -e "APP_INSTALLED=false" >> /pelican-data/.env
fi
sed -i "s/upload_max_filesize = 2M/upload_max_filesize = ${UPLOAD_LIMIT}M/" /usr/local/etc/php/php.ini-production
mkdir -p /pelican-data/database /pelican-data/storage/avatars /pelican-data/storage/fonts /var/www/html/storage/logs/supervisord 2>/dev/null
if ! grep -q "APP_KEY=" .env || grep -q "APP_KEY=$" .env; then
@ -39,6 +40,7 @@ php artisan migrate --force
echo -e "Optimizing Filament"
php artisan filament:optimize
# default to caddy not starting
export SUPERVISORD_CADDY=false
## disable caddy if SKIP_CADDY is set
@ -46,7 +48,14 @@ if [[ "${SKIP_CADDY:-}" == "true" ]]; then
echo "Starting PHP-FPM only"
else
echo "Starting PHP-FPM and Caddy"
# enable caddy
export SUPERVISORD_CADDY=true
# handle trusted proxies for caddy
if [[ ! -z ${TRUSTED_PROXIES} ]]; then
export CADDY_TRUSTED_PROXIES=$(echo "trusted_proxies static ${TRUSTED_PROXIES}" | sed 's/,/ /g')
export CADDY_STRICT_PROXIES="trusted_proxies_strict"
fi
fi
echo "Starting Supervisord"

9
docker/healthcheck.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/ash -e
if [ ${SKIP_CADDY} ! "true" ]; then
curl -f http://localhost/up || exit 1
fi
cgi-fcgi -bind -connect 127.0.0.1:9000 || exit 2
exit 0

View File

@ -97,6 +97,7 @@ return [
'base_url' => 'Base URL',
'display_name' => 'Display Name',
'auth_url' => 'Authorization callback URL',
'create_missing_users' => 'Auto Create Missing Users?',
],
'misc' => [
'auto_allocation' => [

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]").attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default};
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default};

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:d,respond:u})=>{n(({snapshot:f,effect:h})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]").attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||this.getNormalizedState()===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.getNormalizedState()),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[null,void 0].includes(this.$refs.serverState.value)?"":this.$refs.serverState.value.replaceAll('\\"','"')},getNormalizedState(){let e=Alpine.raw(this.state);return[null,void 0].includes(e)?"":e}}}export{o as default};
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:d,respond:u})=>{n(({snapshot:f,effect:h})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||this.getNormalizedState()===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.getNormalizedState()),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[null,void 0].includes(this.$refs.serverState.value)?"":this.$refs.serverState.value.replaceAll('\\"','"')},getNormalizedState(){let e=Alpine.raw(this.state);return[null,void 0].includes(e)?"":e}}}export{o as default};

View File

@ -1 +1 @@
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]").attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default};
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default};

File diff suppressed because one or more lines are too long

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'));
}
}