diff --git a/composer.json b/composer.json index 1195b9b..af1dfe5 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "j4mie/idiorm": "1.4.*", "mf2/mf2": "0.3.*", "indieweb/date-formatter": "0.1.*", - "indieauth/client": "0.2.*", + "indieauth/client": "~1.1", "mpratt/relativetime": ">=1.0", "firebase/php-jwt": "^4.0", "p3k/multipart": "*", diff --git a/controllers/auth.php b/controllers/auth.php index de4cd2e..e38bc1a 100644 --- a/controllers/auth.php +++ b/controllers/auth.php @@ -21,42 +21,6 @@ function build_url($parsed_url) { return "$scheme$user$pass$host$port$path$query$fragment"; } -// Input: Any URL or string like "aaronparecki.com" -// Output: Normlized URL (default to http if no scheme, force "/" path) -// or return false if not a valid URL (has query string params, etc) -function normalizeMeURL($url) { - $me = parse_url($url); - - if(array_key_exists('path', $me) && $me['path'] == '') - return false; - - // parse_url returns just "path" for naked domains - if(count($me) == 1 && array_key_exists('path', $me)) { - $me['host'] = $me['path']; - unset($me['path']); - } - - if(!array_key_exists('scheme', $me)) - $me['scheme'] = 'http'; - - if(!array_key_exists('path', $me)) - $me['path'] = '/'; - - // Invalid scheme - if(!in_array($me['scheme'], array('http','https'))) - return false; - - // Invalid path - // if($me['path'] != '/') - // return false; - - // query and fragment not allowed - if(array_key_exists('query', $me) || array_key_exists('fragment', $me)) - return false; - - return build_url($me); -} - function hostname($url) { return parse_url($url, PHP_URL_HOST); } @@ -89,7 +53,7 @@ $app->get('/auth/start', function() use($app) { // the "me" parameter is user input, and may be in a couple of different forms: // aaronparecki.com http://aaronparecki.com http://aaronparecki.com/ // Normlize the value now (move this into a function in IndieAuth\Client later) - if(!array_key_exists('me', $params) || !($me = normalizeMeURL($params['me']))) { + if(!array_key_exists('me', $params) || !($me = IndieAuth\Client::normalizeMeURL($params['me']))) { render('auth_error', array( 'title' => 'Sign In', 'error' => 'Invalid "me" Parameter', @@ -113,12 +77,23 @@ $app->get('/auth/start', function() use($app) { if($tokenEndpoint && $micropubEndpoint && $authorizationEndpoint) { $scope = 'create'; - $authorizationURL = IndieAuth\Client::buildAuthorizationURL($authorizationEndpoint, $me, buildRedirectURI(), clientID(), $state, $scope); + $authorizationURL = IndieAuth\Client::buildAuthorizationURL($authorizationEndpoint, [ + 'me' => $me, + 'redirect_uri' => buildRedirectURI(), + 'client_id' => clientID(), + 'state' => $state, + 'scope' => $scope + ]); $_SESSION['authorization_endpoint'] = $authorizationEndpoint; $_SESSION['micropub_endpoint'] = $micropubEndpoint; $_SESSION['token_endpoint'] = $tokenEndpoint; } else { - $authorizationURL = IndieAuth\Client::buildAuthorizationURL('https://indieauth.com/auth', $me, buildRedirectURI(), clientID(), $state); + $authorizationURL = IndieAuth\Client::buildAuthorizationURL('https://indielogin.com/auth', [ + 'me' => $me, + 'redirect_uri' => buildRedirectURI(), + 'client_id' => clientID(), + 'state' => $state + ]); } // If the user has already signed in before and has a micropub access token, skip @@ -217,7 +192,7 @@ $app->get('/auth/callback', function() use($app) { } $me = $_SESSION['attempted_me']; - // Now the basic sanity checks have passed. Time to start providing more helpful messages when there is an error. + // Now the basic checks have passed. Time to start providing more helpful messages when there is an error. // An authorization code is in the query string, and we want to exchange that for an access token at the token endpoint. $authorizationEndpoint = isset($_SESSION['authorization_endpoint']) ? $_SESSION['authorization_endpoint'] : false; @@ -232,22 +207,30 @@ $app->get('/auth/callback', function() use($app) { if($tokenEndpoint) { // Exchange auth code for an access token - $token = IndieAuth\Client::getAccessToken($tokenEndpoint, $params['code'], $me, buildRedirectURI(), clientID(), true); + $token = IndieAuth\Client::exchangeAuthorizationCode($tokenEndpoint, [ + 'code' => $params['code'], + 'redirect_uri' => buildRedirectURI(), + 'client_id' => clientID() + ]); // If a valid access token was returned, store the token info in the session and they are signed in - if(k($token['auth'], array('me','access_token','scope'))) { - // Double check that the domain of the returned "me" matches the expected - if(parse_url($token['auth']['me'], PHP_URL_HOST) != parse_url($me, PHP_URL_HOST)) { - render('auth_error', [ - 'title' => 'Error Signing In', - 'error' => 'Invalid user', - 'errorDescription' => 'The user URL that was returned in the access token did not match the domain of the user signing in.' - ]); - return; + if(k($token['response'], array('me','access_token','scope'))) { + + // Verify the authorization endpoint matches + if($token['response']['me'] != $me) { + $newAuthorizationEndpoint = IndieAuth\Client::discoverAuthorizationEndpoint($data['response']['me']); + if($newAuthorizationEndpoint != $authorizationEndpoint) { + render('auth_error', [ + 'title' => 'Error Signing In', + 'error' => 'Invalid authorization endpoint', + 'errorDescription' => 'The authorization endpoint for the returned profile URL did not match the authorization endpoint used to begin the login.' + ]); + return; + } } - $_SESSION['auth'] = $token['auth']; - $_SESSION['me'] = $token['auth']['me']; + $_SESSION['auth'] = $token['response']; + $_SESSION['me'] = $me = $token['response']['me']; } } else { @@ -257,22 +240,23 @@ $app->get('/auth/callback', function() use($app) { $skipDebugScreen = true; if(!$authorizationEndpoint) { - $authorizationEndpoint = 'https://indieauth.com/auth'; + $authorizationEndpoint = 'https://indielogin.com/auth'; } - $token['auth'] = IndieAuth\Client::verifyIndieAuthCode($authorizationEndpoint, $params['code'], $me, buildRedirectURI(), clientID()); + $token = IndieAuth\Client::exchangeAuthorizationCode($authorizationEndpoint, [ + 'code' => $params['code'], + 'redirect_uri' => buildRedirectURI(), + 'client_id' => clientID() + ]); - if(k($token['auth'], 'me')) { - $token['response'] = ''; // hack becuase the verify call doesn't actually return the real response - $token['auth']['scope'] = ''; - $token['auth']['access_token'] = ''; - $_SESSION['auth'] = $token['auth']; - $_SESSION['me'] = $params['me']; + if(k($token['response'], 'me')) { + $_SESSION['auth'] = $token['response']; + $_SESSION['me'] = $me = $token['response']['me']; } } // Verify the login actually succeeded - if(!k($token['auth'], 'me')) { + if(!k($token['response'], 'me')) { render('auth_error', array( 'title' => 'Sign-In Failed', 'error' => 'Unable to verify the sign-in attempt',