friendship ended with social-app. php is my new best friend

Compare changes

Choose any two refs to compare.

Changed files
+3969 -143
lib
pages
public
templates
vendor
chillerlan
php-oauth
src
composer
lcobucci
psr
+17
.env.example
···
+
SERVER_URL=
+
CLIENT_ID=
+
DEFAULT_PDS=
+
CONSTELLATION_INSTANCE=
+
SLINGSHOT_INSTANCE=
+
PLC_DIRECTORY=
+
+
AUTH_ACCOUNT=
+
AUTH_KEYTYPE=
+
AUTH_PUBKEY=
+
AUTH_PRIVKEY_HEX=
+
AUTH_PRIVKEY_MULTI=
+
+
PRETTY_URLS=
+
DEFAULT_THEME=
+
DEFAULT_FONT=
+
SSL_CERT_PATH=
+2 -1
composer.json
···
"jerome/fetch-php": "^3.2",
"react/http": "^1.11",
"react/promise": "^3.3",
-
"react/dns": "^1.13"
+
"react/dns": "^1.13",
+
"lcobucci/jwt": "^5.6"
},
"require-dev": {
"flightphp/tracy-extensions": "^0.2.7"
+122 -1
composer.lock
···
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
-
"content-hash": "c6002ed16d9e230ae38c8eebfa96ea6f",
+
"content-hash": "b58d32e8c5e8307c1994ae27ab44347d",
"packages": [
{
"name": "chillerlan/php-http-message-utils",
···
"time": "2025-10-31T00:53:04+00:00"
},
+
"name": "lcobucci/jwt",
+
"version": "5.6.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/lcobucci/jwt.git",
+
"reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e",
+
"reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e",
+
"shasum": ""
+
},
+
"require": {
+
"ext-openssl": "*",
+
"ext-sodium": "*",
+
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
+
"psr/clock": "^1.0"
+
},
+
"require-dev": {
+
"infection/infection": "^0.29",
+
"lcobucci/clock": "^3.2",
+
"lcobucci/coding-standard": "^11.0",
+
"phpbench/phpbench": "^1.2",
+
"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": {
+
"psr-4": {
+
"Lcobucci\\JWT\\": "src"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"BSD-3-Clause"
+
],
+
"authors": [
+
{
+
"name": "Luรญs Cobucci",
+
"email": "lcobucci@gmail.com",
+
"role": "Developer"
+
}
+
],
+
"description": "A simple library to work with JSON Web Token and JSON Web Signature",
+
"keywords": [
+
"JWS",
+
"jwt"
+
],
+
"support": {
+
"issues": "https://github.com/lcobucci/jwt/issues",
+
"source": "https://github.com/lcobucci/jwt/tree/5.6.0"
+
},
+
"funding": [
+
{
+
"url": "https://github.com/lcobucci",
+
"type": "github"
+
},
+
{
+
"url": "https://www.patreon.com/lcobucci",
+
"type": "patreon"
+
}
+
],
+
"time": "2025-10-17T11:30:53+00:00"
+
},
+
{
"name": "league/commonmark",
"version": "2.7.1",
"source": {
···
"source": "https://github.com/nette/utils/tree/v4.0.8"
},
"time": "2025-08-06T21:43:34+00:00"
+
},
+
{
+
"name": "psr/clock",
+
"version": "1.0.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/php-fig/clock.git",
+
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+
"shasum": ""
+
},
+
"require": {
+
"php": "^7.0 || ^8.0"
+
},
+
"type": "library",
+
"autoload": {
+
"psr-4": {
+
"Psr\\Clock\\": "src/"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"MIT"
+
],
+
"authors": [
+
{
+
"name": "PHP-FIG",
+
"homepage": "https://www.php-fig.org/"
+
}
+
],
+
"description": "Common interface for reading the clock.",
+
"homepage": "https://github.com/php-fig/clock",
+
"keywords": [
+
"clock",
+
"now",
+
"psr",
+
"psr-20",
+
"time"
+
],
+
"support": {
+
"issues": "https://github.com/php-fig/clock/issues",
+
"source": "https://github.com/php-fig/clock/tree/1.0.0"
+
},
+
"time": "2022-11-25T14:36:26+00:00"
},
"name": "psr/event-dispatcher",
+69
config.php.example
···
+
<?php
+
define('SITE_TITLE', 'smallbird social');
+
define('SITE_PATH', ''); # or wherever you keep it
+
define('DB_LOCATION', __DIR__.'/data/sbs.db'); # or something like that. not in the same folder! it is ass in source control!!!
+
define('PUBLIC_API', "https://public.api.bsky.app");
+
# options:
+
# - https://public.api.bsky.app (bluesky)
+
# - that's it until the cool people at blacksky do theirs
+
define('FRONTPAGE_FEED', "at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.generator/aaagllxbcbsje");
+
define('SLINGSHOT_INSTANCE', "https://slingshot.microcosm.blue");
+
define('CONSTELLATION_INSTANCE', "https://constellation.smallbird.social");
+
# alternates:
+
# - constellation.microcosm.blue
+
# - constellation.snek.cc (run by natalie.sh)
+
# - constellation.smallbird.social (run by veryroundbird.house)
+
define('DEFAULT_PDS', "https://bsky.social");
+
# ideally pick a DEFAULT_PDS that has open registration, since this will be used to allow people to
+
# register new accounts.
+
# some options are
+
# - myatproto.social (run by blacksky)
+
# - cryptoanarchy.net (run by blacksky)
+
# - sprk.so
+
define('DEFAULT_RELAY', "https://relay.fire.hose.cam");
+
# some relay options:
+
# - bsky.network (you know who runs this)
+
# - relay1.us-east.bsky.network (bsky US east)
+
# - relay1.us-west.bsky.network (bsky US west)
+
# - relay3.fr.hose.cam (france)
+
# - relay.fire.hose.cam (montreal)
+
# - relay-ovh.demo.bsky.dev
+
# - atproto.africa (blacksky)
+
# - relay.upcloud.world (upcloud)
+
# - relay.hayescmd.net (edavis.dev)
+
# - relay.xero.systems (dane.is.extraordinarily.cool)
+
# - relay.feeds.blue (mackuba.eu; self-hosted only)
+
define('PLC_DIRECTORY', "https://plc.directory");
+
# other plc directory options:
+
# - https://plc.parakeet.at/
+
define('FAVORITE_FEEDS', [ // list of feeds you want to display in the sidebar to users not logged in
+
"at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.generator/aaagllxbcbsje",
+
"at://did:plc:xdtvlh4k3tntzp2vinmgehsz/app.bsky.feed.generator/aaaeb7hxb53jg",
+
"at://did:plc:abv47bjgzjgoh3yrygwoi36x/app.bsky.feed.generator/aaai4f7awwzkc",
+
"at://did:plc:dltmiiweocm22l656iffwn5a/app.bsky.feed.generator/aaagnt6bwkkb2",
+
"at://did:plc:mlq4aycufcuolr7ax6sezpc4/app.bsky.feed.generator/aaad2auzj4ana"
+
]);
+
define('THEMES', [
+
'blank', 'catppuccin', 'rosepine', 'dracula', 'solarized', 'tempus'
+
]);
+
define('PAGES', [
+
'about' => 'About',
+
'terms' => 'Terms',
+
'privacy' => 'Privacy'
+
]);
+
define('LINKS', [
+
'code' => 'https://tangled.org/@veryroundbird.house/smallbird-social',
+
'bsky' => 'https://bsky.app/profile/smallbirdsocial.on.computer',
+
'ko-fi' => 'https://ko-fi.com/veryroundbird'
+
]);
+
define('FONTS', [
+
'alegreya' => 'alegreya',
+
'geometria' => 'geometria',
+
'recursive' => 'recursive mono casual',
+
'comicsans' => 'comic sans ms'
+
]);
+
define('PRETTY_URLS', false);
+
define('DEFAULT_THEME', "catppuccin");
+
define('DEFAULT_FONT', "recursive");
+
define('THREAD_POST_COUNT', 5);
+
?>
+82 -47
index.php
···
use Matrix\Async;
use GuzzleHttp\Psr7\HttpFactory;
use chillerlan\OAuth\OAuthOptions;
+
use chillerlan\OAuth\Storage\SessionStorage;
+
use DateTimeImmutable;
+
use Lcobucci\JWT\Token\Builder;
+
use Lcobucci\JWT\Encoding\ChainedFormatter;
+
use Lcobucci\JWT\Encoding\JoseEncoder;
+
use Lcobucci\JWT\Signer\Key\InMemory;
+
use Lcobucci\JWT\Signer\Ecdsa\Sha256;
use Smallnest\Bsky\BskyProvider;
+
session_start();
$bskyToucher = new BskyToucher();
$favoriteFeeds = array_map(function ($feed) use ($bskyToucher) {
···
Flight::set('publicApi', PUBLIC_API);
Flight::set('frontpageFeed', FRONTPAGE_FEED);
Flight::set('defaultRelay', DEFAULT_RELAY);
-
Flight::set('userAuth', null);
+
Flight::set('userAuth', array_key_exists('sbs_'.SITE_DOMAIN, $_SESSION) ? $_SESSION['sbs_'.SITE_DOMAIN] : null);
+
Flight::set('userPds', array_key_exists('sbs_'.SITE_DOMAIN.'_pds', $_SESSION) ? $_SESSION['sbs_'.SITE_DOMAIN.'_pds'] : null);
+
Flight::set('userInfo', array_key_exists('sbs_'.SITE_DOMAIN.'_userinfo', $_SESSION) ? $_SESSION['sbs_'.SITE_DOMAIN.'_userinfo'] : null);
Flight::set('flight.log_errors', false);
Flight::set('flight.handle_errors', false);
Flight::set('flight.content_length', false);
···
'setTheme' => array_key_exists('sbs_theme', $_COOKIE) ? $_COOKIE['sbs_theme'] : DEFAULT_THEME,
'setFont' => array_key_exists('sbs_font', $_COOKIE) ? $_COOKIE['sbs_font'] : DEFAULT_FONT,
'userAuth' => Flight::get('userAuth'),
+
'userPds' => Flight::get('userPds'),
+
'userInfo' => Flight::Get('userInfo'),
'favFeeds' => $favoriteFeeds,
'pages' => PAGES,
'links' => LINKS,
···
});
Flight::route('/login', function(): void {
-
$options = new OAuthOptions([
-
'key' => 'https://'.SITE_DOMAIN.CLIENT_ID,
-
'secret' => CLIENT_SECRET,
-
'callbackURL' => 'http://127.0.0.1/login',
-
'sessionStart' => true,
-
]);
-
$connector = new React\Socket\Connector([
-
'dns' => '1.1.1.1'
-
]);
-
$http = new React\Http\Browser($connector);
-
$client = new GuzzleHttp\Client([
-
'verify' => true,
-
'headers' => [
-
'User-Agent' => USER_AGENT_STR
-
]
-
]);
-
$httpFactory = new HttpFactory();
-
$provider = new BskyProvider($options, $client, $httpFactory, $httpFactory, $httpFactory);
-
$name = $provider->getName();
-
if (isset($_GET['login']) && $_GET['login'] === $name) {
-
$username = $_GET['username'];
+
if (isset($_GET['username'])) {
+
$username = $_GET['username'];
$bskyToucher = new BskyToucher();
$userInfo = $bskyToucher->getUserInfo($username);
if (!$userInfo) die(1);
$pds = $userInfo->pds;
+
$options = new OAuthOptions([
+
'key' => 'https://'.SITE_DOMAIN.CLIENT_ID,
+
'secret' => CLIENT_SECRET,
+
'callbackURL' => 'https://'.SITE_DOMAIN.'/login',
+
'sessionStart' => true,
+
'sessionStorageVar' => 'sbs_'.SITE_DOMAIN
+
]);
+
$storage = new SessionStorage($options);
+
$connector = new React\Socket\Connector([
+
'dns' => '1.1.1.1'
+
]);
+
$http = new React\Http\Browser($connector);
+
$httpFactory = new HttpFactory();
+
$token_builder = Builder::new(new JoseEncoder(), ChainedFormatter::default());
+
$algorithm = new Sha256();
+
$signing_key = InMemory::file(CERT_PATH);
+
$now = new DateTimeImmutable();
+
$token = $token_builder
+
->withHeader('alg', 'ES256')
+
->withHeader('typ', 'JWT')
+
->withHeader('kid', 'ocwgKj_O7H9at1sL6yWf9ZZ82NOM7D0xlN8HGIyWH6M')
+
->issuedBy('https://'.SITE_DOMAIN.CLIENT_ID)
+
->identifiedBy(uniqid())
+
->relatedTo('https://'.SITE_DOMAIN.CLIENT_ID)
+
->permittedFor($pds)
+
->issuedAt($now->modify('-5 seconds'))
+
->getToken($algorithm, $signing_key);
+
$client = new GuzzleHttp\Client([
+
'verify' => true,
+
'headers' => [
+
'User-Agent' => USER_AGENT_STR,
+
'Authorization' => 'Bearer: '.$token->toString()
+
]
+
]);
+
$provider = new BskyProvider($options, $client, $httpFactory, $httpFactory, $httpFactory);
$provider->setPds($pds);
-
$authUrl = $provider->getAuthorizationUrl();
-
header('Location: '.$authUrl);
-
die(1);
-
} else if (isset($_GET['code'], $_GET['state'])) {
-
$token = $provider->getAccessToken($_GET['code'], $_GET['state']);
-
-
// save the token in a permanent storage
-
// [...]
-
-
// access granted, redirect
-
header('Location: ?granted='.$name);
-
die(1);
-
} else if (isset($_GET['granted']) && $_GET['granted'] === $name) {
-
die(1);
-
} else if (isset($_GET['error'])) {
-
die(1);
+
$name = $provider->getName();
+
if (isset($_GET['login']) && $_GET['login'] === $name) {
+
$auth_url = $provider->getAuthorizationUrl([
+
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
+
'client_assertion' => $token->toString()
+
]);
+
Flight::redirect($auth_url);
+
die(1);
+
} else if (isset($_GET['code'], $_GET['iss'])) {
+
$storage->storeAccessToken($_GET['code'], $name);
+
$_SESSION['sbs_'.SITE_DOMAIN.'_pds'] = $_GET['iss'];
+
$_SESSION['sbs_'.SITE_DOMAIN.'_userinfo'] = $bskyToucher->getUserInfo();
+
Flight::redirect('/');
+
die(1);
+
} else if (isset($_GET['error'])) {
+
die(1);
+
}
+
} else {
+
$latte = new Latte\Engine;
+
$latte->render('./templates/login.latte', array_merge(Flight::get('standardParams'), [
+
'mainClass' => 'form',
+
'ogtitle' => SITE_TITLE." | login",
+
'ogdesc' => SITE_DESC,
+
'ogimage' => '',
+
'ogurl' => 'https://'.SITE_DOMAIN.'/login'
+
]));
}
-
$latte = new Latte\Engine;
-
$latte->render('./templates/login.latte', array_merge(Flight::get('standardParams'), [
-
'mainClass' => 'form',
-
'ogtitle' => SITE_TITLE." | login",
-
'ogdesc' => SITE_DESC,
-
'ogimage' => '',
-
'ogurl' => 'https://'.SITE_DOMAIN.'/login'
-
]));
});
-
// https://shimaenaga.veryroundbird.house/oauth/authorize?client_id=https%3A%2F%2Ftangled.org%2Foauth%2Fclient-metadata.json&request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Areq-2399ff42af66498132ebf8de809254b7
+
Flight::route('/logout', function(): void {
+
unset($_SESSION['sbs_'.SITE_DOMAIN]);
+
unset($_SESSION['sbs_'.SITE_DOMAIN.'_pds']);
+
unset($_SESSION['sbs_'.SITE_DOMAIN.'_userinfo']);
+
Flight::redirect('/');
+
});
Flight::route('/createaccount', function(): void {
$latte = new Latte\Engine;
+40 -2
lib/bskyProvider.php
···
use chillerlan\OAuth\Core\OAuth2Interface;
use chillerlan\OAuth\Core\OAuth2Provider;
-
use chillerlan\OAuth\Core\PARTrait;
use chillerlan\OAuth\Core\PKCETrait;
use chillerlan\OAuth\OAuthOptions;
use chillerlan\OAuth\Storage\SessionStorage;
+
use chillerlan\HTTP\Utils\MessageUtil;
+
use chillerlan\HTTP\Utils\QueryUtil;
+
use chillerlan\OAuth\Providers\ProviderException;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
+
use Psr\Http\Message\UriInterface;
+
use function sprintf;
class BskyProvider extends OAuth2Provider implements \chillerlan\OAuth\Core\PAR, \chillerlan\OAuth\Core\PKCE {
-
use \chillerlan\OAuth\Core\PARTrait;
use \chillerlan\OAuth\Core\PKCETrait;
public const IDENTIFIER = 'BSKYPROVIDER';
···
$this->parAuthorizationURL = (string)$pds->withPath('/oauth/par');
return $this;
+
}
+
+
public function getParRequestUri(array $body):UriInterface{
+
// send the request with the same method and parameters as the token requests
+
// @link https://datatracker.ietf.org/doc/html/rfc9126#name-request
+
$response = $this->sendAccessTokenRequest($this->parAuthorizationURL, $body);
+
$status = $response->getStatusCode();
+
$json = MessageUtil::decodeJSON($response, true);
+
+
// something went horribly wrong
+
if($status !== 201){
+
+
// @link https://datatracker.ietf.org/doc/html/rfc9126#section-2.3
+
if(isset($json['error'], $json['error_description'])){
+
throw new ProviderException(sprintf('PAR error: "%s" (%s)', $json['error'], $json['error_description']));
+
}
+
+
throw new ProviderException(sprintf('PAR request error: (HTTP/%s)', $status)); // @codeCoverageIgnore
+
}
+
+
$url = QueryUtil::merge($this->authorizationURL, $this->getParAuthorizationURLRequestParams($json));
+
+
return $this->uriFactory->createUri($url);
+
}
+
+
protected function getParAuthorizationURLRequestParams(array $response):array{
+
+
if(!isset($response['request_uri'])){
+
throw new ProviderException('PAR response error: "request_uri" missing');
+
}
+
+
return [
+
'client_id' => $this->options->key,
+
'request_uri' => $response['request_uri'],
+
];
}
}
?>
+1
lib/bskyToucher.php
···
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Fusonic\OpenGraph\Consumer;
+
use chillerlan\OAuth\Storage\SessionStorage;
class BskyToucher {
private $bskyApiBase = 'https://public.api.bsky.app/xrpc/';
+2
pages/privacy.md
···
smallbird social is meant to run as lightweight as possible and stores no data on its own server. if you are using a veryroundbird.house pds, your data and activity is on *that* server, but it only stores what's necessary to, you know, interact with the atproto ecosystem. so, user ID information, your posts, follows, likes, etc.
+
the server also caches publicly-available post and user data to speed up requests. none of this data is kept longer than, like, a few days unless it's requested again.
+
there is some minimal tracking via goatcounter, but your data will never touch advertisers and it doesn't track individual users; i mostly just want to know what the site usage numbers are and where people found it from.
i have never been asked to or required to turn over data to any law enforcement agency.
+5
pages/terms.md
···
+
## terms of use
+
+
i reserve the right to block any IPs that are hitting the site too hard. i also reserve the right to end service at any time if for some reason it gets too hard to maintain or something like that. some features may be jank, broken, or missing due to the limitations of my approach and time.
+
+
just be normal, ok. use the site like a normal person. that's what it's for.
-17
public/.env.example
···
-
SERVER_URL=
-
CLIENT_ID=
-
DEFAULT_PDS=
-
CONSTELLATION_INSTANCE=
-
SLINGSHOT_INSTANCE=
-
PLC_DIRECTORY=
-
-
AUTH_ACCOUNT=
-
AUTH_KEYTYPE=
-
AUTH_PUBKEY=
-
AUTH_PRIVKEY_HEX=
-
AUTH_PRIVKEY_MULTI=
-
-
PRETTY_URLS=
-
DEFAULT_THEME=
-
DEFAULT_FONT=
-
SSL_CERT_PATH=
-69
public/config.php.example
···
-
<?php
-
define('SITE_TITLE', 'smallbird social');
-
define('SITE_PATH', ''); # or wherever you keep it
-
define('DB_LOCATION', __DIR__.'/data/sbs.db'); # or something like that. not in the same folder! it is ass in source control!!!
-
define('PUBLIC_API', "https://public.api.bsky.app");
-
# options:
-
# - https://public.api.bsky.app (bluesky)
-
# - that's it until the cool people at blacksky do theirs
-
define('FRONTPAGE_FEED', "at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.generator/aaagllxbcbsje");
-
define('SLINGSHOT_INSTANCE', "https://slingshot.microcosm.blue");
-
define('CONSTELLATION_INSTANCE', "https://constellation.smallbird.social");
-
# alternates:
-
# - constellation.microcosm.blue
-
# - constellation.snek.cc (run by natalie.sh)
-
# - constellation.smallbird.social (run by veryroundbird.house)
-
define('DEFAULT_PDS', "https://bsky.social");
-
# ideally pick a DEFAULT_PDS that has open registration, since this will be used to allow people to
-
# register new accounts.
-
# some options are
-
# - myatproto.social (run by blacksky)
-
# - cryptoanarchy.net (run by blacksky)
-
# - sprk.so
-
define('DEFAULT_RELAY', "https://relay.fire.hose.cam");
-
# some relay options:
-
# - bsky.network (you know who runs this)
-
# - relay1.us-east.bsky.network (bsky US east)
-
# - relay1.us-west.bsky.network (bsky US west)
-
# - relay3.fr.hose.cam (france)
-
# - relay.fire.hose.cam (montreal)
-
# - relay-ovh.demo.bsky.dev
-
# - atproto.africa (blacksky)
-
# - relay.upcloud.world (upcloud)
-
# - relay.hayescmd.net (edavis.dev)
-
# - relay.xero.systems (dane.is.extraordinarily.cool)
-
# - relay.feeds.blue (mackuba.eu; self-hosted only)
-
define('PLC_DIRECTORY', "https://plc.directory");
-
# other plc directory options:
-
# - https://plc.parakeet.at/
-
define('FAVORITE_FEEDS', [ // list of feeds you want to display in the sidebar to users not logged in
-
"at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.generator/aaagllxbcbsje",
-
"at://did:plc:xdtvlh4k3tntzp2vinmgehsz/app.bsky.feed.generator/aaaeb7hxb53jg",
-
"at://did:plc:abv47bjgzjgoh3yrygwoi36x/app.bsky.feed.generator/aaai4f7awwzkc",
-
"at://did:plc:dltmiiweocm22l656iffwn5a/app.bsky.feed.generator/aaagnt6bwkkb2",
-
"at://did:plc:mlq4aycufcuolr7ax6sezpc4/app.bsky.feed.generator/aaad2auzj4ana"
-
]);
-
define('THEMES', [
-
'blank', 'catppuccin', 'rosepine', 'dracula', 'solarized', 'tempus'
-
]);
-
define('PAGES', [
-
'about' => 'About',
-
'terms' => 'Terms',
-
'privacy' => 'Privacy'
-
]);
-
define('LINKS', [
-
'code' => 'https://tangled.org/@veryroundbird.house/smallbird-social',
-
'bsky' => 'https://bsky.app/profile/smallbirdsocial.on.computer',
-
'ko-fi' => 'https://ko-fi.com/veryroundbird'
-
]);
-
define('FONTS', [
-
'alegreya' => 'alegreya',
-
'geometria' => 'geometria',
-
'recursive' => 'recursive mono casual',
-
'comicsans' => 'comic sans ms'
-
]);
-
define('PRETTY_URLS', false);
-
define('DEFAULT_THEME', "catppuccin");
-
define('DEFAULT_FONT', "recursive");
-
define('THREAD_POST_COUNT', 5);
-
?>
+2 -1
templates/_partials/nav.latte
···
<nav>
<ul>
{if $userAuth}
+
<li><a href="/u/{$userInfo->handle}">profile</a></li>
<li><a href="/settings">settings</a></li>
-
<li><a href="#">log out</a></li>
+
<li><a href="/logout">log out</a></li>
{else}
<li><a href="/createaccount">create</a></li>
<li><a href="/login">log in</a></li>
+4
templates/layout.latte
···
data-theme="{$setTheme}"
data-font="{$setFont}"
>
+
<!--
+
{print_r($_SESSION)}
+
{print_r(PHP_SESSION_DISABLED)}
+
-->
<div id="page">
<header>
<h1><a href="/">{include '_partials/logo.latte'}{$siteTitle}</a></h1>
+1 -3
vendor/chillerlan/php-oauth/src/Core/PARTrait.php
···
public function getParRequestUri(array $body):UriInterface{
// send the request with the same method and parameters as the token requests
// @link https://datatracker.ietf.org/doc/html/rfc9126#name-request
-
print_r($this->parAuthorizationURL);
$response = $this->sendAccessTokenRequest($this->parAuthorizationURL, $body);
$status = $response->getStatusCode();
$json = MessageUtil::decodeJSON($response, true);
-
print_r($body);
-
print_r($json);
// something went horribly wrong
if($status !== 200){
+
print_r($json);
// @link https://datatracker.ietf.org/doc/html/rfc9126#section-2.3
if(isset($json['error'], $json['error_description'])){
+2
vendor/composer/autoload_psr4.php
···
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
+
'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
'Nette\\' => array($vendorDir . '/nette/schema/src', $vendorDir . '/nette/utils/src'),
'Matrix\\' => array($vendorDir . '/jerome/matrix/src/Matrix'),
'Masterminds\\' => array($vendorDir . '/masterminds/html5/src'),
'League\\Config\\' => array($vendorDir . '/league/config/src'),
'League\\CommonMark\\' => array($vendorDir . '/league/commonmark/src'),
+
'Lcobucci\\JWT\\' => array($vendorDir . '/lcobucci/jwt/src'),
'Latte\\' => array($vendorDir . '/latte/latte/src/Latte'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
+10
vendor/composer/autoload_static.php
···
'Psr\\Http\\Message\\' => 17,
'Psr\\Http\\Client\\' => 16,
'Psr\\EventDispatcher\\' => 20,
+
'Psr\\Clock\\' => 10,
),
'N' =>
array (
···
array (
'League\\Config\\' => 14,
'League\\CommonMark\\' => 18,
+
'Lcobucci\\JWT\\' => 13,
'Latte\\' => 6,
),
'G' =>
···
array (
0 => __DIR__ . '/..' . '/psr/event-dispatcher/src',
),
+
'Psr\\Clock\\' =>
+
array (
+
0 => __DIR__ . '/..' . '/psr/clock/src',
+
),
'Nette\\' =>
array (
0 => __DIR__ . '/..' . '/nette/schema/src',
···
'League\\CommonMark\\' =>
array (
0 => __DIR__ . '/..' . '/league/commonmark/src',
+
),
+
'Lcobucci\\JWT\\' =>
+
array (
+
0 => __DIR__ . '/..' . '/lcobucci/jwt/src',
),
'Latte\\' =>
array (
+127
vendor/composer/installed.json
···
"install-path": "../latte/latte"
},
+
"name": "lcobucci/jwt",
+
"version": "5.6.0",
+
"version_normalized": "5.6.0.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/lcobucci/jwt.git",
+
"reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e",
+
"reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e",
+
"shasum": ""
+
},
+
"require": {
+
"ext-openssl": "*",
+
"ext-sodium": "*",
+
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
+
"psr/clock": "^1.0"
+
},
+
"require-dev": {
+
"infection/infection": "^0.29",
+
"lcobucci/clock": "^3.2",
+
"lcobucci/coding-standard": "^11.0",
+
"phpbench/phpbench": "^1.2",
+
"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"
+
},
+
"time": "2025-10-17T11:30:53+00:00",
+
"type": "library",
+
"installation-source": "dist",
+
"autoload": {
+
"psr-4": {
+
"Lcobucci\\JWT\\": "src"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"BSD-3-Clause"
+
],
+
"authors": [
+
{
+
"name": "Luรญs Cobucci",
+
"email": "lcobucci@gmail.com",
+
"role": "Developer"
+
}
+
],
+
"description": "A simple library to work with JSON Web Token and JSON Web Signature",
+
"keywords": [
+
"JWS",
+
"jwt"
+
],
+
"support": {
+
"issues": "https://github.com/lcobucci/jwt/issues",
+
"source": "https://github.com/lcobucci/jwt/tree/5.6.0"
+
},
+
"funding": [
+
{
+
"url": "https://github.com/lcobucci",
+
"type": "github"
+
},
+
{
+
"url": "https://www.patreon.com/lcobucci",
+
"type": "patreon"
+
}
+
],
+
"install-path": "../lcobucci/jwt"
+
},
+
{
"name": "league/commonmark",
"version": "2.7.1",
"version_normalized": "2.7.1.0",
···
"source": "https://github.com/nette/utils/tree/v4.0.8"
},
"install-path": "../nette/utils"
+
},
+
{
+
"name": "psr/clock",
+
"version": "1.0.0",
+
"version_normalized": "1.0.0.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/php-fig/clock.git",
+
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+
"shasum": ""
+
},
+
"require": {
+
"php": "^7.0 || ^8.0"
+
},
+
"time": "2022-11-25T14:36:26+00:00",
+
"type": "library",
+
"installation-source": "dist",
+
"autoload": {
+
"psr-4": {
+
"Psr\\Clock\\": "src/"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"MIT"
+
],
+
"authors": [
+
{
+
"name": "PHP-FIG",
+
"homepage": "https://www.php-fig.org/"
+
}
+
],
+
"description": "Common interface for reading the clock.",
+
"homepage": "https://github.com/php-fig/clock",
+
"keywords": [
+
"clock",
+
"now",
+
"psr",
+
"psr-20",
+
"time"
+
],
+
"support": {
+
"issues": "https://github.com/php-fig/clock/issues",
+
"source": "https://github.com/php-fig/clock/tree/1.0.0"
+
},
+
"install-path": "../psr/clock"
},
"name": "psr/event-dispatcher",
+20 -2
vendor/composer/installed.php
···
'name' => '__root__',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
-
'reference' => '9928676233cfb9f6f6af89926171714328098c47',
+
'reference' => '1ea5f55bde378131812f28e47f7d5b6558b04018',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
···
'__root__' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
-
'reference' => '9928676233cfb9f6f6af89926171714328098c47',
+
'reference' => '1ea5f55bde378131812f28e47f7d5b6558b04018',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
···
'aliases' => array(),
'dev_requirement' => false,
),
+
'lcobucci/jwt' => array(
+
'pretty_version' => '5.6.0',
+
'version' => '5.6.0.0',
+
'reference' => 'bb3e9f21e4196e8afc41def81ef649c164bca25e',
+
'type' => 'library',
+
'install_path' => __DIR__ . '/../lcobucci/jwt',
+
'aliases' => array(),
+
'dev_requirement' => false,
+
),
'league/commonmark' => array(
'pretty_version' => '2.7.1',
'version' => '2.7.1.0',
···
'reference' => 'c930ca4e3cf4f17dcfb03037703679d2396d2ede',
'type' => 'library',
'install_path' => __DIR__ . '/../nette/utils',
+
'aliases' => array(),
+
'dev_requirement' => false,
+
),
+
'psr/clock' => array(
+
'pretty_version' => '1.0.0',
+
'version' => '1.0.0.0',
+
'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d',
+
'type' => 'library',
+
'install_path' => __DIR__ . '/../psr/clock',
'aliases' => array(),
'dev_requirement' => false,
),
+27
vendor/lcobucci/jwt/LICENSE
···
+
Copyright (c) 2014, Luรญs Cobucci
+
All rights reserved.
+
+
Redistribution and use in source and binary forms, with or without
+
modification, are permitted provided that the following conditions are met:
+
+
* Redistributions of source code must retain the above copyright notice, this
+
list of conditions and the following disclaimer.
+
+
* Redistributions in binary form must reproduce the above copyright notice,
+
this list of conditions and the following disclaimer in the documentation
+
and/or other materials provided with the distribution.
+
+
* Neither the name of the {organization} nor the names of its
+
contributors may be used to endorse or promote products derived from
+
this software without specific prior written permission.
+
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+63
vendor/lcobucci/jwt/composer.json
···
+
{
+
"name": "lcobucci/jwt",
+
"description": "A simple library to work with JSON Web Token and JSON Web Signature",
+
"license": [
+
"BSD-3-Clause"
+
],
+
"type": "library",
+
"keywords": [
+
"JWT",
+
"JWS"
+
],
+
"authors": [
+
{
+
"name": "Luรญs Cobucci",
+
"email": "lcobucci@gmail.com",
+
"role": "Developer"
+
}
+
],
+
"require": {
+
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
+
"ext-openssl": "*",
+
"ext-sodium": "*",
+
"psr/clock": "^1.0"
+
},
+
"require-dev": {
+
"infection/infection": "^0.29",
+
"lcobucci/clock": "^3.2",
+
"lcobucci/coding-standard": "^11.0",
+
"phpbench/phpbench": "^1.2",
+
"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"
+
},
+
"autoload": {
+
"psr-4": {
+
"Lcobucci\\JWT\\": "src"
+
}
+
},
+
"autoload-dev": {
+
"psr-4": {
+
"Lcobucci\\JWT\\Tests\\": "tests"
+
}
+
},
+
"config": {
+
"allow-plugins": {
+
"dealerdirect/phpcodesniffer-composer-installer": true,
+
"infection/extension-installer": true,
+
"ocramius/package-versions": true,
+
"phpstan/extension-installer": true
+
},
+
"platform": {
+
"php": "8.2.99"
+
},
+
"preferred-install": "dist",
+
"sort-packages": true
+
}
+
}
+85
vendor/lcobucci/jwt/src/Builder.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use DateTimeImmutable;
+
use Lcobucci\JWT\Encoding\CannotEncodeContent;
+
use Lcobucci\JWT\Signer\CannotSignPayload;
+
use Lcobucci\JWT\Signer\Ecdsa\ConversionFailed;
+
use Lcobucci\JWT\Signer\InvalidKeyProvided;
+
use Lcobucci\JWT\Signer\Key;
+
use Lcobucci\JWT\Token\RegisteredClaimGiven;
+
+
/** @immutable */
+
interface Builder
+
{
+
/**
+
* Appends new items to audience
+
*
+
* @param non-empty-string ...$audiences
+
*/
+
public function permittedFor(string ...$audiences): Builder;
+
+
/**
+
* Configures the expiration time
+
*/
+
public function expiresAt(DateTimeImmutable $expiration): Builder;
+
+
/**
+
* Configures the token id
+
*
+
* @param non-empty-string $id
+
*/
+
public function identifiedBy(string $id): Builder;
+
+
/**
+
* Configures the time that the token was issued
+
*/
+
public function issuedAt(DateTimeImmutable $issuedAt): Builder;
+
+
/**
+
* Configures the issuer
+
*
+
* @param non-empty-string $issuer
+
*/
+
public function issuedBy(string $issuer): Builder;
+
+
/**
+
* Configures the time before which the token cannot be accepted
+
*/
+
public function canOnlyBeUsedAfter(DateTimeImmutable $notBefore): Builder;
+
+
/**
+
* Configures the subject
+
*
+
* @param non-empty-string $subject
+
*/
+
public function relatedTo(string $subject): Builder;
+
+
/**
+
* Configures a header item
+
*
+
* @param non-empty-string $name
+
*/
+
public function withHeader(string $name, mixed $value): Builder;
+
+
/**
+
* Configures a claim item
+
*
+
* @param non-empty-string $name
+
*
+
* @throws RegisteredClaimGiven When trying to set a registered claim.
+
*/
+
public function withClaim(string $name, mixed $value): Builder;
+
+
/**
+
* Returns a signed token to be used
+
*
+
* @throws CannotEncodeContent When data cannot be converted to JSON.
+
* @throws CannotSignPayload When payload signing fails.
+
* @throws InvalidKeyProvided When issue key is invalid/incompatible.
+
* @throws ConversionFailed When signature could not be converted.
+
*/
+
public function getToken(Signer $signer, Key $key): UnencryptedToken;
+
}
+14
vendor/lcobucci/jwt/src/ClaimsFormatter.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
interface ClaimsFormatter
+
{
+
/**
+
* @param array<non-empty-string, mixed> $claims
+
*
+
* @return array<non-empty-string, mixed>
+
*/
+
public function formatClaims(array $claims): array;
+
}
+213
vendor/lcobucci/jwt/src/Configuration.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use Closure;
+
use Lcobucci\JWT\Encoding\ChainedFormatter;
+
use Lcobucci\JWT\Encoding\JoseEncoder;
+
use Lcobucci\JWT\Signer\Key;
+
use Lcobucci\JWT\Validation\Constraint;
+
+
/**
+
* Configuration container for the JWT Builder and Parser
+
*
+
* Serves like a small DI container to simplify the creation and usage
+
* of the objects.
+
*/
+
final class Configuration
+
{
+
private Parser $parser;
+
private Validator $validator;
+
+
/** @var Closure(ClaimsFormatter $claimFormatter): Builder */
+
private Closure $builderFactory;
+
+
/** @var Constraint[] */
+
private array $validationConstraints;
+
+
/** @param Closure(ClaimsFormatter $claimFormatter): Builder|null $builderFactory */
+
private function __construct(
+
private readonly Signer $signer,
+
private readonly Key $signingKey,
+
private readonly Key $verificationKey,
+
private readonly Encoder $encoder,
+
private readonly Decoder $decoder,
+
?Parser $parser,
+
?Validator $validator,
+
?Closure $builderFactory,
+
Constraint ...$validationConstraints,
+
) {
+
$this->parser = $parser ?? new Token\Parser($decoder);
+
$this->validator = $validator ?? new Validation\Validator();
+
+
$this->builderFactory = $builderFactory
+
?? static function (ClaimsFormatter $claimFormatter) use ($encoder): Builder {
+
return Token\Builder::new($encoder, $claimFormatter);
+
};
+
+
$this->validationConstraints = $validationConstraints;
+
}
+
+
public static function forAsymmetricSigner(
+
Signer $signer,
+
Key $signingKey,
+
Key $verificationKey,
+
Encoder $encoder = new JoseEncoder(),
+
Decoder $decoder = new JoseEncoder(),
+
): self {
+
return new self(
+
$signer,
+
$signingKey,
+
$verificationKey,
+
$encoder,
+
$decoder,
+
null,
+
null,
+
null,
+
);
+
}
+
+
public static function forSymmetricSigner(
+
Signer $signer,
+
Key $key,
+
Encoder $encoder = new JoseEncoder(),
+
Decoder $decoder = new JoseEncoder(),
+
): self {
+
return new self(
+
$signer,
+
$key,
+
$key,
+
$encoder,
+
$decoder,
+
null,
+
null,
+
null,
+
);
+
}
+
+
/**
+
* @deprecated Deprecated since v5.5, please use {@see self::withBuilderFactory()} instead
+
*
+
* @param callable(ClaimsFormatter): Builder $builderFactory
+
*/
+
public function setBuilderFactory(callable $builderFactory): void
+
{
+
$this->builderFactory = $builderFactory(...);
+
}
+
+
/** @param callable(ClaimsFormatter): Builder $builderFactory */
+
public function withBuilderFactory(callable $builderFactory): self
+
{
+
return new self(
+
$this->signer,
+
$this->signingKey,
+
$this->verificationKey,
+
$this->encoder,
+
$this->decoder,
+
$this->parser,
+
$this->validator,
+
$builderFactory(...),
+
...$this->validationConstraints,
+
);
+
}
+
+
public function builder(?ClaimsFormatter $claimFormatter = null): Builder
+
{
+
return ($this->builderFactory)($claimFormatter ?? ChainedFormatter::default());
+
}
+
+
public function parser(): Parser
+
{
+
return $this->parser;
+
}
+
+
/** @deprecated Deprecated since v5.5, please use {@see self::withParser()} instead */
+
public function setParser(Parser $parser): void
+
{
+
$this->parser = $parser;
+
}
+
+
public function withParser(Parser $parser): self
+
{
+
return new self(
+
$this->signer,
+
$this->signingKey,
+
$this->verificationKey,
+
$this->encoder,
+
$this->decoder,
+
$parser,
+
$this->validator,
+
$this->builderFactory,
+
...$this->validationConstraints,
+
);
+
}
+
+
public function signer(): Signer
+
{
+
return $this->signer;
+
}
+
+
public function signingKey(): Key
+
{
+
return $this->signingKey;
+
}
+
+
public function verificationKey(): Key
+
{
+
return $this->verificationKey;
+
}
+
+
public function validator(): Validator
+
{
+
return $this->validator;
+
}
+
+
/** @deprecated Deprecated since v5.5, please use {@see self::withValidator()} instead */
+
public function setValidator(Validator $validator): void
+
{
+
$this->validator = $validator;
+
}
+
+
public function withValidator(Validator $validator): self
+
{
+
return new self(
+
$this->signer,
+
$this->signingKey,
+
$this->verificationKey,
+
$this->encoder,
+
$this->decoder,
+
$this->parser,
+
$validator,
+
$this->builderFactory,
+
...$this->validationConstraints,
+
);
+
}
+
+
/** @return Constraint[] */
+
public function validationConstraints(): array
+
{
+
return $this->validationConstraints;
+
}
+
+
/** @deprecated Deprecated since v5.5, please use {@see self::withValidationConstraints()} instead */
+
public function setValidationConstraints(Constraint ...$validationConstraints): void
+
{
+
$this->validationConstraints = $validationConstraints;
+
}
+
+
public function withValidationConstraints(Constraint ...$validationConstraints): self
+
{
+
return new self(
+
$this->signer,
+
$this->signingKey,
+
$this->verificationKey,
+
$this->encoder,
+
$this->decoder,
+
$this->parser,
+
$this->validator,
+
$this->builderFactory,
+
...$validationConstraints,
+
);
+
}
+
}
+29
vendor/lcobucci/jwt/src/Decoder.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use Lcobucci\JWT\Encoding\CannotDecodeContent;
+
+
interface Decoder
+
{
+
/**
+
* Decodes from JSON, validating the errors
+
*
+
* @param non-empty-string $json
+
*
+
* @throws CannotDecodeContent When something goes wrong while decoding.
+
*/
+
public function jsonDecode(string $json): mixed;
+
+
/**
+
* Decodes from Base64URL
+
*
+
* @link http://tools.ietf.org/html/rfc4648#section-5
+
*
+
* @return ($data is non-empty-string ? non-empty-string : string)
+
*
+
* @throws CannotDecodeContent When something goes wrong while decoding.
+
*/
+
public function base64UrlDecode(string $data): string;
+
}
+27
vendor/lcobucci/jwt/src/Encoder.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use Lcobucci\JWT\Encoding\CannotEncodeContent;
+
+
interface Encoder
+
{
+
/**
+
* Encodes to JSON, validating the errors
+
*
+
* @return non-empty-string
+
*
+
* @throws CannotEncodeContent When something goes wrong while encoding.
+
*/
+
public function jsonEncode(mixed $data): string;
+
+
/**
+
* Encodes to base64url
+
*
+
* @link http://tools.ietf.org/html/rfc4648#section-5
+
*
+
* @return ($data is non-empty-string ? non-empty-string : string)
+
*/
+
public function base64UrlEncode(string $data): string;
+
}
+21
vendor/lcobucci/jwt/src/Encoding/CannotDecodeContent.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Encoding;
+
+
use JsonException;
+
use Lcobucci\JWT\Exception;
+
use RuntimeException;
+
+
final class CannotDecodeContent extends RuntimeException implements Exception
+
{
+
public static function jsonIssues(JsonException $previous): self
+
{
+
return new self(message: 'Error while decoding from JSON', previous: $previous);
+
}
+
+
public static function invalidBase64String(): self
+
{
+
return new self('Error while decoding from Base64Url, invalid base64 characters detected');
+
}
+
}
+16
vendor/lcobucci/jwt/src/Encoding/CannotEncodeContent.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Encoding;
+
+
use JsonException;
+
use Lcobucci\JWT\Exception;
+
use RuntimeException;
+
+
final class CannotEncodeContent extends RuntimeException implements Exception
+
{
+
public static function jsonIssues(JsonException $previous): self
+
{
+
return new self(message: 'Error while encoding to JSON', previous: $previous);
+
}
+
}
+37
vendor/lcobucci/jwt/src/Encoding/ChainedFormatter.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Encoding;
+
+
use Lcobucci\JWT\ClaimsFormatter;
+
+
final class ChainedFormatter implements ClaimsFormatter
+
{
+
/** @var array<ClaimsFormatter> */
+
private array $formatters;
+
+
public function __construct(ClaimsFormatter ...$formatters)
+
{
+
$this->formatters = $formatters;
+
}
+
+
public static function default(): self
+
{
+
return new self(new UnifyAudience(), new MicrosecondBasedDateConversion());
+
}
+
+
public static function withUnixTimestampDates(): self
+
{
+
return new self(new UnifyAudience(), new UnixTimestampDates());
+
}
+
+
/** @inheritdoc */
+
public function formatClaims(array $claims): array
+
{
+
foreach ($this->formatters as $formatter) {
+
$claims = $formatter->formatClaims($claims);
+
}
+
+
return $claims;
+
}
+
}
+56
vendor/lcobucci/jwt/src/Encoding/JoseEncoder.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Encoding;
+
+
use JsonException;
+
use Lcobucci\JWT\Decoder;
+
use Lcobucci\JWT\Encoder;
+
use Lcobucci\JWT\SodiumBase64Polyfill;
+
+
use function json_decode;
+
use function json_encode;
+
+
use const JSON_THROW_ON_ERROR;
+
use const JSON_UNESCAPED_SLASHES;
+
use const JSON_UNESCAPED_UNICODE;
+
+
/**
+
* A utilitarian class that encodes and decodes data according to JOSE specifications
+
*/
+
final class JoseEncoder implements Encoder, Decoder
+
{
+
public function jsonEncode(mixed $data): string
+
{
+
try {
+
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
+
} catch (JsonException $exception) {
+
throw CannotEncodeContent::jsonIssues($exception);
+
}
+
}
+
+
public function jsonDecode(string $json): mixed
+
{
+
try {
+
return json_decode(json: $json, associative: true, flags: JSON_THROW_ON_ERROR);
+
} catch (JsonException $exception) {
+
throw CannotDecodeContent::jsonIssues($exception);
+
}
+
}
+
+
public function base64UrlEncode(string $data): string
+
{
+
return SodiumBase64Polyfill::bin2base64(
+
$data,
+
SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,
+
);
+
}
+
+
public function base64UrlDecode(string $data): string
+
{
+
return SodiumBase64Polyfill::base642bin(
+
$data,
+
SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,
+
);
+
}
+
}
+36
vendor/lcobucci/jwt/src/Encoding/MicrosecondBasedDateConversion.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Encoding;
+
+
use DateTimeImmutable;
+
use Lcobucci\JWT\ClaimsFormatter;
+
use Lcobucci\JWT\Token\RegisteredClaims;
+
+
use function array_key_exists;
+
+
final class MicrosecondBasedDateConversion implements ClaimsFormatter
+
{
+
/** @inheritdoc */
+
public function formatClaims(array $claims): array
+
{
+
foreach (RegisteredClaims::DATE_CLAIMS as $claim) {
+
if (! array_key_exists($claim, $claims)) {
+
continue;
+
}
+
+
$claims[$claim] = $this->convertDate($claims[$claim]);
+
}
+
+
return $claims;
+
}
+
+
private function convertDate(DateTimeImmutable $date): int|float
+
{
+
if ($date->format('u') === '000000') {
+
return (int) $date->format('U');
+
}
+
+
return (float) $date->format('U.u');
+
}
+
}
+29
vendor/lcobucci/jwt/src/Encoding/UnifyAudience.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Encoding;
+
+
use Lcobucci\JWT\ClaimsFormatter;
+
use Lcobucci\JWT\Token\RegisteredClaims;
+
+
use function array_key_exists;
+
use function count;
+
use function current;
+
+
final class UnifyAudience implements ClaimsFormatter
+
{
+
/** @inheritdoc */
+
public function formatClaims(array $claims): array
+
{
+
if (
+
! array_key_exists(RegisteredClaims::AUDIENCE, $claims)
+
|| count($claims[RegisteredClaims::AUDIENCE]) !== 1
+
) {
+
return $claims;
+
}
+
+
$claims[RegisteredClaims::AUDIENCE] = current($claims[RegisteredClaims::AUDIENCE]);
+
+
return $claims;
+
}
+
}
+32
vendor/lcobucci/jwt/src/Encoding/UnixTimestampDates.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Encoding;
+
+
use DateTimeImmutable;
+
use Lcobucci\JWT\ClaimsFormatter;
+
use Lcobucci\JWT\Token\RegisteredClaims;
+
+
use function array_key_exists;
+
+
final class UnixTimestampDates implements ClaimsFormatter
+
{
+
/** @inheritdoc */
+
public function formatClaims(array $claims): array
+
{
+
foreach (RegisteredClaims::DATE_CLAIMS as $claim) {
+
if (! array_key_exists($claim, $claims)) {
+
continue;
+
}
+
+
$claims[$claim] = $this->convertDate($claims[$claim]);
+
}
+
+
return $claims;
+
}
+
+
private function convertDate(DateTimeImmutable $date): int
+
{
+
return $date->getTimestamp();
+
}
+
}
+10
vendor/lcobucci/jwt/src/Exception.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use Throwable;
+
+
interface Exception extends Throwable
+
{
+
}
+71
vendor/lcobucci/jwt/src/JwtFacade.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use Closure;
+
use DateTimeImmutable;
+
use Lcobucci\JWT\Encoding\ChainedFormatter;
+
use Lcobucci\JWT\Encoding\JoseEncoder;
+
use Lcobucci\JWT\Signer\Key;
+
use Lcobucci\JWT\Validation\Constraint;
+
use Lcobucci\JWT\Validation\SignedWith;
+
use Lcobucci\JWT\Validation\ValidAt;
+
use Lcobucci\JWT\Validation\Validator;
+
use Psr\Clock\ClockInterface as Clock;
+
+
use function assert;
+
+
final class JwtFacade
+
{
+
private readonly Clock $clock;
+
+
public function __construct(
+
private readonly Parser $parser = new Token\Parser(new JoseEncoder()),
+
?Clock $clock = null,
+
) {
+
$this->clock = $clock ?? new class implements Clock {
+
public function now(): DateTimeImmutable
+
{
+
return new DateTimeImmutable();
+
}
+
};
+
}
+
+
/** @param Closure(Builder, DateTimeImmutable):Builder $customiseBuilder */
+
public function issue(
+
Signer $signer,
+
Key $signingKey,
+
Closure $customiseBuilder,
+
): UnencryptedToken {
+
$builder = Token\Builder::new(new JoseEncoder(), ChainedFormatter::withUnixTimestampDates());
+
+
$now = $this->clock->now();
+
$builder = $builder
+
->issuedAt($now)
+
->canOnlyBeUsedAfter($now)
+
->expiresAt($now->modify('+5 minutes'));
+
+
return $customiseBuilder($builder, $now)->getToken($signer, $signingKey);
+
}
+
+
/** @param non-empty-string $jwt */
+
public function parse(
+
string $jwt,
+
SignedWith $signedWith,
+
ValidAt $validAt,
+
Constraint ...$constraints,
+
): UnencryptedToken {
+
$token = $this->parser->parse($jwt);
+
assert($token instanceof UnencryptedToken);
+
+
(new Validator())->assert(
+
$token,
+
$signedWith,
+
$validAt,
+
...$constraints,
+
);
+
+
return $token;
+
}
+
}
+22
vendor/lcobucci/jwt/src/Parser.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use Lcobucci\JWT\Encoding\CannotDecodeContent;
+
use Lcobucci\JWT\Token\InvalidTokenStructure;
+
use Lcobucci\JWT\Token\UnsupportedHeaderFound;
+
+
interface Parser
+
{
+
/**
+
* Parses the JWT and returns a token
+
*
+
* @param non-empty-string $jwt
+
*
+
* @throws CannotDecodeContent When something goes wrong while decoding.
+
* @throws InvalidTokenStructure When token string structure is invalid.
+
* @throws UnsupportedHeaderFound When parsed token has an unsupported header.
+
*/
+
public function parse(string $jwt): Token;
+
}
+36
vendor/lcobucci/jwt/src/Signer/Blake2b.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer;
+
+
use Lcobucci\JWT\Signer;
+
+
use function hash_equals;
+
use function sodium_crypto_generichash;
+
use function strlen;
+
+
final class Blake2b implements Signer
+
{
+
private const MINIMUM_KEY_LENGTH_IN_BITS = 256;
+
+
public function algorithmId(): string
+
{
+
return 'BLAKE2B';
+
}
+
+
public function sign(string $payload, Key $key): string
+
{
+
$actualKeyLength = 8 * strlen($key->contents());
+
+
if ($actualKeyLength < self::MINIMUM_KEY_LENGTH_IN_BITS) {
+
throw InvalidKeyProvided::tooShort(self::MINIMUM_KEY_LENGTH_IN_BITS, $actualKeyLength);
+
}
+
+
return sodium_crypto_generichash($payload, $key->contents());
+
}
+
+
public function verify(string $expected, string $payload, Key $key): bool
+
{
+
return hash_equals($expected, $this->sign($payload, $key));
+
}
+
}
+15
vendor/lcobucci/jwt/src/Signer/CannotSignPayload.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer;
+
+
use InvalidArgumentException;
+
use Lcobucci\JWT\Exception;
+
+
final class CannotSignPayload extends InvalidArgumentException implements Exception
+
{
+
public static function errorHappened(string $error): self
+
{
+
return new self('There was an error while creating the signature:' . $error);
+
}
+
}
+25
vendor/lcobucci/jwt/src/Signer/Ecdsa/ConversionFailed.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Ecdsa;
+
+
use InvalidArgumentException;
+
use Lcobucci\JWT\Exception;
+
+
final class ConversionFailed extends InvalidArgumentException implements Exception
+
{
+
public static function invalidLength(): self
+
{
+
return new self('Invalid signature length.');
+
}
+
+
public static function incorrectStartSequence(): self
+
{
+
return new self('Invalid data. Should start with a sequence.');
+
}
+
+
public static function integerExpected(): self
+
{
+
return new self('Invalid data. Should contain an integer.');
+
}
+
}
+148
vendor/lcobucci/jwt/src/Signer/Ecdsa/MultibyteStringConverter.php
···
+
<?php
+
declare(strict_types=1);
+
+
/*
+
* The MIT License (MIT)
+
*
+
* Copyright (c) 2014-2018 Spomky-Labs
+
*
+
* This software may be modified and distributed under the terms
+
* of the MIT license. See the LICENSE file for details.
+
*
+
* @link https://github.com/web-token/jwt-framework/blob/v1.2/src/Component/Core/Util/ECSignature.php
+
*/
+
+
namespace Lcobucci\JWT\Signer\Ecdsa;
+
+
use function assert;
+
use function bin2hex;
+
use function dechex;
+
use function hex2bin;
+
use function hexdec;
+
use function is_string;
+
use function str_pad;
+
use function strlen;
+
use function substr;
+
+
use const STR_PAD_LEFT;
+
+
/**
+
* ECDSA signature converter using ext-mbstring
+
*
+
* @internal
+
*/
+
final class MultibyteStringConverter implements SignatureConverter
+
{
+
private const ASN1_SEQUENCE = '30';
+
private const ASN1_INTEGER = '02';
+
private const ASN1_MAX_SINGLE_BYTE = 128;
+
private const ASN1_LENGTH_2BYTES = '81';
+
private const ASN1_BIG_INTEGER_LIMIT = '7f';
+
private const ASN1_NEGATIVE_INTEGER = '00';
+
private const BYTE_SIZE = 2;
+
+
public function toAsn1(string $points, int $length): string
+
{
+
$points = bin2hex($points);
+
+
if (self::octetLength($points) !== $length) {
+
throw ConversionFailed::invalidLength();
+
}
+
+
$pointR = self::preparePositiveInteger(substr($points, 0, $length));
+
$pointS = self::preparePositiveInteger(substr($points, $length, null));
+
+
$lengthR = self::octetLength($pointR);
+
$lengthS = self::octetLength($pointS);
+
+
$totalLength = $lengthR + $lengthS + self::BYTE_SIZE + self::BYTE_SIZE;
+
$lengthPrefix = $totalLength > self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : '';
+
+
$asn1 = hex2bin(
+
self::ASN1_SEQUENCE
+
. $lengthPrefix . dechex($totalLength)
+
. self::ASN1_INTEGER . dechex($lengthR) . $pointR
+
. self::ASN1_INTEGER . dechex($lengthS) . $pointS,
+
);
+
assert(is_string($asn1));
+
assert($asn1 !== '');
+
+
return $asn1;
+
}
+
+
private static function octetLength(string $data): int
+
{
+
return (int) (strlen($data) / self::BYTE_SIZE);
+
}
+
+
private static function preparePositiveInteger(string $data): string
+
{
+
if (substr($data, 0, self::BYTE_SIZE) > self::ASN1_BIG_INTEGER_LIMIT) {
+
return self::ASN1_NEGATIVE_INTEGER . $data;
+
}
+
+
while (
+
substr($data, 0, self::BYTE_SIZE) === self::ASN1_NEGATIVE_INTEGER
+
&& substr($data, 2, self::BYTE_SIZE) <= self::ASN1_BIG_INTEGER_LIMIT
+
) {
+
$data = substr($data, 2, null);
+
}
+
+
return $data;
+
}
+
+
public function fromAsn1(string $signature, int $length): string
+
{
+
$message = bin2hex($signature);
+
$position = 0;
+
+
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_SEQUENCE) {
+
throw ConversionFailed::incorrectStartSequence();
+
}
+
+
// @phpstan-ignore-next-line
+
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) === self::ASN1_LENGTH_2BYTES) {
+
$position += self::BYTE_SIZE;
+
}
+
+
$pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
+
$pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
+
+
$points = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT) . str_pad($pointS, $length, '0', STR_PAD_LEFT));
+
assert(is_string($points));
+
assert($points !== '');
+
+
return $points;
+
}
+
+
private static function readAsn1Content(string $message, int &$position, int $length): string
+
{
+
$content = substr($message, $position, $length);
+
$position += $length;
+
+
return $content;
+
}
+
+
private static function readAsn1Integer(string $message, int &$position): string
+
{
+
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_INTEGER) {
+
throw ConversionFailed::integerExpected();
+
}
+
+
$length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE));
+
+
return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE);
+
}
+
+
private static function retrievePositiveInteger(string $data): string
+
{
+
while (
+
substr($data, 0, self::BYTE_SIZE) === self::ASN1_NEGATIVE_INTEGER
+
&& substr($data, 2, self::BYTE_SIZE) > self::ASN1_BIG_INTEGER_LIMIT
+
) {
+
$data = substr($data, 2, null);
+
}
+
+
return $data;
+
}
+
}
+31
vendor/lcobucci/jwt/src/Signer/Ecdsa/Sha256.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Ecdsa;
+
+
use Lcobucci\JWT\Signer\Ecdsa;
+
+
use const OPENSSL_ALGO_SHA256;
+
+
final class Sha256 extends Ecdsa
+
{
+
public function algorithmId(): string
+
{
+
return 'ES256';
+
}
+
+
public function algorithm(): int
+
{
+
return OPENSSL_ALGO_SHA256;
+
}
+
+
public function pointLength(): int
+
{
+
return 64;
+
}
+
+
public function expectedKeyLength(): int
+
{
+
return 256;
+
}
+
}
+31
vendor/lcobucci/jwt/src/Signer/Ecdsa/Sha384.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Ecdsa;
+
+
use Lcobucci\JWT\Signer\Ecdsa;
+
+
use const OPENSSL_ALGO_SHA384;
+
+
final class Sha384 extends Ecdsa
+
{
+
public function algorithmId(): string
+
{
+
return 'ES384';
+
}
+
+
public function algorithm(): int
+
{
+
return OPENSSL_ALGO_SHA384;
+
}
+
+
public function pointLength(): int
+
{
+
return 96;
+
}
+
+
public function expectedKeyLength(): int
+
{
+
return 384;
+
}
+
}
+33
vendor/lcobucci/jwt/src/Signer/Ecdsa/Sha512.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Ecdsa;
+
+
use Lcobucci\JWT\Signer\Ecdsa;
+
+
use const OPENSSL_ALGO_SHA512;
+
+
final class Sha512 extends Ecdsa
+
{
+
public function algorithmId(): string
+
{
+
return 'ES512';
+
}
+
+
public function algorithm(): int
+
{
+
return OPENSSL_ALGO_SHA512;
+
}
+
+
public function pointLength(): int
+
{
+
return 132;
+
}
+
+
public function expectedKeyLength(): int
+
{
+
// ES512 means ECDSA using P-521 and SHA-512.
+
// The key size is indeed 521 bits.
+
return 521;
+
}
+
}
+40
vendor/lcobucci/jwt/src/Signer/Ecdsa/SignatureConverter.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Ecdsa;
+
+
/**
+
* Manipulates the result of a ECDSA signature (points R and S) according to the
+
* JWA specs.
+
*
+
* OpenSSL creates a signature using the ASN.1 format and, according the JWA specs,
+
* the signature for JWTs must be the concatenated values of points R and S (in
+
* big-endian octet order).
+
*
+
* @internal
+
*
+
* @see https://tools.ietf.org/html/rfc7518#page-9
+
* @see https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
+
*/
+
interface SignatureConverter
+
{
+
/**
+
* Converts the signature generated by OpenSSL into what JWA defines
+
*
+
* @return non-empty-string
+
*
+
* @throws ConversionFailed When there was an issue during the format conversion.
+
*/
+
public function fromAsn1(string $signature, int $length): string;
+
+
/**
+
* Converts the JWA signature into something OpenSSL understands
+
*
+
* @param non-empty-string $points
+
*
+
* @return non-empty-string
+
*
+
* @throws ConversionFailed When there was an issue during the format conversion.
+
*/
+
public function toAsn1(string $points, int $length): string;
+
}
+67
vendor/lcobucci/jwt/src/Signer/Ecdsa.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer;
+
+
use Lcobucci\JWT\Signer\Ecdsa\MultibyteStringConverter;
+
use Lcobucci\JWT\Signer\Ecdsa\SignatureConverter;
+
+
use const OPENSSL_KEYTYPE_EC;
+
+
abstract class Ecdsa extends OpenSSL
+
{
+
public function __construct(
+
private readonly SignatureConverter $converter = new MultibyteStringConverter(),
+
) {
+
}
+
+
final public function sign(string $payload, Key $key): string
+
{
+
return $this->converter->fromAsn1(
+
$this->createSignature($key->contents(), $key->passphrase(), $payload),
+
$this->pointLength(),
+
);
+
}
+
+
final public function verify(string $expected, string $payload, Key $key): bool
+
{
+
return $this->verifySignature(
+
$this->converter->toAsn1($expected, $this->pointLength()),
+
$payload,
+
$key->contents(),
+
);
+
}
+
+
/** {@inheritDoc} */
+
final protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void
+
{
+
if ($type !== OPENSSL_KEYTYPE_EC) {
+
throw InvalidKeyProvided::incompatibleKeyType(
+
self::KEY_TYPE_MAP[OPENSSL_KEYTYPE_EC],
+
self::KEY_TYPE_MAP[$type],
+
);
+
}
+
+
$expectedKeyLength = $this->expectedKeyLength();
+
+
if ($lengthInBits !== $expectedKeyLength) {
+
throw InvalidKeyProvided::incompatibleKeyLength($expectedKeyLength, $lengthInBits);
+
}
+
}
+
+
/**
+
* @internal
+
*
+
* @return positive-int
+
*/
+
abstract public function expectedKeyLength(): int;
+
+
/**
+
* Returns the length of each point in the signature, so that we can calculate and verify R and S points properly
+
*
+
* @internal
+
*
+
* @return positive-int
+
*/
+
abstract public function pointLength(): int;
+
}
+36
vendor/lcobucci/jwt/src/Signer/Eddsa.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer;
+
+
use Lcobucci\JWT\Signer;
+
use SodiumException;
+
+
use function sodium_crypto_sign_detached;
+
use function sodium_crypto_sign_verify_detached;
+
+
final class Eddsa implements Signer
+
{
+
public function algorithmId(): string
+
{
+
return 'EdDSA';
+
}
+
+
public function sign(string $payload, Key $key): string
+
{
+
try {
+
return sodium_crypto_sign_detached($payload, $key->contents());
+
} catch (SodiumException $sodiumException) {
+
throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException);
+
}
+
}
+
+
public function verify(string $expected, string $payload, Key $key): bool
+
{
+
try {
+
return sodium_crypto_sign_verify_detached($expected, $payload, $key->contents());
+
} catch (SodiumException $sodiumException) {
+
throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException);
+
}
+
}
+
}
+24
vendor/lcobucci/jwt/src/Signer/Hmac/Sha256.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Hmac;
+
+
use Lcobucci\JWT\Signer\Hmac;
+
+
final class Sha256 extends Hmac
+
{
+
public function algorithmId(): string
+
{
+
return 'HS256';
+
}
+
+
public function algorithm(): string
+
{
+
return 'sha256';
+
}
+
+
public function minimumBitsLengthForKey(): int
+
{
+
return 256;
+
}
+
}
+24
vendor/lcobucci/jwt/src/Signer/Hmac/Sha384.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Hmac;
+
+
use Lcobucci\JWT\Signer\Hmac;
+
+
final class Sha384 extends Hmac
+
{
+
public function algorithmId(): string
+
{
+
return 'HS384';
+
}
+
+
public function algorithm(): string
+
{
+
return 'sha384';
+
}
+
+
public function minimumBitsLengthForKey(): int
+
{
+
return 384;
+
}
+
}
+24
vendor/lcobucci/jwt/src/Signer/Hmac/Sha512.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Hmac;
+
+
use Lcobucci\JWT\Signer\Hmac;
+
+
final class Sha512 extends Hmac
+
{
+
public function algorithmId(): string
+
{
+
return 'HS512';
+
}
+
+
public function algorithm(): string
+
{
+
return 'sha512';
+
}
+
+
public function minimumBitsLengthForKey(): int
+
{
+
return 512;
+
}
+
}
+44
vendor/lcobucci/jwt/src/Signer/Hmac.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer;
+
+
use Lcobucci\JWT\Signer;
+
+
use function hash_equals;
+
use function hash_hmac;
+
use function strlen;
+
+
abstract class Hmac implements Signer
+
{
+
final public function sign(string $payload, Key $key): string
+
{
+
$actualKeyLength = 8 * strlen($key->contents());
+
$expectedKeyLength = $this->minimumBitsLengthForKey();
+
+
if ($actualKeyLength < $expectedKeyLength) {
+
throw InvalidKeyProvided::tooShort($expectedKeyLength, $actualKeyLength);
+
}
+
+
return hash_hmac($this->algorithm(), $payload, $key->contents(), true);
+
}
+
+
final public function verify(string $expected, string $payload, Key $key): bool
+
{
+
return hash_equals($expected, $this->sign($payload, $key));
+
}
+
+
/**
+
* @internal
+
*
+
* @return non-empty-string
+
*/
+
abstract public function algorithm(): string;
+
+
/**
+
* @internal
+
*
+
* @return positive-int
+
*/
+
abstract public function minimumBitsLengthForKey(): int;
+
}
+47
vendor/lcobucci/jwt/src/Signer/InvalidKeyProvided.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer;
+
+
use InvalidArgumentException;
+
use Lcobucci\JWT\Exception;
+
+
final class InvalidKeyProvided extends InvalidArgumentException implements Exception
+
{
+
public static function cannotBeParsed(string $details): self
+
{
+
return new self('It was not possible to parse your key, reason:' . $details);
+
}
+
+
/**
+
* @param non-empty-string $expectedType
+
* @param non-empty-string $actualType
+
*/
+
public static function incompatibleKeyType(string $expectedType, string $actualType): self
+
{
+
return new self(
+
'The type of the provided key is not "' . $expectedType
+
. '", "' . $actualType . '" provided',
+
);
+
}
+
+
/** @param positive-int $expectedLength */
+
public static function incompatibleKeyLength(int $expectedLength, int $actualLength): self
+
{
+
return new self(
+
'The length of the provided key is different than ' . $expectedLength . ' bits, '
+
. $actualLength . ' bits provided',
+
);
+
}
+
+
public static function cannotBeEmpty(): self
+
{
+
return new self('Key cannot be empty');
+
}
+
+
public static function tooShort(int $expectedLength, int $actualLength): self
+
{
+
return new self('Key provided is shorter than ' . $expectedLength . ' bits,'
+
. ' only ' . $actualLength . ' bits provided');
+
}
+
}
+20
vendor/lcobucci/jwt/src/Signer/Key/FileCouldNotBeRead.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Key;
+
+
use InvalidArgumentException;
+
use Lcobucci\JWT\Exception;
+
use Throwable;
+
+
final class FileCouldNotBeRead extends InvalidArgumentException implements Exception
+
{
+
/** @param non-empty-string $path */
+
public static function onPath(string $path, ?Throwable $cause = null): self
+
{
+
return new self(
+
message: 'The path "' . $path . '" does not contain a valid key file',
+
previous: $cause,
+
);
+
}
+
}
+98
vendor/lcobucci/jwt/src/Signer/Key/InMemory.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Key;
+
+
use Lcobucci\JWT\Signer\InvalidKeyProvided;
+
use Lcobucci\JWT\Signer\Key;
+
use Lcobucci\JWT\SodiumBase64Polyfill;
+
use SensitiveParameter;
+
use SplFileObject;
+
use Throwable;
+
+
use function assert;
+
use function is_string;
+
+
final class InMemory implements Key
+
{
+
/** @param non-empty-string $contents */
+
private function __construct(
+
#[SensitiveParameter]
+
public readonly string $contents,
+
#[SensitiveParameter]
+
public readonly string $passphrase,
+
) {
+
}
+
+
/** @param non-empty-string $contents */
+
public static function plainText(
+
#[SensitiveParameter]
+
string $contents,
+
#[SensitiveParameter]
+
string $passphrase = '',
+
): self {
+
self::guardAgainstEmptyKey($contents);
+
+
return new self($contents, $passphrase);
+
}
+
+
/** @param non-empty-string $contents */
+
public static function base64Encoded(
+
#[SensitiveParameter]
+
string $contents,
+
#[SensitiveParameter]
+
string $passphrase = '',
+
): self {
+
$decoded = SodiumBase64Polyfill::base642bin(
+
$contents,
+
SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL,
+
);
+
+
self::guardAgainstEmptyKey($decoded);
+
+
return new self($decoded, $passphrase);
+
}
+
+
/**
+
* @param non-empty-string $path
+
*
+
* @throws FileCouldNotBeRead
+
*/
+
public static function file(
+
string $path,
+
#[SensitiveParameter]
+
string $passphrase = '',
+
): self {
+
try {
+
$file = new SplFileObject($path);
+
} catch (Throwable $exception) {
+
throw FileCouldNotBeRead::onPath($path, $exception);
+
}
+
+
$fileSize = $file->getSize();
+
$contents = $fileSize > 0 ? $file->fread($file->getSize()) : '';
+
assert(is_string($contents));
+
+
self::guardAgainstEmptyKey($contents);
+
+
return new self($contents, $passphrase);
+
}
+
+
/** @phpstan-assert non-empty-string $contents */
+
private static function guardAgainstEmptyKey(string $contents): void
+
{
+
if ($contents === '') {
+
throw InvalidKeyProvided::cannotBeEmpty();
+
}
+
}
+
+
public function contents(): string
+
{
+
return $this->contents;
+
}
+
+
public function passphrase(): string
+
{
+
return $this->passphrase;
+
}
+
}
+12
vendor/lcobucci/jwt/src/Signer/Key.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer;
+
+
interface Key
+
{
+
/** @return non-empty-string */
+
public function contents(): string;
+
+
public function passphrase(): string;
+
}
+133
vendor/lcobucci/jwt/src/Signer/OpenSSL.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer;
+
+
use Lcobucci\JWT\Signer;
+
use OpenSSLAsymmetricKey;
+
use SensitiveParameter;
+
+
use function array_key_exists;
+
use function assert;
+
use function is_array;
+
use function is_bool;
+
use function is_int;
+
use function openssl_error_string;
+
use function openssl_pkey_get_details;
+
use function openssl_pkey_get_private;
+
use function openssl_pkey_get_public;
+
use function openssl_sign;
+
use function openssl_verify;
+
+
use const OPENSSL_KEYTYPE_DH;
+
use const OPENSSL_KEYTYPE_DSA;
+
use const OPENSSL_KEYTYPE_EC;
+
use const OPENSSL_KEYTYPE_RSA;
+
use const PHP_EOL;
+
+
abstract class OpenSSL implements Signer
+
{
+
protected const KEY_TYPE_MAP = [
+
OPENSSL_KEYTYPE_RSA => 'RSA',
+
OPENSSL_KEYTYPE_DSA => 'DSA',
+
OPENSSL_KEYTYPE_DH => 'DH',
+
OPENSSL_KEYTYPE_EC => 'EC',
+
];
+
+
/**
+
* @return non-empty-string
+
*
+
* @throws CannotSignPayload
+
* @throws InvalidKeyProvided
+
*/
+
final protected function createSignature(
+
#[SensitiveParameter]
+
string $pem,
+
#[SensitiveParameter]
+
string $passphrase,
+
string $payload,
+
): string {
+
$key = $this->getPrivateKey($pem, $passphrase);
+
+
$signature = '';
+
+
if (! openssl_sign($payload, $signature, $key, $this->algorithm())) {
+
throw CannotSignPayload::errorHappened($this->fullOpenSSLErrorString());
+
}
+
+
return $signature;
+
}
+
+
/** @throws CannotSignPayload */
+
private function getPrivateKey(
+
#[SensitiveParameter]
+
string $pem,
+
#[SensitiveParameter]
+
string $passphrase,
+
): OpenSSLAsymmetricKey {
+
return $this->validateKey(openssl_pkey_get_private($pem, $passphrase));
+
}
+
+
/** @throws InvalidKeyProvided */
+
final protected function verifySignature(
+
string $expected,
+
string $payload,
+
string $pem,
+
): bool {
+
$key = $this->getPublicKey($pem);
+
$result = openssl_verify($payload, $expected, $key, $this->algorithm());
+
+
return $result === 1;
+
}
+
+
/** @throws InvalidKeyProvided */
+
private function getPublicKey(string $pem): OpenSSLAsymmetricKey
+
{
+
return $this->validateKey(openssl_pkey_get_public($pem));
+
}
+
+
/**
+
* Raises an exception when the key type is not the expected type
+
*
+
* @throws InvalidKeyProvided
+
*/
+
private function validateKey(OpenSSLAsymmetricKey|bool $key): OpenSSLAsymmetricKey
+
{
+
if (is_bool($key)) {
+
throw InvalidKeyProvided::cannotBeParsed($this->fullOpenSSLErrorString());
+
}
+
+
$details = openssl_pkey_get_details($key);
+
assert(is_array($details));
+
+
assert(array_key_exists('bits', $details));
+
assert(is_int($details['bits']));
+
assert(array_key_exists('type', $details));
+
assert(is_int($details['type']));
+
+
$this->guardAgainstIncompatibleKey($details['type'], $details['bits']);
+
+
return $key;
+
}
+
+
private function fullOpenSSLErrorString(): string
+
{
+
$error = '';
+
+
while ($msg = openssl_error_string()) {
+
$error .= PHP_EOL . '* ' . $msg;
+
}
+
+
return $error;
+
}
+
+
/** @throws InvalidKeyProvided */
+
abstract protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void;
+
+
/**
+
* Returns which algorithm to be used to create/verify the signature (using OpenSSL constants)
+
*
+
* @internal
+
*/
+
abstract public function algorithm(): int;
+
}
+21
vendor/lcobucci/jwt/src/Signer/Rsa/Sha256.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Rsa;
+
+
use Lcobucci\JWT\Signer\Rsa;
+
+
use const OPENSSL_ALGO_SHA256;
+
+
final class Sha256 extends Rsa
+
{
+
public function algorithmId(): string
+
{
+
return 'RS256';
+
}
+
+
public function algorithm(): int
+
{
+
return OPENSSL_ALGO_SHA256;
+
}
+
}
+21
vendor/lcobucci/jwt/src/Signer/Rsa/Sha384.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Rsa;
+
+
use Lcobucci\JWT\Signer\Rsa;
+
+
use const OPENSSL_ALGO_SHA384;
+
+
final class Sha384 extends Rsa
+
{
+
public function algorithmId(): string
+
{
+
return 'RS384';
+
}
+
+
public function algorithm(): int
+
{
+
return OPENSSL_ALGO_SHA384;
+
}
+
}
+21
vendor/lcobucci/jwt/src/Signer/Rsa/Sha512.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer\Rsa;
+
+
use Lcobucci\JWT\Signer\Rsa;
+
+
use const OPENSSL_ALGO_SHA512;
+
+
final class Sha512 extends Rsa
+
{
+
public function algorithmId(): string
+
{
+
return 'RS512';
+
}
+
+
public function algorithm(): int
+
{
+
return OPENSSL_ALGO_SHA512;
+
}
+
}
+35
vendor/lcobucci/jwt/src/Signer/Rsa.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Signer;
+
+
use const OPENSSL_KEYTYPE_RSA;
+
+
abstract class Rsa extends OpenSSL
+
{
+
private const MINIMUM_KEY_LENGTH = 2048;
+
+
final public function sign(string $payload, Key $key): string
+
{
+
return $this->createSignature($key->contents(), $key->passphrase(), $payload);
+
}
+
+
final public function verify(string $expected, string $payload, Key $key): bool
+
{
+
return $this->verifySignature($expected, $payload, $key->contents());
+
}
+
+
final protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void
+
{
+
if ($type !== OPENSSL_KEYTYPE_RSA) {
+
throw InvalidKeyProvided::incompatibleKeyType(
+
self::KEY_TYPE_MAP[OPENSSL_KEYTYPE_RSA],
+
self::KEY_TYPE_MAP[$type],
+
);
+
}
+
+
if ($lengthInBits < self::MINIMUM_KEY_LENGTH) {
+
throw InvalidKeyProvided::tooShort(self::MINIMUM_KEY_LENGTH, $lengthInBits);
+
}
+
}
+
}
+43
vendor/lcobucci/jwt/src/Signer.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use Lcobucci\JWT\Signer\CannotSignPayload;
+
use Lcobucci\JWT\Signer\Ecdsa\ConversionFailed;
+
use Lcobucci\JWT\Signer\InvalidKeyProvided;
+
use Lcobucci\JWT\Signer\Key;
+
+
interface Signer
+
{
+
/**
+
* Returns the algorithm id
+
*
+
* @return non-empty-string
+
*/
+
public function algorithmId(): string;
+
+
/**
+
* Creates a hash for the given payload
+
*
+
* @param non-empty-string $payload
+
*
+
* @return non-empty-string
+
*
+
* @throws CannotSignPayload When payload signing fails.
+
* @throws InvalidKeyProvided When issue key is invalid/incompatible.
+
* @throws ConversionFailed When signature could not be converted.
+
*/
+
public function sign(string $payload, Key $key): string;
+
+
/**
+
* Returns if the expected hash matches with the data and key
+
*
+
* @param non-empty-string $expected
+
* @param non-empty-string $payload
+
*
+
* @throws InvalidKeyProvided When issue key is invalid/incompatible.
+
* @throws ConversionFailed When signature could not be converted.
+
*/
+
public function verify(string $expected, string $payload, Key $key): bool;
+
}
+98
vendor/lcobucci/jwt/src/SodiumBase64Polyfill.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use Lcobucci\JWT\Encoding\CannotDecodeContent;
+
use SodiumException;
+
+
use function base64_decode;
+
use function base64_encode;
+
use function function_exists;
+
use function is_string;
+
use function rtrim;
+
use function sodium_base642bin;
+
use function sodium_bin2base64;
+
use function strtr;
+
+
/** @internal */
+
final class SodiumBase64Polyfill
+
{
+
public const SODIUM_BASE64_VARIANT_ORIGINAL = 1;
+
public const SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING = 3;
+
public const SODIUM_BASE64_VARIANT_URLSAFE = 5;
+
public const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING = 7;
+
+
/** @return ($decoded is non-empty-string ? non-empty-string : string) */
+
public static function bin2base64(string $decoded, int $variant): string
+
{
+
if (! function_exists('sodium_bin2base64')) {
+
return self::bin2base64Fallback($decoded, $variant); // @codeCoverageIgnore
+
}
+
+
return sodium_bin2base64($decoded, $variant);
+
}
+
+
/** @return ($decoded is non-empty-string ? non-empty-string : string) */
+
public static function bin2base64Fallback(string $decoded, int $variant): string
+
{
+
$encoded = base64_encode($decoded);
+
+
if (
+
$variant === self::SODIUM_BASE64_VARIANT_URLSAFE
+
|| $variant === self::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING
+
) {
+
$encoded = strtr($encoded, '+/', '-_');
+
}
+
+
if (
+
$variant === self::SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING
+
|| $variant === self::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING
+
) {
+
$encoded = rtrim($encoded, '=');
+
}
+
+
return $encoded;
+
}
+
+
/**
+
* @return ($encoded is non-empty-string ? non-empty-string : string)
+
*
+
* @throws CannotDecodeContent
+
*/
+
public static function base642bin(string $encoded, int $variant): string
+
{
+
if (! function_exists('sodium_base642bin')) {
+
return self::base642binFallback($encoded, $variant); // @codeCoverageIgnore
+
}
+
+
try {
+
return sodium_base642bin($encoded, $variant, '');
+
} catch (SodiumException) {
+
throw CannotDecodeContent::invalidBase64String();
+
}
+
}
+
+
/**
+
* @return ($encoded is non-empty-string ? non-empty-string : string)
+
*
+
* @throws CannotDecodeContent
+
*/
+
public static function base642binFallback(string $encoded, int $variant): string
+
{
+
if (
+
$variant === self::SODIUM_BASE64_VARIANT_URLSAFE
+
|| $variant === self::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING
+
) {
+
$encoded = strtr($encoded, '-_', '+/');
+
}
+
+
$decoded = base64_decode($encoded, true);
+
+
if (! is_string($decoded)) {
+
throw CannotDecodeContent::invalidBase64String();
+
}
+
+
return $decoded;
+
}
+
}
+167
vendor/lcobucci/jwt/src/Token/Builder.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Token;
+
+
use DateTimeImmutable;
+
use Lcobucci\JWT\Builder as BuilderInterface;
+
use Lcobucci\JWT\ClaimsFormatter;
+
use Lcobucci\JWT\Encoder;
+
use Lcobucci\JWT\Encoding\CannotEncodeContent;
+
use Lcobucci\JWT\Signer;
+
use Lcobucci\JWT\Signer\Key;
+
use Lcobucci\JWT\UnencryptedToken;
+
+
use function array_diff;
+
use function array_merge;
+
use function in_array;
+
+
/** @immutable */
+
final class Builder implements BuilderInterface
+
{
+
/** @var array<non-empty-string, mixed> */
+
private array $headers = ['typ' => 'JWT', 'alg' => null];
+
+
/** @var array<non-empty-string, mixed> */
+
private array $claims = [];
+
+
/** @deprecated Deprecated since v5.5, please use {@see self::new()} instead */
+
public function __construct(private readonly Encoder $encoder, private readonly ClaimsFormatter $claimFormatter)
+
{
+
}
+
+
public static function new(Encoder $encoder, ClaimsFormatter $claimFormatter): self
+
{
+
return new self($encoder, $claimFormatter);
+
}
+
+
/**
+
* @inheritDoc
+
* @pure
+
*/
+
public function permittedFor(string ...$audiences): BuilderInterface
+
{
+
$configured = $this->claims[RegisteredClaims::AUDIENCE] ?? [];
+
$toAppend = array_diff($audiences, $configured);
+
+
return $this->setClaim(RegisteredClaims::AUDIENCE, array_merge($configured, $toAppend));
+
}
+
+
/**
+
* @inheritDoc
+
* @pure
+
*/
+
public function expiresAt(DateTimeImmutable $expiration): BuilderInterface
+
{
+
return $this->setClaim(RegisteredClaims::EXPIRATION_TIME, $expiration);
+
}
+
+
/**
+
* @inheritDoc
+
* @pure
+
*/
+
public function identifiedBy(string $id): BuilderInterface
+
{
+
return $this->setClaim(RegisteredClaims::ID, $id);
+
}
+
+
/**
+
* @inheritDoc
+
* @pure
+
*/
+
public function issuedAt(DateTimeImmutable $issuedAt): BuilderInterface
+
{
+
return $this->setClaim(RegisteredClaims::ISSUED_AT, $issuedAt);
+
}
+
+
/**
+
* @inheritDoc
+
* @pure
+
*/
+
public function issuedBy(string $issuer): BuilderInterface
+
{
+
return $this->setClaim(RegisteredClaims::ISSUER, $issuer);
+
}
+
+
/**
+
* @inheritDoc
+
* @pure
+
*/
+
public function canOnlyBeUsedAfter(DateTimeImmutable $notBefore): BuilderInterface
+
{
+
return $this->setClaim(RegisteredClaims::NOT_BEFORE, $notBefore);
+
}
+
+
/**
+
* @inheritDoc
+
* @pure
+
*/
+
public function relatedTo(string $subject): BuilderInterface
+
{
+
return $this->setClaim(RegisteredClaims::SUBJECT, $subject);
+
}
+
+
/**
+
* @inheritDoc
+
* @pure
+
*/
+
public function withHeader(string $name, mixed $value): BuilderInterface
+
{
+
$new = clone $this;
+
$new->headers[$name] = $value;
+
+
return $new;
+
}
+
+
/**
+
* @inheritDoc
+
* @pure
+
*/
+
public function withClaim(string $name, mixed $value): BuilderInterface
+
{
+
if (in_array($name, RegisteredClaims::ALL, true)) {
+
throw RegisteredClaimGiven::forClaim($name);
+
}
+
+
return $this->setClaim($name, $value);
+
}
+
+
/** @param non-empty-string $name */
+
private function setClaim(string $name, mixed $value): BuilderInterface
+
{
+
$new = clone $this;
+
$new->claims[$name] = $value;
+
+
return $new;
+
}
+
+
/**
+
* @param array<non-empty-string, mixed> $items
+
*
+
* @throws CannotEncodeContent When data cannot be converted to JSON.
+
*/
+
private function encode(array $items): string
+
{
+
return $this->encoder->base64UrlEncode(
+
$this->encoder->jsonEncode($items),
+
);
+
}
+
+
public function getToken(Signer $signer, Key $key): UnencryptedToken
+
{
+
$headers = $this->headers;
+
$headers['alg'] = $signer->algorithmId();
+
+
$encodedHeaders = $this->encode($headers);
+
$encodedClaims = $this->encode($this->claimFormatter->formatClaims($this->claims));
+
+
$signature = $signer->sign($encodedHeaders . '.' . $encodedClaims, $key);
+
$encodedSignature = $this->encoder->base64UrlEncode($signature);
+
+
return new Plain(
+
new DataSet($headers, $encodedHeaders),
+
new DataSet($this->claims, $encodedClaims),
+
new Signature($signature, $encodedSignature),
+
);
+
}
+
}
+37
vendor/lcobucci/jwt/src/Token/DataSet.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Token;
+
+
use function array_key_exists;
+
+
final class DataSet
+
{
+
/** @param array<non-empty-string, mixed> $data */
+
public function __construct(private readonly array $data, private readonly string $encoded)
+
{
+
}
+
+
/** @param non-empty-string $name */
+
public function get(string $name, mixed $default = null): mixed
+
{
+
return $this->data[$name] ?? $default;
+
}
+
+
/** @param non-empty-string $name */
+
public function has(string $name): bool
+
{
+
return array_key_exists($name, $this->data);
+
}
+
+
/** @return array<non-empty-string, mixed> */
+
public function all(): array
+
{
+
return $this->data;
+
}
+
+
public function toString(): string
+
{
+
return $this->encoded;
+
}
+
}
+41
vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Token;
+
+
use InvalidArgumentException;
+
use Lcobucci\JWT\Exception;
+
+
final class InvalidTokenStructure extends InvalidArgumentException implements Exception
+
{
+
public static function missingOrNotEnoughSeparators(): self
+
{
+
return new self('The JWT string must have two dots');
+
}
+
+
public static function missingHeaderPart(): self
+
{
+
return new self('The JWT string is missing the Header part');
+
}
+
+
public static function missingClaimsPart(): self
+
{
+
return new self('The JWT string is missing the Claim part');
+
}
+
+
public static function missingSignaturePart(): self
+
{
+
return new self('The JWT string is missing the Signature part');
+
}
+
+
/** @param non-empty-string $part */
+
public static function arrayExpected(string $part): self
+
{
+
return new self($part . ' must be an array with non-empty-string keys');
+
}
+
+
public static function dateIsNotParseable(string $value): self
+
{
+
return new self('Value is not in the allowed date format: ' . $value);
+
}
+
}
+180
vendor/lcobucci/jwt/src/Token/Parser.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Token;
+
+
use DateTimeImmutable;
+
use Lcobucci\JWT\Decoder;
+
use Lcobucci\JWT\Parser as ParserInterface;
+
use Lcobucci\JWT\Token as TokenInterface;
+
+
use function array_key_exists;
+
use function count;
+
use function explode;
+
use function is_array;
+
use function is_numeric;
+
use function number_format;
+
+
final class Parser implements ParserInterface
+
{
+
private const MICROSECOND_PRECISION = 6;
+
+
public function __construct(private readonly Decoder $decoder)
+
{
+
}
+
+
public function parse(string $jwt): TokenInterface
+
{
+
[$encodedHeaders, $encodedClaims, $encodedSignature] = $this->splitJwt($jwt);
+
+
if ($encodedHeaders === '') {
+
throw InvalidTokenStructure::missingHeaderPart();
+
}
+
+
if ($encodedClaims === '') {
+
throw InvalidTokenStructure::missingClaimsPart();
+
}
+
+
if ($encodedSignature === '') {
+
throw InvalidTokenStructure::missingSignaturePart();
+
}
+
+
$header = $this->parseHeader($encodedHeaders);
+
+
return new Plain(
+
new DataSet($header, $encodedHeaders),
+
new DataSet($this->parseClaims($encodedClaims), $encodedClaims),
+
$this->parseSignature($encodedSignature),
+
);
+
}
+
+
/**
+
* Splits the JWT string into an array
+
*
+
* @param non-empty-string $jwt
+
*
+
* @return string[]
+
*
+
* @throws InvalidTokenStructure When JWT doesn't have all parts.
+
*/
+
private function splitJwt(string $jwt): array
+
{
+
$data = explode('.', $jwt);
+
+
if (count($data) !== 3) {
+
throw InvalidTokenStructure::missingOrNotEnoughSeparators();
+
}
+
+
return $data;
+
}
+
+
/**
+
* Parses the header from a string
+
*
+
* @param non-empty-string $data
+
*
+
* @return array<non-empty-string, mixed>
+
*
+
* @throws UnsupportedHeaderFound When an invalid header is informed.
+
* @throws InvalidTokenStructure When parsed content isn't an array.
+
*/
+
private function parseHeader(string $data): array
+
{
+
$header = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));
+
+
if (! is_array($header)) {
+
throw InvalidTokenStructure::arrayExpected('headers');
+
}
+
+
$this->guardAgainstEmptyStringKeys($header, 'headers');
+
+
if (array_key_exists('enc', $header)) {
+
throw UnsupportedHeaderFound::encryption();
+
}
+
+
if (! array_key_exists('typ', $header)) {
+
$header['typ'] = 'JWT';
+
}
+
+
return $header;
+
}
+
+
/**
+
* Parses the claim set from a string
+
*
+
* @param non-empty-string $data
+
*
+
* @return array<non-empty-string, mixed>
+
*
+
* @throws InvalidTokenStructure When parsed content isn't an array or contains non-parseable dates.
+
*/
+
private function parseClaims(string $data): array
+
{
+
$claims = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));
+
+
if (! is_array($claims)) {
+
throw InvalidTokenStructure::arrayExpected('claims');
+
}
+
+
$this->guardAgainstEmptyStringKeys($claims, 'claims');
+
+
if (array_key_exists(RegisteredClaims::AUDIENCE, $claims)) {
+
$claims[RegisteredClaims::AUDIENCE] = (array) $claims[RegisteredClaims::AUDIENCE];
+
}
+
+
foreach (RegisteredClaims::DATE_CLAIMS as $claim) {
+
if (! array_key_exists($claim, $claims)) {
+
continue;
+
}
+
+
$claims[$claim] = $this->convertDate($claims[$claim]);
+
}
+
+
return $claims;
+
}
+
+
/**
+
* @param array<string, mixed> $array
+
* @param non-empty-string $part
+
*
+
* @phpstan-assert array<non-empty-string, mixed> $array
+
*/
+
private function guardAgainstEmptyStringKeys(array $array, string $part): void
+
{
+
foreach ($array as $key => $value) {
+
if ($key === '') {
+
throw InvalidTokenStructure::arrayExpected($part);
+
}
+
}
+
}
+
+
/** @throws InvalidTokenStructure */
+
private function convertDate(int|float|string $timestamp): DateTimeImmutable
+
{
+
if (! is_numeric($timestamp)) {
+
throw InvalidTokenStructure::dateIsNotParseable($timestamp);
+
}
+
+
$normalizedTimestamp = number_format((float) $timestamp, self::MICROSECOND_PRECISION, '.', '');
+
+
$date = DateTimeImmutable::createFromFormat('U.u', $normalizedTimestamp);
+
+
if ($date === false) {
+
throw InvalidTokenStructure::dateIsNotParseable($normalizedTimestamp);
+
}
+
+
return $date;
+
}
+
+
/**
+
* Returns the signature from given data
+
*
+
* @param non-empty-string $data
+
*/
+
private function parseSignature(string $data): Signature
+
{
+
$hash = $this->decoder->base64UrlDecode($data);
+
+
return new Signature($hash, $data);
+
}
+
}
+85
vendor/lcobucci/jwt/src/Token/Plain.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Token;
+
+
use DateTimeInterface;
+
use Lcobucci\JWT\UnencryptedToken;
+
+
use function in_array;
+
+
final class Plain implements UnencryptedToken
+
{
+
public function __construct(
+
private readonly DataSet $headers,
+
private readonly DataSet $claims,
+
private readonly Signature $signature,
+
) {
+
}
+
+
public function headers(): DataSet
+
{
+
return $this->headers;
+
}
+
+
public function claims(): DataSet
+
{
+
return $this->claims;
+
}
+
+
public function signature(): Signature
+
{
+
return $this->signature;
+
}
+
+
public function payload(): string
+
{
+
return $this->headers->toString() . '.' . $this->claims->toString();
+
}
+
+
public function isPermittedFor(string $audience): bool
+
{
+
return in_array($audience, $this->claims->get(RegisteredClaims::AUDIENCE, []), true);
+
}
+
+
public function isIdentifiedBy(string $id): bool
+
{
+
return $this->claims->get(RegisteredClaims::ID) === $id;
+
}
+
+
public function isRelatedTo(string $subject): bool
+
{
+
return $this->claims->get(RegisteredClaims::SUBJECT) === $subject;
+
}
+
+
public function hasBeenIssuedBy(string ...$issuers): bool
+
{
+
return in_array($this->claims->get(RegisteredClaims::ISSUER), $issuers, true);
+
}
+
+
public function hasBeenIssuedBefore(DateTimeInterface $now): bool
+
{
+
return $now >= $this->claims->get(RegisteredClaims::ISSUED_AT);
+
}
+
+
public function isMinimumTimeBefore(DateTimeInterface $now): bool
+
{
+
return $now >= $this->claims->get(RegisteredClaims::NOT_BEFORE);
+
}
+
+
public function isExpired(DateTimeInterface $now): bool
+
{
+
if (! $this->claims->has(RegisteredClaims::EXPIRATION_TIME)) {
+
return false;
+
}
+
+
return $now >= $this->claims->get(RegisteredClaims::EXPIRATION_TIME);
+
}
+
+
public function toString(): string
+
{
+
return $this->headers->toString() . '.'
+
. $this->claims->toString() . '.'
+
. $this->signature->toString();
+
}
+
}
+21
vendor/lcobucci/jwt/src/Token/RegisteredClaimGiven.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Token;
+
+
use InvalidArgumentException;
+
use Lcobucci\JWT\Exception;
+
+
use function sprintf;
+
+
final class RegisteredClaimGiven extends InvalidArgumentException implements Exception
+
{
+
private const DEFAULT_MESSAGE = 'Builder#withClaim() is meant to be used for non-registered claims, '
+
. 'check the documentation on how to set claim "%s"';
+
+
/** @param non-empty-string $name */
+
public static function forClaim(string $name): self
+
{
+
return new self(sprintf(self::DEFAULT_MESSAGE, $name));
+
}
+
}
+77
vendor/lcobucci/jwt/src/Token/RegisteredClaims.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Token;
+
+
/**
+
* Defines the list of claims that are registered in the IANA "JSON Web Token Claims" registry
+
*
+
* @see https://tools.ietf.org/html/rfc7519#section-4.1
+
*/
+
interface RegisteredClaims
+
{
+
public const ALL = [
+
self::AUDIENCE,
+
self::EXPIRATION_TIME,
+
self::ID,
+
self::ISSUED_AT,
+
self::ISSUER,
+
self::NOT_BEFORE,
+
self::SUBJECT,
+
];
+
+
public const DATE_CLAIMS = [
+
self::ISSUED_AT,
+
self::NOT_BEFORE,
+
self::EXPIRATION_TIME,
+
];
+
+
/**
+
* Identifies the recipients that the JWT is intended for
+
*
+
* @see https://tools.ietf.org/html/rfc7519#section-4.1.3
+
*/
+
public const AUDIENCE = 'aud';
+
+
/**
+
* Identifies the expiration time on or after which the JWT MUST NOT be accepted for processing
+
*
+
* @see https://tools.ietf.org/html/rfc7519#section-4.1.4
+
*/
+
public const EXPIRATION_TIME = 'exp';
+
+
/**
+
* Provides a unique identifier for the JWT
+
*
+
* @see https://tools.ietf.org/html/rfc7519#section-4.1.7
+
*/
+
public const ID = 'jti';
+
+
/**
+
* Identifies the time at which the JWT was issued
+
*
+
* @see https://tools.ietf.org/html/rfc7519#section-4.1.6
+
*/
+
public const ISSUED_AT = 'iat';
+
+
/**
+
* Identifies the principal that issued the JWT
+
*
+
* @see https://tools.ietf.org/html/rfc7519#section-4.1.1
+
*/
+
public const ISSUER = 'iss';
+
+
/**
+
* Identifies the time before which the JWT MUST NOT be accepted for processing
+
*
+
* https://tools.ietf.org/html/rfc7519#section-4.1.5
+
*/
+
public const NOT_BEFORE = 'nbf';
+
+
/**
+
* Identifies the principal that is the subject of the JWT.
+
*
+
* https://tools.ietf.org/html/rfc7519#section-4.1.2
+
*/
+
public const SUBJECT = 'sub';
+
}
+31
vendor/lcobucci/jwt/src/Token/Signature.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Token;
+
+
final class Signature
+
{
+
/**
+
* @param non-empty-string $hash
+
* @param non-empty-string $encoded
+
*/
+
public function __construct(private readonly string $hash, private readonly string $encoded)
+
{
+
}
+
+
/** @return non-empty-string */
+
public function hash(): string
+
{
+
return $this->hash;
+
}
+
+
/**
+
* Returns the encoded version of the signature
+
*
+
* @return non-empty-string
+
*/
+
public function toString(): string
+
{
+
return $this->encoded;
+
}
+
}
+15
vendor/lcobucci/jwt/src/Token/UnsupportedHeaderFound.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Token;
+
+
use InvalidArgumentException;
+
use Lcobucci\JWT\Exception;
+
+
final class UnsupportedHeaderFound extends InvalidArgumentException implements Exception
+
{
+
public static function encryption(): self
+
{
+
return new self('Encryption is not supported yet');
+
}
+
}
+65
vendor/lcobucci/jwt/src/Token.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use DateTimeInterface;
+
use Lcobucci\JWT\Token\DataSet;
+
+
interface Token
+
{
+
/**
+
* Returns the token headers
+
*/
+
public function headers(): DataSet;
+
+
/**
+
* Returns if the token is allowed to be used by the audience
+
*
+
* @param non-empty-string $audience
+
*/
+
public function isPermittedFor(string $audience): bool;
+
+
/**
+
* Returns if the token has the given id
+
*
+
* @param non-empty-string $id
+
*/
+
public function isIdentifiedBy(string $id): bool;
+
+
/**
+
* Returns if the token has the given subject
+
*
+
* @param non-empty-string $subject
+
*/
+
public function isRelatedTo(string $subject): bool;
+
+
/**
+
* Returns if the token was issued by any of given issuers
+
*
+
* @param non-empty-string ...$issuers
+
*/
+
public function hasBeenIssuedBy(string ...$issuers): bool;
+
+
/**
+
* Returns if the token was issued before of given time
+
*/
+
public function hasBeenIssuedBefore(DateTimeInterface $now): bool;
+
+
/**
+
* Returns if the token minimum time is before than given time
+
*/
+
public function isMinimumTimeBefore(DateTimeInterface $now): bool;
+
+
/**
+
* Returns if the token is expired
+
*/
+
public function isExpired(DateTimeInterface $now): bool;
+
+
/**
+
* Returns an encoded representation of the token
+
*
+
* @return non-empty-string
+
*/
+
public function toString(): string;
+
}
+27
vendor/lcobucci/jwt/src/UnencryptedToken.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use Lcobucci\JWT\Token\DataSet;
+
use Lcobucci\JWT\Token\Signature;
+
+
interface UnencryptedToken extends Token
+
{
+
/**
+
* Returns the token claims
+
*/
+
public function claims(): DataSet;
+
+
/**
+
* Returns the token signature
+
*/
+
public function signature(): Signature;
+
+
/**
+
* Returns the token payload
+
*
+
* @return non-empty-string
+
*/
+
public function payload(): string;
+
}
+18
vendor/lcobucci/jwt/src/Validation/Constraint/CannotValidateARegisteredClaim.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use InvalidArgumentException;
+
use Lcobucci\JWT\Exception;
+
+
final class CannotValidateARegisteredClaim extends InvalidArgumentException implements Exception
+
{
+
/** @param non-empty-string $claim */
+
public static function create(string $claim): self
+
{
+
return new self(
+
'The claim "' . $claim . '" is a registered claim, another constraint must be used to validate its value',
+
);
+
}
+
}
+35
vendor/lcobucci/jwt/src/Validation/Constraint/HasClaim.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\UnencryptedToken;
+
use Lcobucci\JWT\Validation\Constraint;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
+
use function in_array;
+
+
final class HasClaim implements Constraint
+
{
+
/** @param non-empty-string $claim */
+
public function __construct(private readonly string $claim)
+
{
+
if (in_array($claim, Token\RegisteredClaims::ALL, true)) {
+
throw CannotValidateARegisteredClaim::create($claim);
+
}
+
}
+
+
public function assert(Token $token): void
+
{
+
if (! $token instanceof UnencryptedToken) {
+
throw ConstraintViolation::error('You should pass a plain token', $this);
+
}
+
+
$claims = $token->claims();
+
+
if (! $claims->has($this->claim)) {
+
throw ConstraintViolation::error('The token does not have the claim "' . $this->claim . '"', $this);
+
}
+
}
+
}
+42
vendor/lcobucci/jwt/src/Validation/Constraint/HasClaimWithValue.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\UnencryptedToken;
+
use Lcobucci\JWT\Validation\Constraint;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
+
use function in_array;
+
+
final class HasClaimWithValue implements Constraint
+
{
+
/** @param non-empty-string $claim */
+
public function __construct(private readonly string $claim, private readonly mixed $expectedValue)
+
{
+
if (in_array($claim, Token\RegisteredClaims::ALL, true)) {
+
throw CannotValidateARegisteredClaim::create($claim);
+
}
+
}
+
+
public function assert(Token $token): void
+
{
+
if (! $token instanceof UnencryptedToken) {
+
throw ConstraintViolation::error('You should pass a plain token', $this);
+
}
+
+
$claims = $token->claims();
+
+
if (! $claims->has($this->claim)) {
+
throw ConstraintViolation::error('The token does not have the claim "' . $this->claim . '"', $this);
+
}
+
+
if ($claims->get($this->claim) !== $this->expectedValue) {
+
throw ConstraintViolation::error(
+
'The claim "' . $this->claim . '" does not have the expected value',
+
$this,
+
);
+
}
+
}
+
}
+26
vendor/lcobucci/jwt/src/Validation/Constraint/IdentifiedBy.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\Validation\Constraint;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
+
final class IdentifiedBy implements Constraint
+
{
+
/** @param non-empty-string $id */
+
public function __construct(private readonly string $id)
+
{
+
}
+
+
public function assert(Token $token): void
+
{
+
if (! $token->isIdentifiedBy($this->id)) {
+
throw ConstraintViolation::error(
+
'The token is not identified with the expected ID',
+
$this,
+
);
+
}
+
}
+
}
+30
vendor/lcobucci/jwt/src/Validation/Constraint/IssuedBy.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\Validation\Constraint;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
+
final class IssuedBy implements Constraint
+
{
+
/** @var non-empty-string[] */
+
private readonly array $issuers;
+
+
/** @param non-empty-string ...$issuers */
+
public function __construct(string ...$issuers)
+
{
+
$this->issuers = $issuers;
+
}
+
+
public function assert(Token $token): void
+
{
+
if (! $token->hasBeenIssuedBy(...$this->issuers)) {
+
throw ConstraintViolation::error(
+
'The token was not issued by the given issuers',
+
$this,
+
);
+
}
+
}
+
}
+15
vendor/lcobucci/jwt/src/Validation/Constraint/LeewayCannotBeNegative.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use InvalidArgumentException;
+
use Lcobucci\JWT\Exception;
+
+
final class LeewayCannotBeNegative extends InvalidArgumentException implements Exception
+
{
+
public static function create(): self
+
{
+
return new self('Leeway cannot be negative');
+
}
+
}
+67
vendor/lcobucci/jwt/src/Validation/Constraint/LooseValidAt.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use DateInterval;
+
use DateTimeInterface;
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
use Lcobucci\JWT\Validation\ValidAt as ValidAtInterface;
+
use Psr\Clock\ClockInterface as Clock;
+
+
final class LooseValidAt implements ValidAtInterface
+
{
+
private readonly DateInterval $leeway;
+
+
public function __construct(private readonly Clock $clock, ?DateInterval $leeway = null)
+
{
+
$this->leeway = $this->guardLeeway($leeway);
+
}
+
+
private function guardLeeway(?DateInterval $leeway): DateInterval
+
{
+
if ($leeway === null) {
+
return new DateInterval('PT0S');
+
}
+
+
if ($leeway->invert === 1) {
+
throw LeewayCannotBeNegative::create();
+
}
+
+
return $leeway;
+
}
+
+
public function assert(Token $token): void
+
{
+
$now = $this->clock->now();
+
+
$this->assertIssueTime($token, $now->add($this->leeway));
+
$this->assertMinimumTime($token, $now->add($this->leeway));
+
$this->assertExpiration($token, $now->sub($this->leeway));
+
}
+
+
/** @throws ConstraintViolation */
+
private function assertExpiration(Token $token, DateTimeInterface $now): void
+
{
+
if ($token->isExpired($now)) {
+
throw ConstraintViolation::error('The token is expired', $this);
+
}
+
}
+
+
/** @throws ConstraintViolation */
+
private function assertMinimumTime(Token $token, DateTimeInterface $now): void
+
{
+
if (! $token->isMinimumTimeBefore($now)) {
+
throw ConstraintViolation::error('The token cannot be used yet', $this);
+
}
+
}
+
+
/** @throws ConstraintViolation */
+
private function assertIssueTime(Token $token, DateTimeInterface $now): void
+
{
+
if (! $token->hasBeenIssuedBefore($now)) {
+
throw ConstraintViolation::error('The token was issued in the future', $this);
+
}
+
}
+
}
+26
vendor/lcobucci/jwt/src/Validation/Constraint/PermittedFor.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\Validation\Constraint;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
+
final class PermittedFor implements Constraint
+
{
+
/** @param non-empty-string $audience */
+
public function __construct(private readonly string $audience)
+
{
+
}
+
+
public function assert(Token $token): void
+
{
+
if (! $token->isPermittedFor($this->audience)) {
+
throw ConstraintViolation::error(
+
'The token is not allowed to be used by this audience',
+
$this,
+
);
+
}
+
}
+
}
+26
vendor/lcobucci/jwt/src/Validation/Constraint/RelatedTo.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\Validation\Constraint;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
+
final class RelatedTo implements Constraint
+
{
+
/** @param non-empty-string $subject */
+
public function __construct(private readonly string $subject)
+
{
+
}
+
+
public function assert(Token $token): void
+
{
+
if (! $token->isRelatedTo($this->subject)) {
+
throw ConstraintViolation::error(
+
'The token is not related to the expected subject',
+
$this,
+
);
+
}
+
}
+
}
+32
vendor/lcobucci/jwt/src/Validation/Constraint/SignedWith.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use Lcobucci\JWT\Signer;
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\UnencryptedToken;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
use Lcobucci\JWT\Validation\SignedWith as SignedWithInterface;
+
+
final class SignedWith implements SignedWithInterface
+
{
+
public function __construct(private readonly Signer $signer, private readonly Signer\Key $key)
+
{
+
}
+
+
public function assert(Token $token): void
+
{
+
if (! $token instanceof UnencryptedToken) {
+
throw ConstraintViolation::error('You should pass a plain token', $this);
+
}
+
+
if ($token->headers()->get('alg') !== $this->signer->algorithmId()) {
+
throw ConstraintViolation::error('Token signer mismatch', $this);
+
}
+
+
if (! $this->signer->verify($token->signature()->hash(), $token->payload(), $this->key)) {
+
throw ConstraintViolation::error('Token signature mismatch', $this);
+
}
+
}
+
}
+38
vendor/lcobucci/jwt/src/Validation/Constraint/SignedWithOneInSet.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
use Lcobucci\JWT\Validation\SignedWith as SignedWithInterface;
+
+
use const PHP_EOL;
+
+
final class SignedWithOneInSet implements SignedWithInterface
+
{
+
/** @var array<SignedWithUntilDate> */
+
private readonly array $constraints;
+
+
public function __construct(SignedWithUntilDate ...$constraints)
+
{
+
$this->constraints = $constraints;
+
}
+
+
public function assert(Token $token): void
+
{
+
$errorMessage = 'It was not possible to verify the signature of the token, reasons:';
+
+
foreach ($this->constraints as $constraint) {
+
try {
+
$constraint->assert($token);
+
+
return;
+
} catch (ConstraintViolation $violation) {
+
$errorMessage .= PHP_EOL . '- ' . $violation->getMessage();
+
}
+
}
+
+
throw ConstraintViolation::error($errorMessage, $this);
+
}
+
}
+47
vendor/lcobucci/jwt/src/Validation/Constraint/SignedWithUntilDate.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use DateTimeImmutable;
+
use DateTimeInterface;
+
use Lcobucci\JWT\Signer;
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
use Lcobucci\JWT\Validation\SignedWith as SignedWithInterface;
+
use Psr\Clock\ClockInterface;
+
+
final class SignedWithUntilDate implements SignedWithInterface
+
{
+
private readonly SignedWith $verifySignature;
+
private readonly ClockInterface $clock;
+
+
public function __construct(
+
Signer $signer,
+
Signer\Key $key,
+
private readonly DateTimeImmutable $validUntil,
+
?ClockInterface $clock = null,
+
) {
+
$this->verifySignature = new SignedWith($signer, $key);
+
+
$this->clock = $clock ?? new class () implements ClockInterface {
+
public function now(): DateTimeImmutable
+
{
+
return new DateTimeImmutable();
+
}
+
};
+
}
+
+
public function assert(Token $token): void
+
{
+
if ($this->validUntil < $this->clock->now()) {
+
throw ConstraintViolation::error(
+
'This constraint was only usable until '
+
. $this->validUntil->format(DateTimeInterface::RFC3339),
+
$this,
+
);
+
}
+
+
$this->verifySignature->assert($token);
+
}
+
}
+84
vendor/lcobucci/jwt/src/Validation/Constraint/StrictValidAt.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation\Constraint;
+
+
use DateInterval;
+
use DateTimeInterface;
+
use Lcobucci\JWT\Token;
+
use Lcobucci\JWT\UnencryptedToken;
+
use Lcobucci\JWT\Validation\ConstraintViolation;
+
use Lcobucci\JWT\Validation\ValidAt as ValidAtInterface;
+
use Psr\Clock\ClockInterface as Clock;
+
+
final class StrictValidAt implements ValidAtInterface
+
{
+
private readonly DateInterval $leeway;
+
+
public function __construct(private readonly Clock $clock, ?DateInterval $leeway = null)
+
{
+
$this->leeway = $this->guardLeeway($leeway);
+
}
+
+
private function guardLeeway(?DateInterval $leeway): DateInterval
+
{
+
if ($leeway === null) {
+
return new DateInterval('PT0S');
+
}
+
+
if ($leeway->invert === 1) {
+
throw LeewayCannotBeNegative::create();
+
}
+
+
return $leeway;
+
}
+
+
public function assert(Token $token): void
+
{
+
if (! $token instanceof UnencryptedToken) {
+
throw ConstraintViolation::error('You should pass a plain token', $this);
+
}
+
+
$now = $this->clock->now();
+
+
$this->assertIssueTime($token, $now->add($this->leeway));
+
$this->assertMinimumTime($token, $now->add($this->leeway));
+
$this->assertExpiration($token, $now->sub($this->leeway));
+
}
+
+
/** @throws ConstraintViolation */
+
private function assertExpiration(UnencryptedToken $token, DateTimeInterface $now): void
+
{
+
if (! $token->claims()->has(Token\RegisteredClaims::EXPIRATION_TIME)) {
+
throw ConstraintViolation::error('"Expiration Time" claim missing', $this);
+
}
+
+
if ($token->isExpired($now)) {
+
throw ConstraintViolation::error('The token is expired', $this);
+
}
+
}
+
+
/** @throws ConstraintViolation */
+
private function assertMinimumTime(UnencryptedToken $token, DateTimeInterface $now): void
+
{
+
if (! $token->claims()->has(Token\RegisteredClaims::NOT_BEFORE)) {
+
throw ConstraintViolation::error('"Not Before" claim missing', $this);
+
}
+
+
if (! $token->isMinimumTimeBefore($now)) {
+
throw ConstraintViolation::error('The token cannot be used yet', $this);
+
}
+
}
+
+
/** @throws ConstraintViolation */
+
private function assertIssueTime(UnencryptedToken $token, DateTimeInterface $now): void
+
{
+
if (! $token->claims()->has(Token\RegisteredClaims::ISSUED_AT)) {
+
throw ConstraintViolation::error('"Issued At" claim missing', $this);
+
}
+
+
if (! $token->hasBeenIssuedBefore($now)) {
+
throw ConstraintViolation::error('The token was issued in the future', $this);
+
}
+
}
+
}
+12
vendor/lcobucci/jwt/src/Validation/Constraint.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation;
+
+
use Lcobucci\JWT\Token;
+
+
interface Constraint
+
{
+
/** @throws ConstraintViolation */
+
public function assert(Token $token): void;
+
}
+24
vendor/lcobucci/jwt/src/Validation/ConstraintViolation.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation;
+
+
use Lcobucci\JWT\Exception;
+
use RuntimeException;
+
+
final class ConstraintViolation extends RuntimeException implements Exception
+
{
+
/** @param class-string<Constraint>|null $constraint */
+
public function __construct(
+
string $message = '',
+
public readonly ?string $constraint = null,
+
) {
+
parent::__construct($message);
+
}
+
+
/** @param non-empty-string $message */
+
public static function error(string $message, Constraint $constraint): self
+
{
+
return new self(message: $message, constraint: $constraint::class);
+
}
+
}
+11
vendor/lcobucci/jwt/src/Validation/NoConstraintsGiven.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation;
+
+
use Lcobucci\JWT\Exception;
+
use RuntimeException;
+
+
final class NoConstraintsGiven extends RuntimeException implements Exception
+
{
+
}
+48
vendor/lcobucci/jwt/src/Validation/RequiredConstraintsViolated.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation;
+
+
use Lcobucci\JWT\Exception;
+
use RuntimeException;
+
+
use function array_map;
+
use function implode;
+
+
final class RequiredConstraintsViolated extends RuntimeException implements Exception
+
{
+
/** @param ConstraintViolation[] $violations */
+
public function __construct(
+
string $message = '',
+
public readonly array $violations = [],
+
) {
+
parent::__construct($message);
+
}
+
+
public static function fromViolations(ConstraintViolation ...$violations): self
+
{
+
return new self(message: self::buildMessage($violations), violations: $violations);
+
}
+
+
/** @param ConstraintViolation[] $violations */
+
private static function buildMessage(array $violations): string
+
{
+
$violations = array_map(
+
static function (ConstraintViolation $violation): string {
+
return '- ' . $violation->getMessage();
+
},
+
$violations,
+
);
+
+
$message = "The token violates some mandatory constraints, details:\n";
+
$message .= implode("\n", $violations);
+
+
return $message;
+
}
+
+
/** @return ConstraintViolation[] */
+
public function violations(): array
+
{
+
return $this->violations;
+
}
+
}
+8
vendor/lcobucci/jwt/src/Validation/SignedWith.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation;
+
+
interface SignedWith extends Constraint
+
{
+
}
+8
vendor/lcobucci/jwt/src/Validation/ValidAt.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation;
+
+
interface ValidAt extends Constraint
+
{
+
}
+56
vendor/lcobucci/jwt/src/Validation/Validator.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT\Validation;
+
+
use Lcobucci\JWT\Token;
+
+
final class Validator implements \Lcobucci\JWT\Validator
+
{
+
public function assert(Token $token, Constraint ...$constraints): void
+
{
+
if ($constraints === []) {
+
throw new NoConstraintsGiven('No constraint given.');
+
}
+
+
$violations = [];
+
+
foreach ($constraints as $constraint) {
+
$this->checkConstraint($constraint, $token, $violations);
+
}
+
+
if ($violations) {
+
throw RequiredConstraintsViolated::fromViolations(...$violations);
+
}
+
}
+
+
/** @param ConstraintViolation[] $violations */
+
private function checkConstraint(
+
Constraint $constraint,
+
Token $token,
+
array &$violations,
+
): void {
+
try {
+
$constraint->assert($token);
+
} catch (ConstraintViolation $e) {
+
$violations[] = $e;
+
}
+
}
+
+
public function validate(Token $token, Constraint ...$constraints): bool
+
{
+
if ($constraints === []) {
+
throw new NoConstraintsGiven('No constraint given.');
+
}
+
+
try {
+
foreach ($constraints as $constraint) {
+
$constraint->assert($token);
+
}
+
+
return true;
+
} catch (ConstraintViolation) {
+
return false;
+
}
+
}
+
}
+20
vendor/lcobucci/jwt/src/Validator.php
···
+
<?php
+
declare(strict_types=1);
+
+
namespace Lcobucci\JWT;
+
+
use Lcobucci\JWT\Validation\Constraint;
+
use Lcobucci\JWT\Validation\NoConstraintsGiven;
+
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
+
+
interface Validator
+
{
+
/**
+
* @throws RequiredConstraintsViolated
+
* @throws NoConstraintsGiven
+
*/
+
public function assert(Token $token, Constraint ...$constraints): void;
+
+
/** @throws NoConstraintsGiven */
+
public function validate(Token $token, Constraint ...$constraints): bool;
+
}
+11
vendor/psr/clock/CHANGELOG.md
···
+
# Changelog
+
+
All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+
## 1.0.0
+
+
First stable release after PSR-20 acceptance
+
+
## 0.1.0
+
+
First release
+19
vendor/psr/clock/LICENSE
···
+
Copyright (c) 2017 PHP Framework Interoperability Group
+
+
Permission is hereby granted, free of charge, to any person obtaining a copy
+
of this software and associated documentation files (the "Software"), to deal
+
in the Software without restriction, including without limitation the rights
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+
copies of the Software, and to permit persons to whom the Software is
+
furnished to do so, subject to the following conditions:
+
+
The above copyright notice and this permission notice shall be included in
+
all copies or substantial portions of the Software.
+
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+
THE SOFTWARE.
+61
vendor/psr/clock/README.md
···
+
# PSR Clock
+
+
This repository holds the interface for [PSR-20][psr-url].
+
+
Note that this is not a clock of its own. It is merely an interface that
+
describes a clock. See the specification for more details.
+
+
## Installation
+
+
```bash
+
composer require psr/clock
+
```
+
+
## Usage
+
+
If you need a clock, you can use the interface like this:
+
+
```php
+
<?php
+
+
use Psr\Clock\ClockInterface;
+
+
class Foo
+
{
+
private ClockInterface $clock;
+
+
public function __construct(ClockInterface $clock)
+
{
+
$this->clock = $clock;
+
}
+
+
public function doSomething()
+
{
+
/** @var DateTimeImmutable $currentDateAndTime */
+
$currentDateAndTime = $this->clock->now();
+
// do something useful with that information
+
}
+
}
+
```
+
+
You can then pick one of the [implementations][implementation-url] of the interface to get a clock.
+
+
If you want to implement the interface, you can require this package and
+
implement `Psr\Clock\ClockInterface` in your code.
+
+
Don't forget to add `psr/clock-implementation` to your `composer.json`s `provides`-section like this:
+
+
```json
+
{
+
"provides": {
+
"psr/clock-implementation": "1.0"
+
}
+
}
+
```
+
+
And please read the [specification text][specification-url] for details on the interface.
+
+
[psr-url]: https://www.php-fig.org/psr/psr-20
+
[package-url]: https://packagist.org/packages/psr/clock
+
[implementation-url]: https://packagist.org/providers/psr/clock-implementation
+
[specification-url]: https://github.com/php-fig/fig-standards/blob/master/proposed/clock.md
+21
vendor/psr/clock/composer.json
···
+
{
+
"name": "psr/clock",
+
"description": "Common interface for reading the clock.",
+
"keywords": ["psr", "psr-20", "time", "clock", "now"],
+
"homepage": "https://github.com/php-fig/clock",
+
"license": "MIT",
+
"authors": [
+
{
+
"name": "PHP-FIG",
+
"homepage": "https://www.php-fig.org/"
+
}
+
],
+
"require": {
+
"php": "^7.0 || ^8.0"
+
},
+
"autoload": {
+
"psr-4": {
+
"Psr\\Clock\\": "src/"
+
}
+
}
+
}
+13
vendor/psr/clock/src/ClockInterface.php
···
+
<?php
+
+
namespace Psr\Clock;
+
+
use DateTimeImmutable;
+
+
interface ClockInterface
+
{
+
/**
+
* Returns the current time as a DateTimeImmutable Object
+
*/
+
public function now(): DateTimeImmutable;
+
}
+4
vite.config.js
···
usePHP({
entry: [
'index.php',
+
'config.php',
+
'.env',
+
'client-metadata.json',
+
'jwks.json',
'oauth/*',
'oauth/**/*.json',
'templates/**/*.latte',