2021-06-06 01:18:44 +02:00
< ? php declare ( strict_types = 1 );
namespace Taproot\IndieAuth ;
use Exception ;
2021-06-06 14:47:05 +02:00
use IndieAuth\Client as IndieAuthClient ;
use Mf2 ;
use BarnabyWalters\Mf2 as M ;
use GuzzleHttp\Psr7\Header as HeaderParser ;
2021-06-06 01:18:44 +02:00
use Nyholm\Psr7\Response ;
2021-06-08 00:58:19 +02:00
use Psr\Http\Client\ClientExceptionInterface ;
2021-06-06 14:47:05 +02:00
use Psr\Http\Client\NetworkExceptionInterface ;
use Psr\Http\Client\RequestExceptionInterface ;
2021-06-06 01:18:44 +02:00
use Psr\Http\Message\ResponseInterface ;
2021-06-06 14:47:05 +02:00
use Psr\Http\Message\ServerRequestInterface ;
use Psr\Http\Server\MiddlewareInterface ;
2021-06-06 01:18:44 +02:00
use Psr\Log\LoggerInterface ;
use Psr\Log\NullLogger ;
2021-06-07 20:32:02 +02:00
use Taproot\IndieAuth\Callback\AuthorizationFormInterface ;
use Taproot\IndieAuth\Callback\DefaultAuthorizationForm ;
2021-06-06 01:18:44 +02:00
/**
* Development Reference
2021-06-09 21:56:16 +02:00
*
2021-06-06 01:18:44 +02:00
* Specification : https :// indieauth . spec . indieweb . org /
* Error responses : https :// www . rfc - editor . org / rfc / rfc6749 . html #section-5.2
* indieweb / indieauth - client : https :// github . com / indieweb / indieauth - client - php
2021-06-09 21:56:16 +02:00
* Existing implementation with various validation functions and links to relevant spec portions : https :// github . com / Zegnat / php - mindee / blob / development / index . php
2021-06-06 01:18:44 +02:00
*/
class Server {
2021-06-07 20:32:02 +02:00
const HANDLE_NON_INDIEAUTH_REQUEST = 'handleNonIndieAuthRequestCallback' ;
const HANDLE_AUTHENTICATION_REQUEST = 'handleAuthenticationRequestCallback' ;
const HASH_QUERY_STRING_KEY = 'taproot_indieauth_server_hash' ;
2021-06-06 01:18:44 +02:00
const DEFAULT_CSRF_KEY = 'taproot_indieauth_server_csrf' ;
2021-06-09 00:06:35 +02:00
const APPROVE_ACTION_KEY = 'taproot_indieauth_action' ;
const APPROVE_ACTION_VALUE = 'approve' ;
2021-06-06 01:18:44 +02:00
2021-06-09 00:06:35 +02:00
protected Storage\TokenStorageInterface $authorizationCodeStorage ;
2021-06-06 01:18:44 +02:00
2021-06-09 00:06:35 +02:00
protected Storage\TokenStorageInterface $accessTokenStorage ;
2021-06-06 01:18:44 +02:00
2021-06-09 00:06:35 +02:00
protected AuthorizationFormInterface $authorizationForm ;
2021-06-06 01:18:44 +02:00
2021-06-09 00:06:35 +02:00
protected MiddlewareInterface $csrfMiddleware ;
2021-06-07 20:32:02 +02:00
2021-06-09 00:06:35 +02:00
protected LoggerInterface $logger ;
2021-06-06 01:18:44 +02:00
2021-06-09 00:06:35 +02:00
protected $httpGetWithEffectiveUrl ;
2021-06-06 01:18:44 +02:00
2021-06-09 00:06:35 +02:00
protected $handleAuthenticationRequestCallback ;
2021-06-06 14:47:05 +02:00
2021-06-09 00:06:35 +02:00
protected $handleNonIndieAuthRequest ;
2021-06-07 20:32:02 +02:00
2021-06-09 00:06:35 +02:00
protected string $exceptionTemplatePath ;
2021-06-07 20:32:02 +02:00
protected string $secret ;
2021-06-06 01:18:44 +02:00
2021-06-06 14:47:05 +02:00
public function __construct ( array $config ) {
2021-06-07 20:32:02 +02:00
$config = array_merge ([
2021-06-06 14:47:05 +02:00
'csrfMiddleware' => null ,
'logger' => null ,
2021-06-07 20:32:02 +02:00
self :: HANDLE_NON_INDIEAUTH_REQUEST => function ( ServerRequestInterface $request ) { return null ; }, // Default to no-op.
2021-06-06 14:47:05 +02:00
'authorizationCodeStorage' => null ,
'accessTokenStorage' => null ,
2021-06-07 20:32:02 +02:00
'httpGetWithEffectiveUrl' => null ,
'authorizationForm' => new DefaultAuthorizationForm (),
2021-06-09 00:26:27 +02:00
'exceptionTemplatePath' => __DIR__ . '/../templates/default_exception_response.html.php' ,
2021-06-06 14:47:05 +02:00
], $config );
2021-06-09 00:06:35 +02:00
if ( ! is_string ( $config [ 'exceptionTemplatePath' ])) {
throw new Exception ( " \$ config['secret'] must be a string (path). " );
}
$this -> exceptionTemplatePath = $config [ 'exceptionTemplatePath' ];
2021-06-07 20:32:02 +02:00
$secret = $config [ 'secret' ] ? ? '' ;
if ( ! is_string ( $secret ) || strlen ( $secret ) < 64 ) {
throw new Exception ( " \$ config['secret'] must be a string with a minimum length of 64 characters. " );
}
$this -> secret = $secret ;
if ( ! is_null ( $config [ 'logger' ]) && ! $config [ 'logger' ] instanceof LoggerInterface ) {
2021-06-06 14:47:05 +02:00
throw new Exception ( " \$ config['logger'] must be an instance of \\ Psr \\ Log \\ LoggerInterface or null. " );
}
$this -> logger = $config [ 'logger' ] ? ? new NullLogger ();
2021-06-06 01:18:44 +02:00
2021-06-07 20:32:02 +02:00
if ( ! ( array_key_exists ( self :: HANDLE_AUTHENTICATION_REQUEST , $config ) and is_callable ( $config [ self :: HANDLE_AUTHENTICATION_REQUEST ]))) {
2021-06-06 01:18:44 +02:00
throw new Exception ( '$callbacks[\'' . self :: HANDLE_AUTHENTICATION_REQUEST . '\'] must be present and callable.' );
}
2021-06-07 20:32:02 +02:00
$this -> handleAuthenticationRequestCallback = $config [ self :: HANDLE_AUTHENTICATION_REQUEST ];
2021-06-06 14:47:05 +02:00
2021-06-07 20:32:02 +02:00
if ( ! is_callable ( $config [ self :: HANDLE_NON_INDIEAUTH_REQUEST ])) {
throw new Exception ( " \$ config[' " . self :: HANDLE_NON_INDIEAUTH_REQUEST . " '] must be callable " );
}
$this -> handleNonIndieAuthRequest = $config [ self :: HANDLE_NON_INDIEAUTH_REQUEST ];
2021-06-06 14:47:05 +02:00
$authorizationCodeStorage = $config [ 'authorizationCodeStorage' ];
2021-06-06 15:13:13 +02:00
if ( ! $authorizationCodeStorage instanceof Storage\TokenStorageInterface ) {
2021-06-06 01:18:44 +02:00
if ( is_string ( $authorizationCodeStorage )) {
2021-06-06 15:13:13 +02:00
$authorizationCodeStorage = new Storage\FilesystemJsonStorage ( $authorizationCodeStorage , 600 , true );
2021-06-06 01:18:44 +02:00
} else {
2021-06-07 20:32:02 +02:00
throw new Exception ( " \$ config['authorizationCodeStorage'] must be either a string (path) or an instance of Taproot \ IndieAuth \T okenStorageInterface. " );
2021-06-06 01:18:44 +02:00
}
}
2021-06-06 14:47:05 +02:00
trySetLogger ( $authorizationCodeStorage , $this -> logger );
2021-06-06 01:18:44 +02:00
$this -> authorizationCodeStorage = $authorizationCodeStorage ;
2021-06-06 14:47:05 +02:00
$accessTokenStorage = $config [ 'accessTokenStorage' ];
2021-06-06 15:13:13 +02:00
if ( ! $accessTokenStorage instanceof Storage\TokenStorageInterface ) {
2021-06-06 01:18:44 +02:00
if ( is_string ( $accessTokenStorage )) {
// Create a default access token storage with a TTL of 7 days.
2021-06-06 15:13:13 +02:00
$accessTokenStorage = new Storage\FilesystemJsonStorage ( $accessTokenStorage , 60 * 60 * 24 * 7 , true );
2021-06-06 01:18:44 +02:00
} else {
throw new Exception ( '$accessTokenStorage parameter must be either a string (path) or an instance of Taproot\IndieAuth\TokenStorageInterface.' );
}
}
2021-06-06 14:47:05 +02:00
trySetLogger ( $accessTokenStorage , $this -> logger );
2021-06-06 01:18:44 +02:00
$this -> accessTokenStorage = $accessTokenStorage ;
2021-06-06 14:47:05 +02:00
$csrfMiddleware = $config [ 'csrfMiddleware' ];
2021-06-06 01:18:44 +02:00
if ( ! $csrfMiddleware instanceof MiddlewareInterface ) {
// Default to the statless Double-Submit Cookie CSRF Middleware, with default settings.
2021-06-07 20:32:02 +02:00
$csrfMiddleware = new Middleware\DoubleSubmitCookieCsrfMiddleware ( self :: DEFAULT_CSRF_KEY );
2021-06-06 01:18:44 +02:00
}
2021-06-06 14:47:05 +02:00
trySetLogger ( $csrfMiddleware , $this -> logger );
2021-06-06 01:18:44 +02:00
$this -> csrfMiddleware = $csrfMiddleware ;
2021-06-06 14:47:05 +02:00
$httpGetWithEffectiveUrl = $config [ 'httpGetWithEffectiveUrl' ];
if ( ! is_callable ( $httpGetWithEffectiveUrl )) {
if ( class_exists ( '\GuzzleHttp\Client' )) {
$httpGetWithEffectiveUrl = function ( string $uri ) {
2021-06-09 00:21:33 +02:00
// This code can’ t be tested, ignore it for coverage purposes.
// @codeCoverageIgnoreStart
2021-06-06 14:47:05 +02:00
$resp = ( new \GuzzleHttp\Client ([
\GuzzleHttp\RequestOptions :: ALLOW_REDIRECTS => [
'max' => 10 ,
'strict' => true ,
'referer' => true ,
'track_redirects' => true
]
])) -> get ( $uri );
$rdh = $resp -> getHeader ( 'X-Guzzle-Redirect-History' );
$effectiveUrl = empty ( $rdh ) ? $uri : array_values ( $rdh )[ count ( $rdh ) - 1 ];
return [ $resp , $effectiveUrl ];
2021-06-09 00:21:33 +02:00
// @codeCoverageIgnoreEnd
2021-06-06 14:47:05 +02:00
};
} else {
throw new Exception ( 'No valid $httpGetWithEffectiveUrl was provided, and guzzlehttp/guzzle was not installed. Either require guzzlehttp/guzzle, or provide a valid callable.' );
}
}
trySetLogger ( $httpGetWithEffectiveUrl , $this -> logger );
$this -> httpGetWithEffectiveUrl = $httpGetWithEffectiveUrl ;
2021-06-07 20:32:02 +02:00
if ( ! $config [ 'authorizationForm' ] instanceof AuthorizationFormInterface ) {
throw new Exception ( " When provided, \$ config['authorizationForm'] must implement Taproot \ IndieAuth \ Callback \ AuthorizationForm. " );
}
$this -> authorizationForm = $config [ 'authorizationForm' ];
trySetLogger ( $this -> authorizationForm , $this -> logger );
2021-06-06 01:18:44 +02:00
}
2021-06-09 00:06:35 +02:00
/**
* Handle Authorization Endpoint Request
*
* @ param ServerRequestInterface $request
* @ return ResponseInterface
*/
2021-06-06 01:18:44 +02:00
public function handleAuthorizationEndpointRequest ( ServerRequestInterface $request ) : ResponseInterface {
2021-06-06 14:47:05 +02:00
$this -> logger -> info ( 'Handling an IndieAuth Authorization Endpoint request.' );
2021-06-06 01:18:44 +02:00
// If it’ s a profile information request:
if ( isIndieAuthAuthorizationCodeRedeemingRequest ( $request )) {
2021-06-06 14:47:05 +02:00
$this -> logger -> info ( 'Handling a request to redeem an authorization code for profile information.' );
2021-06-06 01:18:44 +02:00
// Verify that the authorization code is valid and has not yet been used.
$this -> authorizationCodeStorage -> get ( $request -> getParsedBody ()[ 'code' ]);
// Verify that it was issued for the same client_id and redirect_uri
// Check that the supplied code_verifier hashes to the stored code_challenge
// If everything checked out, return {"me": "https://example.com"} response
// (a response containing any additional information must contain a valid scope value, and
// be handled by the token_endpoint).
// TODO: according to the spec, it is technically permitted for the authorization endpoint
// to additional provide profile information. Leave it up to the library consumer to decide
// whether to add it or not.
}
// Because the special case above isn’ t allowed to be CSRF-protected, we have to do some rather silly
2021-06-09 00:06:35 +02:00
// closure gymnastics here to selectively-CSRF-protect requests which do need it.
2021-06-06 15:13:13 +02:00
return $this -> csrfMiddleware -> process ( $request , new Middleware\ClosureRequestHandler ( function ( ServerRequestInterface $request ) {
2021-06-09 00:06:35 +02:00
// Wrap the entire user-facing handler in a try/catch block which catches any exception, converts it
// to IndieAuthException if necessary, then passes it to $this->handleException() to be turned into a
// response.
try {
// If this is an authorization or approval request (allowing POST requests as well to accommodate
// approval requests and custom auth form submission.
if ( isIndieAuthAuthorizationRequest ( $request , [ 'get' , 'post' ])) {
$this -> logger -> info ( 'Handling an authorization request' , [ 'method' => $request -> getMethod ()]);
$queryParams = $request -> getQueryParams ();
// Return an error if we’ re missing required parameters.
$requiredParameters = [ 'client_id' , 'redirect_uri' , 'state' , 'code_challenge' , 'code_challenge_method' ];
$missingRequiredParameters = array_filter ( $requiredParameters , function ( $p ) use ( $queryParams ) {
return ! array_key_exists ( $p , $queryParams ) || empty ( $queryParams [ $p ]);
});
if ( ! empty ( $missingRequiredParameters )) {
$this -> logger -> warning ( 'The authorization request was missing required parameters. Returning an error response.' , [ 'missing' => $missingRequiredParameters ]);
throw IndieAuthException :: create ( IndieAuthException :: REQUEST_MISSING_PARAMETER , $request );
2021-06-06 14:47:05 +02:00
}
2021-06-06 01:18:44 +02:00
2021-06-09 00:06:35 +02:00
// Normalise the me parameter, if it exists.
if ( array_key_exists ( 'me' , $queryParams )) {
$queryParams [ 'me' ] = IndieAuthClient :: normalizeMeURL ( $queryParams [ 'me' ]);
2021-06-06 14:47:05 +02:00
}
2021-06-09 00:06:35 +02:00
// Build a URL containing the indieauth authorization request parameters, hashing them
// to protect them from being changed.
// Make a hash of the protected indieauth-specific parameters.
$hash = hashAuthorizationRequestParameters ( $request , $this -> secret );
// Operate on a copy of $queryParams, otherwise requests will always have a valid hash!
$redirectQueryParams = $queryParams ;
$redirectQueryParams [ self :: HASH_QUERY_STRING_KEY ] = $hash ;
$authenticationRedirect = $request -> getUri () -> withQuery ( buildQueryString ( $redirectQueryParams )) -> __toString ();
2021-06-06 14:47:05 +02:00
2021-06-09 00:06:35 +02:00
// User-facing requests always start by calling the authentication request callback.
$this -> logger -> info ( 'Calling handle_authentication_request callback' );
$authenticationResult = call_user_func ( $this -> handleAuthenticationRequestCallback , $request , $authenticationRedirect );
// If the authentication handler returned a Response, return that as-is.
if ( $authenticationResult instanceof ResponseInterface ) {
return $authenticationResult ;
} elseif ( is_array ( $authenticationResult )) {
// Check the resulting array for errors.
if ( ! array_key_exists ( 'me' , $authenticationResult )) {
$this -> logger -> error ( 'The handle_authentication_request callback returned an array with no me key.' , [ 'array' => $authenticationResult ]);
throw IndieAuthException :: create ( IndieAuthException :: AUTHENTICATION_CALLBACK_MISSING_ME_PARAM , $request );
2021-06-06 14:47:05 +02:00
}
2021-06-09 00:06:35 +02:00
// If this is a POST request sent from the authorization (i.e. scope-choosing) form:
if ( isAuthorizationApprovalRequest ( $request )) {
// Authorization approval requests MUST include a hash protecting the sensitive IndieAuth
// authorization request parameters from being changed, e.g. by a malicious script which
// found its way onto the authorization form.
$expectedHash = hashAuthorizationRequestParameters ( $request , $this -> secret );
if ( is_null ( $expectedHash )) {
// In theory this code should never be reached, as we already checked the request for valid parameters.
// However, it’ s possible for hashAuthorizationRequestParameters() to return null, and if for whatever
// reason it does, the library should handle that case as elegantly as possible.
2021-06-09 00:21:33 +02:00
// @codeCoverageIgnoreStart
2021-06-09 00:06:35 +02:00
$this -> logger -> warning ( " Calculating the expected hash for an authorization approval request failed. This SHOULD NOT happen; if you encounter this error please contact the maintainers of taproot/indieauth. " );
throw IndieAuthException :: create ( IndieAuthException :: REQUEST_MISSING_PARAMETER , $request );
2021-06-09 00:21:33 +02:00
// @codeCoverageIgnoreEnd
2021-06-09 00:06:35 +02:00
}
if ( ! array_key_exists ( self :: HASH_QUERY_STRING_KEY , $queryParams )) {
$this -> logger -> warning ( " An authorization approval request did not have a " . self :: HASH_QUERY_STRING_KEY . " parameter. " );
throw IndieAuthException :: create ( IndieAuthException :: AUTHORIZATION_APPROVAL_REQUEST_MISSING_HASH , $request );
}
if ( ! hash_equals ( $expectedHash , $queryParams [ self :: HASH_QUERY_STRING_KEY ])) {
$this -> logger -> warning ( " The hash provided in the URL was invalid! " , [
'expected' => $expectedHash ,
'actual' => $queryParams [ self :: HASH_QUERY_STRING_KEY ]
]);
throw IndieAuthException :: create ( IndieAuthException :: AUTHORIZATION_APPROVAL_REQUEST_INVALID_HASH , $request );
}
// Assemble the data for the authorization code, store it somewhere persistent.
$code = array_merge ( $authenticationResult , [
'client_id' => $queryParams [ 'client_id' ],
'redirect_uri' => $queryParams [ 'redirect_uri' ],
'state' => $queryParams [ 'state' ],
'code_challenge' => $queryParams [ 'code_challenge' ],
'code_challenge_method' => $queryParams [ 'code_challenge_method' ],
'requested_scope' => $queryParams [ 'scope' ] ? ? '' ,
]);
2021-06-06 01:18:44 +02:00
2021-06-09 00:06:35 +02:00
// Pass it to the auth code customisation callback.
$code = $this -> authorizationForm -> transformAuthorizationCode ( $request , $code );
// Store the authorization code.
$success = $this -> authorizationCodeStorage -> put ( $code [ 'code' ], $code );
if ( ! $success ) {
// If saving the authorization code failed silently, there isn’ t much we can do about it,
// but should at least log and return an error.
$this -> logger -> error ( " Saving the authorization code failed and returned false without raising an exception. " );
throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_ERROR , $request );
}
// Return a redirect to the client app.
return new Response ( 302 , [ 'Location' => appendQueryParams ( $queryParams [ 'redirect_uri' ], [
'code' => $code [ 'code' ],
'state' => $code [ 'state' ]
])]);
2021-06-07 20:32:02 +02:00
}
2021-06-09 00:06:35 +02:00
// Otherwise, the user is authenticated and needs to authorize the client app + choose scopes.
// Fetch the client_id URL to find information about the client to present to the user.
try {
/** @var ResponseInterface $clientIdResponse */
list ( $clientIdResponse , $clientIdEffectiveUrl ) = call_user_func ( $this -> httpGetWithEffectiveUrl , $queryParams [ 'client_id' ]);
$clientIdMf2 = Mf2\parse (( string ) $clientIdResponse -> getBody (), $clientIdEffectiveUrl );
} catch ( ClientExceptionInterface | RequestExceptionInterface | NetworkExceptionInterface $e ) {
$this -> logger -> error ( " Caught an HTTP exception while trying to fetch the client_id. Returning an error response. " , [
'client_id' => $queryParams [ 'client_id' ],
'exception' => $e -> __toString ()
2021-06-07 20:32:02 +02:00
]);
2021-06-09 00:06:35 +02:00
throw IndieAuthException :: create ( IndieAuthException :: HTTP_EXCEPTION_FETCHING_CLIENT_ID , $request , $e );
} catch ( Exception $e ) {
$this -> logger -> error ( " Caught an unknown exception while trying to fetch the client_id. Returning an error response. " , [
'exception' => $e -> __toString ()
]);
throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_EXCEPTION_FETCHING_CLIENT_ID , $request , $e );
}
2021-06-07 20:32:02 +02:00
2021-06-09 00:06:35 +02:00
// Search for an h-app with u-url matching the client_id.
$clientHApps = M\findMicroformatsByProperty ( M\findMicroformatsByType ( $clientIdMf2 , 'h-app' ), 'url' , $queryParams [ 'client_id' ]);
$clientHApp = empty ( $clientHApps ) ? null : $clientHApps [ 0 ];
// Search for all link@rel=redirect_uri at the client_id.
$clientIdRedirectUris = [];
if ( array_key_exists ( 'redirect_uri' , $clientIdMf2 [ 'rels' ])) {
$clientIdRedirectUris = array_merge ( $clientIdRedirectUris , $clientIdMf2 [ 'rels' ][ 'redirect_uri' ]);
}
foreach ( HeaderParser :: parse ( $clientIdResponse -> getHeader ( 'Link' )) as $link ) {
if ( array_key_exists ( 'rel' , $link ) && mb_strpos ( " { $link [ 'rel' ] } " , " redirect_uri " ) !== false ) {
// Strip off the < > which surround the link URL for some reason.
$clientIdRedirectUris [] = substr ( $link [ 0 ], 1 , strlen ( $link [ 0 ]) - 2 );
}
}
// If the authority of the redirect_uri does not match the client_id, or exactly match one of their redirect URLs, return an error.
$clientIdMatchesRedirectUri = urlComponentsMatch ( $queryParams [ 'client_id' ], $queryParams [ 'redirect_uri' ], [ PHP_URL_SCHEME , PHP_URL_HOST , PHP_URL_PORT ]);
$redirectUriValid = $clientIdMatchesRedirectUri || in_array ( $queryParams [ 'redirect_uri' ], $clientIdRedirectUris );
2021-06-06 01:18:44 +02:00
2021-06-09 00:06:35 +02:00
if ( ! $redirectUriValid ) {
$this -> logger -> warning ( " The provided redirect_uri did not match either the client_id, nor the discovered redirect URIs. " , [
'provided_redirect_uri' => $queryParams [ 'redirect_uri' ],
'provided_client_id' => $queryParams [ 'client_id' ],
'discovered_redirect_uris' => $clientIdRedirectUris
]);
throw IndieAuthException :: create ( IndieAuthException :: INVALID_REDIRECT_URI , $request );
}
2021-06-06 01:18:44 +02:00
2021-06-09 00:06:35 +02:00
// Present the authorization UI.
return $this -> authorizationForm -> showForm ( $request , $authenticationResult , $authenticationRedirect , $clientHApp );
}
2021-06-06 01:18:44 +02:00
}
2021-06-09 00:06:35 +02:00
// If the request isn’ t an IndieAuth Authorization or Code-redeeming request, it’ s either an invalid
// request or something to do with a custom auth handler (e.g. sending a one-time code in an email.)
$nonIndieAuthRequestResult = call_user_func ( $this -> handleNonIndieAuthRequest , $request );
if ( $nonIndieAuthRequestResult instanceof ResponseInterface ) {
return $nonIndieAuthRequestResult ;
} else {
throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_ERROR , $request );
}
} catch ( IndieAuthException $e ) {
// All IndieAuthExceptions will already have been logged.
return $this -> handleException ( $e );
} catch ( Exception $e ) {
// Unknown exceptions will not have been logged; do so now.
$this -> logger -> error ( " Caught unknown exception: { $e } " );
return $this -> handleException ( IndieAuthException :: create ( 0 , $request , $e ));
2021-06-06 01:18:44 +02:00
}
2021-06-09 00:06:35 +02:00
}));
2021-06-06 01:18:44 +02:00
}
public function handleTokenEndpointRequest ( ServerRequestInterface $request ) : ResponseInterface {
// This is a request to redeem an authorization_code for an access_token.
// Verify that the authorization code is valid and has not yet been used.
// Verify that it was issued for the same client_id and redirect_uri
// Check that the supplied code_verifier hashes to the stored code_challenge
// If the auth code was issued with no scope, return an error.
// If everything checks out, generate an access token and return it.
}
2021-06-09 00:06:35 +02:00
protected function handleException ( IndieAuthException $exception ) : ResponseInterface {
return new Response ( $exception -> getStatusCode (), [ 'content-type' => 'text/html' ], renderTemplate ( $this -> exceptionTemplatePath , [
'request' => $exception -> getRequest (),
'exception' => $exception
]));
}
2021-06-06 01:18:44 +02:00
}