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