1
0
Fork 0
mirror of synced 2024-06-28 19:20:25 +12:00

Improve OAuth2 Error Handling

Update the OAuth2 class to throw an exception if an API call to the
OAuth2 provider fails and update the endpoint to redirect to the
failure url with the information from the OAuth2 provider.
This commit is contained in:
Steven Nguyen 2023-07-14 17:28:45 -07:00
parent 3499a7028c
commit a7c9e4bb7e
No known key found for this signature in database
2 changed files with 60 additions and 29 deletions

View file

@ -2,6 +2,7 @@
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\OAuth2\Exception as OAuth2Exception;
use Appwrite\Auth\Validator\Password;
use Appwrite\Auth\Validator\Phone;
use Appwrite\Detector\Detector;
@ -413,27 +414,22 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
$providerEnabled = $project->getAttribute('authProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
throw new Exception(Exception::PROJECT_PROVIDER_DISABLED, 'This provider is disabled. Please enable the provider from your ' . APP_NAME . ' console to continue.');
}
if (!empty($appSecret) && isset($appSecret['version'])) {
$key = App::getEnv('_APP_OPENSSL_KEY_V' . $appSecret['version']);
$appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag']));
}
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
if (!\class_exists($className)) {
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$providers = Config::getParam('providers');
$providerName = $providers[$provider]['name'] ?? '';
/** @var Appwrite\Auth\OAuth2 $oauth2 */
$oauth2 = new $className($appId, $appSecret, $callback);
if (!empty($state)) {
try {
$state = \array_merge($defaultState, $oauth2->parseState($state));
} catch (\Exception$exception) {
} catch (\Exception $exception) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to parse login state params as passed from OAuth2 provider');
}
} else {
@ -447,27 +443,54 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) {
throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL);
}
$accessToken = $oauth2->getAccessToken($code);
$refreshToken = $oauth2->getRefreshToken($code);
$accessTokenExpiry = $oauth2->getAccessTokenExpiry($code);
if (empty($accessToken)) {
if (!empty($state['failure'])) {
$response->redirect($state['failure'], 301, 0);
$failure = [];
if (!empty($state['failure'])) {
$failure = URLParser::parse($state['failure']);
}
$failureRedirect = (function (string $type, ?string $message = null, ?int $code = null) use ($failure, $response) {
$exception = new Exception($type, $message, $code);
if (!empty($failure)) {
$query = URLParser::parseQuery($failure['query']);
$query['error'] = json_encode([
'message' => $exception->getMessage(),
'type' => $exception->getType(),
'code' => !\is_null($code) ? $code : $exception->getCode(),
]);
$failure['query'] = URLParser::unparseQuery($query);
$response->redirect(URLParser::unparse($failure), 301);
}
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to obtain access token');
throw $exception;
});
if (!$providerEnabled) {
$failureRedirect(Exception::PROJECT_PROVIDER_DISABLED, 'This provider is disabled. Please enable the provider from your ' . APP_NAME . ' console to continue.');
}
if (!empty($appSecret) && isset($appSecret['version'])) {
$key = App::getEnv('_APP_OPENSSL_KEY_V' . $appSecret['version']);
$appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag']));
}
$accessToken = '';
$refreshToken = '';
$accessTokenExpiry = 0;
try {
$accessToken = $oauth2->getAccessToken($code);
$refreshToken = $oauth2->getRefreshToken($code);
$accessTokenExpiry = $oauth2->getAccessTokenExpiry($code);
} catch (OAuth2Exception $ex) {
$failureRedirect(
$ex->getType(),
'Failed to obtain access token. The ' . $providerName . ' OAuth2 provider returned an error: ' . $ex->getMessage(),
$ex->getCode(),
);
}
$oauth2ID = $oauth2->getUserID($accessToken);
if (empty($oauth2ID)) {
if (!empty($state['failure'])) {
$response->redirect($state['failure'], 301, 0);
}
throw new Exception(Exception::USER_MISSING_ID);
$failureRedirect(Exception::USER_MISSING_ID);
}
$sessions = $user->getAttribute('sessions', []);
@ -515,7 +538,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception(Exception::USER_COUNT_EXCEEDED);
$failureRedirect(Exception::USER_COUNT_EXCEEDED);
}
}
@ -550,13 +573,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
]);
Authorization::skip(fn() => $dbForProject->createDocument('users', $user));
} catch (Duplicate $th) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
$failureRedirect(Exception::USER_ALREADY_EXISTS);
}
}
}
if (false === $user->getAttribute('status')) { // Account is blocked
throw new Exception(Exception::USER_BLOCKED); // User is in status blocked
$failureRedirect(Exception::USER_BLOCKED); // User is in status blocked
}
// Create session token, verify user account and update OAuth2 ID and Access Token

View file

@ -2,6 +2,8 @@
namespace Appwrite\Auth;
use Appwrite\Auth\OAuth2\Exception;
abstract class OAuth2
{
/**
@ -75,7 +77,7 @@ abstract class OAuth2
/**
* @param string $accessToken
*
*
* @return string
*/
abstract public function getUserID(string $accessToken): string;
@ -202,8 +204,14 @@ abstract class OAuth2
// Send the request & save response to $response
$response = \curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
\curl_close($ch);
if ($code != 200) {
throw new Exception($response, $code);
}
return (string)$response;
}
}