Merge pull request #218 from jonnybarnes/php8

Initial php8 work
This commit is contained in:
Jonny Barnes 2020-12-31 14:30:14 +00:00 committed by GitHub
commit 2fbb1f60c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 861 additions and 760 deletions

View file

@ -28,14 +28,11 @@ jobs:
key: ${{ runner.os }}-${{ hashFiles('**/package.json') }} key: ${{ runner.os }}-${{ hashFiles('**/package.json') }}
- name: Install npm dependencies - name: Install npm dependencies
run: npm install run: npm install
- name: Install ImageMagick
run: sudo apt install imagemagick
- name: Setup PHP with pecl extension - name: Setup PHP with pecl extension
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '7.4' php-version: '8.0'
tools: pecl, phpcs tools: phpcs
extensions: imagick
- name: Copy .env - name: Copy .env
run: php -r "file_exists('.env') || copy('.env.github', '.env');" run: php -r "file_exists('.env') || copy('.env.github', '.env');"
- name: Install dependencies - name: Install dependencies

View file

@ -1,13 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidTokenException extends Exception
{
public function __construct($message, $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View file

@ -4,12 +4,13 @@ declare(strict_types=1);
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Exceptions\InvalidTokenException;
use App\Http\Responses\MicropubResponses; use App\Http\Responses\MicropubResponses;
use App\Models\Place; use App\Models\Place;
use App\Services\Micropub\{HCardService, HEntryService, UpdateService}; use App\Services\Micropub\{HCardService, HEntryService, UpdateService};
use App\Services\TokenService; use App\Services\TokenService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Lcobucci\JWT\Token\InvalidTokenStructure;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use Monolog\Handler\StreamHandler; use Monolog\Handler\StreamHandler;
use Monolog\Logger; use Monolog\Logger;
@ -37,19 +38,18 @@ class MicropubController extends Controller
* then passes over the info to the relevant Service class. * then passes over the info to the relevant Service class.
* *
* @return JsonResponse * @return JsonResponse
* @throws InvalidTokenException
*/ */
public function post(): JsonResponse public function post(): JsonResponse
{ {
try { try {
$tokenData = $this->tokenService->validateToken(request()->input('access_token')); $tokenData = $this->tokenService->validateToken(request()->input('access_token'));
} catch (InvalidTokenException $e) { } catch (RequiredConstraintsViolated | InvalidTokenStructure $exception) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->invalidTokenResponse(); return $micropubResponses->invalidTokenResponse();
} }
if ($tokenData->hasClaim('scope') === false) { if ($tokenData->claims()->has('scope') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->tokenHasNoScopeResponse(); return $micropubResponses->tokenHasNoScopeResponse();
@ -58,7 +58,7 @@ class MicropubController extends Controller
$this->logMicropubRequest(request()->all()); $this->logMicropubRequest(request()->all());
if ((request()->input('h') == 'entry') || (request()->input('type.0') == 'h-entry')) { if ((request()->input('h') == 'entry') || (request()->input('type.0') == 'h-entry')) {
if (stristr($tokenData->getClaim('scope'), 'create') === false) { if (stristr($tokenData->claims()->get('scope'), 'create') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->insufficientScopeResponse(); return $micropubResponses->insufficientScopeResponse();
@ -72,7 +72,7 @@ class MicropubController extends Controller
} }
if (request()->input('h') == 'card' || request()->input('type.0') == 'h-card') { if (request()->input('h') == 'card' || request()->input('type.0') == 'h-card') {
if (stristr($tokenData->getClaim('scope'), 'create') === false) { if (stristr($tokenData->claims()->get('scope'), 'create') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->insufficientScopeResponse(); return $micropubResponses->insufficientScopeResponse();
@ -86,7 +86,7 @@ class MicropubController extends Controller
} }
if (request()->input('action') == 'update') { if (request()->input('action') == 'update') {
if (stristr($tokenData->getClaim('scope'), 'update') === false) { if (stristr($tokenData->claims()->get('scope'), 'update') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->insufficientScopeResponse(); return $micropubResponses->insufficientScopeResponse();
@ -115,7 +115,7 @@ class MicropubController extends Controller
{ {
try { try {
$tokenData = $this->tokenService->validateToken(request()->input('access_token')); $tokenData = $this->tokenService->validateToken(request()->input('access_token'));
} catch (InvalidTokenException $e) { } catch (RequiredConstraintsViolated | InvalidTokenStructure $exception) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->invalidTokenResponse(); return $micropubResponses->invalidTokenResponse();
@ -156,9 +156,9 @@ class MicropubController extends Controller
return response()->json([ return response()->json([
'response' => 'token', 'response' => 'token',
'token' => [ 'token' => [
'me' => $tokenData->getClaim('me'), 'me' => $tokenData->claims()->get('me'),
'scope' => $tokenData->getClaim('scope'), 'scope' => $tokenData->claims()->get('scope'),
'client_id' => $tokenData->getClaim('client_id'), 'client_id' => $tokenData->claims()->get('client_id'),
], ],
]); ]);
} }
@ -167,13 +167,13 @@ class MicropubController extends Controller
* Determine the client id from the access token sent with the request. * Determine the client id from the access token sent with the request.
* *
* @return string * @return string
* @throws InvalidTokenException * @throws RequiredConstraintsViolated
*/ */
private function getClientId(): string private function getClientId(): string
{ {
return resolve(TokenService::class) return resolve(TokenService::class)
->validateToken(request()->input('access_token')) ->validateToken(request()->input('access_token'))
->getClaim('client_id'); ->claims()->get('client_id');
} }
/** /**

View file

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Exceptions\InvalidTokenException;
use App\Http\Responses\MicropubResponses; use App\Http\Responses\MicropubResponses;
use App\Jobs\ProcessMedia; use App\Jobs\ProcessMedia;
use App\Models\Media; use App\Models\Media;
@ -21,6 +20,8 @@ use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Intervention\Image\Exception\NotReadableException; use Intervention\Image\Exception\NotReadableException;
use Intervention\Image\ImageManager; use Intervention\Image\ImageManager;
use Lcobucci\JWT\Token\InvalidTokenStructure;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
class MicropubMediaController extends Controller class MicropubMediaController extends Controller
@ -36,19 +37,19 @@ class MicropubMediaController extends Controller
{ {
try { try {
$tokenData = $this->tokenService->validateToken(request()->input('access_token')); $tokenData = $this->tokenService->validateToken(request()->input('access_token'));
} catch (InvalidTokenException $e) { } catch (RequiredConstraintsViolated | InvalidTokenStructure $exception) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->invalidTokenResponse(); return $micropubResponses->invalidTokenResponse();
} }
if ($tokenData->hasClaim('scope') === false) { if ($tokenData->claims()->has('scope') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->tokenHasNoScopeResponse(); return $micropubResponses->tokenHasNoScopeResponse();
} }
if (Str::contains($tokenData->getClaim('scope'), 'create') === false) { if (Str::contains($tokenData->claims()->get('scope'), 'create') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->insufficientScopeResponse(); return $micropubResponses->insufficientScopeResponse();
@ -103,19 +104,19 @@ class MicropubMediaController extends Controller
{ {
try { try {
$tokenData = $this->tokenService->validateToken(request()->input('access_token')); $tokenData = $this->tokenService->validateToken(request()->input('access_token'));
} catch (InvalidTokenException $e) { } catch (RequiredConstraintsViolated | InvalidTokenStructure $exception) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->invalidTokenResponse(); return $micropubResponses->invalidTokenResponse();
} }
if ($tokenData->hasClaim('scope') === false) { if ($tokenData->claims()->has('scope') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->tokenHasNoScopeResponse(); return $micropubResponses->tokenHasNoScopeResponse();
} }
if (Str::contains($tokenData->getClaim('scope'), 'create') === false) { if (Str::contains($tokenData->claims()->get('scope'), 'create') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses();
return $micropubResponses->insufficientScopeResponse(); return $micropubResponses->insufficientScopeResponse();

View file

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
class SessionStoreController extends Controller
{
/**
* Save the selected colour scheme in the session.
*
* @return string[]
*/
public function saveColour(): array
{
$css = request()->input('css');
session(['css' => $css]);
return ['status' => 'ok'];
}
}

View file

@ -11,6 +11,10 @@ use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Laravel\Dusk\DuskServiceProvider; use Laravel\Dusk\DuskServiceProvider;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -73,6 +77,17 @@ class AppServiceProvider extends ServiceProvider
] ]
); );
}); });
// Configure JWT builder
$this->app->bind('Lcobucci\JWT\Configuration', function () {
$key = InMemory::plainText('testing');
$config = Configuration::forSymmetricSigner(new Sha256(), $key);
$config->setValidationConstraints(new SignedWith(new Sha256(), $key));
return $config;
});
} }
/** /**

View file

@ -4,10 +4,9 @@ declare(strict_types=1);
namespace App\Services; namespace App\Services;
use App\Exceptions\InvalidTokenException;
use App\Jobs\AddClientToDatabase; use App\Jobs\AddClientToDatabase;
use Lcobucci\JWT\Signer\Hmac\Sha256; use DateTimeImmutable;
use Lcobucci\JWT\{Builder, Parser, Token}; use Lcobucci\JWT\{Configuration, Token};
class TokenService class TokenService
{ {
@ -19,17 +18,19 @@ class TokenService
*/ */
public function getNewToken(array $data): string public function getNewToken(array $data): string
{ {
$signer = new Sha256(); $config = resolve(Configuration::class);
$token = (new Builder())->set('me', $data['me'])
->set('client_id', $data['client_id']) $token = $config->builder()
->set('scope', $data['scope']) ->issuedAt(new DateTimeImmutable())
->set('date_issued', time()) ->withClaim('client_id', $data['client_id'])
->set('nonce', bin2hex(random_bytes(8))) ->withClaim('me', $data['me'])
->sign($signer, config('app.key')) ->withClaim('scope', $data['scope'])
->getToken(); ->withClaim('nonce', bin2hex(random_bytes(8)))
->getToken($config->signer(), $config->signingKey());
dispatch(new AddClientToDatabase($data['client_id'])); dispatch(new AddClientToDatabase($data['client_id']));
return (string) $token; return $token->toString();
} }
/** /**
@ -40,15 +41,13 @@ class TokenService
*/ */
public function validateToken(string $bearerToken): Token public function validateToken(string $bearerToken): Token
{ {
$signer = new Sha256(); $config = resolve('Lcobucci\JWT\Configuration');
try {
$token = (new Parser())->parse((string) $bearerToken); $token = $config->parser()->parse($bearerToken);
} catch (\InvalidArgumentException $e) {
throw new InvalidTokenException('Token could not be parsed'); $constraints = $config->validationConstraints();
}
if (! $token->verify($signer, config('app.key'))) { $config->validator()->assert($token, ...$constraints);
throw new InvalidTokenException('Token failed validation');
}
return $token; return $token;
} }

View file

@ -9,7 +9,7 @@
], ],
"license": "CC0-1.0", "license": "CC0-1.0",
"require": { "require": {
"php": "^7.4", "php": "^7.4|^8.0",
"ext-intl": "*", "ext-intl": "*",
"ext-json": "*", "ext-json": "*",
"ext-dom": "*", "ext-dom": "*",
@ -17,7 +17,7 @@
"fideloper/proxy": "~4.0", "fideloper/proxy": "~4.0",
"fruitcake/laravel-cors": "^2.0", "fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1", "guzzlehttp/guzzle": "^7.0.1",
"indieauth/client": "~0.1", "indieauth/client": "^1.1",
"intervention/image": "^2.4", "intervention/image": "^2.4",
"jonnybarnes/indieweb": "~0.2", "jonnybarnes/indieweb": "~0.2",
"jonnybarnes/webmentions-parser": "~0.5", "jonnybarnes/webmentions-parser": "~0.5",
@ -27,11 +27,11 @@
"laravel/scout": "^8.0", "laravel/scout": "^8.0",
"laravel/telescope": "^4.0", "laravel/telescope": "^4.0",
"laravel/tinker": "^2.0", "laravel/tinker": "^2.0",
"lcobucci/jwt": "^3.1", "lcobucci/jwt": "^4.0",
"league/commonmark": "^1.0", "league/commonmark": "^1.0",
"league/flysystem-aws-s3-v3": "^1.0", "league/flysystem-aws-s3-v3": "^1.0",
"mf2/mf2": "~0.3", "mf2/mf2": "~0.3",
"pmatseykanets/laravel-scout-postgres": "^7.0", "pmatseykanets/laravel-scout-postgres": "dev-php8",
"predis/predis": "~1.0", "predis/predis": "~1.0",
"sensiolabs/security-checker": "^6.0", "sensiolabs/security-checker": "^6.0",
"spatie/browsershot": "~3.0", "spatie/browsershot": "~3.0",
@ -43,10 +43,11 @@
"barryvdh/laravel-ide-helper": "^2.6", "barryvdh/laravel-ide-helper": "^2.6",
"beyondcode/laravel-dump-server": "^1.0", "beyondcode/laravel-dump-server": "^1.0",
"facade/ignition": "^2.3.6", "facade/ignition": "^2.3.6",
"fzaninotto/faker": "^1.9.1", "fakerphp/faker": "^1.9.2",
"laravel/dusk": "^6.0", "laravel/dusk": "^6.0",
"mockery/mockery": "^1.0", "mockery/mockery": "^1.0",
"nunomaduro/collision": "^5.0", "nunomaduro/collision": "^5.0",
"phpunit/php-code-coverage": "^9.2",
"phpunit/phpunit": "^9.0", "phpunit/phpunit": "^9.0",
"vimeo/psalm": "^4.0" "vimeo/psalm": "^4.0"
}, },
@ -91,5 +92,11 @@
"test": [ "test": [
"vendor/bin/phpunit --stop-on-failure" "vendor/bin/phpunit --stop-on-failure"
] ]
} },
"repositories": [
{
"type": "vcs",
"url": "https://github.com/jonnybarnes/laravel-scout-postgres"
}
]
} }

1348
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,6 @@ return [
| |
*/ */
'driver' => 'imagick', 'driver' => 'gd',
]; ];

View file

@ -16,7 +16,7 @@ class ArticlesTableSeeder extends Seeder
*/ */
public function run() public function run()
{ {
$now = Carbon::now()->subMonth(); $now = Carbon::now()->subMonth()->subDays(5);
$articleFirst = Article::create([ $articleFirst = Article::create([
'title' => 'My New Blog', 'title' => 'My New Blog',
'main' => 'This is *my* new blog. It uses `Markdown`.', 'main' => 'This is *my* new blog. It uses `Markdown`.',

View file

@ -19,11 +19,6 @@
<directory suffix="Test.php">./tests/Feature</directory> <directory suffix="Test.php">./tests/Feature</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
</whitelist>
</filter>
<php> <php>
<env name="APP_ENV" value="testing"/> <env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/> <env name="BCRYPT_ROUNDS" value="4"/>
@ -32,5 +27,14 @@
<env name="QUEUE_CONNECTION" value="sync"/> <env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/> <env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/> <env name="TELESCOPE_ENABLED" value="false"/>
<ini name="memory_limit" value="512M"/>
</php> </php>
<coverage pathCoverage="true" includeUncoveredFiles="true" processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
<report>
<html outputDirectory="./public/coverage"/>
</report>
</coverage>
</phpunit> </phpunit>

View file

@ -5,19 +5,16 @@ namespace Tests\Feature;
use Carbon\Carbon; use Carbon\Carbon;
use Tests\TestCase; use Tests\TestCase;
use Tests\TestToken; use Tests\TestToken;
use App\Jobs\ProcessMedia;
use App\Jobs\SendWebMentions; use App\Jobs\SendWebMentions;
use App\Models\{Media, Place}; use App\Models\{Media, Place};
use Illuminate\Http\UploadedFile;
use App\Jobs\SyndicateNoteToTwitter; use App\Jobs\SyndicateNoteToTwitter;
use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;
use MStaack\LaravelPostgis\Geometries\Point;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
class MicropubControllerTest extends TestCase class MicropubControllerTest extends TestCase
{ {
use DatabaseTransactions, TestToken; use DatabaseTransactions;
use TestToken;
/** /**
* Test a GET request for the micropub endpoint without a token gives a * Test a GET request for the micropub endpoint without a token gives a

View file

@ -2,11 +2,12 @@
namespace Tests\Feature; namespace Tests\Feature;
use DateTimeImmutable;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use Tests\TestCase; use Tests\TestCase;
use Lcobucci\JWT\Builder;
use App\Services\TokenService; use App\Services\TokenService;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use App\Exceptions\InvalidTokenException;
class TokenServiceTest extends TestCase class TokenServiceTest extends TestCase
{ {
@ -27,33 +28,35 @@ class TokenServiceTest extends TestCase
$token = $tokenService->getNewToken($data); $token = $tokenService->getNewToken($data);
$valid = $tokenService->validateToken($token); $valid = $tokenService->validateToken($token);
$validData = [ $validData = [
'me' => $valid->getClaim('me'), 'me' => $valid->claims()->get('me'),
'client_id' => $valid->getClaim('client_id'), 'client_id' => $valid->claims()->get('client_id'),
'scope' => $valid->getClaim('scope') 'scope' => $valid->claims()->get('scope')
]; ];
$this->assertSame($data, $validData); $this->assertSame($data, $validData);
} }
public function test_token_with_different_signing_key_throws_exception() public function test_token_with_different_signing_key_throws_exception()
{ {
$this->expectException(InvalidTokenException::class); $this->expectException(RequiredConstraintsViolated::class);
$this->expectExceptionMessage('Token failed validation');
$data = [ $data = [
'me' => 'https://example.org', 'me' => 'https://example.org',
'client_id' => 'https://quill.p3k.io', 'client_id' => 'https://quill.p3k.io',
'scope' => 'post' 'scope' => 'post'
]; ];
$signer = new Sha256();
$token = (new Builder())->set('me', $data['me']) $config = resolve(Configuration::class);
->set('client_id', $data['client_id'])
->set('scope', $data['scope']) $token = $config->builder()
->set('date_issued', time()) ->issuedAt(new DateTimeImmutable())
->set('nonce', bin2hex(random_bytes(8))) ->withClaim('client_id', $data['client_id'])
->sign($signer, 'r4ndomk3y') ->withClaim('me', $data['me'])
->getToken(); ->withClaim('scope', $data['scope'])
->withClaim('nonce', bin2hex(random_bytes(8)))
->getToken($config->signer(), InMemory::plainText('r4andomk3y'))
->toString();
$service = new TokenService(); $service = new TokenService();
$token = $service->validateToken($token); $service->validateToken($token);
} }
} }

View file

@ -2,50 +2,47 @@
namespace Tests; namespace Tests;
use Lcobucci\JWT\Builder; use DateTimeImmutable;
use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Configuration;
trait TestToken trait TestToken
{ {
public function getToken() public function getToken()
{ {
$signer = new Sha256(); $config = $this->app->make(Configuration::class);
$token = (new Builder())
->set('client_id', 'https://quill.p3k.io')
->set('me', 'https://jonnybarnes.localhost')
->set('scope', 'create update')
->set('issued_at', time())
->sign($signer, env('APP_KEY'))
->getToken();
return $token; return $config->builder()
->issuedAt(new DateTimeImmutable())
->withClaim('client_id', 'https://quill.p3k.io')
->withClaim('me', 'https://jonnybarnes.localhost')
->withClaim('scope', 'create update')
->getToken($config->signer(), $config->signingKey())
->toString();
} }
public function getTokenWithIncorrectScope() public function getTokenWithIncorrectScope()
{ {
$signer = new Sha256(); $config = $this->app->make(Configuration::class);
$token = (new Builder())
->set('client_id', 'https://quill.p3k.io')
->set('me', 'https://jonnybarnes.localhost')
->set('scope', 'view') //error here
->set('issued_at', time())
->sign($signer, env('APP_KEY'))
->getToken();
return $token; return $config->builder()
->issuedAt(new DateTimeImmutable())
->withClaim('client_id', 'https://quill.p3k.io')
->withClaim('me', 'https://jonnybarnes.localhost')
->withClaim('scope', 'view')
->getToken($config->signer(), $config->signingKey())
->toString();
} }
public function getTokenWithNoScope() public function getTokenWithNoScope()
{ {
$signer = new Sha256(); $config = $this->app->make(Configuration::class);
$token = (new Builder())
->set('client_id', 'https://quill.p3k.io')
->set('me', 'https://jonnybarnes.localhost')
->set('issued_at', time())
->sign($signer, env('APP_KEY'))
->getToken();
return $token; return $config->builder()
->issuedAt(new DateTimeImmutable())
->withClaim('client_id', 'https://quill.p3k.io')
->withClaim('me', 'https://jonnybarnes.localhost')
->getToken($config->signer(), $config->signingKey())
->toString();
} }
public function getInvalidToken() public function getInvalidToken()