diff --git a/app/config/errors.php b/app/config/errors.php new file mode 100644 index 0000000000..6d7c9544db --- /dev/null +++ b/app/config/errors.php @@ -0,0 +1,473 @@ + [ + 'name' => Exception::GENERAL_UNKNOWN, + 'description' => 'An unknown error has occured. Please check the logs for more information.', + 'code' => 500, + ], + Exception::GENERAL_MOCK => [ + 'name' => Exception::GENERAL_MOCK, + 'description' => 'General errors thrown by the mock controller used for testing.', + 'code' => 400, + ], + Exception::GENERAL_ACCESS_FORBIDDEN => [ + 'name' => Exception::GENERAL_ACCESS_FORBIDDEN, + 'description' => 'Access to this API is forbidden.', + 'code' => 401, + ], + Exception::GENERAL_UNKNOWN_ORIGIN => [ + 'name' => Exception::GENERAL_UNKNOWN_ORIGIN, + 'description' => 'The request originated from an unknown origin. If you trust this domain, please list it as a trusted platform in the Appwrite console.', + 'code' => 403, + ], + Exception::GENERAL_SERVICE_DISABLED => [ + 'name' => Exception::GENERAL_SERVICE_DISABLED, + 'description' => 'The requested service is disabled. You can enable the service from the Appwrite console.', + 'code' => 503, + ], + Exception::GENERAL_UNAUTHORIZED_SCOPE => [ + 'name' => Exception::GENERAL_UNAUTHORIZED_SCOPE, + 'description' => 'The current user or API key does not have the required scopes to access the requested resource.', + 'code' => 401, + ], + Exception::GENERAL_RATE_LIMIT_EXCEEDED => [ + 'name' => Exception::GENERAL_RATE_LIMIT_EXCEEDED, + 'description' => 'Rate limit for the current endpoint has been exceeded. Please try again after some time.', + 'code' => 429, + ], + Exception::GENERAL_SMTP_DISABLED => [ + 'name' => Exception::GENERAL_SMTP_DISABLED, + 'description' => 'SMTP is disabled on your Appwrite instance. You can learn more about setting up SMTP in our docs.', + 'code' => 503, + ], + Exception::GENERAL_ARGUMENT_INVALID => [ + 'name' => Exception::GENERAL_ARGUMENT_INVALID, + 'description' => 'The request contains one or more invalid arguments. Please refer to the endpoint documentation.', + 'code' => 400, + ], + Exception::GENERAL_QUERY_LIMIT_EXCEEDED => [ + 'name' => Exception::GENERAL_QUERY_LIMIT_EXCEEDED, + 'description' => 'Query limit exceeded for the current attribute. Usage of more than 100 query values on a single attribute is prohibited.', + 'code' => 400, + ], + Exception::GENERAL_QUERY_INVALID => [ + 'name' => Exception::GENERAL_QUERY_INVALID, + 'description' => 'The query\'s syntax is invalid. Please check the query and try again.', + 'code' => 400, + ], + Exception::GENERAL_ROUTE_NOT_FOUND => [ + 'name' => Exception::GENERAL_ROUTE_NOT_FOUND, + 'description' => 'The requested route was not found. Please refer to the docs and try again.', + 'code' => 404, + ], + Exception::GENERAL_CURSOR_NOT_FOUND => [ + 'name' => Exception::GENERAL_CURSOR_NOT_FOUND, + 'description' => 'The cursor is invalid. This can happen if the item represented by the cursor has been deleted.', + 'code' => 400, + ], + Exception::GENERAL_SERVER_ERROR => [ + 'name' => Exception::GENERAL_SERVER_ERROR, + 'description' => 'An internal server error occurred.', + 'code' => 500, + ], + + /** User Errors */ + Exception::USER_COUNT_EXCEEDED => [ + 'name' => Exception::USER_COUNT_EXCEEDED, + 'description' => 'The current project has exceeded the maximum number of users. Please check your user limit in the Appwrite console.', + 'code' => 501, + ], + Exception::USER_JWT_INVALID => [ + 'name' => Exception::USER_JWT_INVALID, + 'description' => 'The JWT token is invalid. Please check the value of the X-Appwrite-JWT header to ensure the correct token is being used.', + 'code' => 401, + ], + Exception::USER_ALREADY_EXISTS => [ + 'name' => Exception::USER_ALREADY_EXISTS, + 'description' => 'A user with the same email ID already exists in your project.', + 'code' => 409, + ], + Exception::USER_BLOCKED => [ + 'name' => Exception::USER_BLOCKED, + 'description' => 'The current user has been blocked. You can unblock the user from the Appwrite console.', + 'code' => 401, + ], + Exception::USER_INVALID_TOKEN => [ + 'name' => Exception::USER_INVALID_TOKEN, + 'description' => 'Invalid token passed in the request.', + 'code' => 401, + ], + Exception::USER_PASSWORD_RESET_REQUIRED => [ + 'name' => Exception::USER_PASSWORD_RESET_REQUIRED, + 'description' => 'The current user requires a password reset.', + 'code' => 412, + ], + Exception::USER_EMAIL_NOT_WHITELISTED => [ + 'name' => Exception::USER_EMAIL_NOT_WHITELISTED, + 'description' => 'The user\'s email is not part of the whitelist. Please check the _APP_CONSOLE_WHITELIST_EMAILS environment variable of your Appwrite server.', + 'code' => 401, + ], + Exception::USER_IP_NOT_WHITELISTED => [ + 'name' => Exception::USER_IP_NOT_WHITELISTED, + 'description' => 'The user\'s IP address is not part of the whitelist. Please check the _APP_CONSOLE_WHITELIST_IPS environment variable of your Appwrite server.', + 'code' => 401, + ], + Exception::USER_INVALID_CREDENTIALS => [ + 'name' => Exception::USER_INVALID_CREDENTIALS, + 'description' => 'Invalid credentials. Please check the email and password.', + 'code' => 401, + ], + Exception::USER_ANONYMOUS_CONSOLE_PROHIBITED => [ + 'name' => Exception::USER_ANONYMOUS_CONSOLE_PROHIBITED, + 'description' => 'Anonymous users cannot be created for the console project.', + 'code' => 401, + ], + Exception::USER_SESSION_ALREADY_EXISTS => [ + 'name' => Exception::USER_SESSION_ALREADY_EXISTS, + 'description' => 'Creation of anonymous users is prohibited when a session is active.', + 'code' => 401, + ], + Exception::USER_NOT_FOUND => [ + 'name' => Exception::USER_NOT_FOUND, + 'description' => 'User with the requested ID could not be found.', + 'code' => 404, + ], + Exception::USER_EMAIL_ALREADY_EXISTS => [ + 'name' => Exception::USER_EMAIL_ALREADY_EXISTS, + 'description' => 'Another user with the same email already exists in the current project.', + 'code' => 409, + ], + Exception::USER_PASSWORD_MISMATCH => [ + 'name' => Exception::USER_PASSWORD_MISMATCH, + 'description' => 'Passwords do not match. Please check the password and confirm password.', + 'code' => 400, + ], + Exception::USER_SESSION_NOT_FOUND => [ + 'name' => Exception::USER_SESSION_NOT_FOUND, + 'description' => 'The current user session could not be found.', + 'code' => 404, + ], + Exception::USER_UNAUTHORIZED => [ + 'name' => Exception::USER_UNAUTHORIZED, + 'description' => 'The current user is not authorized to perform the requested action.', + 'code' => 401, + ], + Exception::USER_AUTH_METHOD_UNSUPPORTED => [ + 'name' => Exception::USER_AUTH_METHOD_UNSUPPORTED, + 'description' => 'The requested authentication method is either disabled or unsupported. Please check the supported authentication methods in the Appwrite console.', + 'code' => 501, + ], + + /** Teams */ + Exception::TEAM_NOT_FOUND => [ + 'name' => Exception::TEAM_NOT_FOUND, + 'description' => 'Team with the requested ID could not be found.', + 'code' => 404, + ], + Exception::TEAM_INVITE_ALREADY_EXISTS => [ + 'name' => Exception::TEAM_INVITE_ALREADY_EXISTS, + 'description' => 'The current user has already received an invitation to join the team.', + 'code' => 409, + ], + Exception::TEAM_INVITE_NOT_FOUND => [ + 'name' => Exception::TEAM_INVITE_NOT_FOUND, + 'description' => 'The requested team invitation could not be found.', + 'code' => 404, + ], + Exception::TEAM_INVALID_SECRET => [ + 'name' => Exception::TEAM_INVALID_SECRET, + 'description' => 'The team invitation secret is invalid.', + 'code' => 401, + ], + Exception::TEAM_MEMBERSHIP_MISMATCH => [ + 'name' => Exception::TEAM_MEMBERSHIP_MISMATCH, + 'description' => 'The membership ID does not belong to the team ID.', + 'code' => 404, + ], + Exception::TEAM_INVITE_MISMATCH => [ + 'name' => Exception::TEAM_INVITE_MISMATCH, + 'description' => 'The invite does not belong to the current user.', + 'code' => 401, + ], + + + /** Membership */ + Exception::MEMBERSHIP_NOT_FOUND => [ + 'name' => Exception::MEMBERSHIP_NOT_FOUND, + 'description' => 'Membership with the requested ID could not be found.', + 'code' => 404, + ], + + /** Avatars */ + Exception::AVATAR_SET_NOT_FOUND => [ + 'name' => Exception::AVATAR_SET_NOT_FOUND, + 'description' => 'The requested avatar set could not be found.', + 'code' => 404 + ], + Exception::AVATAR_NOT_FOUND => [ + 'name' => Exception::AVATAR_NOT_FOUND, + 'description' => 'The request avatar could not be found.', + 'code' => 404, + ], + Exception::AVATAR_IMAGE_NOT_FOUND => [ + 'name' => Exception::AVATAR_IMAGE_NOT_FOUND, + 'description' => 'The requested image was not found at the URL.', + 'code' => 404, + ], + Exception::AVATAR_REMOTE_URL_FAILED => [ + 'name' => Exception::AVATAR_REMOTE_URL_FAILED, + 'description' => 'Failed to fetch favicon from the requested URL.', + 'code' => 404, + ], + Exception::AVATAR_ICON_NOT_FOUND => [ + 'name' => Exception::AVATAR_ICON_NOT_FOUND, + 'description' => 'The requested favicon could not be found.', + 'code' => 404, + ], + + /** Storage */ + Exception::STORAGE_FILE_NOT_FOUND => [ + 'name' => Exception::STORAGE_FILE_NOT_FOUND, + 'description' => 'The requested file could not be found.', + 'code' => 404, + ], + Exception::STORAGE_DEVICE_NOT_FOUND => [ + 'name' => Exception::STORAGE_DEVICE_NOT_FOUND, + 'description' => 'The requested storage device could not be found.', + 'code' => 400, + ], + Exception::STORAGE_FILE_EMPTY => [ + 'name' => Exception::STORAGE_FILE_EMPTY, + 'description' => 'Empty file passed to the endpoint.', + 'code' => 400, + ], + Exception::STORAGE_FILE_TYPE_UNSUPPORTED => [ + 'name' => Exception::STORAGE_FILE_TYPE_UNSUPPORTED, + 'description' => 'The file type is not supported.', + 'code' => 400, + ], + Exception::STORAGE_INVALID_FILE_SIZE => [ + 'name' => Exception::STORAGE_INVALID_FILE_SIZE, + 'description' => 'The file size is either not valid or exceeds the maximum allowed size. Please check the file or the value of the _APP_STORAGE_LIMIT environment variable.', + 'code' => 400, + ], + Exception::STORAGE_INVALID_FILE => [ + 'name' => Exception::STORAGE_INVALID_FILE, + 'description' => 'The uploaded file is invalid. Please check the file and try again.', + 'code' => 403, + ], + Exception::STORAGE_BUCKET_ALREADY_EXISTS => [ + 'name' => Exception::STORAGE_BUCKET_ALREADY_EXISTS, + 'description' => 'A storage bucket with the requested ID already exists.', + 'code' => 409, + ], + Exception::STORAGE_BUCKET_NOT_FOUND => [ + 'name' => Exception::STORAGE_BUCKET_NOT_FOUND, + 'description' => 'Storage bucket with the requested ID could not be found.', + 'code' => 404, + ], + Exception::STORAGE_INVALID_CONTENT_RANGE => [ + 'name' => Exception::STORAGE_INVALID_CONTENT_RANGE, + 'description' => 'The content range is invalid. Please check the value of the Content-Range header.', + 'code' => 400, + ], + Exception::STORAGE_INVALID_RANGE => [ + 'name' => Exception::STORAGE_INVALID_RANGE, + 'description' => 'The requested range is not satisfiable. Please check the value of the Range header.', + 'code' => 416, + ], + + /** Functions */ + Exception::FUNCTION_NOT_FOUND => [ + 'name' => Exception::FUNCTION_NOT_FOUND, + 'description' => 'Function with the requested ID could not be found.', + 'code' => 404, + ], + + /** Deployments */ + Exception::DEPLOYMENT_NOT_FOUND => [ + 'name' => Exception::DEPLOYMENT_NOT_FOUND, + 'description' => 'Deployment with the requested ID could not be found.', + 'code' => 404, + ], + + /** Executions */ + Exception::EXECUTION_NOT_FOUND => [ + 'name' => Exception::EXECUTION_NOT_FOUND, + 'description' => 'Execution with the requested ID could not be found.', + 'code' => 404, + ], + + /** Collections */ + Exception::COLLECTION_NOT_FOUND => [ + 'name' => Exception::COLLECTION_NOT_FOUND, + 'description' => 'Collection with the requested ID could not be found.', + 'code' => 404, + ], + Exception::COLLECTION_ALREADY_EXISTS => [ + 'name' => Exception::COLLECTION_ALREADY_EXISTS, + 'description' => 'A collection with the requested ID already exists.', + 'code' => 409, + ], + Exception::COLLECTION_LIMIT_EXCEEDED => [ + 'name' => Exception::COLLECTION_LIMIT_EXCEEDED, + 'description' => 'The maximum number of collections has been reached.', + 'code' => 400, + ], + + /** Documents */ + Exception::DOCUMENT_NOT_FOUND => [ + 'name' => Exception::DOCUMENT_NOT_FOUND, + 'description' => 'Document with the requested ID could not be found.', + 'code' => 404, + ], + Exception::DOCUMENT_INVALID_STRUCTURE => [ + 'name' => Exception::DOCUMENT_INVALID_STRUCTURE, + 'description' => 'The document structure is invalid. Please ensure the attributes match the collection definition.', + 'code' => 400, + ], + Exception::DOCUMENT_MISSING_PAYLOAD => [ + 'name' => Exception::DOCUMENT_MISSING_PAYLOAD, + 'description' => 'The document payload is missing.', + 'code' => 400, + ], + Exception::DOCUMENT_ALREADY_EXISTS => [ + 'name' => Exception::DOCUMENT_ALREADY_EXISTS, + 'description' => 'Document with the requested ID already exists.', + 'code' => 409, + ], + + /** Attributes */ + Exception::ATTRIBUTE_NOT_FOUND => [ + 'name' => Exception::ATTRIBUTE_NOT_FOUND, + 'description' => 'Attribute with the requested ID could not be found.', + 'code' => 404, + ], + Exception::ATTRIBUTE_UNKNOWN => [ + 'name' => Exception::ATTRIBUTE_UNKNOWN, + 'description' => 'The attribute required for the index could not be found. Please confirm all your attributes are in the available state.', + 'code' => 400, + ], + Exception::ATTRIBUTE_NOT_AVAILABLE => [ + 'name' => Exception::ATTRIBUTE_NOT_AVAILABLE, + 'description' => 'The requested attribute is not yet available. Please try again later.', + 'code' => 400, + ], + Exception::ATTRIBUTE_FORMAT_UNSUPPORTED => [ + 'name' => Exception::ATTRIBUTE_FORMAT_UNSUPPORTED, + 'description' => 'The requested attribute format is not supported.', + 'code' => 400, + ], + Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED => [ + 'name' => Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED, + 'description' => 'Default values cannot be set for array and required attributes.', + 'code' => 400, + ], + Exception::ATTRIBUTE_ALREADY_EXISTS => [ + 'name' => Exception::ATTRIBUTE_ALREADY_EXISTS, + 'description' => 'Attribute with the requested ID already exists.', + 'code' => 409, + ], + Exception::ATTRIBUTE_LIMIT_EXCEEDED => [ + 'name' => Exception::ATTRIBUTE_LIMIT_EXCEEDED, + 'description' => 'The maximum number of attributes has been reached.', + 'code' => 400, + ], + Exception::ATTRIBUTE_VALUE_INVALID => [ + 'name' => Exception::ATTRIBUTE_VALUE_INVALID, + 'description' => 'The attribute value is invalid. Please check the type, range and value of the attribute.', + 'code' => 400, + ], + + /** Indexes */ + Exception::INDEX_NOT_FOUND => [ + 'name' => Exception::INDEX_NOT_FOUND, + 'description' => 'Index with the requested ID could not be found.', + 'code' => 404, + ], + Exception::INDEX_LIMIT_EXCEEDED => [ + 'name' => Exception::INDEX_LIMIT_EXCEEDED, + 'description' => 'The maximum number of indexes has been reached.', + 'code' => 400, + ], + Exception::INDEX_ALREADY_EXISTS => [ + 'name' => Exception::INDEX_ALREADY_EXISTS, + 'description' => 'Index with the requested ID already exists.', + 'code' => 409, + ], + + /** Project Errors */ + Exception::PROJECT_NOT_FOUND => [ + 'name' => Exception::PROJECT_NOT_FOUND, + 'description' => 'Project with the requested ID could not be found. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.', + 'code' => 404, + ], + Exception::PROJECT_UNKNOWN => [ + 'name' => Exception::PROJECT_UNKNOWN, + 'description' => 'The project ID is either missing or not valid. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.', + 'code' => 400, + ], + Exception::PROJECT_PROVIDER_DISABLED => [ + 'name' => Exception::PROJECT_PROVIDER_DISABLED, + 'description' => 'The chosen OAuth provider is disabled. You can enable the OAuth provider using the Appwrite console.', + 'code' => 412, + ], + Exception::PROJECT_PROVIDER_UNSUPPORTED => [ + 'name' => Exception::PROJECT_PROVIDER_UNSUPPORTED, + 'description' => 'The chosen OAuth provider is unsupported. Please check the docs for the complete list of supported OAuth providers.', + 'code' => 501, + ], + Exception::PROJECT_INVALID_SUCCESS_URL => [ + 'name' => Exception::PROJECT_INVALID_SUCCESS_URL, + 'description' => 'Invalid URL received for OAuth success redirect.', + 'code' => 400, + ], + Exception::PROJECT_INVALID_FAILURE_URL => [ + 'name' => Exception::PROJECT_INVALID_FAILURE_URL, + 'description' => 'Invalid URL received for OAuth failure redirect.', + 'code' => 400, + ], + Exception::PROJECT_MISSING_USER_ID => [ + 'name' => Exception::PROJECT_MISSING_USER_ID, + 'description' => 'Failed to obtain user ID from the OAuth provider.', + 'code' => 400, + ], + Exception::WEBHOOK_NOT_FOUND => [ + 'name' => Exception::WEBHOOK_NOT_FOUND, + 'description' => 'Webhook with the requested ID could not be found.', + 'code' => 404, + ], + Exception::KEY_NOT_FOUND => [ + 'name' => Exception::KEY_NOT_FOUND, + 'description' => 'Key with the requested ID could not be found.', + 'code' => 404, + ], + Exception::PLATFORM_NOT_FOUND => [ + 'name' => Exception::PLATFORM_NOT_FOUND, + 'description' => 'Platform with the requested ID could not be found.', + 'code' => 404, + ], + Exception::DOMAIN_NOT_FOUND => [ + 'name' => Exception::DOMAIN_NOT_FOUND, + 'description' => 'Domain with the requested ID could not be found.', + 'code' => 404, + ], + Exception::DOMAIN_ALREADY_EXISTS => [ + 'name' => Exception::DOMAIN_ALREADY_EXISTS, + 'description' => 'A Domain with the requested ID already exists.', + 'code' => 409, + ], + Exception::DOMAIN_VERIFICATION_FAILED => [ + 'name' => Exception::DOMAIN_VERIFICATION_FAILED, + 'description' => 'Domain verification for the requested domain has failed.', + 'code' => 401, + ] +]; \ No newline at end of file diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 6e8f0fc0a1..69602dc42c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -20,7 +20,7 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; -use Utopia\Exception; +use Appwrite\Extend\Exception; use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; use Utopia\Validator\Boolean; @@ -69,11 +69,11 @@ App::post('/v1/account') $whitelistIPs = $project->getAttribute('authWhitelistIPs'); if (!empty($whitelistEmails) && !\in_array($email, $whitelistEmails)) { - throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401); + throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401, Exception::USER_EMAIL_NOT_WHITELISTED); } if (!empty($whitelistIPs) && !\in_array($request->getIP(), $whitelistIPs)) { - throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401); + throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401, Exception::USER_IP_NOT_WHITELISTED); } } @@ -85,7 +85,7 @@ App::post('/v1/account') ], APP_LIMIT_USERS); if ($sum >= $limit) { - throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); + throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED); } } @@ -111,7 +111,7 @@ App::post('/v1/account') 'deleted' => false ]))); } catch (Duplicate $th) { - throw new Exception('Account already exists', 409); + throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS); } Authorization::unsetRole('role:' . Auth::USER_ROLE_GUEST); @@ -176,11 +176,11 @@ App::post('/v1/account/sessions') ->setParam('resource', 'user/'.($profile ? $profile->getId() : '')) ; - throw new Exception('Invalid credentials', 401); // Wrong password or username + throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); // Wrong password or username } if (false === $profile->getAttribute('status')) { // Account is blocked - throw new Exception('Invalid credentials. User is blocked', 401); // User is in status blocked + throw new Exception('Invalid credentials. User is blocked', 401, Exception::USER_BLOCKED); // User is in status blocked } $detector = new Detector($request->getUserAgent('UNKNOWN')); @@ -283,13 +283,13 @@ App::get('/v1/account/sessions/oauth2/:provider') } if (empty($appId) || empty($appSecret)) { - throw new Exception('This provider is disabled. Please configure the provider app ID and app secret key from your ' . APP_NAME . ' console to continue.', 412); + throw new Exception('This provider is disabled. Please configure the provider app ID and app secret key from your ' . APP_NAME . ' console to continue.', 412, Exception::PROJECT_PROVIDER_DISABLED); } $className = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider); if (!\class_exists($className)) { - throw new Exception('Provider is not supported', 501); + throw new Exception('Provider is not supported', 501, Exception::PROJECT_PROVIDER_UNSUPPORTED); } if(empty($success)) { @@ -407,7 +407,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider); if (!\class_exists($className)) { - throw new Exception('Provider is not supported', 501); + throw new Exception('Provider is not supported', 501, Exception::PROJECT_PROVIDER_UNSUPPORTED); } $oauth2 = new $className($appId, $appSecret, $callback); @@ -416,18 +416,18 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') try { $state = \array_merge($defaultState, $oauth2->parseState($state)); } catch (\Exception$exception) { - throw new Exception('Failed to parse login state params as passed from OAuth2 provider'); + throw new Exception('Failed to parse login state params as passed from OAuth2 provider', 500, Exception::GENERAL_SERVER_ERROR); } } else { $state = $defaultState; } if (!$validateURL->isValid($state['success'])) { - throw new Exception('Invalid redirect URL for success login', 400); + throw new Exception('Invalid redirect URL for success login', 400, Exception::PROJECT_INVALID_SUCCESS_URL); } if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) { - throw new Exception('Invalid redirect URL for failure login', 400); + throw new Exception('Invalid redirect URL for failure login', 400, Exception::PROJECT_INVALID_FAILURE_URL); } $state['failure'] = null; @@ -441,7 +441,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $response->redirect($state['failure'], 301, 0); } - throw new Exception('Failed to obtain access token'); + throw new Exception('Failed to obtain access token', 500, Exception::GENERAL_SERVER_ERROR); } $oauth2ID = $oauth2->getUserID($accessToken); @@ -451,7 +451,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $response->redirect($state['failure'], 301, 0); } - throw new Exception('Missing ID from OAuth2 provider', 400); + throw new Exception('Missing ID from OAuth2 provider', 400, Exception::PROJECT_MISSING_USER_ID); } $sessions = $user->getAttribute('sessions', []); @@ -486,7 +486,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $sum = $dbForProject->count('users', [ new Query('deleted', Query::TYPE_EQUAL, [false]),], APP_LIMIT_USERS); if ($sum >= $limit) { - throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); + throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED); } } @@ -512,13 +512,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'deleted' => false ]))); } catch (Duplicate $th) { - throw new Exception('Account already exists', 409); + throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS); } } } if (false === $user->getAttribute('status')) { // Account is blocked - throw new Exception('Invalid credentials. User is blocked', 401); // User is in status blocked + throw new Exception('Invalid credentials. User is blocked', 401, Exception::USER_BLOCKED); // User is in status blocked } // Create session token, verify user account and update OAuth2 ID and Access Token @@ -643,7 +643,7 @@ App::post('/v1/account/sessions/magic-url') /** @var Appwrite\Event\Event $mails */ if(empty(App::getEnv('_APP_SMTP_HOST'))) { - throw new Exception('SMTP Disabled', 503); + throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED); } $roles = Authorization::getRoles(); @@ -661,7 +661,7 @@ App::post('/v1/account/sessions/magic-url') ], APP_LIMIT_USERS); if ($sum >= $limit) { - throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); + throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED); } } @@ -711,7 +711,7 @@ App::post('/v1/account/sessions/magic-url') $user = $dbForProject->updateDocument('users', $user->getId(), $user); if (false === $user) { - throw new Exception('Failed to save user to DB', 500); + throw new Exception('Failed to save user to DB', 500, Exception::GENERAL_SERVER_ERROR); } if(empty($url)) { @@ -789,13 +789,13 @@ App::put('/v1/account/sessions/magic-url') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $token = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_MAGIC_URL, $secret); if (!$token) { - throw new Exception('Invalid login token', 401); + throw new Exception('Invalid login token', 401, Exception::USER_INVALID_TOKEN); } $detector = new Detector($request->getUserAgent('UNKNOWN')); @@ -845,7 +845,7 @@ App::put('/v1/account/sessions/magic-url') $user = $dbForProject->updateDocument('users', $user->getId(), $user); if (false === $user) { - throw new Exception('Failed saving user to DB', 500); + throw new Exception('Failed saving user to DB', 500, Exception::GENERAL_SERVER_ERROR); } $audits @@ -918,11 +918,11 @@ App::post('/v1/account/sessions/anonymous') $protocol = $request->getProtocol(); if ('console' === $project->getId()) { - throw new Exception('Failed to create anonymous user.', 401); + throw new Exception('Failed to create anonymous user.', 401, Exception::USER_ANONYMOUS_CONSOLE_PROHIBITED); } if (!$user->isEmpty()) { - throw new Exception('Cannot create an anonymous user when logged in.', 401); + throw new Exception('Cannot create an anonymous user when logged in.', 401, Exception::USER_SESSION_ALREADY_EXISTS); } $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -933,7 +933,7 @@ App::post('/v1/account/sessions/anonymous') ], APP_LIMIT_USERS); if ($sum >= $limit) { - throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); + throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED); } } @@ -1057,7 +1057,7 @@ App::post('/v1/account/jwt') } if ($current->isEmpty()) { - throw new Exception('No valid session found', 401); + throw new Exception('No valid session found', 404, Exception::USER_SESSION_NOT_FOUND); } $jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway. @@ -1303,7 +1303,7 @@ App::get('/v1/account/sessions/:sessionId') } } - throw new Exception('Session not found', 404); + throw new Exception('Session not found', 404, Exception::USER_SESSION_NOT_FOUND); }); App::patch('/v1/account/name') @@ -1377,7 +1377,7 @@ App::patch('/v1/account/password') // Check old password only if its an existing user. if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password - throw new Exception('Invalid credentials', 401); + throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); } $user = $dbForProject->updateDocument('users', $user->getId(), $user @@ -1429,14 +1429,14 @@ App::patch('/v1/account/email') !$isAnonymousUser && !Auth::passwordVerify($password, $user->getAttribute('password')) ) { // Double check user password - throw new Exception('Invalid credentials', 401); + throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); } $email = \strtolower($email); $profile = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address if ($profile) { - throw new Exception('User already registered', 409); + throw new Exception('User already registered', 409, Exception::USER_ALREADY_EXISTS); } try { @@ -1447,7 +1447,7 @@ App::patch('/v1/account/email') ->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')])) ); } catch(Duplicate $th) { - throw new Exception('Email already exists', 409); + throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS); } $audits @@ -1651,7 +1651,7 @@ App::delete('/v1/account/sessions/:sessionId') } } - throw new Exception('Session not found', 404); + throw new Exception('Session not found', 404, Exception::USER_SESSION_NOT_FOUND); }); App::patch('/v1/account/sessions/:sessionId') @@ -1873,7 +1873,7 @@ App::post('/v1/account/recovery') /** @var Appwrite\Stats\Stats $usage */ if(empty(App::getEnv('_APP_SMTP_HOST'))) { - throw new Exception('SMTP Disabled', 503); + throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED); } $roles = Authorization::getRoles(); @@ -1884,11 +1884,11 @@ App::post('/v1/account/recovery') $profile = $dbForProject->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address if (!$profile) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } if (false === $profile->getAttribute('status')) { // Account is blocked - throw new Exception('Invalid credentials. User is blocked', 401); + throw new Exception('Invalid credentials. User is blocked', 401, Exception::USER_BLOCKED); } $expire = \time() + Auth::TOKEN_EXPIRATION_RECOVERY; @@ -1979,20 +1979,20 @@ App::put('/v1/account/recovery') /** @var Appwrite\Stats\Stats $usage */ if ($password !== $passwordAgain) { - throw new Exception('Passwords must match', 400); + throw new Exception('Passwords must match', 400, Exception::USER_PASSWORD_MISMATCH); } $profile = $dbForProject->getDocument('users', $userId); if ($profile->isEmpty() || $profile->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $tokens = $profile->getAttribute('tokens', []); $recovery = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_RECOVERY, $secret); if (!$recovery) { - throw new Exception('Invalid recovery token', 401); + throw new Exception('Invalid recovery token', 401, Exception::USER_INVALID_TOKEN); } Authorization::setRole('user:' . $profile->getId()); @@ -2066,7 +2066,7 @@ App::post('/v1/account/verification') /** @var Appwrite\Stats\Stats $usage */ if(empty(App::getEnv('_APP_SMTP_HOST'))) { - throw new Exception('SMTP Disabled', 503); + throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED); } $roles = Authorization::getRoles(); @@ -2164,14 +2164,14 @@ App::put('/v1/account/verification') $profile = $dbForProject->getDocument('users', $userId); if ($profile->isEmpty()) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $tokens = $profile->getAttribute('tokens', []); $verification = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_VERIFICATION, $secret); if (!$verification) { - throw new Exception('Invalid verification token', 401); + throw new Exception('Invalid verification token', 401, Exception::USER_INVALID_TOKEN); } Authorization::setRole('user:' . $profile->getId()); diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 161e87b130..af84e4542c 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -8,7 +8,7 @@ use Utopia\App; use Utopia\Cache\Adapter\Filesystem; use Utopia\Cache\Cache; use Utopia\Config\Config; -use Utopia\Exception; +use Appwrite\Extend\Exception; use Utopia\Image\Image; use Utopia\Validator\Boolean; use Utopia\Validator\HexColor; @@ -25,15 +25,15 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, $response) $set = Config::getParam('avatar-' . $type, []); if (empty($set)) { - throw new Exception('Avatar set not found', 404); + throw new Exception('Avatar set not found', 404, Exception::AVATAR_SET_NOT_FOUND); } if (!\array_key_exists($code, $set)) { - throw new Exception('Avatar not found', 404); + throw new Exception('Avatar not found', 404, Exception::AVATAR_NOT_FOUND); } if (!\extension_loaded('imagick')) { - throw new Exception('Imagick extension is missing', 500); + throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR); } $output = 'png'; @@ -43,7 +43,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, $response) $type = 'png'; if (!\is_readable($path)) { - throw new Exception('File not readable in ' . $path, 500); + throw new Exception('File not readable in ' . $path, 500, Exception::GENERAL_SERVER_ERROR); } $cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size @@ -169,19 +169,19 @@ App::get('/v1/avatars/image') } if (!\extension_loaded('imagick')) { - throw new Exception('Imagick extension is missing', 500); + throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR); } $fetch = @\file_get_contents($url, false); if (!$fetch) { - throw new Exception('Image not found', 404); + throw new Exception('Image not found', 404, Exception::AVATAR_IMAGE_NOT_FOUND); } try { $image = new Image($fetch); } catch (\Exception$exception) { - throw new Exception('Unable to parse image', 500); + throw new Exception('Unable to parse image', 500, Exception::GENERAL_SERVER_ERROR); } $image->crop((int) $width, (int) $height); @@ -238,7 +238,7 @@ App::get('/v1/avatars/favicon') } if (!\extension_loaded('imagick')) { - throw new Exception('Imagick extension is missing', 500); + throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR); } $curl = \curl_init(); @@ -259,7 +259,7 @@ App::get('/v1/avatars/favicon') \curl_close($curl); if (!$html) { - throw new Exception('Failed to fetch remote URL', 404); + throw new Exception('Failed to fetch remote URL', 404, Exception::AVATAR_REMOTE_URL_FAILED); } $doc = new DOMDocument(); @@ -317,7 +317,7 @@ App::get('/v1/avatars/favicon') $data = @\file_get_contents($outputHref, false); if (empty($data) || (\mb_substr($data, 0, 5) === 'save($key, $data); @@ -333,7 +333,7 @@ App::get('/v1/avatars/favicon') $fetch = @\file_get_contents($outputHref, false); if (!$fetch) { - throw new Exception('Icon not found', 404); + throw new Exception('Icon not found', 404, Exception::AVATAR_ICON_NOT_FOUND); } $image = new Image($fetch); diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 0ed47d4b5f..ca56e080a3 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1,7 +1,7 @@ getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } if (!empty($format)) { if (!Structure::hasFormat($format, $type)) { - throw new Exception("Format {$format} not available for {$type} attributes.", 400); + throw new Exception("Format {$format} not available for {$type} attributes.", 400, Exception::ATTRIBUTE_FORMAT_UNSUPPORTED); } } // Must throw here since dbForProject->createAttribute is performed by db worker if ($required && $default) { - throw new Exception('Cannot set default value for required attribute', 400); + throw new Exception('Cannot set default value for required attribute', 400, Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED); } if ($array && $default) { - throw new Exception('Cannot set default value for array attributes', 400); + throw new Exception('Cannot set default value for array attributes', 400, Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED); } try { @@ -104,10 +104,10 @@ function createAttribute(string $collectionId, Document $attribute, Response $re $attribute = $dbForProject->createDocument('attributes', $attribute); } catch (DuplicateException $exception) { - throw new Exception('Attribute already exists', 409); + throw new Exception('Attribute already exists', 409, Exception::ATTRIBUTE_ALREADY_EXISTS); } catch (LimitException $exception) { - throw new Exception('Attribute limit exceeded', 400); + throw new Exception('Attribute limit exceeded', 400, Exception::ATTRIBUTE_LIMIT_EXCEEDED); } $dbForProject->deleteCachedDocument('collections', $collectionId); @@ -180,9 +180,9 @@ App::post('/v1/database/collections') $dbForProject->createCollection('collection_' . $collectionId); } catch (DuplicateException $th) { - throw new Exception('Collection already exists', 409); + throw new Exception('Collection already exists', 409, Exception::COLLECTION_ALREADY_EXISTS); } catch (LimitException $th) { - throw new Exception('Collection limit exceeded', 400); + throw new Exception('Collection limit exceeded', 400, Exception::COLLECTION_LIMIT_EXCEEDED); } $audits @@ -225,7 +225,7 @@ App::get('/v1/database/collections') $cursorCollection = $dbForProject->getDocument('collections', $cursor); if ($cursorCollection->isEmpty()) { - throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400); + throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -265,7 +265,7 @@ App::get('/v1/database/collections/:collectionId') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $usage->setParam('database.collections.read', 1); @@ -405,7 +405,7 @@ App::get('/v1/database/:collectionId/usage') $collection = $dbForProject->getCollection('collection_' . $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $usage = []; @@ -516,7 +516,7 @@ App::get('/v1/database/collections/:collectionId/logs') $collection = $dbForProject->getCollection('collection_' . $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $audit = new Audit($dbForProject); @@ -605,7 +605,7 @@ App::put('/v1/database/collections/:collectionId') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $read ??= $collection->getRead() ?? []; // By default inherit read permissions @@ -624,10 +624,10 @@ App::put('/v1/database/collections/:collectionId') ); } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } catch (StructureException $exception) { - throw new Exception('Bad structure. '.$exception->getMessage(), 400); + throw new Exception('Bad structure. '.$exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE); } $usage->setParam('database.collections.update', 1); @@ -669,11 +669,11 @@ App::delete('/v1/database/collections/:collectionId') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } if (!$dbForProject->deleteDocument('collections', $collectionId)) { - throw new Exception('Failed to remove collection from DB', 500); + throw new Exception('Failed to remove collection from DB', 500, Exception::GENERAL_SERVER_ERROR); } $dbForProject->deleteCachedCollection('collection_' . $collectionId); @@ -731,7 +731,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string') // Ensure attribute default is within required size $validator = new Text($size); if (!is_null($default) && !$validator->isValid($default)) { - throw new Exception($validator->getDescription(), 400); + throw new Exception($validator->getDescription(), 400, Exception::ATTRIBUTE_VALUE_INVALID); } $attribute = createAttribute($collectionId, new Document([ @@ -823,14 +823,14 @@ App::post('/v1/database/collections/:collectionId/attributes/enum') foreach ($elements as $element) { $length = \strlen($element); if ($length === 0) { - throw new Exception('Each enum element must not be empty', 400); + throw new Exception('Each enum element must not be empty', 400, Exception::ATTRIBUTE_VALUE_INVALID); } $size = ($length > $size) ? $length : $size; } if (!is_null($default) && !in_array($default, $elements)) { - throw new Exception('Default value not found in elements', 400); + throw new Exception('Default value not found in elements', 400, Exception::ATTRIBUTE_VALUE_INVALID); } $attribute = createAttribute($collectionId, new Document([ @@ -966,13 +966,13 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') $max = (is_null($max)) ? PHP_INT_MAX : \intval($max); if ($min > $max) { - throw new Exception('Minimum value must be lesser than maximum value', 400); + throw new Exception('Minimum value must be lesser than maximum value', 400, Exception::ATTRIBUTE_VALUE_INVALID); } $validator = new Range($min, $max, Database::VAR_INTEGER); if (!is_null($default) && !$validator->isValid($default)) { - throw new Exception($validator->getDescription(), 400); + throw new Exception($validator->getDescription(), 400, Exception::ATTRIBUTE_VALUE_INVALID); } $size = $max > 2147483647 ? 8 : 4; // Automatically create BigInt depending on max value @@ -1037,7 +1037,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float') $max = (is_null($max)) ? PHP_FLOAT_MAX : \floatval($max); if ($min > $max) { - throw new Exception('Minimum value must be lesser than maximum value', 400); + throw new Exception('Minimum value must be lesser than maximum value', 400, Exception::ATTRIBUTE_VALUE_INVALID); } // Ensure default value is a float @@ -1048,7 +1048,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float') $validator = new Range($min, $max, Database::VAR_FLOAT); if (!is_null($default) && !$validator->isValid($default)) { - throw new Exception($validator->getDescription(), 400); + throw new Exception($validator->getDescription(), 400, Exception::ATTRIBUTE_VALUE_INVALID); } $attribute = createAttribute($collectionId, new Document([ @@ -1138,7 +1138,7 @@ App::get('/v1/database/collections/:collectionId/attributes') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $attributes = $collection->getAttribute('attributes'); @@ -1182,13 +1182,13 @@ App::get('/v1/database/collections/:collectionId/attributes/:key') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $attribute = $dbForProject->getDocument('attributes', $collectionId.'_'.$key); if ($attribute->isEmpty()) { - throw new Exception('Attribute not found', 404); + throw new Exception('Attribute not found', 404, Exception::ATTRIBUTE_NOT_FOUND); } // Select response model based on type and format @@ -1244,13 +1244,13 @@ App::delete('/v1/database/collections/:collectionId/attributes/:key') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $attribute = $dbForProject->getDocument('attributes', $collectionId.'_'.$key); if ($attribute->isEmpty()) { - throw new Exception('Attribute not found', 404); + throw new Exception('Attribute not found', 404, Exception::ATTRIBUTE_NOT_FOUND); } // Only update status if removing available attribute @@ -1332,7 +1332,7 @@ App::post('/v1/database/collections/:collectionId/indexes') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $count = $dbForProject->count('indexes', [ @@ -1342,7 +1342,7 @@ App::post('/v1/database/collections/:collectionId/indexes') $limit = 64 - MariaDB::getNumberOfDefaultIndexes(); if ($count >= $limit) { - throw new Exception('Index limit exceeded', 400); + throw new Exception('Index limit exceeded', 400, Exception::INDEX_LIMIT_EXCEEDED); } // Convert Document[] to array of attribute metadata @@ -1356,7 +1356,7 @@ App::post('/v1/database/collections/:collectionId/indexes') $attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key')); if ($attributeIndex === false) { - throw new Exception('Unknown attribute: ' . $attribute, 400); + throw new Exception('Unknown attribute: ' . $attribute, 400, Exception::ATTRIBUTE_UNKNOWN); } $attributeStatus = $oldAttributes[$attributeIndex]['status']; @@ -1365,7 +1365,7 @@ App::post('/v1/database/collections/:collectionId/indexes') // ensure attribute is available if ($attributeStatus !== 'available') { - throw new Exception ('Attribute not available: ' . $oldAttributes[$attributeIndex]['key'], 400); + throw new Exception ('Attribute not available: ' . $oldAttributes[$attributeIndex]['key'], 400, Exception::ATTRIBUTE_NOT_AVAILABLE); } // set attribute size as index length only for strings @@ -1384,7 +1384,7 @@ App::post('/v1/database/collections/:collectionId/indexes') 'orders' => $orders, ])); } catch (DuplicateException $th) { - throw new Exception('Index already exists', 409); + throw new Exception('Index already exists', 409, Exception::INDEX_ALREADY_EXISTS); } $dbForProject->deleteCachedDocument('collections', $collectionId); @@ -1429,7 +1429,7 @@ App::get('/v1/database/collections/:collectionId/indexes') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $indexes = $collection->getAttribute('indexes'); @@ -1465,7 +1465,7 @@ App::get('/v1/database/collections/:collectionId/indexes/:key') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $indexes = $collection->getAttribute('indexes'); @@ -1474,7 +1474,7 @@ App::get('/v1/database/collections/:collectionId/indexes/:key') $indexIndex = array_search($key, array_column($indexes, 'key')); if ($indexIndex === false) { - throw new Exception('Index not found', 404); + throw new Exception('Index not found', 404, Exception::INDEX_NOT_FOUND); } $index = new Document([\array_merge($indexes[$indexIndex], [ @@ -1516,13 +1516,13 @@ App::delete('/v1/database/collections/:collectionId/indexes/:key') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $index = $dbForProject->getDocument('indexes', $collectionId.'_'.$key); if (empty($index->getId())) { - throw new Exception('Index not found', 404); + throw new Exception('Index not found', 404, Exception::INDEX_NOT_FOUND); } // Only update status if removing available index @@ -1589,11 +1589,11 @@ App::post('/v1/database/collections/:collectionId/documents') $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array if (empty($data)) { - throw new Exception('Missing payload', 400); + throw new Exception('Missing payload', 400, Exception::DOCUMENT_MISSING_PAYLOAD); } if (isset($data['$id'])) { - throw new Exception('$id is not allowed for creating new documents, try update instead', 400); + throw new Exception('$id is not allowed for creating new documents, try update instead', 400, Exception::DOCUMENT_INVALID_STRUCTURE); } /** @@ -1605,7 +1605,7 @@ App::post('/v1/database/collections/:collectionId/documents') if ($collection->isEmpty() || !$collection->getAttribute('enabled')) { if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } } @@ -1613,7 +1613,7 @@ App::post('/v1/database/collections/:collectionId/documents') if ($collection->getAttribute('permission') === 'collection') { $validator = new Authorization('write'); if (!$validator->isValid($collection->getWrite())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -1628,12 +1628,13 @@ App::post('/v1/database/collections/:collectionId/documents') if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) { foreach ($data['$read'] as $read) { if (!Authorization::isRole($read)) { - throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400); + // TODO: Isn't this a 401: Unauthorized Error ? + throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400, Exception::USER_UNAUTHORIZED); } } foreach ($data['$write'] as $write) { if (!Authorization::isRole($write)) { - throw new Exception('Write permissions must be one of: ('.\implode(', ', $roles).')', 400); + throw new Exception('Write permissions must be one of: ('.\implode(', ', $roles).')', 400, Exception::USER_UNAUTHORIZED); } } } @@ -1648,10 +1649,10 @@ App::post('/v1/database/collections/:collectionId/documents') $document->setAttribute('$collection', $collectionId); } catch (StructureException $exception) { - throw new Exception($exception->getMessage(), 400); + throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE); } catch (DuplicateException $exception) { - throw new Exception('Document already exists', 409); + throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS); } $events->setParam('collection', $collection->getArrayCopy()); @@ -1709,7 +1710,7 @@ App::get('/v1/database/collections/:collectionId/documents') if ($collection->isEmpty() || !$collection->getAttribute('enabled')) { if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } } @@ -1717,7 +1718,7 @@ App::get('/v1/database/collections/:collectionId/documents') if ($collection->getAttribute('permission') === 'collection') { $validator = new Authorization('read'); if (!$validator->isValid($collection->getRead())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -1725,7 +1726,7 @@ App::get('/v1/database/collections/:collectionId/documents') $query = Query::parse($query); if (\count($query->getValues()) > 100) { - throw new Exception("You cannot use more than 100 query values on attribute '{$query->getAttribute()}'", 400); + throw new Exception("You cannot use more than 100 query values on attribute '{$query->getAttribute()}'", 400, Exception::GENERAL_QUERY_LIMIT_EXCEEDED); } return $query; @@ -1734,7 +1735,7 @@ App::get('/v1/database/collections/:collectionId/documents') if (!empty($queries)) { $validator = new QueriesValidator(new QueryValidator($collection->getAttribute('attributes', [])), $collection->getAttribute('indexes', []), true); if (!$validator->isValid($queries)) { - throw new Exception($validator->getDescription(), 400); + throw new Exception($validator->getDescription(), 400, Exception::GENERAL_QUERY_INVALID); } } @@ -1745,7 +1746,7 @@ App::get('/v1/database/collections/:collectionId/documents') : $dbForProject->getDocument('collection_' . $collectionId, $cursor); if ($cursorDocument->isEmpty()) { - throw new Exception("Document '{$cursor}' for the 'cursor' value not found.", 400); + throw new Exception("Document '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -1803,7 +1804,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') if ($collection->isEmpty() || !$collection->getAttribute('enabled')) { if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } } @@ -1811,7 +1812,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') if ($collection->getAttribute('permission') === 'collection') { $validator = new Authorization('read'); if (!$validator->isValid($collection->getRead())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -1828,7 +1829,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') $document->setAttribute('$collection', $collectionId); if ($document->isEmpty()) { - throw new Exception('No document found', 404); + throw new Exception('No document found', 404, Exception::DOCUMENT_NOT_FOUND); } $usage @@ -1868,13 +1869,13 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId/logs') $collection = $dbForProject->getDocument('collections', $collectionId); if ($collection->isEmpty()) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } $document = $dbForProject->getDocument('collection_' . $collectionId, $documentId); if ($document->isEmpty()) { - throw new Exception('No document found', 404); + throw new Exception('No document found', 404, Exception::DOCUMENT_NOT_FOUND); } $audit = new Audit($dbForProject); @@ -1969,7 +1970,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') if ($collection->isEmpty() || !$collection->getAttribute('enabled')) { if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } } @@ -1977,7 +1978,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') if ($collection->getAttribute('permission') === 'collection') { $validator = new Authorization('write'); if (!$validator->isValid($collection->getWrite())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } $document = Authorization::skip(fn() => $dbForProject->getDocument('collection_' . $collectionId, $documentId)); @@ -1987,17 +1988,17 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') if ($document->isEmpty()) { - throw new Exception('Document not found', 404); + throw new Exception('Document not found', 404, Exception::DOCUMENT_NOT_FOUND); } $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array if (empty($data)) { - throw new Exception('Missing payload', 400); + throw new Exception('Missing payload', 400, Exception::DOCUMENT_MISSING_PAYLOAD); } if (!\is_array($data)) { - throw new Exception('Data param should be a valid JSON object', 400); + throw new Exception('Data param should be a valid JSON object', 400, Exception::DOCUMENT_INVALID_STRUCTURE); } $data = \array_merge($document->getArrayCopy(), $data); @@ -2014,14 +2015,14 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') if(!is_null($read)) { foreach ($data['$read'] as $read) { if (!Authorization::isRole($read)) { - throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400); + throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400, Exception::USER_UNAUTHORIZED); } } } if(!is_null($write)) { foreach ($data['$write'] as $write) { if (!Authorization::isRole($write)) { - throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400); + throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED); } } } @@ -2040,13 +2041,13 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') $document->setAttribute('$collection', $collectionId); } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } catch (DuplicateException $exception) { - throw new Exception('Document already exists', 409); + throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS); } catch (StructureException $exception) { - throw new Exception($exception->getMessage(), 400); + throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE); } $events->setParam('collection', $collection->getArrayCopy()); @@ -2101,7 +2102,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') if ($collection->isEmpty() || !$collection->getAttribute('enabled')) { if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception('Collection not found', 404); + throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND); } } @@ -2109,7 +2110,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') if ($collection->getAttribute('permission') === 'collection') { $validator = new Authorization('write'); if (!$validator->isValid($collection->getWrite())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -2121,7 +2122,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') } if ($document->isEmpty()) { - throw new Exception('No document found', 404); + throw new Exception('No document found', 404, Exception::DOCUMENT_NOT_FOUND); } if ($collection->getAttribute('permission') === 'collection') { diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index ab4fdfaa5e..32c07c6e0c 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -12,7 +12,7 @@ use Utopia\Storage\Validator\Upload; use Appwrite\Utopia\Response; use Appwrite\Task\Validator\Cron; use Utopia\App; -use Utopia\Exception; +use Appwrite\Extend\Exception; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; @@ -103,7 +103,7 @@ App::get('/v1/functions') $cursorFunction = $dbForProject->getDocument('functions', $cursor); if ($cursorFunction->isEmpty()) { - throw new Exception("Function '{$cursor}' for the 'cursor' value not found.", 400); + throw new Exception("Function '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -168,7 +168,7 @@ App::get('/v1/functions/:functionId') $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } $response->dynamic($function, Response::MODEL_FUNCTION); @@ -197,7 +197,7 @@ App::get('/v1/functions/:functionId/usage') $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } $usage = []; @@ -306,7 +306,7 @@ App::put('/v1/functions/:functionId') $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } $original = $function->getAttribute('schedule', ''); @@ -364,11 +364,11 @@ App::patch('/v1/functions/:functionId/tag') $tag = $dbForProject->getDocument('tags', $tag); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } if ($tag->isEmpty()) { - throw new Exception('Tag not found', 404); + throw new Exception('Tag not found', 404, Exception::DEPLOYMENT_NOT_FOUND); } $schedule = $function->getAttribute('schedule', ''); @@ -416,11 +416,11 @@ App::delete('/v1/functions/:functionId') $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } if (!$dbForProject->deleteDocument('functions', $function->getId())) { - throw new Exception('Failed to remove function from DB', 500); + throw new Exception('Failed to remove function from DB', 500, Exception::GENERAL_SERVER_ERROR); } $deletes @@ -465,7 +465,7 @@ App::post('/v1/functions/:functionId/tags') $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } $file = $request->getFiles('code'); @@ -474,7 +474,7 @@ App::post('/v1/functions/:functionId/tags') $upload = new Upload(); if (empty($file)) { - throw new Exception('No file sent', 400); + throw new Exception('No file sent', 400, Exception::STORAGE_FILE_EMPTY); } // Make sure we handle a single file and multiple files the same way @@ -483,7 +483,7 @@ App::post('/v1/functions/:functionId/tags') $fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size']; if (!$fileExt->isValid($file['name'])) { // Check if file type is allowed - throw new Exception('File type not allowed', 400); + throw new Exception('File type not allowed', 400, Exception::STORAGE_FILE_TYPE_UNSUPPORTED); } $contentRange = $request->getHeader('content-range'); @@ -497,7 +497,7 @@ App::post('/v1/functions/:functionId/tags') $fileSize = $request->getContentRangeSize(); $tagId = $request->getHeader('x-appwrite-id', $tagId); if(is_null($start) || is_null($end) || is_null($fileSize)) { - throw new Exception('Invalid content-range header', 400); + throw new Exception('Invalid content-range header', 400, Exception::STORAGE_INVALID_CONTENT_RANGE); } if ($end === $fileSize) { @@ -511,11 +511,11 @@ App::post('/v1/functions/:functionId/tags') } if (!$fileSizeValidator->isValid($fileSize)) { // Check if file size is exceeding allowed limit - throw new Exception('File size not allowed', 400); + throw new Exception('File size not allowed', 400, Exception::STORAGE_INVALID_FILE_SIZE); } if (!$upload->isValid($fileTmpName)) { - throw new Exception('Invalid file', 403); + throw new Exception('Invalid file', 403, Exception::STORAGE_INVALID_FILE); } // Save to storage @@ -534,7 +534,7 @@ App::post('/v1/functions/:functionId/tags') $chunksUploaded = $deviceFunctions->upload($fileTmpName, $path, $chunk, $chunks); if (empty($chunksUploaded)) { - throw new Exception('Failed moving file', 500); + throw new Exception('Failed moving file', 500, Exception::GENERAL_SERVER_ERROR); } if($chunksUploaded === $chunks) { @@ -610,14 +610,15 @@ App::get('/v1/functions/:functionId/tags') $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } if (!empty($cursor)) { $cursorTag = $dbForProject->getDocument('tags', $cursor); if ($cursorTag->isEmpty()) { - throw new Exception("Tag '{$cursor}' for the 'cursor' value not found.", 400); + // TODO: Shouldn't this be a 404 error ? + throw new Exception("Tag '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -660,17 +661,17 @@ App::get('/v1/functions/:functionId/tags/:tagId') $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } $tag = $dbForProject->getDocument('tags', $tagId); if ($tag->getAttribute('functionId') !== $function->getId()) { - throw new Exception('Tag not found', 404); + throw new Exception('Tag not found', 404, Exception::DEPLOYMENT_NOT_FOUND); } if ($tag->isEmpty()) { - throw new Exception('Tag not found', 404); + throw new Exception('Tag not found', 404, Exception::DEPLOYMENT_NOT_FOUND); } $response->dynamic($tag, Response::MODEL_TAG); @@ -702,22 +703,22 @@ App::delete('/v1/functions/:functionId/tags/:tagId') $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } $tag = $dbForProject->getDocument('tags', $tagId); if ($tag->getAttribute('functionId') !== $function->getId()) { - throw new Exception('Tag not found', 404); + throw new Exception('Tag not found', 404, Exception::DEPLOYMENT_NOT_FOUND); } if ($tag->isEmpty()) { - throw new Exception('Tag not found', 404); + throw new Exception('Tag not found', 404, Exception::DEPLOYMENT_NOT_FOUND); } if ($deviceFunctions->delete($tag->getAttribute('path', ''))) { if (!$dbForProject->deleteDocument('tags', $tag->getId())) { - throw new Exception('Failed to remove tag from DB', 500); + throw new Exception('Failed to remove tag from DB', 500, Exception::GENERAL_SERVER_ERROR); } } @@ -764,23 +765,23 @@ App::post('/v1/functions/:functionId/executions') $function = Authorization::skip(fn() => $dbForProject->getDocument('functions', $functionId)); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } $tag = Authorization::skip(fn() => $dbForProject->getDocument('tags', $function->getAttribute('tag'))); if ($tag->getAttribute('functionId') !== $function->getId()) { - throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404); + throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404, Exception::DEPLOYMENT_NOT_FOUND); } if ($tag->isEmpty()) { - throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404); + throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404, Exception::DEPLOYMENT_NOT_FOUND); } $validator = new Authorization('execute'); if (!$validator->isValid($function->getAttribute('execute'))) { // Check if user has write access to execute function - throw new Exception($validator->getDescription(), 401); + throw new Exception($validator->getDescription(), 401, Exception::USER_UNAUTHORIZED); } $executionId = $dbForProject->getId(); @@ -863,14 +864,14 @@ App::get('/v1/functions/:functionId/executions') $function = Authorization::skip(fn() => $dbForProject->getDocument('functions', $functionId)); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } if (!empty($cursor)) { $cursorExecution = $dbForProject->getDocument('executions', $cursor); if ($cursorExecution->isEmpty()) { - throw new Exception("Execution '{$cursor}' for the 'cursor' value not found.", 400); + throw new Exception("Execution '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -913,17 +914,17 @@ App::get('/v1/functions/:functionId/executions/:executionId') $function = Authorization::skip(fn() => $dbForProject->getDocument('functions', $functionId)); if ($function->isEmpty()) { - throw new Exception('Function not found', 404); + throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } $execution = $dbForProject->getDocument('executions', $executionId); if ($execution->getAttribute('functionId') !== $function->getId()) { - throw new Exception('Execution not found', 404); + throw new Exception('Execution not found', 404, Exception::EXECUTION_NOT_FOUND); } if ($execution->isEmpty()) { - throw new Exception('Execution not found', 404); + throw new Exception('Execution not found', 404, Exception::EXECUTION_NOT_FOUND); } $response->dynamic($execution, Response::MODEL_EXECUTION); diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 037e65b727..bc57aa8ccb 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -2,7 +2,7 @@ use Appwrite\Utopia\Response; use Utopia\App; -use Utopia\Exception; +use Appwrite\Extend\Exception; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; use Appwrite\ClamAV\Network; @@ -75,7 +75,7 @@ App::get('/v1/health/db') $statement->execute(); } catch (Exception $_e) { - throw new Exception('Database is not available', 500); + throw new Exception('Database is not available', 500, Exception::GENERAL_SERVER_ERROR); } $output = [ @@ -109,7 +109,7 @@ App::get('/v1/health/cache') $redis = $utopia->getResource('cache'); if (!$redis->ping(true)) { - throw new Exception('Cache is not available', 500); + throw new Exception('Cache is not available', 500, Exception::GENERAL_SERVER_ERROR); } $output = [ @@ -166,7 +166,7 @@ App::get('/v1/health/time') $diff = ($timestamp - \time()); if ($diff > $gap || $diff < ($gap * -1)) { - throw new Exception('Server time gaps detected'); + throw new Exception('Server time gaps detected', 500, Exception::GENERAL_SERVER_ERROR); } $output = [ @@ -294,11 +294,11 @@ App::get('/v1/health/storage/local') $device = new Local($volume); if (!\is_readable($device->getRoot())) { - throw new Exception('Device '.$key.' dir is not readable'); + throw new Exception('Device '.$key.' dir is not readable', 500, Exception::GENERAL_SERVER_ERROR); } if (!\is_writable($device->getRoot())) { - throw new Exception('Device '.$key.' dir is not writable'); + throw new Exception('Device '.$key.' dir is not writable', 500, Exception::GENERAL_SERVER_ERROR); } } @@ -341,7 +341,7 @@ App::get('/v1/health/anti-virus') $output['version'] = @$antivirus->version(); $output['status'] = (@$antivirus->ping()) ? 'pass' : 'fail'; } catch( \Exception $e) { - throw new Exception('Antivirus is not available', 500); + throw new Exception('Antivirus is not available', 500, Exception::GENERAL_SERVER_ERROR); } } diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 4b127cd672..fd7e8d66fe 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -17,7 +17,7 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; -use Utopia\Exception; +use Appwrite\Extend\Exception; use Utopia\Validator\ArrayList; use Utopia\Validator\Boolean; use Utopia\Validator\Integer; @@ -29,7 +29,7 @@ App::init(function ($project) { /** @var Utopia\Database\Document $project */ if ($project->getId() !== 'console') { - throw new Exception('Access to this API is forbidden.', 401); + throw new Exception('Access to this API is forbidden.', 401, Exception::GENERAL_ACCESS_FORBIDDEN); } }, ['project'], 'projects'); @@ -66,7 +66,7 @@ App::post('/v1/projects') $team = $dbForConsole->getDocument('teams', $teamId); if ($team->isEmpty()) { - throw new Exception('Team not found', 404); + throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } $auth = Config::getParam('auth', []); @@ -175,7 +175,7 @@ App::get('/v1/projects') $cursorProject = $dbForConsole->getDocument('projects', $cursor); if ($cursorProject->isEmpty()) { - throw new Exception("Project '{$cursor}' for the 'cursor' value not found.", 400); + throw new Exception("Project '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -214,7 +214,7 @@ App::get('/v1/projects/:projectId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $response->dynamic($project, Response::MODEL_PROJECT); @@ -245,7 +245,7 @@ App::get('/v1/projects/:projectId/usage') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $usage = []; @@ -364,7 +364,7 @@ App::patch('/v1/projects/:projectId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $project = $dbForConsole->updateDocument('projects', $project->getId(), $project @@ -407,7 +407,7 @@ App::patch('/v1/projects/:projectId/service') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $services = $project->getAttribute('services', []); @@ -441,7 +441,7 @@ App::patch('/v1/projects/:projectId/oauth2') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $providers = $project->getAttribute('providers', []); @@ -474,7 +474,7 @@ App::patch('/v1/projects/:projectId/auth/limit') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $auths = $project->getAttribute('auths', []); @@ -512,7 +512,7 @@ App::patch('/v1/projects/:projectId/auth/:method') $status = ($status === '1' || $status === 'true' || $status === 1 || $status === true); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $auths = $project->getAttribute('auths', []); @@ -545,13 +545,13 @@ App::delete('/v1/projects/:projectId') /** @var Appwrite\Event\Event $deletes */ if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password - throw new Exception('Invalid credentials', 401); + throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); } $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $deletes @@ -560,11 +560,11 @@ App::delete('/v1/projects/:projectId') ; if (!$dbForConsole->deleteDocument('teams', $project->getAttribute('teamId', null))) { - throw new Exception('Failed to remove project team from DB', 500); + throw new Exception('Failed to remove project team from DB', 500, Exception::GENERAL_SERVER_ERROR); } if (!$dbForConsole->deleteDocument('projects', $projectId)) { - throw new Exception('Failed to remove project from DB', 500); + throw new Exception('Failed to remove project from DB', 500, Exception::GENERAL_SERVER_ERROR); } $response->noContent(); @@ -598,7 +598,7 @@ App::post('/v1/projects/:projectId/webhooks') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); @@ -644,7 +644,7 @@ App::get('/v1/projects/:projectId/webhooks') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $webhooks = $dbForConsole->find('webhooks', [ @@ -678,7 +678,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $webhook = $dbForConsole->findOne('webhooks', [ @@ -687,7 +687,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') ]); if ($webhook === false || $webhook->isEmpty()) { - throw new Exception('Webhook not found', 404); + throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND); } $response->dynamic($webhook, Response::MODEL_WEBHOOK); @@ -720,7 +720,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); @@ -731,7 +731,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ]); if ($webhook === false || $webhook->isEmpty()) { - throw new Exception('Webhook not found', 404); + throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND); } $webhook @@ -770,7 +770,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $webhook = $dbForConsole->findOne('webhooks', [ @@ -779,7 +779,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') ]); if($webhook === false || $webhook->isEmpty()) { - throw new Exception('Webhook not found', 404); + throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND); } $dbForConsole->deleteDocument('webhooks', $webhook->getId()); @@ -813,7 +813,7 @@ App::post('/v1/projects/:projectId/keys') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $key = new Document([ @@ -854,7 +854,7 @@ App::get('/v1/projects/:projectId/keys') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $keys = $dbForConsole->find('keys', [ @@ -888,7 +888,7 @@ App::get('/v1/projects/:projectId/keys/:keyId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $key = $dbForConsole->findOne('keys', [ @@ -897,7 +897,7 @@ App::get('/v1/projects/:projectId/keys/:keyId') ]); if ($key === false || $key->isEmpty()) { - throw new Exception('Key not found', 404); + throw new Exception('Key not found', 404, Exception::KEY_NOT_FOUND); } $response->dynamic($key, Response::MODEL_KEY); @@ -926,7 +926,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $key = $dbForConsole->findOne('keys', [ @@ -935,7 +935,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') ]); if ($key === false || $key->isEmpty()) { - throw new Exception('Key not found', 404); + throw new Exception('Key not found', 404, Exception::KEY_NOT_FOUND); } $key @@ -970,7 +970,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $key = $dbForConsole->findOne('keys', [ @@ -979,7 +979,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId') ]); if($key === false || $key->isEmpty()) { - throw new Exception('Key not found', 404); + throw new Exception('Key not found', 404, Exception::KEY_NOT_FOUND); } $dbForConsole->deleteDocument('keys', $key->getId()); @@ -1016,7 +1016,7 @@ App::post('/v1/projects/:projectId/platforms') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $platform = new Document([ @@ -1061,7 +1061,7 @@ App::get('/v1/projects/:projectId/platforms') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $platforms = $dbForConsole->find('platforms', [ @@ -1095,7 +1095,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $platform = $dbForConsole->findOne('platforms', [ @@ -1104,7 +1104,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId') ]); if ($platform === false || $platform->isEmpty()) { - throw new Exception('Platform not found', 404); + throw new Exception('Platform not found', 404, Exception::PLATFORM_NOT_FOUND); } $response->dynamic($platform, Response::MODEL_PLATFORM); @@ -1135,7 +1135,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $platform = $dbForConsole->findOne('platforms', [ @@ -1144,7 +1144,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ]); if ($platform === false || $platform->isEmpty()) { - throw new Exception('Platform not found', 404); + throw new Exception('Platform not found', 404, Exception::PLATFORM_NOT_FOUND); } $platform @@ -1182,7 +1182,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $platform = $dbForConsole->findOne('platforms', [ @@ -1191,7 +1191,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') ]); if ($platform === false || $platform->isEmpty()) { - throw new Exception('Platform not found', 404); + throw new Exception('Platform not found', 404, Exception::PLATFORM_NOT_FOUND); } $dbForConsole->deleteDocument('platforms', $platformId); @@ -1224,7 +1224,7 @@ App::post('/v1/projects/:projectId/domains') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $document = $dbForConsole->findOne('domains', [ @@ -1233,13 +1233,13 @@ App::post('/v1/projects/:projectId/domains') ]); if ($document && !$document->isEmpty()) { - throw new Exception('Domain already exists', 409); + throw new Exception('Domain already exists', 409, Exception::DOMAIN_ALREADY_EXISTS); } $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); if (!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500); + throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500, Exception::GENERAL_SERVER_ERROR); } $domain = new Domain($domain); @@ -1285,7 +1285,7 @@ App::get('/v1/projects/:projectId/domains') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $domains = $dbForConsole->find('domains', [ @@ -1319,7 +1319,7 @@ App::get('/v1/projects/:projectId/domains/:domainId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $domain = $dbForConsole->findOne('domains', [ @@ -1328,7 +1328,7 @@ App::get('/v1/projects/:projectId/domains/:domainId') ]); if ($domain === false || $domain->isEmpty()) { - throw new Exception('Domain not found', 404); + throw new Exception('Domain not found', 404, Exception::DOMAIN_NOT_FOUND); } $response->dynamic($domain, Response::MODEL_DOMAIN); @@ -1355,7 +1355,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $domain = $dbForConsole->findOne('domains', [ @@ -1364,13 +1364,13 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') ]); if ($domain === false || $domain->isEmpty()) { - throw new Exception('Domain not found', 404); + throw new Exception('Domain not found', 404, Exception::DOMAIN_NOT_FOUND); } $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); if (!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500); + throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500, Exception::GENERAL_SERVER_ERROR); } if ($domain->getAttribute('verification') === true) { @@ -1380,7 +1380,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') $validator = new CNAME($target->get()); // Verify Domain with DNS records if (!$validator->isValid($domain->getAttribute('domain', ''))) { - throw new Exception('Failed to verify domain', 401); + throw new Exception('Failed to verify domain', 401, Exception::DOMAIN_VERIFICATION_FAILED); } @@ -1417,7 +1417,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId') $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } $domain = $dbForConsole->findOne('domains', [ @@ -1426,7 +1426,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ]); if ($domain === false || $domain->isEmpty()) { - throw new Exception('Domain not found', 404); + throw new Exception('Domain not found', 404, Exception::DOMAIN_NOT_FOUND); } $dbForConsole->deleteDocument('domains', $domain->getId()); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 03339634d8..691c870c9b 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -18,7 +18,7 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\UID; -use Utopia\Exception; +use Appwrite\Extend\Exception; use Utopia\Image\Image; use Utopia\Storage\Compression\Algorithms\GZIP; use Utopia\Storage\Device\Local; @@ -71,7 +71,7 @@ App::post('/v1/storage/buckets') try { $files = Config::getParam('collections', [])['files'] ?? []; if (empty($files)) { - throw new Exception('Files collection is not configured.'); + throw new Exception('Files collection is not configured.', 500, Exception::GENERAL_SERVER_ERROR); } $attributes = []; @@ -118,7 +118,7 @@ App::post('/v1/storage/buckets') 'search' => implode(' ', [$bucketId, $name]), ])); } catch (Duplicate $th) { - throw new Exception('Bucket already exists', 409); + throw new Exception('Bucket already exists', 409, Exception::STORAGE_BUCKET_ALREADY_EXISTS); } $audits @@ -164,7 +164,7 @@ App::get('/v1/storage/buckets') $cursorBucket = $dbForProject->getDocument('buckets', $cursor); if ($cursorBucket->isEmpty()) { - throw new Exception("Bucket '{$cursor}' for the 'cursor' value not found.", 400); + throw new Exception("Bucket '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -199,7 +199,7 @@ App::get('/v1/storage/buckets/:bucketId') $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty()) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } $usage->setParam('storage.buckets.read', 1); @@ -242,7 +242,7 @@ App::put('/v1/storage/buckets/:bucketId') $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty()) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } $read??=$bucket->getAttribute('$read', []); // By default inherit read permissions @@ -305,11 +305,11 @@ App::delete('/v1/storage/buckets/:bucketId') $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty()) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } if (!$dbForProject->deleteDocument('buckets', $bucketId)) { - throw new Exception('Failed to remove project from DB', 500); + throw new Exception('Failed to remove project from DB', 500, Exception::GENERAL_SERVER_ERROR); } $deletes @@ -378,7 +378,7 @@ App::post('/v1/storage/buckets/:bucketId/files') if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } // Check bucket permissions when enforced @@ -386,7 +386,7 @@ App::post('/v1/storage/buckets/:bucketId/files') if ($permissionBucket) { $validator = new Authorization('write'); if (!$validator->isValid($bucket->getWrite())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -399,12 +399,12 @@ App::post('/v1/storage/buckets/:bucketId/files') if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) { foreach ($read as $role) { if (!Authorization::isRole($role)) { - throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400); + throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED); } } foreach ($write as $role) { if (!Authorization::isRole($role)) { - throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400); + throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED); } } } @@ -419,12 +419,12 @@ App::post('/v1/storage/buckets/:bucketId/files') $maximumFileSize = $bucket->getAttribute('maximumFileSize', 0); if ($maximumFileSize > (int) App::getEnv('_APP_STORAGE_LIMIT', 0)) { - throw new Exception('Error bucket maximum file size is larger than _APP_STORAGE_LIMIT', 500); + throw new Exception('Error bucket maximum file size is larger than _APP_STORAGE_LIMIT', 500, Exception::GENERAL_SERVER_ERROR); } $file = $request->getFiles('file'); if (empty($file)) { - throw new Exception('No file sent', 400); + throw new Exception('No file sent', 400, Exception::STORAGE_FILE_EMPTY); } // Make sure we handle a single file and multiple files the same way @@ -443,7 +443,7 @@ App::post('/v1/storage/buckets/:bucketId/files') $fileSize = $request->getContentRangeSize(); $fileId = $request->getHeader('x-appwrite-id', $fileId); if (is_null($start) || is_null($end) || is_null($fileSize)) { - throw new Exception('Invalid content-range header', 400); + throw new Exception('Invalid content-range header', 400, Exception::STORAGE_INVALID_CONTENT_RANGE); } if ($end === $fileSize) { @@ -457,24 +457,24 @@ App::post('/v1/storage/buckets/:bucketId/files') } /** - * Validators - */ + * Validators + */ // Check if file type is allowed $allowedFileExtensions = $bucket->getAttribute('allowedFileExtensions', []); $fileExt = new FileExt($allowedFileExtensions); if (!empty($allowedFileExtensions) && !$fileExt->isValid($fileName)) { - throw new Exception('File extension not allowed', 400); + throw new Exception('File extension not allowed', 400, Exception::STORAGE_FILE_TYPE_UNSUPPORTED); } // Check if file size is exceeding allowed limit $fileSizeValidator = new FileSize($maximumFileSize); if (!$fileSizeValidator->isValid($fileSize)) { - throw new Exception('File size not allowed', 400); + throw new Exception('File size not allowed', 400, Exception::STORAGE_INVALID_FILE_SIZE); } $upload = new Upload(); if (!$upload->isValid($fileTmpName)) { - throw new Exception('Invalid file', 403); + throw new Exception('Invalid file', 403, Exception::STORAGE_INVALID_FILE); } // Save to storage @@ -501,7 +501,7 @@ App::post('/v1/storage/buckets/:bucketId/files') $chunksUploaded = $deviceFiles->upload($fileTmpName, $path, $chunk, $chunks, $metadata); if (empty($chunksUploaded)) { - throw new Exception('Failed uploading file', 500); + throw new Exception('Failed uploading file', 500, Exception::GENERAL_SERVER_ERROR); } $read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; @@ -513,7 +513,7 @@ App::post('/v1/storage/buckets/:bucketId/files') if (!$antivirus->fileScan($path)) { $deviceFiles->delete($path); - throw new Exception('Invalid file', 400); + throw new Exception('Invalid file', 400, Exception::STORAGE_INVALID_FILE); } } @@ -537,7 +537,7 @@ App::post('/v1/storage/buckets/:bucketId/files') if (!empty($data)) { if (!$deviceFiles->write($path, $data, $mimeType)) { - throw new Exception('Failed to save file', 500); + throw new Exception('Failed to save file', 500, Exception::GENERAL_SERVER_ERROR); } } @@ -610,9 +610,9 @@ App::post('/v1/storage/buckets/:bucketId/files') } } catch (StructureException $exception) { - throw new Exception($exception->getMessage(), 400); + throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE); } catch (DuplicateException $exception) { - throw new Exception('Document already exists', 409); + throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS); } $audits @@ -669,9 +669,9 @@ App::post('/v1/storage/buckets/:bucketId/files') } } } catch (StructureException $exception) { - throw new Exception($exception->getMessage(), 400); + throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE); } catch (DuplicateException $exception) { - throw new Exception('Document already exists', 409); + throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS); } } @@ -717,14 +717,14 @@ App::get('/v1/storage/buckets/:bucketId/files') if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } // Check bucket permissions when enforced if ($bucket->getAttribute('permission') === 'bucket') { $validator = new Authorization('read'); if (!$validator->isValid($bucket->getRead())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -744,7 +744,7 @@ App::get('/v1/storage/buckets/:bucketId/files') } if ($cursorFile->isEmpty()) { - throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400); + throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -801,14 +801,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } // Check bucket permissions when enforced if ($bucket->getAttribute('permission') === 'bucket') { $validator = new Authorization('read'); if (!$validator->isValid($bucket->getRead())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -821,7 +821,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { - throw new Exception('File not found', 404); + throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND); } $usage ->setParam('storage.files.read', 1) @@ -871,21 +871,21 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') /** @var string $mode */ if (!\extension_loaded('imagick')) { - throw new Exception('Imagick extension is missing', 500); + throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR); } $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } // Check bucket permissions when enforced if ($bucket->getAttribute('permission') === 'bucket') { $validator = new Authorization('read'); if (!$validator->isValid($bucket->getRead())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::STORAGE_BUCKET_NOT_FOUND); } } @@ -910,7 +910,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { - throw new Exception('File not found', 404); + throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND); } $path = $file->getAttribute('path'); @@ -938,7 +938,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $compressor = new GZIP(); if (!$deviceFiles->exists($path)) { - throw new Exception('File not found', 404); + throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND); } $cache = new Cache(new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId() . DIRECTORY_SEPARATOR . $bucketId . DIRECTORY_SEPARATOR . $fileId)); // Limit file number or size @@ -1049,14 +1049,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } // Check bucket permissions when enforced if ($bucket->getAttribute('permission') === 'bucket') { $validator = new Authorization('read'); if (!$validator->isValid($bucket->getRead())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -1069,13 +1069,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { - throw new Exception('File not found', 404); + throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND); } $path = $file->getAttribute('path', ''); if (!$deviceFiles->exists($path)) { - throw new Exception('File not found in ' . $path, 404); + throw new Exception('File not found in ' . $path, 404, Exception::STORAGE_FILE_NOT_FOUND); } $usage @@ -1103,7 +1103,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') } if ($unit !== 'bytes' || $start >= $end || $end >= $size) { - throw new Exception('Invalid range', 416); + throw new Exception('Invalid range', 416, Exception::STORAGE_INVALID_RANGE); } $response @@ -1194,14 +1194,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } // Check bucket permissions when enforced if ($bucket->getAttribute('permission') === 'bucket') { $validator = new Authorization('read'); if (!$validator->isValid($bucket->getRead())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -1216,13 +1216,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') $mimes = Config::getParam('storage-mimes'); if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { - throw new Exception('File not found', 404); + throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND); } $path = $file->getAttribute('path', ''); if (!$deviceFiles->exists($path)) { - throw new Exception('File not found in ' . $path, 404); + throw new Exception('File not found in ' . $path, 404, Exception::STORAGE_FILE_NOT_FOUND); } $compressor = new GZIP(); @@ -1255,7 +1255,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') } if ($unit != 'bytes' || $start >= $end || $end >= $size) { - throw new Exception('Invalid range', 416); + throw new Exception('Invalid range', 416, Exception::STORAGE_INVALID_RANGE); } $response @@ -1363,26 +1363,26 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) { foreach ($read as $role) { if (!Authorization::isRole($role)) { - throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400); + throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED); } } foreach ($write as $role) { if (!Authorization::isRole($role)) { - throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400); + throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED); } } } if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } // Check bucket permissions when enforced if ($bucket->getAttribute('permission') === 'bucket') { $validator = new Authorization('write'); if (!$validator->isValid($bucket->getWrite())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -1395,7 +1395,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { - throw new Exception('File not found', 404); + throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND); } if ($bucket->getAttribute('permission') === 'bucket') { @@ -1463,14 +1463,14 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } // Check bucket permissions when enforced if ($bucket->getAttribute('permission') === 'bucket') { $validator = new Authorization('write'); if (!$validator->isValid($bucket->getWrite())) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } } @@ -1483,7 +1483,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { - throw new Exception('File not found', 404); + throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND); } $deviceDeleted = false; @@ -1510,10 +1510,10 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') $deleted = $dbForProject->deleteDocument('bucket_' . $bucketId, $fileId); } if (!$deleted) { - throw new Exception('Failed to remove file from DB', 500); + throw new Exception('Failed to remove file from DB', 500, Exception::GENERAL_SERVER_ERROR); } } else { - throw new Exception('Failed to delete file from device', 500); + throw new Exception('Failed to delete file from device', 500, Exception::GENERAL_SERVER_ERROR); } $audits @@ -1667,7 +1667,7 @@ App::get('/v1/storage/:bucketId/usage') $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty()) { - throw new Exception('Bucket not found', 404); + throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } $usage = []; diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index de3b2c4cba..fb388a5c12 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -8,6 +8,7 @@ use Appwrite\Template\Template; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Response; use Utopia\App; +use Appwrite\Extend\Exception; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -17,7 +18,6 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Key; use Utopia\Database\Validator\UID; -use Utopia\Exception; use Utopia\Validator\Text; use Utopia\Validator\Range; use Utopia\Validator\ArrayList; @@ -117,7 +117,7 @@ App::get('/v1/teams') $cursorTeam = $dbForProject->getDocument('teams', $cursor); if ($cursorTeam->isEmpty()) { - throw new Exception("Team '{$cursor}' for the 'cursor' value not found.", 400); + throw new Exception("Team '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -157,7 +157,7 @@ App::get('/v1/teams/:teamId') $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { - throw new Exception('Team not found', 404); + throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } $response->dynamic($team, Response::MODEL_TEAM); @@ -186,7 +186,7 @@ App::put('/v1/teams/:teamId') $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { - throw new Exception('Team not found', 404); + throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } $team = $dbForProject->updateDocument('teams', $team->getId(),$team @@ -222,7 +222,7 @@ App::delete('/v1/teams/:teamId') $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { - throw new Exception('Team not found', 404); + throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } $memberships = $dbForProject->find('memberships', [ @@ -232,12 +232,12 @@ App::delete('/v1/teams/:teamId') // TODO delete all members individually from the user object foreach ($memberships as $membership) { if (!$dbForProject->deleteDocument('memberships', $membership->getId())) { - throw new Exception('Failed to remove membership for team from DB', 500); + throw new Exception('Failed to remove membership for team from DB', 500, Exception::GENERAL_SERVER_ERROR); } } if (!$dbForProject->deleteDocument('teams', $teamId)) { - throw new Exception('Failed to remove team from DB', 500); + throw new Exception('Failed to remove team from DB', 500, Exception::GENERAL_SERVER_ERROR); } $deletes @@ -287,7 +287,7 @@ App::post('/v1/teams/:teamId/memberships') /** @var Appwrite\Event\Event $mails */ if(empty(App::getEnv('_APP_SMTP_HOST'))) { - throw new Exception('SMTP Disabled', 503); + throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED); } $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -298,7 +298,7 @@ App::post('/v1/teams/:teamId/memberships') $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { - throw new Exception('Team not found', 404); + throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } $invitee = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address @@ -311,7 +311,7 @@ App::post('/v1/teams/:teamId/memberships') $sum = $dbForProject->count('users', [], APP_LIMIT_USERS); if($sum >= $limit) { - throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); + throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED); } } @@ -341,14 +341,14 @@ App::post('/v1/teams/:teamId/memberships') 'search' => implode(' ', [$userId, $email, $name]), ]))); } catch (Duplicate $th) { - throw new Exception('Account already exists', 409); + throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS); } } $isOwner = Authorization::isRole('team:'.$team->getId().'/owner');; if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) - throw new Exception('User is not allowed to send invitations for this team', 401); + throw new Exception('User is not allowed to send invitations for this team', 401, Exception::USER_UNAUTHORIZED); } $secret = Auth::tokenGenerator(); @@ -370,7 +370,7 @@ App::post('/v1/teams/:teamId/memberships') try { $membership = Authorization::skip(fn() => $dbForProject->createDocument('memberships', $membership)); } catch (Duplicate $th) { - throw new Exception('User has already been invited or is already a member of this team', 409); + throw new Exception('User has already been invited or is already a member of this team', 409, Exception::TEAM_INVITE_ALREADY_EXISTS); } $team->setAttribute('sum', $team->getAttribute('sum', 0) + 1); $team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team)); @@ -383,7 +383,7 @@ App::post('/v1/teams/:teamId/memberships') try { $membership = $dbForProject->createDocument('memberships', $membership); } catch (Duplicate $th) { - throw new Exception('User has already been invited or is already a member of this team', 409); + throw new Exception('User has already been invited or is already a member of this team', 409, Exception::TEAM_INVITE_ALREADY_EXISTS); } } @@ -447,14 +447,14 @@ App::get('/v1/teams/:teamId/memberships') $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { - throw new Exception('Team not found', 404); + throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } if (!empty($cursor)) { $cursorMembership = $dbForProject->getDocument('memberships', $cursor); if ($cursorMembership->isEmpty()) { - throw new Exception("Membership '{$cursor}' for the 'cursor' value not found.", 400); + throw new Exception("Membership '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -502,13 +502,13 @@ App::get('/v1/teams/:teamId/memberships/:membershipId') $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { - throw new Exception('Team not found', 404); + throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } $membership = $dbForProject->getDocument('memberships', $membershipId); if($membership->isEmpty() || empty($membership->getAttribute('userId'))) { - throw new Exception('Membership not found', 404); + throw new Exception('Membership not found', 404, Exception::MEMBERSHIP_NOT_FOUND); } $user = $dbForProject->getDocument('users', $membership->getAttribute('userId')); @@ -550,17 +550,17 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { - throw new Exception('Team not found', 404); + throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } $membership = $dbForProject->getDocument('memberships', $membershipId); if ($membership->isEmpty()) { - throw new Exception('Membership not found', 404); + throw new Exception('Membership not found', 404, Exception::MEMBERSHIP_NOT_FOUND); } $profile = $dbForProject->getDocument('users', $membership->getAttribute('userId')); if ($profile->isEmpty()) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -568,7 +568,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') $isOwner = Authorization::isRole('team:'.$team->getId().'/owner');; if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) - throw new Exception('User is not allowed to modify roles', 401); + throw new Exception('User is not allowed to modify roles', 401, Exception::USER_UNAUTHORIZED); } // Update the roles @@ -621,25 +621,25 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $membership = $dbForProject->getDocument('memberships', $membershipId); if ($membership->isEmpty()) { - throw new Exception('Membership not found', 404); + throw new Exception('Membership not found', 404, Exception::MEMBERSHIP_NOT_FOUND); } if ($membership->getAttribute('teamId') !== $teamId) { - throw new Exception('Team IDs don\'t match', 404); + throw new Exception('Team IDs don\'t match', 404, Exception::TEAM_MEMBERSHIP_MISMATCH); } $team = Authorization::skip(fn() => $dbForProject->getDocument('teams', $teamId)); if ($team->isEmpty()) { - throw new Exception('Team not found', 404); + throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } if (Auth::hash($secret) !== $membership->getAttribute('secret')) { - throw new Exception('Secret key not valid', 401); + throw new Exception('Secret key not valid', 401, Exception::TEAM_INVALID_SECRET); } if ($userId != $membership->getAttribute('userId')) { - throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401); + throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401, Exception::TEAM_INVITE_MISMATCH); } if ($user->isEmpty()) { @@ -647,7 +647,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') } if ($membership->getAttribute('userId') !== $user->getId()) { - throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401); + throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401, Exception::TEAM_INVITE_MISMATCH); } $membership // Attach user to team @@ -743,7 +743,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') $membership = $dbForProject->getDocument('memberships', $membershipId); if ($membership->isEmpty()) { - throw new Exception('Invite not found', 404); + throw new Exception('Invite not found', 404, Exception::TEAM_INVITE_NOT_FOUND); } if ($membership->getAttribute('teamId') !== $teamId) { @@ -753,21 +753,21 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') $user = $dbForProject->getDocument('users', $membership->getAttribute('userId')); if ($user->isEmpty()) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { - throw new Exception('Team not found', 404); + throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } try { $dbForProject->deleteDocument('memberships', $membership->getId()); } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized permissions', 401); + throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED); } catch (\Exception $exception) { - throw new Exception('Failed to remove membership from DB', 500); + throw new Exception('Failed to remove membership from DB', 500, Exception::GENERAL_SERVER_ERROR); } $memberships = $user->getAttribute('memberships', []); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 0bd8b1fc00..f8bc8ca154 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -9,7 +9,7 @@ use Appwrite\Utopia\Response; use Utopia\App; use Utopia\Audit\Audit; use Utopia\Config\Config; -use Utopia\Exception; +use Appwrite\Extend\Exception; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Validator\UID; @@ -70,7 +70,7 @@ App::post('/v1/users') 'deleted' => false ])); } catch (Duplicate $th) { - throw new Exception('Account already exists', 409); + throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS); } $usage @@ -110,7 +110,7 @@ App::get('/v1/users') $cursorUser = $dbForProject->getDocument('users', $cursor); if ($cursorUser->isEmpty()) { - throw new Exception("User '{$cursor}' for the 'cursor' value not found.", 400); + throw new Exception("User '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } } @@ -155,7 +155,7 @@ App::get('/v1/users/:userId') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $usage @@ -187,7 +187,7 @@ App::get('/v1/users/:userId/prefs') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $prefs = $user->getAttribute('prefs', new \stdClass()); @@ -223,7 +223,7 @@ App::get('/v1/users/:userId/sessions') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $sessions = $user->getAttribute('sessions', []); @@ -277,7 +277,7 @@ App::get('/v1/users/:userId/logs') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $audit = new Audit($dbForProject); @@ -378,7 +378,7 @@ App::patch('/v1/users/:userId/status') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', (bool) $status)); @@ -414,7 +414,7 @@ App::patch('/v1/users/:userId/verification') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', $emailVerification)); @@ -450,7 +450,7 @@ App::patch('/v1/users/:userId/name') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('name', $name)); @@ -489,7 +489,7 @@ App::patch('/v1/users/:userId/password') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $user @@ -532,7 +532,7 @@ App::patch('/v1/users/:userId/email') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password')); // Check if request is from an anonymous account for converting @@ -545,7 +545,7 @@ App::patch('/v1/users/:userId/email') try { $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('email', $email)); } catch(Duplicate $th) { - throw new Exception('Email already exists', 409); + throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS); } $audits @@ -582,7 +582,7 @@ App::patch('/v1/users/:userId/prefs') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs)); @@ -619,7 +619,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $sessions = $user->getAttribute('sessions', []); @@ -674,7 +674,7 @@ App::delete('/v1/users/:userId/sessions') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } $sessions = $user->getAttribute('sessions', []); @@ -723,7 +723,7 @@ App::delete('/v1/users/:userId') $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { - throw new Exception('User not found', 404); + throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } /** diff --git a/app/controllers/general.php b/app/controllers/general.php index 5b347cefa4..afe9a5f5e2 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -8,7 +8,7 @@ use Utopia\Logger\Log\User; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Appwrite\Utopia\View; -use Utopia\Exception; +use Appwrite\Extend\Exception; use Utopia\Config\Config; use Utopia\Domains\Domain; use Appwrite\Auth\Auth; @@ -106,11 +106,11 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons } if ($project->isEmpty()) { - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } if (!empty($route->getLabel('sdk.auth', [])) && $project->isEmpty() && ($route->getLabel('scope', '') !== 'public')) { - throw new Exception('Missing or unknown project ID', 400); + throw new Exception('Missing or unknown project ID', 400, Exception::PROJECT_UNKNOWN); } $referrer = $request->getReferer(); @@ -203,7 +203,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons && \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE]) && $route->getLabel('origin', false) !== '*' && empty($request->getHeader('x-appwrite-key', ''))) { - throw new Exception($originValidator->getDescription(), 403); + throw new Exception($originValidator->getDescription(), 403, Exception::GENERAL_UNKNOWN_ORIGIN); } /* @@ -272,24 +272,24 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons if(array_key_exists($service, $project->getAttribute('services',[])) && !$project->getAttribute('services',[])[$service] && !Auth::isPrivilegedUser(Authorization::getRoles())) { - throw new Exception('Service is disabled', 503); + throw new Exception('Service is disabled', 503, Exception::GENERAL_SERVICE_DISABLED); } } if (!\in_array($scope, $scopes)) { if ($project->isEmpty()) { // Check if permission is denied because project is missing - throw new Exception('Project not found', 404); + throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND); } - throw new Exception($user->getAttribute('email', 'User').' (role: '.\strtolower($roles[$role]['label']).') missing scope ('.$scope.')', 401); + throw new Exception($user->getAttribute('email', 'User').' (role: '.\strtolower($roles[$role]['label']).') missing scope ('.$scope.')', 401, Exception::GENERAL_UNAUTHORIZED_SCOPE); } if (false === $user->getAttribute('status')) { // Account is blocked - throw new Exception('Invalid credentials. User is blocked', 401); + throw new Exception('Invalid credentials. User is blocked', 401, Exception::USER_BLOCKED); } if ($user->getAttribute('reset')) { - throw new Exception('Password reset is required', 412); + throw new Exception('Password reset is required', 412, Exception::USER_PASSWORD_RESET_REQUIRED); } }, ['utopia', 'request', 'response', 'console', 'project', 'dbForConsole', 'user', 'locale', 'clients']); @@ -376,6 +376,26 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l throw $error; } + + /** Handle Utopia Errors */ + if ($error instanceof Utopia\Exception) { + $code = $error->getCode(); + $error = new Exception($error->getMessage(), $code, Exception::GENERAL_UNKNOWN, $error); + switch($code) { + case 400: + $error->setType(Exception::GENERAL_ARGUMENT_INVALID); + break; + case 404: + $error->setType(Exception::GENERAL_ROUTE_NOT_FOUND); + break; + } + } + + /** Wrap all exceptions inside Appwrite\Extend\Exception */ + if (!($error instanceof Exception)) { + $error = new Exception($error->getMessage(), $error->getCode(), Exception::GENERAL_UNKNOWN, $error); + } + $template = ($route) ? $route->getLabel('error', null) : null; if (php_sapi_name() === 'cli') { @@ -421,10 +441,12 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l 'line' => $error->getLine(), 'trace' => $error->getTrace(), 'version' => $version, + 'type' => $error->getType(), ] : [ 'message' => $message, 'code' => $code, 'version' => $version, + 'type' => $error->getType(), ]; $response @@ -537,7 +559,7 @@ App::get('/.well-known/acme-challenge') $absolute = \realpath($base.'/.well-known/acme-challenge/'.$path); if (!$base) { - throw new Exception('Storage error', 500); + throw new Exception('Storage error', 500, Exception::GENERAL_SERVER_ERROR); } if (!$absolute) { @@ -555,7 +577,7 @@ App::get('/.well-known/acme-challenge') $content = @\file_get_contents($absolute); if (!$content) { - throw new Exception('Failed to get contents', 500); + throw new Exception('Failed to get contents', 500, Exception::GENERAL_SERVER_ERROR); } $response->text($content); diff --git a/app/controllers/mock.php b/app/controllers/mock.php index ea437bf3c5..a45bdf641e 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -2,6 +2,7 @@ global $utopia, $request, $response; +use Appwrite\Extend\Exception; use Utopia\Database\Document; use Appwrite\Network\Validator\Host; use Appwrite\Utopia\Response; @@ -251,32 +252,32 @@ App::post('/v1/mock/tests/general/upload') $file['size'] = (\is_array($file['size'])) ? $file['size'] : [$file['size']]; if(is_null($start) || is_null($end) || is_null($size)) { - throw new Exception('Invalid content-range header', 400); + throw new Exception('Invalid content-range header', 400, Exception::GENERAL_MOCK); } if($start > $end || $end > $size) { - throw new Exception('Invalid content-range header', 400); + throw new Exception('Invalid content-range header', 400, Exception::GENERAL_MOCK); } if($start === 0 && !empty($id)) { - throw new Exception('First chunked request cannot have id header', 400); + throw new Exception('First chunked request cannot have id header', 400, Exception::GENERAL_MOCK); } if($start !== 0 && $id !== 'newfileid') { - throw new Exception('All chunked request must have id header (except first)', 400); + throw new Exception('All chunked request must have id header (except first)', 400, Exception::GENERAL_MOCK); } if($end !== $size && $end-$start+1 !== 5*1024*1024) { - throw new Exception('Chunk size must be 5MB (except last chunk)', 400); + throw new Exception('Chunk size must be 5MB (except last chunk)', 400, Exception::GENERAL_MOCK); } foreach ($file['size'] as $i => $sz) { if ($end !== $size && $sz !== 5*1024*1024) { - throw new Exception('Wrong chunk size', 400); + throw new Exception('Wrong chunk size', 400, Exception::GENERAL_MOCK); } if($sz > 5*1024*1024) { - throw new Exception('Chunk size must be 5MB or less', 400); + throw new Exception('Chunk size must be 5MB or less', 400, Exception::GENERAL_MOCK); } } if($end !== $size) { @@ -289,19 +290,19 @@ App::post('/v1/mock/tests/general/upload') foreach ($file['name'] as $i => $name) { if ($name !== 'file.png') { - throw new Exception('Wrong file name', 400); + throw new Exception('Wrong file name', 400, Exception::GENERAL_MOCK); } } foreach ($file['size'] as $i => $size) { if ($size !== 38756) { - throw new Exception('Wrong file size', 400); + throw new Exception('Wrong file size', 400, Exception::GENERAL_MOCK); } } foreach ($file['tmp_name'] as $i => $tmpName) { if (\md5(\file_get_contents($tmpName)) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') { - throw new Exception('Wrong file uploaded', 400); + throw new Exception('Wrong file uploaded', 400, Exception::GENERAL_MOCK); } } } @@ -379,7 +380,7 @@ App::get('/v1/mock/tests/general/get-cookie') /** @var Appwrite\Utopia\Request $request */ if ($request->getCookie('cookieName', '') !== 'cookieValue') { - throw new Exception('Missing cookie value', 400); + throw new Exception('Missing cookie value', 400, Exception::GENERAL_MOCK); } }); @@ -414,7 +415,7 @@ App::get('/v1/mock/tests/general/400-error') ->label('sdk.response.model', Response::MODEL_ERROR) ->label('sdk.mock', true) ->action(function () { - throw new Exception('Mock 400 error', 400); + throw new Exception('Mock 400 error', 400, Exception::GENERAL_MOCK); }); App::get('/v1/mock/tests/general/500-error') @@ -430,7 +431,7 @@ App::get('/v1/mock/tests/general/500-error') ->label('sdk.response.model', Response::MODEL_ERROR) ->label('sdk.mock', true) ->action(function () { - throw new Exception('Mock 500 error', 500); + throw new Exception('Mock 500 error', 500, Exception::GENERAL_MOCK); }); App::get('/v1/mock/tests/general/502-error') @@ -489,11 +490,11 @@ App::get('/v1/mock/tests/general/oauth2/token') /** @var Appwrite\Utopia\Response $response */ if ($client_id != '1') { - throw new Exception('Invalid client ID'); + throw new Exception('Invalid client ID', 400, Exception::GENERAL_MOCK); } if ($client_secret != '123456') { - throw new Exception('Invalid client secret'); + throw new Exception('Invalid client secret', 400, Exception::GENERAL_MOCK); } $responseJson = [ @@ -504,18 +505,18 @@ App::get('/v1/mock/tests/general/oauth2/token') if($grantType === 'authorization_code') { if ($code !== 'abcdef') { - throw new Exception('Invalid token'); + throw new Exception('Invalid token', 400, Exception::GENERAL_MOCK); } $response->json($responseJson); } else if($grantType === 'refresh_token') { if ($refreshToken !== 'tuvwxyz') { - throw new Exception('Invalid refresh token'); + throw new Exception('Invalid refresh token', 400, Exception::GENERAL_MOCK); } $response->json($responseJson); } else { - throw new Exception('Invalid grant type'); + throw new Exception('Invalid grant type', 400, Exception::GENERAL_MOCK); } }); @@ -530,7 +531,7 @@ App::get('/v1/mock/tests/general/oauth2/user') /** @var Appwrite\Utopia\Response $response */ if ($token != '123456') { - throw new Exception('Invalid token'); + throw new Exception('Invalid token', 400, Exception::GENERAL_MOCK); } $response->json([ @@ -581,7 +582,7 @@ App::shutdown(function($utopia, $response, $request) { $tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : []; if (!\is_array($tests)) { - throw new Exception('Failed to read results', 500); + throw new Exception('Failed to read results', 500, Exception::GENERAL_MOCK); } $result[$route->getMethod() . ':' . $route->getPath()] = true; @@ -589,7 +590,7 @@ App::shutdown(function($utopia, $response, $request) { $tests = \array_merge($tests, $result); if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) { - throw new Exception('Failed to save results', 500); + throw new Exception('Failed to save results', 500, Exception::GENERAL_MOCK); } $response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 51b6bfe5f8..ab338bdd2e 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -3,12 +3,12 @@ use Appwrite\Auth\Auth; use Appwrite\Messaging\Adapter\Realtime; use Utopia\App; +use Appwrite\Extend\Exception; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Database\Document; use Utopia\Storage\Device\DOSpaces; use Utopia\Database\Validator\Authorization; -use Utopia\Exception; use Utopia\Storage\Device\Local; use Utopia\Storage\Device\S3; use Utopia\Storage\Storage; @@ -31,7 +31,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud $route = $utopia->match($request); if ($project->isEmpty() && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope - throw new Exception('Missing or unknown project ID', 400); + throw new Exception('Missing or unknown project ID', 400, Exception::PROJECT_UNKNOWN); } /* @@ -80,7 +80,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not disabled && (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key { - throw new Exception('Too many requests', 429); + throw new Exception('Too many requests', 429, Exception::GENERAL_RATE_LIMIT_EXCEEDED); } } @@ -149,36 +149,36 @@ App::init(function ($utopia, $request, $project) { switch ($route->getLabel('auth.type', '')) { case 'emailPassword': if(($auths['emailPassword'] ?? true) === false) { - throw new Exception('Email / Password authentication is disabled for this project', 501); + throw new Exception('Email / Password authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED); } break; case 'magic-url': if($project->getAttribute('usersAuthMagicURL', true) === false) { - throw new Exception('Magic URL authentication is disabled for this project', 501); + throw new Exception('Magic URL authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED); } break; case 'anonymous': if(($auths['anonymous'] ?? true) === false) { - throw new Exception('Anonymous authentication is disabled for this project', 501); + throw new Exception('Anonymous authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED); } break; case 'invites': if(($auths['invites'] ?? true) === false) { - throw new Exception('Invites authentication is disabled for this project', 501); + throw new Exception('Invites authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED); } break; case 'jwt': if(($auths['JWT'] ?? true) === false) { - throw new Exception('JWT authentication is disabled for this project', 501); + throw new Exception('JWT authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED); } break; default: - throw new Exception('Unsupported authentication route'); + throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED); break; } diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 9911b5c7c5..12cb625a8e 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -1,5 +1,6 @@ json(['version' => $version['version']]); } else { - throw new Exception('Failed to check for a newer version', 500); + throw new Exception('Failed to check for a newer version', 500, Exception::GENERAL_SERVER_ERROR); } } catch (\Throwable $th) { - throw new Exception('Failed to check for a newer version', 500); + throw new Exception('Failed to check for a newer version', 500, Exception::GENERAL_SERVER_ERROR); } }); \ No newline at end of file diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 124833427a..30e629a02f 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -1,14 +1,8 @@ set('logger', function () { // Register error logger } if(!Logger::hasProvider($providerName)) { - throw new Exception("Logging provider not supported. Logging disabled."); + throw new Exception("Logging provider not supported. Logging disabled.", 500, Exception::GENERAL_SERVER_ERROR); } $classname = '\\Utopia\\Logger\\Adapter\\'.\ucfirst($providerName); @@ -707,7 +709,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response try { $payload = $jwt->decode($authJWT); } catch (JWTException $error) { - throw new Exception('Failed to verify JWT. '.$error->getMessage(), 401); + throw new Exception('Failed to verify JWT. '.$error->getMessage(), 401, Exception::USER_JWT_INVALID); } $jwtUserId = $payload['userId'] ?? ''; diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php new file mode 100644 index 0000000000..c39606fdd3 --- /dev/null +++ b/src/Appwrite/Extend/Exception.php @@ -0,0 +1,189 @@ +_ + * + * Appwrite has the follwing entities: + * - General + * - Users + * - Teams + * - Memberships + * - Avatars + * - Storage + * - Functions + * - Deployments + * - Executions + * - Collections + * - Documents + * - Attributes + * - Indexes + * - Projects + * - Webhooks + * - Keys + * - Platform + * - Domain + */ + + /** General */ + const GENERAL_UNKNOWN = 'general_unknown'; + const GENERAL_MOCK = 'general_mock'; + const GENERAL_ACCESS_FORBIDDEN = 'general_access_forbidden'; + const GENERAL_UNKNOWN_ORIGIN = 'general_unknown_origin'; + const GENERAL_SERVICE_DISABLED = 'general_service_disabled'; + const GENERAL_UNAUTHORIZED_SCOPE = 'general_unauthorized_scope'; + const GENERAL_RATE_LIMIT_EXCEEDED = 'general_rate_limit_exceeded'; + const GENERAL_SMTP_DISABLED = 'general_smtp_disabled'; + const GENERAL_ARGUMENT_INVALID = 'general_argument_invalid'; + const GENERAL_QUERY_LIMIT_EXCEEDED = 'general_query_limit_exceeded'; + const GENERAL_QUERY_INVALID = 'general_query_invalid'; + const GENERAL_ROUTE_NOT_FOUND = 'general_route_not_found'; + const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found'; + const GENERAL_SERVER_ERROR = 'general_server_error'; + + /** Users */ + const USER_COUNT_EXCEEDED = 'user_count_exceeded'; + const USER_JWT_INVALID = 'user_jwt_invalid'; + const USER_ALREADY_EXISTS = 'user_already_exists'; + const USER_BLOCKED = 'user_blocked'; + const USER_INVALID_TOKEN = 'user_invalid_token'; + const USER_PASSWORD_RESET_REQUIRED = 'user_password_reset_required'; + const USER_EMAIL_NOT_WHITELISTED = 'user_email_not_whitelisted'; + const USER_IP_NOT_WHITELISTED = 'user_ip_not_whitelisted'; + const USER_INVALID_CREDENTIALS = 'user_invalid_credentials'; + const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited'; + const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists'; + const USER_NOT_FOUND = 'user_not_found'; + const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists'; + const USER_PASSWORD_MISMATCH = 'user_password_mismatch'; + const USER_SESSION_NOT_FOUND = 'user_session_not_found'; + const USER_UNAUTHORIZED = 'user_unauthorized'; + const USER_AUTH_METHOD_UNSUPPORTED = 'user_auth_method_unsupported'; + + /** Teams */ + const TEAM_NOT_FOUND = 'team_not_found'; + const TEAM_INVITE_ALREADY_EXISTS = 'team_invite_already_exists'; + const TEAM_INVITE_NOT_FOUND = 'team_invite_not_found'; + const TEAM_INVALID_SECRET = 'team_invalid_secret'; + const TEAM_MEMBERSHIP_MISMATCH = 'team_membership_mismatch'; + const TEAM_INVITE_MISMATCH = 'team_invite_mismatch'; + + /** Membership */ + const MEMBERSHIP_NOT_FOUND = 'membership_not_found'; + + /** Avatars */ + const AVATAR_SET_NOT_FOUND = 'avatar_set_not_found'; + const AVATAR_NOT_FOUND = 'avatar_not_found'; + const AVATAR_IMAGE_NOT_FOUND = 'avatar_image_not_found'; + const AVATAR_REMOTE_URL_FAILED = 'avatar_remote_url_failed'; + const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found'; + + /** Storage */ + const STORAGE_FILE_NOT_FOUND = 'storage_file_not_found'; + const STORAGE_DEVICE_NOT_FOUND = 'storage_device_not_found'; + const STORAGE_FILE_EMPTY = 'storage_file_empty'; + const STORAGE_FILE_TYPE_UNSUPPORTED = 'storage_file_type_unsupported'; + const STORAGE_INVALID_FILE_SIZE = 'storage_invalid_file_size'; + const STORAGE_INVALID_FILE = 'storage_invalid_file'; + const STORAGE_BUCKET_ALREADY_EXISTS = 'storage_bucket_already_exists'; + const STORAGE_BUCKET_NOT_FOUND = 'storage_bucket_not_found'; + const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range'; + const STORAGE_INVALID_RANGE = 'storage_invalid_range'; + + /** Functions */ + const FUNCTION_NOT_FOUND = 'function_not_found'; + + /** Deployments */ + const DEPLOYMENT_NOT_FOUND = 'deployment_not_found'; + + /** Execution */ + const EXECUTION_NOT_FOUND = 'execution_not_found'; + + /** Collections */ + const COLLECTION_NOT_FOUND = 'collection_not_found'; + const COLLECTION_ALREADY_EXISTS = 'collection_already_exists'; + const COLLECTION_LIMIT_EXCEEDED = 'collection_limit_exceeded'; + + /** Documents */ + const DOCUMENT_NOT_FOUND = 'document_not_found'; + const DOCUMENT_INVALID_STRUCTURE = 'document_invalid_structure'; + const DOCUMENT_MISSING_PAYLOAD = 'document_missing_payload'; + const DOCUMENT_ALREADY_EXISTS = 'document_already_exists'; + + /** Attribute */ + const ATTRIBUTE_NOT_FOUND = 'attribute_not_found'; + const ATTRIBUTE_UNKNOWN = 'attribute_unknown'; + const ATTRIBUTE_NOT_AVAILABLE = 'attribute_not_available'; + const ATTRIBUTE_FORMAT_UNSUPPORTED = 'attribute_format_unsupported'; + const ATTRIBUTE_DEFAULT_UNSUPPORTED = 'attribute_default_unsupported'; + const ATTRIBUTE_ALREADY_EXISTS = 'attribute_already_exists'; + const ATTRIBUTE_LIMIT_EXCEEDED = 'attribute_limit_exceeded'; + const ATTRIBUTE_VALUE_INVALID = 'attribute_value_invalid'; + + /** Indexes */ + const INDEX_NOT_FOUND = 'index_not_found'; + const INDEX_LIMIT_EXCEEDED = 'index_limit_exceeded'; + const INDEX_ALREADY_EXISTS = 'index_already_exists'; + + /** Projects */ + const PROJECT_NOT_FOUND = 'project_not_found'; + const PROJECT_UNKNOWN = 'project_unknown'; + const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled'; + const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported'; + const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url'; + const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url'; + const PROJECT_MISSING_USER_ID = 'project_missing_user_id'; + + /** Webhooks */ + const WEBHOOK_NOT_FOUND = 'webhook_not_found'; + + /** Keys */ + const KEY_NOT_FOUND = 'key_not_found'; + + /** Platform */ + const PLATFORM_NOT_FOUND = 'platform_not_found'; + + /** Domain */ + const DOMAIN_NOT_FOUND = 'domain_not_found'; + const DOMAIN_ALREADY_EXISTS = 'domain_already_exists'; + const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed'; + + + private $type = ''; + + public function __construct(string $message, int $code = 0, string $type = Exception::GENERAL_UNKNOWN, \Throwable $previous = null) + { + $this->type = $type; + + parent::__construct($message, $code, $previous); + } + + /** + * Get the type of the exception. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Set the type of the exception. + * + * @param string $type + * + * @return void + */ + public function setType(string $type): void + { + $this->type = $type; + } + +} \ No newline at end of file diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index d00318f212..044641335c 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -7,7 +7,7 @@ use Utopia\Database\Document; use Utopia\Database\Database; use Utopia\CLI\Console; use Utopia\Config\Config; -use Utopia\Exception; +use Exception; abstract class Migration { diff --git a/src/Appwrite/Utopia/Response/Model/Error.php b/src/Appwrite/Utopia/Response/Model/Error.php index 507b252093..20c91fa7c0 100644 --- a/src/Appwrite/Utopia/Response/Model/Error.php +++ b/src/Appwrite/Utopia/Response/Model/Error.php @@ -22,6 +22,12 @@ class Error extends Model 'default' => '', 'example' => '404', ]) + ->addRule('type', [ + 'type' => self::TYPE_STRING, + 'description' => 'Error type. You can learn more about all the error types at https://appwrite.io/docs/error-codes#errorTypes', + 'default' => 'unknown', + 'example' => 'not_found', + ]) ->addRule('version', [ 'type' => self::TYPE_STRING, 'description' => 'Server version number.', diff --git a/tests/e2e/General/HTTPTest.php b/tests/e2e/General/HTTPTest.php index c4811f0f41..cc0664f343 100644 --- a/tests/e2e/General/HTTPTest.php +++ b/tests/e2e/General/HTTPTest.php @@ -2,7 +2,7 @@ namespace Tests\E2E\General; -use Exception; +use Appwrite\Extend\Exception; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectNone; use Tests\E2E\Scopes\Scope; @@ -45,6 +45,7 @@ class HTTPTest extends Scope $this->assertEquals(404, $response['headers']['status-code']); $this->assertEquals('Not Found', $response['body']['message']); + $this->assertEquals(Exception::GENERAL_ROUTE_NOT_FOUND, $response['body']['type']); $this->assertEquals(404, $response['body']['code']); $this->assertEquals('dev', $response['body']['version']); }