friendship ended with social-app. php is my new best friend
at main 3.3 kB view raw
1<?php 2namespace Smallnest\Bsky; 3 4require_once('config.php'); 5require_once('vendor/autoload.php'); 6 7use chillerlan\OAuth\Core\OAuth2Interface; 8use chillerlan\OAuth\Core\OAuth2Provider; 9use chillerlan\OAuth\Core\PKCETrait; 10use chillerlan\OAuth\OAuthOptions; 11use chillerlan\OAuth\Storage\SessionStorage; 12use chillerlan\HTTP\Utils\MessageUtil; 13use chillerlan\HTTP\Utils\QueryUtil; 14use chillerlan\OAuth\Providers\ProviderException; 15use GuzzleHttp\Client; 16use GuzzleHttp\Psr7\HttpFactory; 17use Psr\Http\Message\UriInterface; 18use function sprintf; 19 20class BskyProvider extends OAuth2Provider implements \chillerlan\OAuth\Core\PAR, \chillerlan\OAuth\Core\PKCE { 21 use \chillerlan\OAuth\Core\PKCETrait; 22 23 public const IDENTIFIER = 'BSKYPROVIDER'; 24 public const SCOPE_ATPROTO = 'atproto'; 25 public const SCOPE_TRANSITION_GENERIC = 'transition:generic'; 26 public const AUTH_METHOD = self::AUTH_METHOD_HEADER; 27 28 protected string $authorizationURL = 'https://bsky.app/oauth/authorize'; 29 protected string $accessTokenURL = 'https://bsky.app/oauth/token'; 30 protected string $apiURL = 'https://bsky.app/xrpc'; 31 protected string $parAuthorizationURL = 'https://bsky.app/oauth/par'; 32 33 public const DEFAULT_SCOPES = [ 34 self::SCOPE_ATPROTO, 35 self::SCOPE_TRANSITION_GENERIC, 36 ]; 37 38 public function setPds(UriInterface|string $pds):static{ 39 if(!$pds instanceof UriInterface){ 40 $pds = $this->uriFactory->createUri($pds); 41 } 42 43 // throw if the host is empty 44 if($pds->getHost() === ''){ 45 throw new OAuthException('invalid PDS URL'); 46 } 47 48 // enforce https and remove unnecessary parts 49 $pds = $pds->withScheme('https')->withQuery('')->withFragment(''); 50 51 // set the provider URLs 52 $this->authorizationURL = (string)$pds->withPath('/oauth/authorize'); 53 $this->accessTokenURL = (string)$pds->withPath('/oauth/token'); 54 $this->apiURL = (string)$pds->withPath('/xrpc'); 55 $this->parAuthorizationURL = (string)$pds->withPath('/oauth/par'); 56 57 return $this; 58 } 59 60 public function getParRequestUri(array $body):UriInterface{ 61 // send the request with the same method and parameters as the token requests 62 // @link https://datatracker.ietf.org/doc/html/rfc9126#name-request 63 $response = $this->sendAccessTokenRequest($this->parAuthorizationURL, $body); 64 $status = $response->getStatusCode(); 65 $json = MessageUtil::decodeJSON($response, true); 66 67 // something went horribly wrong 68 if($status !== 201){ 69 70 // @link https://datatracker.ietf.org/doc/html/rfc9126#section-2.3 71 if(isset($json['error'], $json['error_description'])){ 72 throw new ProviderException(sprintf('PAR error: "%s" (%s)', $json['error'], $json['error_description'])); 73 } 74 75 throw new ProviderException(sprintf('PAR request error: (HTTP/%s)', $status)); // @codeCoverageIgnore 76 } 77 78 $url = QueryUtil::merge($this->authorizationURL, $this->getParAuthorizationURLRequestParams($json)); 79 80 return $this->uriFactory->createUri($url); 81 } 82 83 protected function getParAuthorizationURLRequestParams(array $response):array{ 84 85 if(!isset($response['request_uri'])){ 86 throw new ProviderException('PAR response error: "request_uri" missing'); 87 } 88 89 return [ 90 'client_id' => $this->options->key, 91 'request_uri' => $response['request_uri'], 92 ]; 93 } 94} 95?>