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?>