1
0
Fork 0
mirror of synced 2024-06-03 03:14:50 +12:00

Merge branch 'feat-password-hash-algos' of https://github.com/appwrite/appwrite into feat-missing-phone-param

This commit is contained in:
Eldad Fux 2022-08-14 19:43:53 +03:00
commit 22c38a484f
142 changed files with 4285 additions and 2888 deletions

View file

@ -22,6 +22,7 @@ ports:
vscode:
extensions:
- ms-azuretools.vscode-docker
- zobo.php-intellisense
github:
# https://www.gitpod.io/docs/prebuilds#github-specific-configuration

View file

@ -1,3 +1,14 @@
# Version 0.15.3
## Features
- Added hint during Installation for DNS Configuration by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/2450
## Bugs
- Fixed Migration for Attributes and Indexes by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3568
- Fixed Closed Icon in the alerts to be centered by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3594
- Fixed Response Model for Get and Update Database Endpoint by @ishanvyas22 in https://github.com/appwrite/appwrite/pull/3553
- Fixed Missing Usage on Functions exection by @Meldiron in https://github.com/appwrite/appwrite/pull/3543
- Fixed Validation for Permissions to only accept a maximum of 100 Permissions for all endpoints by @Meldiron in https://github.com/appwrite/appwrite/pull/3532
- Fixed backwards compatibility for Create Email Session Endpoint by @stnguyen90 in https://github.com/appwrite/appwrite/pull/3517
# Version 0.15.2
## Bugs
- Fixed Realtime Authentication for the Console by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3506

View file

@ -59,7 +59,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:0.15.2
appwrite/appwrite:0.15.3
```
### Windows
@ -71,7 +71,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:0.15.2
appwrite/appwrite:0.15.3
```
#### PowerShell
@ -81,7 +81,7 @@ docker run -it --rm ,
--volume /var/run/docker.sock:/var/run/docker.sock ,
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
--entrypoint="install" ,
appwrite/appwrite:0.15.2
appwrite/appwrite:0.15.3
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。

View file

@ -65,7 +65,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:0.15.2
appwrite/appwrite:0.15.3
```
### Windows
@ -77,7 +77,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:0.15.2
appwrite/appwrite:0.15.3
```
#### PowerShell
@ -87,7 +87,7 @@ docker run -it --rm ,
--volume /var/run/docker.sock:/var/run/docker.sock ,
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
--entrypoint="install" ,
appwrite/appwrite:0.15.2
appwrite/appwrite:0.15.3
```
Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after installation completes.

View file

@ -102,7 +102,7 @@ return [
],
Exception::USER_ALREADY_EXISTS => [
'name' => Exception::USER_ALREADY_EXISTS,
'description' => 'A user with the same email ID already exists in your project.',
'description' => 'A user with the same email already exists in your project.',
'code' => 409,
],
Exception::USER_BLOCKED => [
@ -122,12 +122,12 @@ return [
],
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.',
'description' => 'Console registration is restricted to specific emails. Contact your administrator for more information.',
'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.',
'description' => 'Console registration is restricted to specific IPs. Contact your administrator for more information.',
'code' => 401,
],
Exception::USER_INVALID_CREDENTIALS => [
@ -152,7 +152,7 @@ return [
],
Exception::USER_EMAIL_ALREADY_EXISTS => [
'name' => Exception::USER_EMAIL_ALREADY_EXISTS,
'description' => 'Another user with the same email already exists in the current project.',
'description' => 'A user with the same email already exists in the current project.',
'code' => 409,
],
Exception::USER_PASSWORD_MISMATCH => [
@ -185,6 +185,11 @@ return [
'description' => 'The current user does not have a phone number associated with their account.',
'code' => 400,
],
Exception::USER_MISSING_ID => [
'name' => Exception::USER_MISSING_ID,
'description' => 'Missing ID from OAuth2 provider.',
'code' => 400,
],
/** Teams */
Exception::TEAM_NOT_FOUND => [
@ -194,7 +199,7 @@ return [
],
Exception::TEAM_INVITE_ALREADY_EXISTS => [
'name' => Exception::TEAM_INVITE_ALREADY_EXISTS,
'description' => 'The current user has already received an invitation to join the team.',
'description' => 'User has already been invited or is already a member of this team',
'code' => 409,
],
Exception::TEAM_INVITE_NOT_FOUND => [
@ -218,13 +223,17 @@ return [
'code' => 401,
],
/** Membership */
Exception::MEMBERSHIP_NOT_FOUND => [
'name' => Exception::MEMBERSHIP_NOT_FOUND,
'description' => 'Membership with the requested ID could not be found.',
'code' => 404,
],
Exception::MEMBERSHIP_ALREADY_CONFIRMED => [
'name' => Exception::MEMBERSHIP_ALREADY_CONFIRMED,
'description' => 'Membership already confirmed',
'code' => 409,
],
/** Avatars */
Exception::AVATAR_SET_NOT_FOUND => [
@ -271,7 +280,7 @@ return [
],
Exception::STORAGE_FILE_TYPE_UNSUPPORTED => [
'name' => Exception::STORAGE_FILE_TYPE_UNSUPPORTED,
'description' => 'The file type is not supported.',
'description' => 'The given file extension is not supported.',
'code' => 400,
],
Exception::STORAGE_INVALID_FILE_SIZE => [
@ -325,7 +334,7 @@ return [
],
Exception::BUILD_NOT_READY => [
'name' => Exception::BUILD_NOT_READY,
'description' => 'Build with the requested ID is builing and not ready for execution.',
'description' => 'Build with the requested ID is building and not ready for execution.',
'code' => 400,
],
Exception::BUILD_IN_PROGRESS => [
@ -348,6 +357,19 @@ return [
'code' => 404,
],
/** Databases */
Exception::DATABASE_NOT_FOUND => [
'name' => Exception::DATABASE_NOT_FOUND,
'description' => 'Database not found',
'code' => 404
],
Exception::DATABASE_ALREADY_EXISTS => [
'name' => Exception::DATABASE_ALREADY_EXISTS,
'description' => 'Database already exists',
'code' => 409
],
/** Collections */
Exception::COLLECTION_NOT_FOUND => [
'name' => Exception::COLLECTION_NOT_FOUND,
@ -469,19 +491,24 @@ return [
],
Exception::PROJECT_INVALID_SUCCESS_URL => [
'name' => Exception::PROJECT_INVALID_SUCCESS_URL,
'description' => 'Invalid URL received for OAuth success redirect.',
'description' => 'Invalid redirect URL for OAuth success.',
'code' => 400,
],
Exception::PROJECT_INVALID_FAILURE_URL => [
'name' => Exception::PROJECT_INVALID_FAILURE_URL,
'description' => 'Invalid URL received for OAuth failure redirect.',
'description' => 'Invalid redirect URL for OAuth failure.',
'code' => 400,
],
Exception::PROJECT_MISSING_USER_ID => [
'name' => Exception::PROJECT_MISSING_USER_ID,
'description' => 'Failed to obtain user ID from the OAuth provider.',
Exception::PROJECT_RESERVED_PROJECT => [
'name' => Exception::PROJECT_RESERVED_PROJECT,
'description' => 'The project ID is reserved. Please choose another project ID.',
'code' => 400,
],
Exception::PROJECT_KEY_EXPIRED => [
'name' => Exception::PROJECT_KEY_EXPIRED,
'description' => 'The project key has expired. Please generate a new key using the Appwrite console.',
'code' => 401,
],
Exception::WEBHOOK_NOT_FOUND => [
'name' => Exception::WEBHOOK_NOT_FOUND,
'description' => 'Webhook with the requested ID could not be found.',
@ -511,5 +538,5 @@ return [
'name' => Exception::DOMAIN_VERIFICATION_FAILED,
'description' => 'Domain verification for the requested domain has failed.',
'code' => 401,
]
],
];

View file

@ -31,6 +31,16 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false,
],
'authentik' => [
'name' => 'Authentik',
'developers' => 'https://goauthentik.io/docs/',
'icon' => 'icon-authentik',
'enabled' => true,
'sandbox' => false,
'form' => 'authentik.phtml',
'beta' => false,
'mock' => false,
],
'autodesk' => [
'name' => 'Autodesk',
'developers' => 'https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/',
@ -91,6 +101,16 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false,
],
'disqus' => [
'name' => 'Disqus',
'developers' => 'https://disqus.com/api/docs/auth/',
'icon' => 'icon-disqus',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
],
'dropbox' => [
'name' => 'Dropbox',
'developers' => 'https://www.dropbox.com/developers/documentation',
@ -201,6 +221,16 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false
],
'podio' => [
'name' => 'Podio',
'developers' => 'https://developers.podio.com/doc/oauth-authorization',
'icon' => 'icon-podio',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
],
'salesforce' => [
'name' => 'Salesforce',
'developers' => 'https://developer.salesforce.com/docs/',

View file

@ -2,7 +2,7 @@
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\SMS\Mock;
use Appwrite\SMS\Adapter\Mock;
use Appwrite\Auth\Validator\Password;
use Appwrite\Auth\Validator\Phone;
use Appwrite\Detector\Detector;
@ -74,11 +74,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, Exception::USER_EMAIL_NOT_WHITELISTED);
throw new Exception(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, Exception::USER_IP_NOT_WHITELISTED);
throw new Exception(Exception::USER_IP_NOT_WHITELISTED);
}
}
@ -88,7 +88,7 @@ App::post('/v1/account')
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
throw new Exception(Exception::USER_COUNT_EXCEEDED);
}
}
@ -117,7 +117,7 @@ App::post('/v1/account')
'search' => implode(' ', [$userId, $email, $name])
])));
} catch (Duplicate $th) {
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
Authorization::unsetRole('role:' . Auth::USER_ROLE_GUEST);
@ -171,11 +171,11 @@ App::post('/v1/account/sessions/email')
new Query('email', Query::TYPE_EQUAL, [$email])]);
if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) {
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); // Wrong password or username
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
}
if (false === $profile->getAttribute('status')) { // Account is blocked
throw new Exception('Invalid credentials. User is blocked', 401, Exception::USER_BLOCKED); // User is in status blocked
throw new Exception(Exception::USER_BLOCKED); // User is in status blocked
}
$detector = new Detector($request->getUserAgent('UNKNOWN'));
@ -289,13 +289,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, Exception::PROJECT_PROVIDER_DISABLED);
throw new Exception(Exception::PROJECT_PROVIDER_DISABLED, 'This provider is disabled. Please configure the provider app ID and app secret key from your ' . APP_NAME . ' console to continue.');
}
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
if (!\class_exists($className)) {
throw new Exception('Provider is not supported', 501, Exception::PROJECT_PROVIDER_UNSUPPORTED);
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
if (empty($success)) {
@ -401,7 +401,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, Exception::PROJECT_PROVIDER_UNSUPPORTED);
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$oauth2 = new $className($appId, $appSecret, $callback);
@ -410,18 +410,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', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to parse login state params as passed from OAuth2 provider');
}
} else {
$state = $defaultState;
}
if (!$validateURL->isValid($state['success'])) {
throw new Exception('Invalid redirect URL for success login', 400, Exception::PROJECT_INVALID_SUCCESS_URL);
throw new Exception(Exception::PROJECT_INVALID_SUCCESS_URL);
}
if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) {
throw new Exception('Invalid redirect URL for failure login', 400, Exception::PROJECT_INVALID_FAILURE_URL);
throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL);
}
$state['failure'] = null;
@ -435,7 +435,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$response->redirect($state['failure'], 301, 0);
}
throw new Exception('Failed to obtain access token', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to obtain access token');
}
$oauth2ID = $oauth2->getUserID($accessToken);
@ -445,7 +445,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$response->redirect($state['failure'], 301, 0);
}
throw new Exception('Missing ID from OAuth2 provider', 400, Exception::PROJECT_MISSING_USER_ID);
throw new Exception(Exception::USER_MISSING_ID);
}
$sessions = $user->getAttribute('sessions', []);
@ -483,7 +483,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
throw new Exception(Exception::USER_COUNT_EXCEEDED);
}
}
@ -510,13 +510,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'search' => implode(' ', [$userId, $email, $name])
])));
} catch (Duplicate $th) {
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
}
}
if (false === $user->getAttribute('status')) { // Account is blocked
throw new Exception('Invalid credentials. User is blocked', 401, Exception::USER_BLOCKED); // User is in status blocked
throw new Exception(Exception::USER_BLOCKED); // User is in status blocked
}
// Create session token, verify user account and update OAuth2 ID and Access Token
@ -634,7 +634,7 @@ App::post('/v1/account/sessions/magic-url')
->action(function (string $userId, string $email, string $url, Request $request, Response $response, Document $project, Database $dbForProject, Locale $locale, Audit $audits, Event $events, Mail $mails) {
if (empty(App::getEnv('_APP_SMTP_HOST'))) {
throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED);
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
}
$roles = Authorization::getRoles();
@ -650,7 +650,7 @@ App::post('/v1/account/sessions/magic-url')
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
throw new Exception(Exception::USER_COUNT_EXCEEDED);
}
}
@ -767,13 +767,13 @@ App::put('/v1/account/sessions/magic-url')
$user = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(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, Exception::USER_INVALID_TOKEN);
throw new Exception(Exception::USER_INVALID_TOKEN);
}
$detector = new Detector($request->getUserAgent('UNKNOWN'));
@ -819,7 +819,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, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed saving user to DB');
}
$audits->setResource('user/' . $user->getId());
@ -874,10 +874,9 @@ App::post('/v1/account/sessions/phone')
->inject('audits')
->inject('events')
->inject('messaging')
->inject('sms')
->action(function (string $userId, string $phone, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Event $events, EventPhone $messaging) {
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED);
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
$roles = Authorization::getRoles();
@ -893,7 +892,7 @@ App::post('/v1/account/sessions/phone')
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
throw new Exception(Exception::USER_COUNT_EXCEEDED);
}
}
@ -920,7 +919,7 @@ App::post('/v1/account/sessions/phone')
])));
}
$secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$defaultDigits : Auth::codeGenerator();
$secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$digits : Auth::codeGenerator();
$expire = \time() + Auth::TOKEN_EXPIRATION_PHONE;
@ -997,13 +996,13 @@ App::put('/v1/account/sessions/phone')
$user = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$token = Auth::phoneTokenVerify($user->getAttribute('tokens', []), $secret);
if (!$token) {
throw new Exception('Invalid login token', 401, Exception::USER_INVALID_TOKEN);
throw new Exception(Exception::USER_INVALID_TOKEN);
}
$detector = new Detector($request->getUserAgent('UNKNOWN'));
@ -1047,7 +1046,7 @@ App::put('/v1/account/sessions/phone')
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
if (false === $user) {
throw new Exception('Failed saving user to DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed saving user to DB');
}
$audits->setResource('user/' . $user->getId());
@ -1109,11 +1108,11 @@ App::post('/v1/account/sessions/anonymous')
$protocol = $request->getProtocol();
if ('console' === $project->getId()) {
throw new Exception('Failed to create anonymous user.', 401, Exception::USER_ANONYMOUS_CONSOLE_PROHIBITED);
throw new Exception(Exception::USER_ANONYMOUS_CONSOLE_PROHIBITED, 'Failed to create anonymous user');
}
if (!$user->isEmpty()) {
throw new Exception('Cannot create an anonymous user when logged in.', 401, Exception::USER_SESSION_ALREADY_EXISTS);
throw new Exception(Exception::USER_SESSION_ALREADY_EXISTS, 'Cannot create an anonymous user when logged in');
}
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
@ -1122,7 +1121,7 @@ App::post('/v1/account/sessions/anonymous')
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
throw new Exception(Exception::USER_COUNT_EXCEEDED);
}
}
@ -1241,7 +1240,7 @@ App::post('/v1/account/jwt')
}
if ($current->isEmpty()) {
throw new Exception('No valid session found', 404, Exception::USER_SESSION_NOT_FOUND);
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
}
$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
@ -1434,7 +1433,7 @@ App::get('/v1/account/sessions/:sessionId')
}
}
throw new Exception('Session not found', 404, Exception::USER_SESSION_NOT_FOUND);
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
});
App::patch('/v1/account/name')
@ -1460,7 +1459,7 @@ App::patch('/v1/account/name')
$user = $dbForProject->updateDocument('users', $user->getId(), $user
->setAttribute('name', $name)
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email')])));
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email', ''), $user->getAttribute('phone', '')])));
$audits
->setResource('user/' . $user->getId())
@ -1496,7 +1495,7 @@ App::patch('/v1/account/password')
->action(function (string $password, string $oldPassword, Response $response, Document $user, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
// Check old password only if its an existing user.
if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
}
$user = $dbForProject->updateDocument('users', $user->getId(), $user
@ -1543,7 +1542,7 @@ App::patch('/v1/account/email')
!$isAnonymousUser &&
!Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))
) { // Double check user password
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
}
$email = \strtolower($email);
@ -1554,12 +1553,12 @@ App::patch('/v1/account/email')
->setAttribute('hashOptions', $isAnonymousUser ? Auth::DEFAULT_ALGO_OPTIONS : $user->getAttribute('hashOptions', ''))
->setAttribute('email', $email)
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]));
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $email, $user->getAttribute('phone', '')]));
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
} catch (Duplicate $th) {
throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
}
$audits
@ -1601,18 +1600,18 @@ App::patch('/v1/account/phone')
!$isAnonymousUser &&
!Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))
) { // Double check user password
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
}
$user
->setAttribute('phone', $phone)
->setAttribute('phoneVerification', false) // After this user needs to confirm phone number again
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]));
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $user->getAttribute('email', ''), $phone]));
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
} catch (Duplicate $th) {
throw new Exception('Phone number already exists', 409, Exception::USER_PHONE_ALREADY_EXISTS);
throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS);
}
$audits
@ -1770,7 +1769,7 @@ App::delete('/v1/account/sessions/:sessionId')
}
}
throw new Exception('Session not found', 404, Exception::USER_SESSION_NOT_FOUND);
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
});
App::patch('/v1/account/sessions/:sessionId')
@ -1824,7 +1823,7 @@ App::patch('/v1/account/sessions/:sessionId')
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
if (!\class_exists($className)) {
throw new Exception('Provider is not supported', 501, Exception::PROJECT_PROVIDER_UNSUPPORTED);
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$oauth2 = new $className($appId, $appSecret, '', [], []);
@ -1857,7 +1856,7 @@ App::patch('/v1/account/sessions/:sessionId')
}
}
throw new Exception('Session not found', 404, Exception::USER_SESSION_NOT_FOUND);
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
});
App::delete('/v1/account/sessions')
@ -1956,7 +1955,7 @@ App::post('/v1/account/recovery')
->action(function (string $email, string $url, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Mail $mails, Audit $audits, Event $events, Stats $usage) {
if (empty(App::getEnv('_APP_SMTP_HOST'))) {
throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED);
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
$roles = Authorization::getRoles();
@ -1970,11 +1969,11 @@ App::post('/v1/account/recovery')
]);
if (!$profile) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
if (false === $profile->getAttribute('status')) { // Account is blocked
throw new Exception('Invalid credentials. User is blocked', 401, Exception::USER_BLOCKED);
throw new Exception(Exception::USER_BLOCKED);
}
$expire = \time() + Auth::TOKEN_EXPIRATION_RECOVERY;
@ -2057,20 +2056,20 @@ App::put('/v1/account/recovery')
->inject('events')
->action(function (string $userId, string $secret, string $password, string $passwordAgain, Response $response, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
if ($password !== $passwordAgain) {
throw new Exception('Passwords must match', 400, Exception::USER_PASSWORD_MISMATCH);
throw new Exception(Exception::USER_PASSWORD_MISMATCH);
}
$profile = $dbForProject->getDocument('users', $userId);
if ($profile->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(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, Exception::USER_INVALID_TOKEN);
throw new Exception(Exception::USER_INVALID_TOKEN);
}
Authorization::setRole('user:' . $profile->getId());
@ -2131,7 +2130,7 @@ App::post('/v1/account/verification')
->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Audit $audits, Event $events, Mail $mails, Stats $usage) {
if (empty(App::getEnv('_APP_SMTP_HOST'))) {
throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED);
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
$roles = Authorization::getRoles();
@ -2220,14 +2219,14 @@ App::put('/v1/account/verification')
$profile = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
if ($profile->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(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, Exception::USER_INVALID_TOKEN);
throw new Exception(Exception::USER_INVALID_TOKEN);
}
Authorization::setRole('user:' . $profile->getId());
@ -2279,12 +2278,12 @@ App::post('/v1/account/verification/phone')
->inject('messaging')
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage, EventPhone $messaging) {
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) {
throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED);
}
if (empty($user->getAttribute('phone'))) {
throw new Exception('User has no phone number.', 400, Exception::USER_PHONE_NOT_FOUND);
throw new Exception(Exception::USER_PHONE_NOT_FOUND);
}
$roles = Authorization::getRoles();
@ -2293,7 +2292,7 @@ App::post('/v1/account/verification/phone')
$verificationSecret = Auth::tokenGenerator();
$secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$defaultDigits : Auth::codeGenerator();
$secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$digits : Auth::codeGenerator();
$expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM;
$verification = new Document([
@ -2367,13 +2366,13 @@ App::put('/v1/account/verification/phone')
$profile = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
if ($profile->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$verification = Auth::phoneTokenVerify($user->getAttribute('tokens', []), $secret);
if (!$verification) {
throw new Exception('Invalid verification token', 401, Exception::USER_INVALID_TOKEN);
throw new Exception(Exception::USER_INVALID_TOKEN);
}
Authorization::setRole('user:' . $profile->getId());

View file

@ -25,15 +25,15 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
$set = Config::getParam('avatar-' . $type, []);
if (empty($set)) {
throw new Exception('Avatar set not found', 404, Exception::AVATAR_SET_NOT_FOUND);
throw new Exception(Exception::AVATAR_SET_NOT_FOUND);
}
if (!\array_key_exists($code, $set)) {
throw new Exception('Avatar not found', 404, Exception::AVATAR_NOT_FOUND);
throw new Exception(Exception::AVATAR_NOT_FOUND);
}
if (!\extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
}
$output = 'png';
@ -43,7 +43,7 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
$type = 'png';
if (!\is_readable($path)) {
throw new Exception('File not readable in ' . $path, 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'File not readable in ' . $path);
}
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
@ -166,19 +166,19 @@ App::get('/v1/avatars/image')
}
if (!\extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
}
$fetch = @\file_get_contents($url, false);
if (!$fetch) {
throw new Exception('Image not found', 404, Exception::AVATAR_IMAGE_NOT_FOUND);
throw new Exception(Exception::AVATAR_IMAGE_NOT_FOUND);
}
try {
$image = new Image($fetch);
} catch (\Exception $exception) {
throw new Exception('Unable to parse image', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unable to parse image');
}
$image->crop((int) $width, (int) $height);
@ -232,7 +232,7 @@ App::get('/v1/avatars/favicon')
}
if (!\extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
}
$curl = \curl_init();
@ -254,7 +254,7 @@ App::get('/v1/avatars/favicon')
\curl_close($curl);
if (!$html) {
throw new Exception('Failed to fetch remote URL', 404, Exception::AVATAR_REMOTE_URL_FAILED);
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
}
$doc = new DOMDocument();
@ -312,7 +312,7 @@ App::get('/v1/avatars/favicon')
$data = @\file_get_contents($outputHref, false);
if (empty($data) || (\mb_substr($data, 0, 5) === '<html') || \mb_substr($data, 0, 5) === '<!doc') {
throw new Exception('Favicon not found', 404, Exception::AVATAR_ICON_NOT_FOUND);
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND, 'Favicon not found');
}
$cache->save($key, $data);
@ -327,7 +327,7 @@ App::get('/v1/avatars/favicon')
$fetch = @\file_get_contents($outputHref, false);
if (!$fetch) {
throw new Exception('Icon not found', 404, Exception::AVATAR_ICON_NOT_FOUND);
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND);
}
$image = new Image($fetch);

View file

@ -67,28 +67,28 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
if (!empty($format)) {
if (!Structure::hasFormat($format, $type)) {
throw new Exception("Format {$format} not available for {$type} attributes.", 400, Exception::ATTRIBUTE_FORMAT_UNSUPPORTED);
throw new Exception(Exception::ATTRIBUTE_FORMAT_UNSUPPORTED, "Format {$format} not available for {$type} attributes.");
}
}
// 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, Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED);
throw new Exception(Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED, 'Cannot set default value for required attribute');
}
if ($array && $default) {
throw new Exception('Cannot set default value for array attributes', 400, Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED);
throw new Exception(Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED, 'Cannot set default value for array attributes');
}
try {
@ -114,9 +114,9 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
$dbForProject->checkAttribute($collection, $attribute);
$attribute = $dbForProject->createDocument('attributes', $attribute);
} catch (DuplicateException $exception) {
throw new Exception('Attribute already exists', 409, Exception::ATTRIBUTE_ALREADY_EXISTS);
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS);
} catch (LimitException $exception) {
throw new Exception('Attribute limit exceeded', 400, Exception::ATTRIBUTE_LIMIT_EXCEEDED);
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded');
}
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId);
@ -184,7 +184,7 @@ App::post('/v1/databases')
$collections = Config::getParam('collections', [])['collections'] ?? [];
if (empty($collections)) {
throw new Exception('Collections collection is not configured.', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'The "collections" collection is not configured.');
}
$attributes = [];
@ -215,7 +215,7 @@ App::post('/v1/databases')
}
$dbForProject->createCollection('database_' . $database->getInternalId(), $attributes, $indexes);
} catch (DuplicateException $th) {
throw new Exception('Database already exists', 409, Exception::DATABASE_ALREADY_EXISTS);
throw new Exception(Exception::DATABASE_ALREADY_EXISTS);
}
$audits
@ -256,7 +256,7 @@ App::get('/v1/databases')
$cursorDocument = $dbForProject->getDocument('databases', $cursor);
if ($cursorDocument->isEmpty()) {
throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Collection '{$cursor}' for the 'cursor' value not found.");
}
}
@ -294,7 +294,7 @@ App::get('/v1/databases/:databaseId')
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$usage->setParam('databases.read', 1);
@ -325,7 +325,7 @@ App::get('/v1/databases/:databaseId/logs')
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$audit = new Audit($dbForProject);
@ -405,7 +405,7 @@ App::put('/v1/databases/:databaseId')
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
try {
@ -413,9 +413,9 @@ App::put('/v1/databases/:databaseId')
->setAttribute('name', $name)
->setAttribute('search', implode(' ', [$databaseId, $name])));
} catch (AuthorizationException $exception) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (StructureException $exception) {
throw new Exception('Bad structure. ' . $exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, 'Bad structure. ' . $exception->getMessage());
}
$audits
@ -452,11 +452,11 @@ App::delete('/v1/databases/:databaseId')
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
if (!$dbForProject->deleteDocument('databases', $databaseId)) {
throw new Exception('Failed to remove collection from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove collection from DB');
}
$dbForProject->deleteCachedCollection('databases' . $database->getInternalId());
@ -510,7 +510,7 @@ App::post('/v1/databases/:databaseId/collections')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collectionId = $collectionId == 'unique()' ? $dbForProject->getId() : $collectionId;
@ -531,9 +531,9 @@ App::post('/v1/databases/:databaseId/collections')
$dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
} catch (DuplicateException $th) {
throw new Exception('Collection already exists', 409, Exception::COLLECTION_ALREADY_EXISTS);
throw new Exception(Exception::COLLECTION_ALREADY_EXISTS);
} catch (LimitException $th) {
throw new Exception('Collection limit exceeded', 400, Exception::COLLECTION_LIMIT_EXCEEDED);
throw new Exception(Exception::COLLECTION_LIMIT_EXCEEDED);
}
$audits
@ -581,14 +581,14 @@ App::get('/v1/databases/:databaseId/collections')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
if (!empty($cursor)) {
$cursorCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $cursor);
if ($cursorCollection->isEmpty()) {
throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Collection '{$cursor}' for the 'cursor' value not found.");
}
}
@ -630,12 +630,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$usage
@ -670,13 +670,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collectionDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
$collection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $collectionDocument->getInternalId());
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$audit = new Audit($dbForProject);
@ -765,12 +765,12 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$read ??= $collection->getRead() ?? []; // By default inherit read permissions
@ -786,9 +786,9 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
->setAttribute('enabled', $enabled)
->setAttribute('search', implode(' ', [$collectionId, $name])));
} catch (AuthorizationException $exception) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (StructureException $exception) {
throw new Exception('Bad structure. ' . $exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, 'Bad structure. ' . $exception->getMessage());
}
$audits
@ -833,17 +833,17 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
if (!$dbForProject->deleteDocument('database_' . $database->getInternalId(), $collectionId)) {
throw new Exception('Failed to remove collection from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove collection from DB');
}
$dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
@ -882,11 +882,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
->label('sdk.namespace', 'databases')
->label('sdk.method', 'createStringAttribute')
->label('sdk.description', '/docs/references/databases/create-string-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_STRING)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.')
->param('required', null, new Boolean(), 'Is attribute required?')
@ -903,7 +903,7 @@ App::post('/v1/databases/:databaseId/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, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription());
}
$attribute = createAttribute($databaseId, $collectionId, new Document([
@ -915,6 +915,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
'array' => $array,
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
});
@ -928,11 +929,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email'
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createEmailAttribute')
->label('sdk.description', '/docs/references/databases/create-email-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_EMAIL)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Email(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
@ -955,6 +956,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email'
'format' => APP_DATABASE_ATTRIBUTE_EMAIL,
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL);
});
@ -968,11 +970,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createEnumAttribute')
->label('sdk.description', '/docs/references/databases/create-attribute-enum.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_ENUM)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('elements', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.')
->param('required', null, new Boolean(), 'Is attribute required?')
@ -991,13 +993,13 @@ App::post('/v1/databases/:databaseId/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, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Each enum element must not be empty');
}
$size = ($length > $size) ? $length : $size;
}
if (!is_null($default) && !in_array($default, $elements)) {
throw new Exception('Default value not found in elements', 400, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Default value not found in elements');
}
$attribute = createAttribute($databaseId, $collectionId, new Document([
@ -1011,6 +1013,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
'formatOptions' => ['elements' => $elements],
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM);
});
@ -1024,11 +1027,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createIpAttribute')
->label('sdk.description', '/docs/references/databases/create-ip-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_IP)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new IP(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
@ -1051,6 +1054,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
'format' => APP_DATABASE_ATTRIBUTE_IP,
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP);
});
@ -1064,11 +1068,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createUrlAttribute')
->label('sdk.description', '/docs/references/databases/create-url-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_URL)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new URL(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
@ -1091,6 +1095,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
'format' => APP_DATABASE_ATTRIBUTE_URL,
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL);
});
@ -1104,11 +1109,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createIntegerAttribute')
->label('sdk.description', '/docs/references/databases/create-integer-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_INTEGER)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new Integer(), 'Minimum value to enforce on new documents', true)
@ -1128,13 +1133,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
$max = (is_null($max)) ? PHP_INT_MAX : \intval($max);
if ($min > $max) {
throw new Exception('Minimum value must be lesser than maximum value', 400, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
}
$validator = new Range($min, $max, Database::VAR_INTEGER);
if (!is_null($default) && !$validator->isValid($default)) {
throw new Exception($validator->getDescription(), 400, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription());
}
$size = $max > 2147483647 ? 8 : 4; // Automatically create BigInt depending on max value
@ -1160,6 +1165,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
$attribute->setAttribute('max', \intval($formatOptions['max']));
}
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER);
});
@ -1173,11 +1179,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createFloatAttribute')
->label('sdk.description', '/docs/references/databases/create-float-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_FLOAT)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents', true)
@ -1197,7 +1203,7 @@ App::post('/v1/databases/:databaseId/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, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
}
// Ensure default value is a float
@ -1208,7 +1214,7 @@ App::post('/v1/databases/:databaseId/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, Exception::ATTRIBUTE_VALUE_INVALID);
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription());
}
$attribute = createAttribute($databaseId, $collectionId, new Document([
@ -1232,6 +1238,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
$attribute->setAttribute('max', \floatval($formatOptions['max']));
}
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT);
});
@ -1245,11 +1252,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createBooleanAttribute')
->label('sdk.description', '/docs/references/databases/create-boolean-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_BOOLEAN)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Boolean(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
@ -1271,6 +1278,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea
'array' => $array,
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN);
});
@ -1287,7 +1295,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_LIST)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->inject('response')
->inject('dbForProject')
->inject('usage')
@ -1296,12 +1304,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$attributes = $collection->getAttribute('attributes');
@ -1337,7 +1345,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
Response::MODEL_ATTRIBUTE_IP,
Response::MODEL_ATTRIBUTE_STRING,])// needs to be last, since its condition would dominate any other string attribute
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->inject('response')
->inject('dbForProject')
@ -1347,19 +1355,19 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$attribute = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $collection->getInternalId() . '_' . $key);
if ($attribute->isEmpty()) {
throw new Exception('Attribute not found', 404, Exception::ATTRIBUTE_NOT_FOUND);
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
}
// Select response model based on type and format
@ -1400,7 +1408,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->inject('response')
->inject('dbForProject')
@ -1413,18 +1421,18 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$attribute = $dbForProject->getDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key);
if ($attribute->isEmpty()) {
throw new Exception('Attribute not found', 404, Exception::ATTRIBUTE_NOT_FOUND);
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
}
// Only update status if removing available attribute
@ -1491,11 +1499,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
->label('sdk.namespace', 'databases')
->label('sdk.method', 'createIndex')
->label('sdk.description', '/docs/references/databases/create-index.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INDEX)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', null, new Key(), 'Index Key.')
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.')
->param('attributes', null, new ArrayList(new Key(true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.')
@ -1511,12 +1519,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$count = $dbForProject->count('indexes', [
@ -1527,7 +1535,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$limit = 64 - MariaDB::getNumberOfDefaultIndexes();
if ($count >= $limit) {
throw new Exception('Index limit exceeded', 400, Exception::INDEX_LIMIT_EXCEEDED);
throw new Exception(Exception::INDEX_LIMIT_EXCEEDED, 'Index limit exceeded');
}
// Convert Document[] to array of attribute metadata
@ -1573,7 +1581,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key'));
if ($attributeIndex === false) {
throw new Exception('Unknown attribute: ' . $attribute, 400, Exception::ATTRIBUTE_UNKNOWN);
throw new Exception(Exception::ATTRIBUTE_UNKNOWN, 'Unknown attribute: ' . $attribute);
}
$attributeStatus = $oldAttributes[$attributeIndex]['status'];
@ -1582,7 +1590,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
// ensure attribute is available
if ($attributeStatus !== 'available') {
throw new Exception('Attribute not available: ' . $oldAttributes[$attributeIndex]['key'], 400, Exception::ATTRIBUTE_NOT_AVAILABLE);
throw new Exception(Exception::ATTRIBUTE_NOT_AVAILABLE, 'Attribute not available: ' . $oldAttributes[$attributeIndex]['key']);
}
// set attribute size as index length only for strings
@ -1604,7 +1612,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
'orders' => $orders,
]));
} catch (DuplicateException $th) {
throw new Exception('Index already exists', 409, Exception::INDEX_ALREADY_EXISTS);
throw new Exception(Exception::INDEX_ALREADY_EXISTS);
}
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId);
@ -1633,7 +1641,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
->setPayload($index->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($index, Response::MODEL_INDEX);
});
@ -1650,7 +1658,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INDEX_LIST)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->inject('response')
->inject('dbForProject')
->inject('usage')
@ -1659,12 +1667,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$indexes = $collection->getAttribute('indexes');
@ -1692,7 +1700,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INDEX)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', null, new Key(), 'Index Key.')
->inject('response')
->inject('dbForProject')
@ -1702,12 +1710,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$indexes = $collection->getAttribute('indexes');
@ -1716,7 +1724,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
$indexIndex = array_search($key, array_column($indexes, 'key'));
if ($indexIndex === false) {
throw new Exception('Index not found', 404, Exception::INDEX_NOT_FOUND);
throw new Exception(Exception::INDEX_NOT_FOUND);
}
$index = new Document([\array_merge($indexes[$indexIndex], [
@ -1743,7 +1751,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Index Key.')
->inject('response')
->inject('dbForProject')
@ -1756,18 +1764,18 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$index = $dbForProject->getDocument('indexes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key);
if (empty($index->getId())) {
throw new Exception('Index not found', 404, Exception::INDEX_NOT_FOUND);
throw new Exception(Exception::INDEX_NOT_FOUND);
}
// Only update status if removing available index
@ -1820,7 +1828,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('databaseId', '', new UID(), 'Database ID.')
->param('documentId', '', new CustomId(), 'Document ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection). Make sure to define attributes before creating documents.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
->param('data', [], new JSON(), 'Document data as JSON object.')
->param('read', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
@ -1836,16 +1844,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
if (empty($data)) {
throw new Exception('Missing payload', 400, Exception::DOCUMENT_MISSING_PAYLOAD);
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD);
}
if (isset($data['$id'])) {
throw new Exception('$id is not allowed for creating new documents, try update instead', 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, '$id is not allowed for creating new documents, try update instead');
}
/**
@ -1857,7 +1865,7 @@ App::post('/v1/databases/:databaseId/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, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
}
@ -1865,7 +1873,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
if ($collection->getAttribute('permission') === 'collection') {
$validator = new Authorization('write');
if (!$validator->isValid($collection->getWrite())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -1880,13 +1888,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach ($data['$read'] as $read) {
if (!Authorization::isRole($read)) {
// TODO: Isn't this a 401: Unauthorized Error ?
throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Read permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
foreach ($data['$write'] as $write) {
if (!Authorization::isRole($write)) {
throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Write permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
}
@ -1900,9 +1907,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
$document->setAttribute('$collection', $collectionId);
} catch (StructureException $exception) {
throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
} catch (DuplicateException $exception) {
throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS);
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
}
$events
@ -1941,8 +1948,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/database#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. By default will return maximum 25 results. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the document used as the starting point for the query, excluding the document itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
@ -1958,7 +1965,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
/**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
@ -1969,7 +1976,7 @@ App::get('/v1/databases/:databaseId/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, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
}
@ -1977,7 +1984,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
if ($collection->getAttribute('permission') === 'collection') {
$validator = new Authorization('read');
if (!$validator->isValid($collection->getRead())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -1985,7 +1992,7 @@ App::get('/v1/databases/:databaseId/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, Exception::GENERAL_QUERY_LIMIT_EXCEEDED);
throw new Exception(Exception::GENERAL_QUERY_LIMIT_EXCEEDED, "You cannot use more than 100 query values on attribute '{$query->getAttribute()}'");
}
return $query;
@ -1994,14 +2001,14 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
if (!empty($orderAttributes)) {
$validator = new OrderAttributes($collection->getAttribute('attributes', []), $collection->getAttribute('indexes', []), true);
if (!$validator->isValid($orderAttributes)) {
throw new Exception($validator->getDescription(), 400, Exception::GENERAL_QUERY_INVALID);
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
}
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, Exception::GENERAL_QUERY_INVALID);
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
}
@ -2012,7 +2019,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
: $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor);
if ($cursorDocument->isEmpty()) {
throw new Exception("Document '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Document '{$cursor}' for the 'cursor' value not found.");
}
}
@ -2055,7 +2062,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('documentId', null, new UID(), 'Document ID.')
->inject('response')
->inject('dbForProject')
@ -2066,7 +2073,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
/**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
@ -2075,7 +2082,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
}
@ -2083,7 +2090,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
if ($collection->getAttribute('permission') === 'collection') {
$validator = new Authorization('read');
if (!$validator->isValid($collection->getRead())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -2100,7 +2107,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
$document->setAttribute('$collection', $collectionId);
if ($document->isEmpty()) {
throw new Exception('No document found', 404, Exception::DOCUMENT_NOT_FOUND);
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$usage
@ -2138,18 +2145,18 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId);
if ($document->isEmpty()) {
throw new Exception('No document found', 404, Exception::DOCUMENT_NOT_FOUND);
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$audit = new Audit($dbForProject);
@ -2236,7 +2243,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
/**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
@ -2245,7 +2252,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
}
@ -2253,7 +2260,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
if ($collection->getAttribute('permission') === 'collection') {
$validator = new Authorization('write');
if (!$validator->isValid($collection->getWrite())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$document = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
@ -2263,17 +2270,17 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
if ($document->isEmpty()) {
throw new Exception('Document not found', 404, Exception::DOCUMENT_NOT_FOUND);
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
if (empty($data) && empty($read) && empty($write)) {
throw new Exception('Missing payload or read/write permissions', 400, Exception::DOCUMENT_MISSING_PAYLOAD);
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD, 'Missing payload or read/write permissions');
}
if (!\is_array($data)) {
throw new Exception('Data param should be a valid JSON object', 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, 'Data param should be a valid JSON object');
}
$data = \array_merge($document->getArrayCopy(), $data);
@ -2291,14 +2298,14 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Read permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
}
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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Write permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
}
@ -2316,11 +2323,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
*/
$document->setAttribute('$collection', $collectionId);
} catch (AuthorizationException $exception) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (DuplicateException $exception) {
throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS);
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
} catch (StructureException $exception) {
throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
}
$events
@ -2358,7 +2365,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('documentId', null, new UID(), 'Document ID.')
->inject('response')
->inject('dbForProject')
@ -2372,7 +2379,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND);
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
/**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
@ -2381,7 +2388,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
}
@ -2389,7 +2396,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
if ($collection->getAttribute('permission') === 'collection') {
$validator = new Authorization('write');
if (!$validator->isValid($collection->getWrite())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -2401,7 +2408,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
}
if ($document->isEmpty()) {
throw new Exception('No document found', 404, Exception::DOCUMENT_NOT_FOUND);
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
if ($collection->getAttribute('permission') === 'collection') {
@ -2692,7 +2699,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
$collection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $collectionDocument->getInternalId());
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404, Exception::COLLECTION_NOT_FOUND);
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$usage = [];

View file

@ -111,7 +111,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, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Function '{$cursor}' for the 'cursor' value not found.");
}
}
@ -172,7 +172,7 @@ App::get('/v1/functions/:functionId')
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
throw new Exception(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, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$usage = [];
@ -305,7 +305,7 @@ App::put('/v1/functions/:functionId')
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$original = $function->getAttribute('schedule', '');
@ -365,19 +365,19 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId')
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
if ($function->isEmpty()) {
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
if ($deployment->isEmpty()) {
throw new Exception('Deployment not found', 404, Exception::DEPLOYMENT_NOT_FOUND);
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($build->isEmpty()) {
throw new Exception('Build not found', 404, Exception::BUILD_NOT_FOUND);
throw new Exception(Exception::BUILD_NOT_FOUND);
}
if ($build->getAttribute('status') !== 'ready') {
throw new Exception('Build not ready', 400, Exception::BUILD_NOT_READY);
throw new Exception(Exception::BUILD_NOT_READY);
}
$schedule = $function->getAttribute('schedule', '');
@ -426,11 +426,11 @@ App::delete('/v1/functions/:functionId')
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
if (!$dbForProject->deleteDocument('functions', $function->getId())) {
throw new Exception('Failed to remove function from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove function from DB');
}
$deletes
@ -453,7 +453,7 @@ App::post('/v1/functions/:functionId/deployments')
->label('sdk.description', '/docs/references/functions/create-deployment.md')
->label('sdk.packaging', true)
->label('sdk.request.type', 'multipart/form-data')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DEPLOYMENT)
->param('functionId', '', new UID(), 'Function ID.')
@ -473,7 +473,7 @@ App::post('/v1/functions/:functionId/deployments')
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$file = $request->getFiles('code');
@ -482,7 +482,7 @@ App::post('/v1/functions/:functionId/deployments')
$upload = new Upload();
if (empty($file)) {
throw new Exception('No file sent', 400, Exception::STORAGE_FILE_EMPTY);
throw new Exception(Exception::STORAGE_FILE_EMPTY, 'No file sent');
}
// Make sure we handle a single file and multiple files the same way
@ -491,7 +491,7 @@ App::post('/v1/functions/:functionId/deployments')
$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, Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
}
$contentRange = $request->getHeader('content-range');
@ -505,7 +505,7 @@ App::post('/v1/functions/:functionId/deployments')
$fileSize = $request->getContentRangeSize();
$deploymentId = $request->getHeader('x-appwrite-id', $deploymentId);
if (is_null($start) || is_null($end) || is_null($fileSize)) {
throw new Exception('Invalid content-range header', 400, Exception::STORAGE_INVALID_CONTENT_RANGE);
throw new Exception(Exception::STORAGE_INVALID_CONTENT_RANGE);
}
if ($end === $fileSize) {
@ -519,11 +519,11 @@ App::post('/v1/functions/:functionId/deployments')
}
if (!$fileSizeValidator->isValid($fileSize)) { // Check if file size is exceeding allowed limit
throw new Exception('File size not allowed', 400, Exception::STORAGE_INVALID_FILE_SIZE);
throw new Exception(Exception::STORAGE_INVALID_FILE_SIZE);
}
if (!$upload->isValid($fileTmpName)) {
throw new Exception('Invalid file', 403, Exception::STORAGE_INVALID_FILE);
throw new Exception(Exception::STORAGE_INVALID_FILE);
}
// Save to storage
@ -544,7 +544,7 @@ App::post('/v1/functions/:functionId/deployments')
$chunksUploaded = $deviceFunctions->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
if (empty($chunksUploaded)) {
throw new Exception('Failed moving file', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed moving file');
}
$activate = (bool) filter_var($activate, FILTER_VALIDATE_BOOLEAN);
@ -622,7 +622,7 @@ App::post('/v1/functions/:functionId/deployments')
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
});
@ -651,14 +651,14 @@ App::get('/v1/functions/:functionId/deployments')
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
if (!empty($cursor)) {
$cursorDeployment = $dbForProject->getDocument('deployments', $cursor);
if ($cursorDeployment->isEmpty()) {
throw new Exception("Tag '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Tag '{$cursor}' for the 'cursor' value not found.");
}
}
@ -707,17 +707,17 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId')
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new Exception('Deployment not found', 404, Exception::DEPLOYMENT_NOT_FOUND);
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($deployment->isEmpty()) {
throw new Exception('Deployment not found', 404, Exception::DEPLOYMENT_NOT_FOUND);
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
@ -746,21 +746,21 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId')
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception('Deployment not found', 404, Exception::DEPLOYMENT_NOT_FOUND);
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new Exception('Deployment not found', 404, Exception::DEPLOYMENT_NOT_FOUND);
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($deviceFunctions->delete($deployment->getAttribute('path', ''))) {
if (!$dbForProject->deleteDocument('deployments', $deployment->getId())) {
throw new Exception('Failed to remove deployment from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from DB');
}
}
@ -812,7 +812,7 @@ App::post('/v1/functions/:functionId/executions')
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
if ($function->isEmpty()) {
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$runtimes = Config::getParam('runtimes', []);
@ -820,33 +820,33 @@ App::post('/v1/functions/:functionId/executions')
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
if (\is_null($runtime)) {
throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported', 400, Exception::FUNCTION_RUNTIME_UNSUPPORTED);
throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new Exception('Deployment not found. Create a deployment before trying to execute a function', 404, Exception::DEPLOYMENT_NOT_FOUND);
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
}
if ($deployment->isEmpty()) {
throw new Exception('Deployment not found. Create a deployment before trying to execute a function', 404, Exception::DEPLOYMENT_NOT_FOUND);
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
}
/** Check if build has completed */
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
if ($build->isEmpty()) {
throw new Exception('Build not found', 404, Exception::BUILD_NOT_FOUND);
throw new Exception(Exception::BUILD_NOT_FOUND);
}
if ($build->getAttribute('status') !== 'ready') {
throw new Exception('Build not ready', 400, Exception::BUILD_NOT_READY);
throw new Exception(Exception::BUILD_NOT_READY);
}
$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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
}
$executionId = $dbForProject->getId();
@ -906,7 +906,7 @@ App::post('/v1/functions/:functionId/executions')
$event->trigger();
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
return $response->dynamic($execution, Response::MODEL_EXECUTION);
}
@ -994,14 +994,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, Exception::FUNCTION_NOT_FOUND);
throw new Exception(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, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Execution '{$cursor}' for the 'cursor' value not found.");
}
}
@ -1042,17 +1042,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, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$execution = $dbForProject->getDocument('executions', $executionId);
if ($execution->getAttribute('functionId') !== $function->getId()) {
throw new Exception('Execution not found', 404, Exception::EXECUTION_NOT_FOUND);
throw new Exception(Exception::EXECUTION_NOT_FOUND);
}
if ($execution->isEmpty()) {
throw new Exception('Execution not found', 404, Exception::EXECUTION_NOT_FOUND);
throw new Exception(Exception::EXECUTION_NOT_FOUND);
}
$response->dynamic($execution, Response::MODEL_EXECUTION);
@ -1082,21 +1082,21 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
if ($deployment->isEmpty()) {
throw new Exception('Deployment not found', 404, Exception::DEPLOYMENT_NOT_FOUND);
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $buildId));
if ($build->isEmpty()) {
throw new Exception('Build not found', 404, Exception::BUILD_NOT_FOUND);
throw new Exception(Exception::BUILD_NOT_FOUND);
}
if ($build->getAttribute('status') !== 'failed') {
throw new Exception('Build not failed', 400, Exception::BUILD_IN_PROGRESS);
throw new Exception(Exception::BUILD_IN_PROGRESS, 'Build not failed');
}
$events

View file

@ -19,6 +19,6 @@ App::post('/v1/graphql')
->label('scope', 'public')
->action(
function () {
throw new Exception('GraphQL support is coming soon!', 502, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'GraphQL support is coming soon!', 503);
}
);

View file

@ -73,7 +73,7 @@ App::get('/v1/health/db')
$statement->execute();
} catch (Exception $_e) {
throw new Exception('Database is not available', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Database is not available');
}
$output = [
@ -104,7 +104,7 @@ App::get('/v1/health/cache')
$redis = $utopia->getResource('cache');
if (!$redis->ping(true)) {
throw new Exception('Cache is not available', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Cache is not available');
}
$output = [
@ -160,7 +160,7 @@ App::get('/v1/health/time')
$diff = ($timestamp - \time());
if ($diff > $gap || $diff < ($gap * -1)) {
throw new Exception('Server time gaps detected', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Server time gaps detected');
}
$output = [
@ -267,11 +267,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', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Device ' . $key . ' dir is not readable');
}
if (!\is_writable($device->getRoot())) {
throw new Exception('Device ' . $key . ' dir is not writable', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Device ' . $key . ' dir is not writable');
}
}
@ -315,7 +315,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, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Antivirus is not available');
}
}

View file

@ -31,12 +31,14 @@ use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
App::init(function (Document $project) {
if ($project->getId() !== 'console') {
throw new Exception('Access to this API is forbidden.', 401, Exception::GENERAL_ACCESS_FORBIDDEN);
}
}, ['project'], 'projects');
App::init()
->groups(['projects'])
->inject('project')
->action(function (Document $project) {
if ($project->getId() !== 'console') {
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN);
}
});
App::post('/v1/projects')
->desc('Create Project')
@ -68,7 +70,7 @@ App::post('/v1/projects')
$team = $dbForConsole->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$auth = Config::getParam('auth', []);
@ -80,7 +82,7 @@ App::post('/v1/projects')
$projectId = ($projectId == 'unique()') ? $dbForConsole->getId() : $projectId;
if ($projectId === 'console') {
throw new Exception("'console' is a reserved project.", 400, Exception::PROJECT_RESERVED_PROJECT);
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
}
$project = $dbForConsole->createDocument('projects', new Document([
@ -183,7 +185,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, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Project '{$cursor}' for the 'cursor' value not found.");
}
}
@ -220,7 +222,7 @@ App::get('/v1/projects/:projectId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$response->dynamic($project, Response::MODEL_PROJECT);
@ -247,7 +249,7 @@ App::get('/v1/projects/:projectId/usage')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$usage = [];
@ -364,7 +366,7 @@ App::patch('/v1/projects/:projectId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project
@ -403,7 +405,7 @@ App::patch('/v1/projects/:projectId/service')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$services = $project->getAttribute('services', []);
@ -435,7 +437,7 @@ App::patch('/v1/projects/:projectId/oauth2')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$providers = $project->getAttribute('authProviders', []);
@ -466,7 +468,7 @@ App::patch('/v1/projects/:projectId/auth/limit')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
@ -501,7 +503,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, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
@ -530,13 +532,13 @@ App::delete('/v1/projects/:projectId')
->action(function (string $projectId, string $password, Response $response, Document $user, Database $dbForConsole, Delete $deletes) {
if (!Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
}
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$deletes
@ -545,11 +547,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, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove project team from DB');
}
if (!$dbForConsole->deleteDocument('projects', $projectId)) {
throw new Exception('Failed to remove project from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove project from DB');
}
$response->noContent();
@ -581,7 +583,7 @@ App::post('/v1/projects/:projectId/webhooks')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN);
@ -627,7 +629,7 @@ App::get('/v1/projects/:projectId/webhooks')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$webhooks = $dbForConsole->find('webhooks', [
@ -659,7 +661,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$webhook = $dbForConsole->findOne('webhooks', [
@ -668,7 +670,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
]);
if ($webhook === false || $webhook->isEmpty()) {
throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND);
throw new Exception(Exception::WEBHOOK_NOT_FOUND);
}
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
@ -699,7 +701,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
@ -710,7 +712,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
]);
if ($webhook === false || $webhook->isEmpty()) {
throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND);
throw new Exception(Exception::WEBHOOK_NOT_FOUND);
}
$webhook
@ -747,7 +749,7 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$webhook = $dbForConsole->findOne('webhooks', [
@ -756,7 +758,7 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
]);
if ($webhook === false || $webhook->isEmpty()) {
throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND);
throw new Exception(Exception::WEBHOOK_NOT_FOUND);
}
$webhook->setAttribute('signatureKey', \bin2hex(\random_bytes(64)));
@ -785,7 +787,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$webhook = $dbForConsole->findOne('webhooks', [
@ -794,7 +796,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
]);
if ($webhook === false || $webhook->isEmpty()) {
throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND);
throw new Exception(Exception::WEBHOOK_NOT_FOUND);
}
$dbForConsole->deleteDocument('webhooks', $webhook->getId());
@ -827,7 +829,7 @@ App::post('/v1/projects/:projectId/keys')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$key = new Document([
@ -868,7 +870,7 @@ App::get('/v1/projects/:projectId/keys')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$keys = $dbForConsole->find('keys', [
@ -900,7 +902,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$key = $dbForConsole->findOne('keys', [
@ -909,7 +911,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
]);
if ($key === false || $key->isEmpty()) {
throw new Exception('Key not found', 404, Exception::KEY_NOT_FOUND);
throw new Exception(Exception::KEY_NOT_FOUND);
}
$response->dynamic($key, Response::MODEL_KEY);
@ -937,7 +939,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$key = $dbForConsole->findOne('keys', [
@ -946,7 +948,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
]);
if ($key === false || $key->isEmpty()) {
throw new Exception('Key not found', 404, Exception::KEY_NOT_FOUND);
throw new Exception(Exception::KEY_NOT_FOUND);
}
$key
@ -980,7 +982,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$key = $dbForConsole->findOne('keys', [
@ -989,7 +991,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
]);
if ($key === false || $key->isEmpty()) {
throw new Exception('Key not found', 404, Exception::KEY_NOT_FOUND);
throw new Exception(Exception::KEY_NOT_FOUND);
}
$dbForConsole->deleteDocument('keys', $key->getId());
@ -1023,7 +1025,7 @@ App::post('/v1/projects/:projectId/platforms')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$platform = new Document([
@ -1065,7 +1067,7 @@ App::get('/v1/projects/:projectId/platforms')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$platforms = $dbForConsole->find('platforms', [
@ -1097,7 +1099,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$platform = $dbForConsole->findOne('platforms', [
@ -1106,7 +1108,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
]);
if ($platform === false || $platform->isEmpty()) {
throw new Exception('Platform not found', 404, Exception::PLATFORM_NOT_FOUND);
throw new Exception(Exception::PLATFORM_NOT_FOUND);
}
$response->dynamic($platform, Response::MODEL_PLATFORM);
@ -1134,7 +1136,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$platform = $dbForConsole->findOne('platforms', [
@ -1143,7 +1145,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
]);
if ($platform === false || $platform->isEmpty()) {
throw new Exception('Platform not found', 404, Exception::PLATFORM_NOT_FOUND);
throw new Exception(Exception::PLATFORM_NOT_FOUND);
}
$platform
@ -1178,7 +1180,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$platform = $dbForConsole->findOne('platforms', [
@ -1187,7 +1189,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
]);
if ($platform === false || $platform->isEmpty()) {
throw new Exception('Platform not found', 404, Exception::PLATFORM_NOT_FOUND);
throw new Exception(Exception::PLATFORM_NOT_FOUND);
}
$dbForConsole->deleteDocument('platforms', $platformId);
@ -1218,7 +1220,7 @@ App::post('/v1/projects/:projectId/domains')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$document = $dbForConsole->findOne('domains', [
@ -1227,13 +1229,13 @@ App::post('/v1/projects/:projectId/domains')
]);
if ($document && !$document->isEmpty()) {
throw new Exception('Domain already exists', 409, Exception::DOMAIN_ALREADY_EXISTS);
throw new Exception(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, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.');
}
$domain = new Domain($domain);
@ -1278,7 +1280,7 @@ App::get('/v1/projects/:projectId/domains')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$domains = $dbForConsole->find('domains', [
@ -1310,7 +1312,7 @@ App::get('/v1/projects/:projectId/domains/:domainId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$domain = $dbForConsole->findOne('domains', [
@ -1319,7 +1321,7 @@ App::get('/v1/projects/:projectId/domains/:domainId')
]);
if ($domain === false || $domain->isEmpty()) {
throw new Exception('Domain not found', 404, Exception::DOMAIN_NOT_FOUND);
throw new Exception(Exception::DOMAIN_NOT_FOUND);
}
$response->dynamic($domain, Response::MODEL_DOMAIN);
@ -1344,7 +1346,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$domain = $dbForConsole->findOne('domains', [
@ -1353,13 +1355,13 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
]);
if ($domain === false || $domain->isEmpty()) {
throw new Exception('Domain not found', 404, Exception::DOMAIN_NOT_FOUND);
throw new Exception(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, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.');
}
if ($domain->getAttribute('verification') === true) {
@ -1369,7 +1371,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, Exception::DOMAIN_VERIFICATION_FAILED);
throw new Exception(Exception::DOMAIN_VERIFICATION_FAILED);
}
@ -1404,7 +1406,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$domain = $dbForConsole->findOne('domains', [
@ -1413,7 +1415,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
]);
if ($domain === false || $domain->isEmpty()) {
throw new Exception('Domain not found', 404, Exception::DOMAIN_NOT_FOUND);
throw new Exception(Exception::DOMAIN_NOT_FOUND);
}
$dbForConsole->deleteDocument('domains', $domain->getId());

View file

@ -74,7 +74,7 @@ App::post('/v1/storage/buckets')
try {
$files = Config::getParam('collections', [])['files'] ?? [];
if (empty($files)) {
throw new Exception('Files collection is not configured.', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Files collection is not configured.');
}
$attributes = [];
@ -123,7 +123,7 @@ App::post('/v1/storage/buckets')
$dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
} catch (Duplicate $th) {
throw new Exception('Bucket already exists', 409, Exception::STORAGE_BUCKET_ALREADY_EXISTS);
throw new Exception(Exception::STORAGE_BUCKET_ALREADY_EXISTS);
}
$audits
@ -169,7 +169,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, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Bucket '{$cursor}' for the 'cursor' value not found.");
}
}
@ -201,7 +201,7 @@ App::get('/v1/storage/buckets/:bucketId')
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$usage->setParam('storage.buckets.read', 1);
@ -240,7 +240,7 @@ App::put('/v1/storage/buckets/:bucketId')
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$read ??= $bucket->getAttribute('$read', []); // By default inherit read permissions
@ -298,11 +298,11 @@ App::delete('/v1/storage/buckets/:bucketId')
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
if (!$dbForProject->deleteDocument('buckets', $bucketId)) {
throw new Exception('Failed to remove project from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove bucket from DB');
}
$deletes
@ -361,7 +361,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
// Check bucket permissions when enforced
@ -369,7 +369,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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -382,12 +382,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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400);
}
}
foreach ($write as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400);
}
}
}
@ -402,12 +402,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('Maximum bucket file size is larger than _APP_STORAGE_LIMIT', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Maximum bucket file size is larger than _APP_STORAGE_LIMIT');
}
$file = $request->getFiles('file');
if (empty($file)) {
throw new Exception('No file sent', 400, Exception::STORAGE_FILE_EMPTY);
throw new Exception(Exception::STORAGE_FILE_EMPTY);
}
// Make sure we handle a single file and multiple files the same way
@ -426,7 +426,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, Exception::STORAGE_INVALID_CONTENT_RANGE);
throw new Exception(Exception::STORAGE_INVALID_CONTENT_RANGE);
}
if ($end === $fileSize) {
@ -446,18 +446,18 @@ App::post('/v1/storage/buckets/:bucketId/files')
$allowedFileExtensions = $bucket->getAttribute('allowedFileExtensions', []);
$fileExt = new FileExt($allowedFileExtensions);
if (!empty($allowedFileExtensions) && !$fileExt->isValid($fileName)) {
throw new Exception('File extension not allowed', 400, Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED, 'File extension not allowed');
}
// Check if file size is exceeding allowed limit
$fileSizeValidator = new FileSize($maximumFileSize);
if (!$fileSizeValidator->isValid($fileSize)) {
throw new Exception('File size not allowed', 400, Exception::STORAGE_INVALID_FILE_SIZE);
throw new Exception(Exception::STORAGE_INVALID_FILE_SIZE, 'File size not allowed');
}
$upload = new Upload();
if (!$upload->isValid($fileTmpName)) {
throw new Exception('Invalid file', 403, Exception::STORAGE_INVALID_FILE);
throw new Exception(Exception::STORAGE_INVALID_FILE);
}
// Save to storage
@ -484,7 +484,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, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed uploading file');
}
$read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? [];
@ -498,7 +498,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
if (!$antivirus->fileScan($path)) {
$deviceFiles->delete($path);
throw new Exception('Invalid file', 400, Exception::STORAGE_INVALID_FILE);
throw new Exception(Exception::STORAGE_INVALID_FILE);
}
}
@ -522,7 +522,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, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to save file');
}
}
@ -589,9 +589,9 @@ App::post('/v1/storage/buckets/:bucketId/files')
}
}
} catch (StructureException $exception) {
throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
} catch (DuplicateException $exception) {
throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS);
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
}
$audits
@ -641,9 +641,9 @@ App::post('/v1/storage/buckets/:bucketId/files')
}
}
} catch (StructureException $exception) {
throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
} catch (DuplicateException $exception) {
throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS);
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
}
}
@ -690,14 +690,14 @@ App::get('/v1/storage/buckets/:bucketId/files')
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -715,7 +715,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
}
if ($cursorFile->isEmpty()) {
throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "File '{$cursor}' for the 'cursor' value not found.");
}
}
@ -768,14 +768,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -786,7 +786,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$usage
@ -833,7 +833,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, Stats $usage, string $mode, Device $deviceFiles, Device $deviceLocal) {
if (!\extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
}
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -842,14 +842,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(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, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND, 'Unauthorized permissions');
}
}
@ -872,7 +872,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$path = $file->getAttribute('path');
@ -901,7 +901,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$compressor = new GZIP();
if (!$deviceFiles->exists($path)) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
throw new Exception(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
@ -1010,14 +1010,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -1028,13 +1028,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$path = $file->getAttribute('path', '');
if (!$deviceFiles->exists($path)) {
throw new Exception('File not found in ' . $path, 404, Exception::STORAGE_FILE_NOT_FOUND);
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
}
$usage
@ -1062,7 +1062,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
}
if ($unit !== 'bytes' || $start >= $end || $end >= $size) {
throw new Exception('Invalid range', 416, Exception::STORAGE_INVALID_RANGE);
throw new Exception(Exception::STORAGE_INVALID_RANGE);
}
$response
@ -1149,14 +1149,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -1169,13 +1169,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, Exception::STORAGE_FILE_NOT_FOUND);
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$path = $file->getAttribute('path', '');
if (!$deviceFiles->exists($path)) {
throw new Exception('File not found in ' . $path, 404, Exception::STORAGE_FILE_NOT_FOUND);
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
}
$compressor = new GZIP();
@ -1208,7 +1208,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
}
if ($unit != 'bytes' || $start >= $end || $end >= $size) {
throw new Exception('Invalid range', 416, Exception::STORAGE_INVALID_RANGE);
throw new Exception(Exception::STORAGE_INVALID_RANGE);
}
$response
@ -1308,12 +1308,12 @@ 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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400);
}
}
foreach ($write as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400);
}
}
}
@ -1322,14 +1322,14 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -1340,7 +1340,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$file
@ -1399,14 +1399,14 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
}
}
@ -1417,7 +1417,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$deviceDeleted = false;
@ -1442,10 +1442,10 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
if (!$deleted) {
throw new Exception('Failed to remove file from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove file from DB');
}
} else {
throw new Exception('Failed to delete file from device', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to delete file from device');
}
$audits->setResource('file/' . $file->getId());
@ -1594,7 +1594,7 @@ App::get('/v1/storage/:bucketId/usage')
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$usage = [];

View file

@ -129,7 +129,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, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Team '{$cursor}' for the 'cursor' value not found.");
}
}
@ -167,7 +167,7 @@ App::get('/v1/teams/:teamId')
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$response->dynamic($team, Response::MODEL_TEAM);
@ -196,7 +196,7 @@ App::put('/v1/teams/:teamId')
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$team = $dbForProject->updateDocument('teams', $team->getId(), $team
@ -231,7 +231,7 @@ App::delete('/v1/teams/:teamId')
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$memberships = $dbForProject->find('memberships', [
@ -241,12 +241,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, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership for team from DB');
}
}
if (!$dbForProject->deleteDocument('teams', $teamId)) {
throw new Exception('Failed to remove team from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
}
$deletes
@ -300,7 +300,7 @@ App::post('/v1/teams/:teamId/memberships')
$isAppUser = Auth::isAppUser(Authorization::getRoles());
if (!$isPrivilegedUser && !$isAppUser && empty(App::getEnv('_APP_SMTP_HOST'))) {
throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED);
throw new Exception(Exception::GENERAL_SMTP_DISABLED);
}
$email = \strtolower($email);
@ -308,7 +308,7 @@ App::post('/v1/teams/:teamId/memberships')
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$invitee = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
@ -320,7 +320,7 @@ App::post('/v1/teams/:teamId/memberships')
$total = $dbForProject->count('users', [], APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
throw new Exception(Exception::USER_COUNT_EXCEEDED, 'Project registration is restricted. Contact your administrator for more information.');
}
}
@ -352,14 +352,14 @@ App::post('/v1/teams/:teamId/memberships')
'search' => implode(' ', [$userId, $email, $name])
])));
} catch (Duplicate $th) {
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
throw new Exception(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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
}
$secret = Auth::tokenGenerator();
@ -385,7 +385,7 @@ App::post('/v1/teams/:teamId/memberships')
try {
$membership = Authorization::skip(fn() => $dbForProject->createDocument('memberships', $membership));
} catch (Duplicate $th) {
throw new Exception('User is already a member of this team', 409, Exception::TEAM_INVITE_ALREADY_EXISTS);
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
}
$team->setAttribute('total', $team->getAttribute('total', 0) + 1);
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
@ -395,7 +395,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, Exception::TEAM_INVITE_ALREADY_EXISTS);
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
}
}
@ -429,8 +429,8 @@ App::post('/v1/teams/:teamId/memberships')
$response->dynamic(
$membership
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email')),
->setAttribute('userName', $invitee->getAttribute('name'))
->setAttribute('userEmail', $invitee->getAttribute('email')),
Response::MODEL_MEMBERSHIP
);
});
@ -460,14 +460,14 @@ App::get('/v1/teams/:teamId/memberships')
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(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, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Membership '{$cursor}' for the 'cursor' value not found.");
}
}
@ -533,13 +533,13 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$membership = $dbForProject->getDocument('memberships', $membershipId);
if ($membership->isEmpty() || empty($membership->getAttribute('userId'))) {
throw new Exception('Membership not found', 404, Exception::MEMBERSHIP_NOT_FOUND);
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
}
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
@ -578,17 +578,17 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$membership = $dbForProject->getDocument('memberships', $membershipId);
if ($membership->isEmpty()) {
throw new Exception('Membership not found', 404, Exception::MEMBERSHIP_NOT_FOUND);
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
}
$profile = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
if ($profile->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
@ -596,7 +596,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, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to modify roles');
}
/**
@ -654,25 +654,25 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$membership = $dbForProject->getDocument('memberships', $membershipId);
if ($membership->isEmpty()) {
throw new Exception('Membership not found', 404, Exception::MEMBERSHIP_NOT_FOUND);
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
}
if ($membership->getAttribute('teamId') !== $teamId) {
throw new Exception('Team IDs don\'t match', 404, Exception::TEAM_MEMBERSHIP_MISMATCH);
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
}
$team = Authorization::skip(fn() => $dbForProject->getDocument('teams', $teamId));
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(Exception::TEAM_NOT_FOUND);
}
if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
throw new Exception('Secret key not valid', 401, Exception::TEAM_INVALID_SECRET);
throw new Exception(Exception::TEAM_INVALID_SECRET);
}
if ($userId !== $membership->getAttribute('userId')) {
throw new Exception('Invite does not belong to current user (' . $user->getAttribute('email') . ')', 401, Exception::TEAM_INVITE_MISMATCH);
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')');
}
if ($user->isEmpty()) {
@ -680,11 +680,11 @@ 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, Exception::TEAM_INVITE_MISMATCH);
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')');
}
if ($membership->getAttribute('confirm') === true) {
throw new Exception('Membership already confirmed', 409);
throw new Exception(Exception::MEMBERSHIP_ALREADY_CONFIRMED);
}
$membership // Attach user to team
@ -780,31 +780,31 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
$membership = $dbForProject->getDocument('memberships', $membershipId);
if ($membership->isEmpty()) {
throw new Exception('Invite not found', 404, Exception::TEAM_INVITE_NOT_FOUND);
throw new Exception(Exception::TEAM_INVITE_NOT_FOUND);
}
if ($membership->getAttribute('teamId') !== $teamId) {
throw new Exception('Team IDs don\'t match', 404);
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
}
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(Exception::TEAM_NOT_FOUND);
}
try {
$dbForProject->deleteDocument('memberships', $membership->getId());
} catch (AuthorizationException $exception) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (\Exception $exception) {
throw new Exception('Failed to remove membership from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership from DB');
}
$dbForProject->deleteCachedDocument('users', $user->getId());
@ -853,7 +853,7 @@ App::get('/v1/teams/:teamId/logs')
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$audit = new Audit($dbForProject);

View file

@ -30,6 +30,7 @@ use Utopia\Validator\Boolean;
use MaxMind\Db\Reader;
use Utopia\Validator\Integer;
/** TODO: Remove function when we move to using utopia/platform */
function createUser(string $hash, mixed $hashOptions, string $userId, string $email, string $password, string $name, Database $dbForProject, Stats $usage, Event $events): Document
{
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
@ -58,7 +59,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, string $em
'search' => implode(' ', [$userId, $email, $name])
]));
} catch (Duplicate $th) {
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
$usage
@ -99,7 +100,7 @@ App::post('/v1/users')
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/import/bcrypt')
App::post('/v1/users/bcrypt')
->desc('Create User with Bcrypt Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@ -126,7 +127,7 @@ App::post('/v1/users/import/bcrypt')
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/import/md5')
App::post('/v1/users/md5')
->desc('Create User with MD5 Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@ -153,7 +154,7 @@ App::post('/v1/users/import/md5')
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/import/argon2')
App::post('/v1/users/argon2')
->desc('Create User with Argon2 Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@ -180,7 +181,7 @@ App::post('/v1/users/import/argon2')
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/import/sha')
App::post('/v1/users/sha')
->desc('Create User with SHA Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@ -214,7 +215,7 @@ App::post('/v1/users/import/sha')
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/import/phpass')
App::post('/v1/users/phpass')
->desc('Create User with PHPass Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@ -241,7 +242,7 @@ App::post('/v1/users/import/phpass')
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/import/scrypt')
App::post('/v1/users/scrypt')
->desc('Create User with Scrypt Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@ -281,7 +282,7 @@ App::post('/v1/users/import/scrypt')
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/import/scrypt-modified')
App::post('/v1/users/scrypt-modified')
->desc('Create User with Scrypt Modified Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@ -337,7 +338,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, Exception::GENERAL_CURSOR_NOT_FOUND);
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "User '{$cursor}' for the 'cursor' value not found.");
}
}
@ -377,7 +378,7 @@ App::get('/v1/users/:userId')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$usage
@ -406,7 +407,7 @@ App::get('/v1/users/:userId/prefs')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$prefs = $user->getAttribute('prefs', new \stdClass());
@ -438,7 +439,7 @@ App::get('/v1/users/:userId/sessions')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$sessions = $user->getAttribute('sessions', []);
@ -481,7 +482,7 @@ App::get('/v1/users/:userId/memberships')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$memberships = array_map(function ($membership) use ($dbForProject, $user) {
@ -525,7 +526,7 @@ App::get('/v1/users/:userId/logs')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$audit = new Audit($dbForProject);
@ -606,7 +607,7 @@ App::patch('/v1/users/:userId/status')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', (bool) $status));
@ -645,7 +646,7 @@ App::patch('/v1/users/:userId/verification')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', $emailVerification));
@ -684,7 +685,7 @@ App::patch('/v1/users/:userId/verification/phone')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('phoneVerification', $phoneVerification));
@ -723,12 +724,12 @@ App::patch('/v1/users/:userId/name')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$user
->setAttribute('name', $name)
->setAttribute('search', \implode(' ', [$user->getId(), $user->getAttribute('email'), $name]));
->setAttribute('search', \implode(' ', [$user->getId(), $user->getAttribute('email', ''), $name, $user->getAttribute('phone', '')]));
;
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
@ -766,7 +767,7 @@ App::patch('/v1/users/:userId/password')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$user
@ -811,7 +812,7 @@ App::patch('/v1/users/:userId/email')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$email = \strtolower($email);
@ -819,13 +820,13 @@ App::patch('/v1/users/:userId/email')
$user
->setAttribute('email', $email)
->setAttribute('emailVerification', false)
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name')]))
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name', ''), $user->getAttribute('phone', '')]))
;
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
} catch (Duplicate $th) {
throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
}
@ -863,18 +864,19 @@ App::patch('/v1/users/:userId/phone')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$user
->setAttribute('phone', $number)
->setAttribute('phoneVerification', false)
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $user->getAttribute('email', ''), $number]));
;
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
} catch (Duplicate $th) {
throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
}
@ -912,7 +914,7 @@ App::patch('/v1/users/:userId/prefs')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
@ -950,13 +952,13 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$session = $dbForProject->getDocument('sessions', $sessionId);
if ($session->isEmpty()) {
throw new Exception('Session not found', 404, Exception::USER_SESSION_NOT_FOUND);
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
}
$dbForProject->deleteDocument('sessions', $session->getId());
@ -997,7 +999,7 @@ App::delete('/v1/users/:userId/sessions')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
$sessions = $user->getAttribute('sessions', []);
@ -1044,7 +1046,7 @@ App::delete('/v1/users/:userId')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
throw new Exception(Exception::USER_NOT_FOUND);
}
// clone user object to send to workers

View file

@ -35,485 +35,506 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
App::init(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients) {
App::init()
->inject('utopia')
->inject('request')
->inject('response')
->inject('console')
->inject('project')
->inject('dbForConsole')
->inject('user')
->inject('locale')
->inject('clients')
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients) {
/*
* Request format
*/
$route = $utopia->match($request);
Request::setRoute($route);
/*
* Request format
*/
$route = $utopia->match($request);
Request::setRoute($route);
$requestFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($requestFormat) {
switch ($requestFormat) {
case version_compare($requestFormat, '0.12.0', '<'):
Request::setFilter(new RequestV12());
break;
case version_compare($requestFormat, '0.13.0', '<'):
Request::setFilter(new RequestV13());
break;
case version_compare($requestFormat, '0.14.0', '<'):
Request::setFilter(new RequestV14());
break;
default:
Request::setFilter(null);
}
} else {
Request::setFilter(null);
}
$domain = $request->getHostname();
$domains = Config::getParam('domains', []);
if (!array_key_exists($domain, $domains)) {
$domain = new Domain(!empty($domain) ? $domain : '');
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
$domains[$domain->get()] = false;
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
Console::warning('Skipping SSL certificates generation on ACME challenge.');
} else {
Authorization::disable();
$envDomain = App::getEnv('_APP_DOMAIN', '');
$mainDomain = null;
if (!empty($envDomain) && $envDomain !== 'localhost') {
$mainDomain = $envDomain;
} else {
$domainDocument = $dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
$requestFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($requestFormat) {
switch ($requestFormat) {
case version_compare($requestFormat, '0.12.0', '<'):
Request::setFilter(new RequestV12());
break;
case version_compare($requestFormat, '0.13.0', '<'):
Request::setFilter(new RequestV13());
break;
case version_compare($requestFormat, '0.14.0', '<'):
Request::setFilter(new RequestV14());
break;
default:
Request::setFilter(null);
}
} else {
Request::setFilter(null);
}
if ($mainDomain !== $domain->get()) {
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
$domain = $request->getHostname();
$domains = Config::getParam('domains', []);
if (!array_key_exists($domain, $domains)) {
$domain = new Domain(!empty($domain) ? $domain : '');
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
$domains[$domain->get()] = false;
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
Console::warning('Skipping SSL certificates generation on ACME challenge.');
} else {
$domainDocument = $dbForConsole->findOne('domains', [
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
]);
Authorization::disable();
if (!$domainDocument) {
$domainDocument = new Document([
'domain' => $domain->get(),
'tld' => $domain->getSuffix(),
'registerable' => $domain->getRegisterable(),
'verification' => false,
'certificateId' => null,
$envDomain = App::getEnv('_APP_DOMAIN', '');
$mainDomain = null;
if (!empty($envDomain) && $envDomain !== 'localhost') {
$mainDomain = $envDomain;
} else {
$domainDocument = $dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
}
if ($mainDomain !== $domain->get()) {
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
} else {
$domainDocument = $dbForConsole->findOne('domains', [
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
]);
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
if (!$domainDocument) {
$domainDocument = new Document([
'domain' => $domain->get(),
'tld' => $domain->getSuffix(),
'registerable' => $domain->getRegisterable(),
'verification' => false,
'certificateId' => null,
]);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
(new Certificate())
->setDomain($domainDocument)
->trigger();
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
(new Certificate())
->setDomain($domainDocument)
->trigger();
}
}
$domains[$domain->get()] = true;
Authorization::reset(); // ensure authorization is re-enabled
}
$domains[$domain->get()] = true;
Authorization::reset(); // ensure authorization is re-enabled
}
Config::setParam('domains', $domains);
}
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
if (\in_array($localeParam, Config::getParam('locale-codes'))) {
$locale->setDefault($localeParam);
}
if ($project->isEmpty()) {
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
}
if (!empty($route->getLabel('sdk.auth', [])) && $project->isEmpty() && ($route->getLabel('scope', '') !== 'public')) {
throw new AppwriteException('Missing or unknown project ID', 400, AppwriteException::PROJECT_UNKNOWN);
}
$referrer = $request->getReferer();
$origin = \parse_url($request->getOrigin($referrer), PHP_URL_HOST);
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
$refDomainOrigin = 'localhost';
$validator = new Hostname($clients);
if ($validator->isValid($origin)) {
$refDomainOrigin = $origin;
}
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : '');
$refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible
? $refDomain
: (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
$selfDomain = new Domain($request->getHostname());
$endDomain = new Domain((string)$origin);
Config::setParam(
'domainVerification',
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
$endDomain->getRegisterable() !== ''
);
Config::setParam('cookieDomain', (
$request->getHostname() === 'localhost' ||
$request->getHostname() === 'localhost:' . $request->getPort() ||
(\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false)
)
? null
: '.' . $request->getHostname());
/*
* Response format
*/
$responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($responseFormat) {
switch ($responseFormat) {
case version_compare($responseFormat, '0.11.2', '<='):
Response::setFilter(new ResponseV11());
break;
case version_compare($responseFormat, '0.12.4', '<='):
Response::setFilter(new ResponseV12());
break;
case version_compare($responseFormat, '0.13.4', '<='):
Response::setFilter(new ResponseV13());
break;
case version_compare($responseFormat, '0.14.0', '<='):
Response::setFilter(new ResponseV14());
break;
default:
Response::setFilter(null);
}
} else {
Response::setFilter(null);
}
/*
* Security Headers
*
* As recommended at:
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
*/
if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https') {
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException('Method unsupported over HTTP.', 500, AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED);
}
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
Config::setParam('domains', $domains);
}
$response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days
}
$response
->addHeader('Server', 'Appwrite')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $refDomain)
->addHeader('Access-Control-Allow-Credentials', 'true')
;
/*
* Validate Client Domain - Check to avoid CSRF attack
* Adding Appwrite API domains to allow XDOMAIN communication
* Skip this check for non-web platforms which are not required to send an origin header
*/
$origin = $request->getOrigin($request->getReferer(''));
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
if (
!$originValidator->isValid($origin)
&& \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 AppwriteException($originValidator->getDescription(), 403, AppwriteException::GENERAL_UNKNOWN_ORIGIN);
}
/*
* ACL Check
*/
$role = ($user->isEmpty()) ? Auth::USER_ROLE_GUEST : Auth::USER_ROLE_MEMBER;
// Add user roles
$memberships = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships');
if ($memberships) {
foreach ($memberships->getAttribute('roles', []) as $memberRole) {
switch ($memberRole) {
case 'owner':
$role = Auth::USER_ROLE_OWNER;
break;
case 'admin':
$role = Auth::USER_ROLE_ADMIN;
break;
case 'developer':
$role = Auth::USER_ROLE_DEVELOPER;
break;
}
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
if (\in_array($localeParam, Config::getParam('locale-codes'))) {
$locale->setDefault($localeParam);
}
}
$roles = Config::getParam('roles', []);
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
if ($project->isEmpty()) {
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
}
$authKey = $request->getHeader('x-appwrite-key', '');
if (!empty($route->getLabel('sdk.auth', [])) && $project->isEmpty() && ($route->getLabel('scope', '') !== 'public')) {
throw new AppwriteException(AppwriteException::PROJECT_UNKNOWN);
}
if (!empty($authKey)) { // API Key authentication
// Check if given key match project API keys
$key = $project->find('secret', $authKey, 'keys');
$referrer = $request->getReferer();
$origin = \parse_url($request->getOrigin($referrer), PHP_URL_HOST);
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
$refDomainOrigin = 'localhost';
$validator = new Hostname($clients);
if ($validator->isValid($origin)) {
$refDomainOrigin = $origin;
}
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : '');
$refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible
? $refDomain
: (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
$selfDomain = new Domain($request->getHostname());
$endDomain = new Domain((string)$origin);
Config::setParam(
'domainVerification',
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
$endDomain->getRegisterable() !== ''
);
Config::setParam('cookieDomain', (
$request->getHostname() === 'localhost' ||
$request->getHostname() === 'localhost:' . $request->getPort() ||
(\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false)
)
? null
: '.' . $request->getHostname());
/*
* Try app auth when we have project key and no user
* Mock user to app and grant API key scopes in addition to default app scopes
*/
if ($key && $user->isEmpty()) {
$user = new Document([
'$id' => '',
'status' => true,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
]);
* Response format
*/
$responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($responseFormat) {
switch ($responseFormat) {
case version_compare($responseFormat, '0.11.2', '<='):
Response::setFilter(new ResponseV11());
break;
case version_compare($responseFormat, '0.12.4', '<='):
Response::setFilter(new ResponseV12());
break;
case version_compare($responseFormat, '0.13.4', '<='):
Response::setFilter(new ResponseV13());
break;
case version_compare($responseFormat, '0.14.0', '<='):
Response::setFilter(new ResponseV14());
break;
default:
Response::setFilter(null);
}
} else {
Response::setFilter(null);
}
$role = Auth::USER_ROLE_APP;
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
/*
* Security Headers
*
* As recommended at:
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
*/
if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https') {
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP.');
}
$expire = $key->getAttribute('expire', 0);
if (!empty($expire) && $expire < \time()) {
throw new AppwriteException('Project key expired', 401, AppwriteException:: PROJECT_KEY_EXPIRED);
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
}
Authorization::setRole('role:' . Auth::USER_ROLE_APP);
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
$response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days
}
}
Authorization::setRole('role:' . $role);
$response
->addHeader('Server', 'Appwrite')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $refDomain)
->addHeader('Access-Control-Allow-Credentials', 'true')
;
foreach (Auth::getRoles($user) as $authRole) {
Authorization::setRole($authRole);
}
/*
* Validate Client Domain - Check to avoid CSRF attack
* Adding Appwrite API domains to allow XDOMAIN communication
* Skip this check for non-web platforms which are not required to send an origin header
*/
$origin = $request->getOrigin($request->getReferer(''));
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
$service = $route->getLabel('sdk.namespace', '');
if (!empty($service)) {
if (
array_key_exists($service, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$service]
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
!$originValidator->isValid($origin)
&& \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 AppwriteException('Service is disabled', 503, AppwriteException::GENERAL_SERVICE_DISABLED);
}
}
if (!\in_array($scope, $scopes)) {
if ($project->isEmpty()) { // Check if permission is denied because project is missing
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
throw new AppwriteException(AppwriteException::GENERAL_UNKNOWN_ORIGIN, $originValidator->getDescription());
}
throw new AppwriteException($user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')', 401, AppwriteException::GENERAL_UNAUTHORIZED_SCOPE);
}
/*
* ACL Check
*/
$role = ($user->isEmpty()) ? Auth::USER_ROLE_GUEST : Auth::USER_ROLE_MEMBER;
if (false === $user->getAttribute('status')) { // Account is blocked
throw new AppwriteException('Invalid credentials. User is blocked', 401, AppwriteException::USER_BLOCKED);
}
// Add user roles
$memberships = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships');
if ($user->getAttribute('reset')) {
throw new AppwriteException('Password reset is required', 412, AppwriteException::USER_PASSWORD_RESET_REQUIRED);
}
}, ['utopia', 'request', 'response', 'console', 'project', 'dbForConsole', 'user', 'locale', 'clients']);
if ($memberships) {
foreach ($memberships->getAttribute('roles', []) as $memberRole) {
switch ($memberRole) {
case 'owner':
$role = Auth::USER_ROLE_OWNER;
break;
case 'admin':
$role = Auth::USER_ROLE_ADMIN;
break;
case 'developer':
$role = Auth::USER_ROLE_DEVELOPER;
break;
}
}
}
App::options(function (Request $request, Response $response) {
$roles = Config::getParam('roles', []);
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
$origin = $request->getOrigin();
$authKey = $request->getHeader('x-appwrite-key', '');
$response
->addHeader('Server', 'Appwrite')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $origin)
->addHeader('Access-Control-Allow-Credentials', 'true')
->noContent();
}, ['request', 'response']);
if (!empty($authKey)) { // API Key authentication
// Check if given key match project API keys
$key = $project->find('secret', $authKey, 'keys');
App::error(function (Throwable $error, App $utopia, Request $request, Response $response, View $layout, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
/*
* Try app auth when we have project key and no user
* Mock user to app and grant API key scopes in addition to default app scopes
*/
if ($key && $user->isEmpty()) {
$user = new Document([
'$id' => '',
'status' => true,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
]);
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->match($request);
$role = Auth::USER_ROLE_APP;
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
/** Delegate PDO exceptions to the global handler so the database connection can be returned to the pool */
if ($error instanceof PDOException) {
throw $error;
}
$expire = $key->getAttribute('expire', 0);
if ($logger) {
if ($error->getCode() >= 500 || $error->getCode() === 0) {
try {
/** @var Utopia\Database\Document $user */
$user = $utopia->getResource('user');
} catch (\Throwable $th) {
// All good, user is optional information for logger
if (!empty($expire) && $expire < \time()) {
throw new AppwriteException(AppwriteException:: PROJECT_KEY_EXPIRED);
}
Authorization::setRole('role:' . Auth::USER_ROLE_APP);
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
}
}
Authorization::setRole('role:' . $role);
foreach (Auth::getRoles($user) as $authRole) {
Authorization::setRole($authRole);
}
$service = $route->getLabel('sdk.namespace', '');
if (!empty($service)) {
if (
array_key_exists($service, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$service]
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_SERVICE_DISABLED);
}
}
if (!\in_array($scope, $scopes)) {
if ($project->isEmpty()) { // Check if permission is denied because project is missing
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
}
$log = new Utopia\Logger\Log();
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId());
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::$roles);
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
$log->addBreadcrumb($loggerBreadcrumb);
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: ' . $responseCode);
}
}
$code = $error->getCode();
$message = $error->getMessage();
$file = $error->getFile();
$line = $error->getLine();
$trace = $error->getTrace();
if (php_sapi_name() === 'cli') {
Console::error('[Error] Timestamp: ' . date('c', time()));
if ($route) {
Console::error('[Error] Method: ' . $route->getMethod());
Console::error('[Error] URL: ' . $route->getPath());
throw new AppwriteException(AppwriteException::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')');
}
Console::error('[Error] Type: ' . get_class($error));
Console::error('[Error] Message: ' . $message);
Console::error('[Error] File: ' . $file);
Console::error('[Error] Line: ' . $line);
}
if (false === $user->getAttribute('status')) { // Account is blocked
throw new AppwriteException(AppwriteException::USER_BLOCKED);
}
/** Handle Utopia Errors */
if ($error instanceof Utopia\Exception) {
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
switch ($code) {
case 400:
$error->setType(AppwriteException::GENERAL_ARGUMENT_INVALID);
break;
case 404:
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
if ($user->getAttribute('reset')) {
throw new AppwriteException(AppwriteException::USER_PASSWORD_RESET_REQUIRED);
}
});
App::options()
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$origin = $request->getOrigin();
$response
->addHeader('Server', 'Appwrite')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $origin)
->addHeader('Access-Control-Allow-Credentials', 'true')
->noContent();
});
App::error()
->inject('error')
->inject('utopia')
->inject('request')
->inject('response')
->inject('layout')
->inject('project')
->inject('logger')
->inject('loggerBreadcrumbs')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, View $layout, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->match($request);
/** Delegate PDO exceptions to the global handler so the database connection can be returned to the pool */
if ($error instanceof PDOException) {
throw $error;
}
if ($logger) {
if ($error->getCode() >= 500 || $error->getCode() === 0) {
try {
/** @var Utopia\Database\Document $user */
$user = $utopia->getResource('user');
} catch (\Throwable $th) {
// All good, user is optional information for logger
}
$log = new Utopia\Logger\Log();
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId());
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::$roles);
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
$log->addBreadcrumb($loggerBreadcrumb);
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: ' . $responseCode);
}
}
$code = $error->getCode();
$message = $error->getMessage();
$file = $error->getFile();
$line = $error->getLine();
$trace = $error->getTrace();
if (php_sapi_name() === 'cli') {
Console::error('[Error] Timestamp: ' . date('c', time()));
if ($route) {
Console::error('[Error] Method: ' . $route->getMethod());
Console::error('[Error] URL: ' . $route->getPath());
}
Console::error('[Error] Type: ' . get_class($error));
Console::error('[Error] Message: ' . $message);
Console::error('[Error] File: ' . $file);
Console::error('[Error] Line: ' . $line);
}
/** Handle Utopia Errors */
if ($error instanceof Utopia\Exception) {
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
switch ($code) {
case 400:
$error->setType(AppwriteException::GENERAL_ARGUMENT_INVALID);
break;
case 404:
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
break;
}
}
/** Wrap all exceptions inside Appwrite\Extend\Exception */
if (!($error instanceof AppwriteException)) {
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
}
switch ($code) { // Don't show 500 errors!
case 400: // Error allowed publicly
case 401: // Error allowed publicly
case 402: // Error allowed publicly
case 403: // Error allowed publicly
case 404: // Error allowed publicly
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 416: // Error allowed publicly
case 429: // Error allowed publicly
case 501: // Error allowed publicly
case 503: // Error allowed publicly
break;
default:
$code = 500; // All other errors get the generic 500 server error status code
$message = 'Server Error';
}
}
/** Wrap all exceptions inside Appwrite\Extend\Exception */
if (!($error instanceof AppwriteException)) {
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
}
//$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
switch ($code) { // Don't show 500 errors!
case 400: // Error allowed publicly
case 401: // Error allowed publicly
case 402: // Error allowed publicly
case 403: // Error allowed publicly
case 404: // Error allowed publicly
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 416: // Error allowed publicly
case 429: // Error allowed publicly
case 501: // Error allowed publicly
case 503: // Error allowed publicly
break;
default:
$code = 500; // All other errors get the generic 500 server error status code
$message = 'Server Error';
}
$type = $error->getType();
//$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
$output = ((App::isDevelopment())) ? [
'message' => $message,
'code' => $code,
'file' => $file,
'line' => $line,
'trace' => $trace,
'version' => $version,
'type' => $type,
] : [
'message' => $message,
'code' => $code,
'version' => $version,
'type' => $type,
];
$type = $error->getType();
$output = ((App::isDevelopment())) ? [
'message' => $message,
'code' => $code,
'file' => $file,
'line' => $line,
'trace' => $trace,
'version' => $version,
'type' => $type,
] : [
'message' => $message,
'code' => $code,
'version' => $version,
'type' => $type,
];
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
->addHeader('Pragma', 'no-cache')
->setStatusCode($code)
;
$template = ($route) ? $route->getLabel('error', null) : null;
if ($template) {
$comp = new View($template);
$comp
->setParam('development', App::isDevelopment())
->setParam('projectName', $project->getAttribute('name'))
->setParam('projectURL', $project->getAttribute('url'))
->setParam('message', $error->getMessage())
->setParam('code', $code)
->setParam('trace', $trace)
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
->addHeader('Pragma', 'no-cache')
->setStatusCode($code)
;
$layout
->setParam('title', $project->getAttribute('name') . ' - Error')
->setParam('description', 'No Description')
->setParam('body', $comp)
->setParam('version', $version)
->setParam('litespeed', false)
;
$template = ($route) ? $route->getLabel('error', null) : null;
$response->html($layout->render());
}
if ($template) {
$comp = new View($template);
$response->dynamic(
new Document($output),
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
);
}, ['error', 'utopia', 'request', 'response', 'layout', 'project', 'logger', 'loggerBreadcrumbs']);
$comp
->setParam('development', App::isDevelopment())
->setParam('projectName', $project->getAttribute('name'))
->setParam('projectURL', $project->getAttribute('url'))
->setParam('message', $error->getMessage())
->setParam('code', $code)
->setParam('trace', $trace)
;
$layout
->setParam('title', $project->getAttribute('name') . ' - Error')
->setParam('description', 'No Description')
->setParam('body', $comp)
->setParam('version', $version)
->setParam('litespeed', false)
;
$response->html($layout->render());
}
$response->dynamic(
new Document($output),
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
);
});
App::get('/manifest.json')
->desc('Progressive app manifest file')
@ -580,32 +601,32 @@ App::get('/.well-known/acme-challenge')
]);
if (!$validator->isValid($token) || \count($uriChunks) !== 4) {
throw new AppwriteException('Invalid challenge token.', 400);
throw new AppwriteException(AppwriteException::GENERAL_ARGUMENT_INVALID, 'Invalid challenge token.');
}
$base = \realpath(APP_STORAGE_CERTIFICATES);
$absolute = \realpath($base . '/.well-known/acme-challenge/' . $token);
if (!$base) {
throw new AppwriteException('Storage error', 500, AppwriteException::GENERAL_SERVER_ERROR);
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Storage error');
}
if (!$absolute) {
throw new AppwriteException('Unknown path', 404);
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND, 'Unknown path');
}
if (!\substr($absolute, 0, \strlen($base)) === $base) {
throw new AppwriteException('Invalid path', 401);
throw new AppwriteException(AppwriteException::GENERAL_UNAUTHORIZED_SCOPE, 'Invalid path');
}
if (!\file_exists($absolute)) {
throw new AppwriteException('Unknown path', 404);
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND, 'Unknown path');
}
$content = @\file_get_contents($absolute);
if (!$content) {
throw new AppwriteException('Failed to get contents', 500, AppwriteException::GENERAL_SERVER_ERROR);
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Failed to get contents');
}
$response->text($content);

View file

@ -214,7 +214,7 @@ App::get('/v1/mock/tests/general/download')
->addHeader('Content-Disposition', 'attachment; filename="test.txt"')
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
->send("Download test passed.")
->send("GET:/v1/mock/tests/general/download:passed")
;
});
@ -253,31 +253,31 @@ App::post('/v1/mock/tests/general/upload')
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
if (is_null($start) || is_null($end) || is_null($size)) {
throw new Exception('Invalid content-range header', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
}
if ($start > $end || $end > $size) {
throw new Exception('Invalid content-range header', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
}
if ($start === 0 && !empty($id)) {
throw new Exception('First chunked request cannot have id header', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'First chunked request cannot have id header');
}
if ($start !== 0 && $id !== 'newfileid') {
throw new Exception('All chunked request must have id header (except first)', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'All chunked request must have id header (except first)');
}
if ($end !== $size && $end - $start + 1 !== $chunkSize) {
throw new Exception('Chunk size must be 5MB (except last chunk)', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB (except last chunk)');
}
if ($end !== $size && $file['size'] !== $chunkSize) {
throw new Exception('Wrong chunk size', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Wrong chunk size');
}
if ($file['size'] > $chunkSize) {
throw new Exception('Chunk size must be 5MB or less', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB or less');
}
if ($end !== $size) {
@ -293,15 +293,15 @@ App::post('/v1/mock/tests/general/upload')
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
if ($file['name'] !== 'file.png') {
throw new Exception('Wrong file name', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file name');
}
if ($file['size'] !== 38756) {
throw new Exception('Wrong file size', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file size');
}
if (\md5(\file_get_contents($file['tmp_name'])) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
throw new Exception('Wrong file uploaded', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file uploaded');
}
}
});
@ -374,7 +374,7 @@ App::get('/v1/mock/tests/general/get-cookie')
->action(function (Request $request) {
if ($request->getCookie('cookieName', '') !== 'cookieValue') {
throw new Exception('Missing cookie value', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Missing cookie value');
}
});
@ -408,7 +408,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, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Mock 400 error');
});
App::get('/v1/mock/tests/general/500-error')
@ -424,7 +424,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, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Mock 500 error', 500);
});
App::get('/v1/mock/tests/general/502-error')
@ -480,11 +480,11 @@ App::get('/v1/mock/tests/general/oauth2/token')
->action(function (string $client_id, string $client_secret, string $grantType, string $redirectURI, string $code, string $refreshToken, Response $response) {
if ($client_id != '1') {
throw new Exception('Invalid client ID', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Invalid client ID');
}
if ($client_secret != '123456') {
throw new Exception('Invalid client secret', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Invalid client secret');
}
$responseJson = [
@ -495,18 +495,18 @@ App::get('/v1/mock/tests/general/oauth2/token')
if ($grantType === 'authorization_code') {
if ($code !== 'abcdef') {
throw new Exception('Invalid token', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Invalid token');
}
$response->json($responseJson);
} elseif ($grantType === 'refresh_token') {
if ($refreshToken !== 'tuvwxyz') {
throw new Exception('Invalid refresh token', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Invalid refresh token');
}
$response->json($responseJson);
} else {
throw new Exception('Invalid grant type', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Invalid grant type');
}
});
@ -520,7 +520,7 @@ App::get('/v1/mock/tests/general/oauth2/user')
->action(function (string $token, Response $response) {
if ($token != '123456') {
throw new Exception('Invalid token', 400, Exception::GENERAL_MOCK);
throw new Exception(Exception::GENERAL_MOCK, 'Invalid token');
}
$response->json([
@ -558,24 +558,29 @@ App::get('/v1/mock/tests/general/oauth2/failure')
]);
});
App::shutdown(function (App $utopia, Response $response, Request $request) {
App::shutdown()
->groups(['mock'])
->inject('utopia')
->inject('response')
->inject('request')
->action(function (App $utopia, Response $response, Request $request) {
$result = [];
$route = $utopia->match($request);
$path = APP_STORAGE_CACHE . '/tests.json';
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
$result = [];
$route = $utopia->match($request);
$path = APP_STORAGE_CACHE . '/tests.json';
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
if (!\is_array($tests)) {
throw new Exception('Failed to read results', 500, Exception::GENERAL_MOCK);
}
if (!\is_array($tests)) {
throw new Exception(Exception::GENERAL_MOCK, 'Failed to read results', 500);
}
$result[$route->getMethod() . ':' . $route->getPath()] = true;
$result[$route->getMethod() . ':' . $route->getPath()] = true;
$tests = \array_merge($tests, $result);
$tests = \array_merge($tests, $result);
if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
throw new Exception('Failed to save results', 500, Exception::GENERAL_MOCK);
}
if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
throw new Exception(Exception::GENERAL_MOCK, 'Failed to save results', 500);
}
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);
}, ['utopia', 'response', 'request'], 'mock');
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);
});

View file

@ -19,234 +19,267 @@ use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Registry\Registry;
App::init(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
App::init()
->groups(['api'])
->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
->inject('user')
->inject('events')
->inject('audits')
->inject('mails')
->inject('usage')
->inject('deletes')
->inject('database')
->inject('dbForProject')
->inject('mode')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
$route = $utopia->match($request);
$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, Exception::PROJECT_UNKNOWN);
}
if ($project->isEmpty() && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
throw new Exception(Exception::PROJECT_UNKNOWN);
}
/*
* Abuse Check
*/
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$timeLimitArray = [];
/*
* Abuse Check
*/
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$timeLimitArray = [];
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
foreach ($abuseKeyLabel as $abuseKey) {
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$timeLimit
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname() . $route->getPath());
$timeLimitArray[] = $timeLimit;
}
foreach ($abuseKeyLabel as $abuseKey) {
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$timeLimit
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname() . $route->getPath());
$timeLimitArray[] = $timeLimit;
}
$closestLimit = null;
$closestLimit = null;
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
foreach ($timeLimitArray as $timeLimit) {
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if (!empty($value)) {
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
foreach ($timeLimitArray as $timeLimit) {
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if (!empty($value)) {
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
}
}
$abuse = new Abuse($timeLimit);
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
$closestLimit = $timeLimit->remaining();
$response
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
;
}
if (
(App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled' // Route is rate-limited
&& $abuse->check()) // Abuse is not disabled
&& (!$isAppUser && !$isPrivilegedUser)
) { // User is not an admin or API key
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED);
}
}
$abuse = new Abuse($timeLimit);
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
$closestLimit = $timeLimit->remaining();
$response
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
;
}
if (
(App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled' // Route is rate-limited
&& $abuse->check()) // Abuse is not disabled
&& (!$isAppUser && !$isPrivilegedUser)
) { // User is not an admin or API key
throw new Exception('Too many requests', 429, Exception::GENERAL_RATE_LIMIT_EXCEEDED);
}
}
/*
* Background Jobs
*/
$events
->setEvent($route->getLabel('event', ''))
->setProject($project)
->setUser($user)
;
$mails
->setProject($project)
->setUser($user)
;
$audits
->setMode($mode)
->setUserAgent($request->getUserAgent(''))
->setIP($request->getIP())
->setEvent($route->getLabel('event', ''))
->setProject($project)
->setUser($user)
;
$usage
->setParam('projectId', $project->getId())
->setParam('httpRequest', 1)
->setParam('httpUrl', $request->getHostname() . $request->getURI())
->setParam('httpMethod', $request->getMethod())
->setParam('httpPath', $route->getPath())
->setParam('networkRequestSize', 0)
->setParam('networkResponseSize', 0)
->setParam('storage', 0)
;
$deletes->setProject($project);
$database->setProject($project);
}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'mails', 'usage', 'deletes', 'database', 'dbForProject', 'mode'], 'api');
App::init(function (App $utopia, Request $request, Document $project) {
$route = $utopia->match($request);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
}
$auths = $project->getAttribute('auths', []);
switch ($route->getLabel('auth.type', '')) {
case 'emailPassword':
if (($auths['emailPassword'] ?? true) === false) {
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, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
case 'anonymous':
if (($auths['anonymous'] ?? true) === false) {
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, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
case 'jwt':
if (($auths['JWT'] ?? true) === false) {
throw new Exception('JWT authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
default:
throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
break;
}
}, ['utopia', 'request', 'project'], 'auth');
App::shutdown(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) {
if (!empty($events->getEvent())) {
if (empty($events->getPayload())) {
$events->setPayload($response->getPayload());
}
/**
* Trigger functions.
*/
/*
* Background Jobs
*/
$events
->setClass(Event::FUNCTIONS_CLASS_NAME)
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
->trigger();
->setEvent($route->getLabel('event', ''))
->setProject($project)
->setUser($user)
;
/**
* Trigger webhooks.
*/
$events
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->trigger();
$mails
->setProject($project)
->setUser($user)
;
/**
* Trigger realtime.
*/
if ($project->getId() !== 'console') {
$allEvents = Event::generateEvents($events->getEvent(), $events->getParams());
$payload = new Document($events->getPayload());
$audits
->setMode($mode)
->setUserAgent($request->getUserAgent(''))
->setIP($request->getIP())
->setEvent($route->getLabel('event', ''))
->setProject($project)
->setUser($user)
;
$db = $events->getContext('database');
$collection = $events->getContext('collection');
$bucket = $events->getContext('bucket');
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $payload,
project: $project,
database: $db,
collection: $collection,
bucket: $bucket,
);
Realtime::send(
projectId: $target['projectId'] ?? $project->getId(),
payload: $events->getPayload(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
options: [
'permissionsChanged' => $target['permissionsChanged'],
'userId' => $events->getParam('userId')
]
);
}
}
if (!empty($audits->getResource())) {
foreach ($events->getParams() as $key => $value) {
$audits->setParam($key, $value);
}
$audits->trigger();
}
if (!empty($deletes->getType())) {
$deletes->trigger();
}
if (!empty($database->getType())) {
$database->trigger();
}
$route = $utopia->match($request);
if (
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
&& $project->getId()
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
&& !empty($route->getLabel('sdk.namespace', null))
) { // Don't calculate console usage on admin mode
$usage
->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
->setParam('networkResponseSize', $response->getSize())
->submit();
}
}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode', 'dbForProject'], 'api');
->setParam('projectId', $project->getId())
->setParam('httpRequest', 1)
->setParam('httpUrl', $request->getHostname() . $request->getURI())
->setParam('httpMethod', $request->getMethod())
->setParam('httpPath', $route->getPath())
->setParam('networkRequestSize', 0)
->setParam('networkResponseSize', 0)
->setParam('storage', 0)
;
$deletes->setProject($project);
$database->setProject($project);
});
App::init()
->groups(['auth'])
->inject('utopia')
->inject('request')
->inject('project')
->action(function (App $utopia, Request $request, Document $project) {
$route = $utopia->match($request);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
}
$auths = $project->getAttribute('auths', []);
switch ($route->getLabel('auth.type', '')) {
case 'emailPassword':
if (($auths['emailPassword'] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email / Password authentication is disabled for this project');
}
break;
case 'magic-url':
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project');
}
break;
case 'anonymous':
if (($auths['anonymous'] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Anonymous authentication is disabled for this project');
}
break;
case 'invites':
if (($auths['invites'] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project');
}
break;
case 'jwt':
if (($auths['JWT'] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'JWT authentication is disabled for this project');
}
break;
default:
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route');
break;
}
});
App::shutdown()
->groups(['api'])
->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
->inject('events')
->inject('audits')
->inject('usage')
->inject('deletes')
->inject('database')
->inject('mode')
->inject('dbForProject')
->action(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) {
if (!empty($events->getEvent())) {
if (empty($events->getPayload())) {
$events->setPayload($response->getPayload());
}
/**
* Trigger functions.
*/
$events
->setClass(Event::FUNCTIONS_CLASS_NAME)
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
->trigger();
/**
* Trigger webhooks.
*/
$events
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->trigger();
/**
* Trigger realtime.
*/
if ($project->getId() !== 'console') {
$allEvents = Event::generateEvents($events->getEvent(), $events->getParams());
$payload = new Document($events->getPayload());
$db = $events->getContext('database');
$collection = $events->getContext('collection');
$bucket = $events->getContext('bucket');
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $payload,
project: $project,
database: $db,
collection: $collection,
bucket: $bucket,
);
Realtime::send(
projectId: $target['projectId'] ?? $project->getId(),
payload: $events->getPayload(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
options: [
'permissionsChanged' => $target['permissionsChanged'],
'userId' => $events->getParam('userId')
]
);
}
}
if (!empty($audits->getResource())) {
foreach ($events->getParams() as $key => $value) {
$audits->setParam($key, $value);
}
$audits->trigger();
}
if (!empty($deletes->getType())) {
$deletes->trigger();
}
if (!empty($database->getType())) {
$database->trigger();
}
$route = $utopia->match($request);
if (
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
&& $project->getId()
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
&& !empty($route->getLabel('sdk.namespace', null))
) { // Don't calculate console usage on admin mode
$usage
->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
->setParam('networkResponseSize', $response->getSize())
->submit();
}
});

View file

@ -6,54 +6,59 @@ use Appwrite\Utopia\Response;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\View;
App::init(function (App $utopia, Request $request, Response $response, View $layout) {
App::init()
->groups(['web'])
->inject('utopia')
->inject('request')
->inject('response')
->inject('layout')
->action(function (App $utopia, Request $request, Response $response, View $layout) {
/* AJAX check */
if (!empty($request->getQuery('version', ''))) {
$layout->setPath(__DIR__ . '/../../views/layouts/empty.phtml');
}
/* AJAX check */
if (!empty($request->getQuery('version', ''))) {
$layout->setPath(__DIR__ . '/../../views/layouts/empty.phtml');
}
$port = $request->getPort();
$protocol = $request->getProtocol();
$domain = $request->getHostname();
$port = $request->getPort();
$protocol = $request->getProtocol();
$domain = $request->getHostname();
$layout
->setParam('title', APP_NAME)
->setParam('protocol', $protocol)
->setParam('domain', $domain)
->setParam('endpoint', $protocol . '://' . $domain . ($port != 80 && $port != 443 ? ':' . $port : ''))
->setParam('home', App::getEnv('_APP_HOME'))
->setParam('setup', App::getEnv('_APP_SETUP'))
->setParam('class', 'unknown')
->setParam('icon', '/images/favicon.png')
->setParam('roles', [
['type' => 'owner', 'label' => 'Owner'],
['type' => 'developer', 'label' => 'Developer'],
['type' => 'admin', 'label' => 'Admin'],
])
->setParam('runtimes', Config::getParam('runtimes'))
->setParam('mode', App::getMode())
;
$layout
->setParam('title', APP_NAME)
->setParam('protocol', $protocol)
->setParam('domain', $domain)
->setParam('endpoint', $protocol . '://' . $domain . ($port != 80 && $port != 443 ? ':' . $port : ''))
->setParam('home', App::getEnv('_APP_HOME'))
->setParam('setup', App::getEnv('_APP_SETUP'))
->setParam('class', 'unknown')
->setParam('icon', '/images/favicon.png')
->setParam('roles', [
['type' => 'owner', 'label' => 'Owner'],
['type' => 'developer', 'label' => 'Developer'],
['type' => 'admin', 'label' => 'Admin'],
])
->setParam('runtimes', Config::getParam('runtimes'))
->setParam('mode', App::getMode())
;
$time = (60 * 60 * 24 * 45); // 45 days cache
$time = (60 * 60 * 24 * 45); // 45 days cache
$response
->addHeader('Cache-Control', 'public, max-age=' . $time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
;
$response
->addHeader('Cache-Control', 'public, max-age=' . $time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
;
$route = $utopia->match($request);
$route = $utopia->match($request);
$route->label('error', __DIR__ . '/../../views/general/error.phtml');
$route->label('error', __DIR__ . '/../../views/general/error.phtml');
$scope = $route->getLabel('scope', '');
$scope = $route->getLabel('scope', '');
$layout
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
->setParam('isDev', App::isDevelopment())
->setParam('class', $scope)
;
}, ['utopia', 'request', 'response', 'layout'], 'web');
$layout
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
->setParam('isDev', App::isDevelopment())
->setParam('class', $scope)
;
});

View file

@ -9,31 +9,36 @@ use Utopia\Domains\Domain;
use Utopia\Database\Validator\UID;
use Utopia\Storage\Storage;
App::init(function (View $layout) {
App::init()
->groups(['console'])
->inject('layout')
->action(function (View $layout) {
$layout
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
->setParam('analytics', 'UA-26264668-5')
;
});
$layout
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
->setParam('analytics', 'UA-26264668-5')
;
}, ['layout'], 'console');
App::shutdown()
->groups(['console'])
->inject('response')
->inject('layout')
->action(function (Response $response, View $layout) {
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
App::shutdown(function (Response $response, View $layout) {
$footer
->setParam('home', App::getEnv('_APP_HOME', ''))
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
$layout
->setParam('header', [$header])
->setParam('footer', [$footer])
;
$footer
->setParam('home', App::getEnv('_APP_HOME', ''))
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$layout
->setParam('header', [$header])
->setParam('footer', [$footer])
;
$response->html($layout->render());
}, ['response', 'layout'], 'console');
$response->html($layout->render());
});
App::get('/error/:code')
->groups(['web', 'console'])
@ -507,9 +512,9 @@ App::get('/console/version')
if ($version && isset($version['version'])) {
return $response->json(['version' => $version['version']]);
} else {
throw new Exception('Failed to check for a newer version', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to check for a newer version');
}
} catch (\Throwable $th) {
throw new Exception('Failed to check for a newer version', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to check for a newer version');
}
});

View file

@ -7,29 +7,34 @@ use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
App::init(function (View $layout) {
App::init()
->groups(['home'])
->inject('layout')
->action(function (View $layout) {
$header = new View(__DIR__ . '/../../views/home/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/home/comps/footer.phtml');
$header = new View(__DIR__ . '/../../views/home/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/home/comps/footer.phtml');
$footer
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$footer
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$layout
->setParam('title', APP_NAME)
->setParam('description', '')
->setParam('class', 'home')
->setParam('platforms', Config::getParam('platforms'))
->setParam('header', [$header])
->setParam('footer', [$footer])
;
});
$layout
->setParam('title', APP_NAME)
->setParam('description', '')
->setParam('class', 'home')
->setParam('platforms', Config::getParam('platforms'))
->setParam('header', [$header])
->setParam('footer', [$footer])
;
}, ['layout'], 'home');
App::shutdown(function (Response $response, View $layout) {
$response->html($layout->render());
}, ['response', 'layout'], 'home');
App::shutdown()
->groups(['home'])
->inject('response')
->inject('layout')
->action(function (Response $response, View $layout) {
$response->html($layout->render());
});
App::get('/')
->groups(['web', 'home'])

View file

@ -581,57 +581,64 @@ App::setResource('orchestrationPool', fn() => $orchestrationPool);
App::setResource('activeRuntimes', fn() => $activeRuntimes);
/** Set callbacks */
App::error(function ($utopia, $error, $request, $response) {
$route = $utopia->match($request);
logError($error, "httpError", $route);
App::error()
->inject('utopia')
->inject('error')
->inject('request')
->inject('response')
->action(function (App $utopia, throwable $error, Request $request, Response $response) {
$route = $utopia->match($request);
logError($error, "httpError", $route);
switch ($error->getCode()) {
case 400: // Error allowed publicly
case 401: // Error allowed publicly
case 402: // Error allowed publicly
case 403: // Error allowed publicly
case 404: // Error allowed publicly
case 406: // Error allowed publicly
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 425: // Error allowed publicly
case 429: // Error allowed publicly
case 501: // Error allowed publicly
case 503: // Error allowed publicly
$code = $error->getCode();
break;
default:
$code = 500; // All other errors get the generic 500 server error status code
}
switch ($error->getCode()) {
case 400: // Error allowed publicly
case 401: // Error allowed publicly
case 402: // Error allowed publicly
case 403: // Error allowed publicly
case 404: // Error allowed publicly
case 406: // Error allowed publicly
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 425: // Error allowed publicly
case 429: // Error allowed publicly
case 501: // Error allowed publicly
case 503: // Error allowed publicly
$code = $error->getCode();
break;
default:
$code = 500; // All other errors get the generic 500 server error status code
}
$output = [
'message' => $error->getMessage(),
'code' => $error->getCode(),
'file' => $error->getFile(),
'line' => $error->getLine(),
'trace' => $error->getTrace(),
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
];
$output = [
'message' => $error->getMessage(),
'code' => $error->getCode(),
'file' => $error->getFile(),
'line' => $error->getLine(),
'trace' => $error->getTrace(),
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
];
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
->addHeader('Pragma', 'no-cache')
->setStatusCode($code);
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
->addHeader('Pragma', 'no-cache')
->setStatusCode($code);
$response->json($output);
}, ['utopia', 'error', 'request', 'response']);
$response->json($output);
});
App::init(function ($request, $response) {
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
if (empty($secretKey)) {
throw new Exception('Missing executor key', 401);
}
App::init()
->inject('request')
->action(function (Request $request) {
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
if (empty($secretKey)) {
throw new Exception('Missing executor key', 401);
}
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
throw new Exception('Missing executor key', 401);
}
}, ['request', 'response']);
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
throw new Exception('Missing executor key', 401);
}
});
$http->on('start', function ($http) {

View file

@ -89,7 +89,7 @@ const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
const APP_LIMIT_SUBQUERY = 1000;
const APP_CACHE_BUSTER = 402;
const APP_VERSION_STABLE = '0.15.2';
const APP_VERSION_STABLE = '0.15.3';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@ -448,7 +448,7 @@ $register->set('logger', function () {
}
if (!Logger::hasProvider($providerName)) {
throw new Exception("Logging provider not supported. Logging disabled.", 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled");
}
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
@ -818,7 +818,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
try {
$payload = $jwt->decode($authJWT);
} catch (JWTException $error) {
throw new Exception('Failed to verify JWT. ' . $error->getMessage(), 401, Exception::USER_JWT_INVALID);
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
}
$jwtUserId = $payload['userId'] ?? '';

View file

@ -123,7 +123,7 @@
data-analytics-category="console/navigation"
data-analytics-label="Users Link">
<i class="icon-users"></i>
Users
Authentication
</a>
</li>
<li>

View file

@ -583,6 +583,11 @@ $logs = $this->getParam('logs', null);
<input id="id" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-collection.$id}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<label>Database ID</label>
<div class="input-copy margin-bottom">
<input type="text" autocomplete="off" placeholder="" data-ls-bind="{{router.params.databaseId}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"

View file

@ -348,6 +348,11 @@ $logs = $this->getParam('logs', null);
<input type="text" autocomplete="off" placeholder="" data-ls-bind="{{router.params.collection}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<label>Database ID</label>
<div class="input-copy margin-bottom">
<input type="text" autocomplete="off" placeholder="" data-ls-bind="{{router.params.databaseId}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<ul class="margin-bottom-large text-fade text-size-small" data-ls-if="({{project-document.$id}})">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"

View file

@ -11,7 +11,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
<a data-ls-attrs="href=/console/home?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Home</a>
<br />
<span>Users</span>
<span>Authentication</span>
</h1>
</div>

View file

@ -0,0 +1,12 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Client ID<span class="tooltip" data-tooltip="Provided in the Provider you created in authentik"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="Client ID" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret">Client Secret <span class="tooltip" data-tooltip="Provided in the Provider you created in authentik"><i class="icon-info-circled"></i></span></label>
<input name="clientSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="password" autocomplete="off" placeholder="Client Secret" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain">authentik Base-Domain<span class="tooltip" data-tooltip="Your authentik Base-Domain (without 'https://')"><i class="icon-info-circled"></i></span></label>
<input name="authentikDomain" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain" type="text" autocomplete="off" placeholder="auth.example.com" />
<?php /*Hidden input for the final secret. Gets filled with a JSON via JS. */ ?>
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />

View file

@ -18,8 +18,8 @@
data-scope="console"
data-event="submit"
data-success="alert,redirect"
data-success-param-alert="Password Reset Completed"
data-success-param-url="/auth/signin"
data-success-param-alert-text="Password Reset Completed"
data-success-param-redirect-url="/auth/signin"
data-failure="alert"
data-failure-param-alert-text="Password Reset Failed"
data-failure-param-alert-classname="error">

View file

@ -595,7 +595,7 @@ services:
- _APP_REDIS_PASS
mariadb:
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
image: mariadb:10.8.3 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
restart: unless-stopped
@ -611,7 +611,7 @@ services:
command: 'mysqld --innodb-flush-method=fsync'
redis:
image: redis:6.2-alpine
image: redis:7.0.4-alpine
container_name: appwrite-redis
<<: *x-logging
restart: unless-stopped

View file

@ -20,11 +20,6 @@ Console::success(APP_NAME . ' deletes worker v1 has started' . "\n");
class DeletesV1 extends Worker
{
/**
* @var Database
*/
protected $consoleDB = null;
public function getName(): string
{
return "deletes";

View file

@ -23,6 +23,7 @@
"autoload-dev": {
"psr-4": {
"Tests\\E2E\\": "tests/e2e",
"Tests\\Unit\\": "tests/unit",
"Appwrite\\Tests\\": "tests/extensions"
}
},
@ -42,7 +43,7 @@
"ext-sockets": "*",
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.10.*",
"utopia-php/framework": "0.19.*",
"utopia-php/framework": "0.20.*",
"utopia-php/logger": "0.3.*",
"utopia-php/abuse": "0.7.*",
"utopia-php/analytics": "0.2.*",
@ -76,7 +77,7 @@
}
],
"require-dev": {
"appwrite/sdk-generator": "0.19.5",
"appwrite/sdk-generator": "0.20.0",
"phpunit/phpunit": "9.5.20",
"squizlabs/php_codesniffer": "^3.6",
"swoole/ide-helper": "4.8.9",

56
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "677b1b47c8567f0b7b05645e2bbc7bc7",
"content-hash": "993486075710ab0cdbba6c33f0b09218",
"packages": [
{
"name": "adhocore/jwt",
@ -1894,16 +1894,16 @@
},
{
"name": "utopia-php/cache",
"version": "0.6.0",
"version": "0.6.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/cache.git",
"reference": "8ea1353a4bbab617e23c865a7c97b60d8074aee3"
"reference": "9889235a6d3da6cbb1f435201529da4d27c30e79"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/cache/zipball/8ea1353a4bbab617e23c865a7c97b60d8074aee3",
"reference": "8ea1353a4bbab617e23c865a7c97b60d8074aee3",
"url": "https://api.github.com/repos/utopia-php/cache/zipball/9889235a6d3da6cbb1f435201529da4d27c30e79",
"reference": "9889235a6d3da6cbb1f435201529da4d27c30e79",
"shasum": ""
},
"require": {
@ -1941,9 +1941,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/cache/issues",
"source": "https://github.com/utopia-php/cache/tree/0.6.0"
"source": "https://github.com/utopia-php/cache/tree/0.6.1"
},
"time": "2022-04-04T12:30:05+00:00"
"time": "2022-08-10T08:12:46+00:00"
},
{
"name": "utopia-php/cli",
@ -2169,16 +2169,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.19.21",
"version": "0.20.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/framework.git",
"reference": "3b7bd8e4acf84fd7d560ced8e0142221d302575d"
"reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/3b7bd8e4acf84fd7d560ced8e0142221d302575d",
"reference": "3b7bd8e4acf84fd7d560ced8e0142221d302575d",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/beb5e861c7d0a6256a1272e6b9d70b060ca8629a",
"reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a",
"shasum": ""
},
"require": {
@ -2212,9 +2212,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/framework/issues",
"source": "https://github.com/utopia-php/framework/tree/0.19.21"
"source": "https://github.com/utopia-php/framework/tree/0.20.0"
},
"time": "2022-05-12T18:42:28+00:00"
"time": "2022-07-30T09:55:28+00:00"
},
{
"name": "utopia-php/image",
@ -2828,29 +2828,29 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.19.5",
"version": "0.20.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "04de540cf683e2b08b3192c137dde7f2c37003d9"
"reference": "af7dd08848a78a0d38befa3e63083eb0186806f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/04de540cf683e2b08b3192c137dde7f2c37003d9",
"reference": "04de540cf683e2b08b3192c137dde7f2c37003d9",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/af7dd08848a78a0d38befa3e63083eb0186806f7",
"reference": "af7dd08848a78a0d38befa3e63083eb0186806f7",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"matthiasmullie/minify": "^1.3",
"matthiasmullie/minify": "^1.3.68",
"php": ">=7.0.0",
"twig/twig": "^3.3"
"twig/twig": "^3.4.1"
},
"require-dev": {
"brianium/paratest": "^6.4",
"phpunit/phpunit": "^9.5.13"
"phpunit/phpunit": "^9.5.21"
},
"type": "library",
"autoload": {
@ -2872,9 +2872,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.19.5"
"source": "https://github.com/appwrite/sdk-generator/tree/0.20.0"
},
"time": "2022-07-06T11:05:57+00:00"
"time": "2022-08-02T10:09:48+00:00"
},
{
"name": "doctrine/instantiator",
@ -5271,16 +5271,16 @@
},
{
"name": "twig/twig",
"version": "v3.4.1",
"version": "v3.4.2",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "e939eae92386b69b49cfa4599dd9bead6bf4a342"
"reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/e939eae92386b69b49cfa4599dd9bead6bf4a342",
"reference": "e939eae92386b69b49cfa4599dd9bead6bf4a342",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077",
"reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077",
"shasum": ""
},
"require": {
@ -5331,7 +5331,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.4.1"
"source": "https://github.com/twigphp/Twig/tree/v3.4.2"
},
"funding": [
{
@ -5343,7 +5343,7 @@
"type": "tidelift"
}
],
"time": "2022-05-17T05:48:52+00:00"
"time": "2022-08-12T06:47:24+00:00"
}
],
"aliases": [],

View file

@ -638,7 +638,7 @@ services:
- _APP_REDIS_PASS
mariadb:
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
image: mariadb:10.8.3 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
networks:
@ -668,7 +668,7 @@ services:
# - SMARTHOST_PORT=587
redis:
image: redis:6.2-alpine
image: redis:7.0.4-alpine
<<: *x-logging
container_name: appwrite-redis
command: >

View file

@ -1,3 +1,3 @@
You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.
You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard.
When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.

View file

@ -1 +1 @@
Create a new Collection. Before using this route, you should create a new database resource using either a [server integration](/docs/server/database#databaseCreateCollection) API or directly from your database console.
Create a new Collection. Before using this route, you should create a new database resource using either a [server integration](/docs/server/databases#databasesCreateCollection) API or directly from your database console.

View file

@ -1 +1 @@
Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](/docs/server/database#databaseCreateCollection) API or directly from your database console.
Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](/docs/server/databases#databasesCreateCollection) API or directly from your database console.

View file

@ -1,4 +1,4 @@
Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](/docs/server/database#storageCreateBucket) API or directly from your Appwrite console.
Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](/docs/server/storage#storageCreateBucket) API or directly from your Appwrite console.
Larger files should be uploaded using multiple requests with the [content-range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.

View file

@ -1 +1 @@
Create a new user. Password entered must be hashed with the the [Argon2](https://en.wikipedia.org/wiki/Argon2) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to import plain text password.
Create a new user. Password provided must be hashed with the [Argon2](https://en.wikipedia.org/wiki/Argon2) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.

View file

@ -1 +1 @@
Create a new user. Password entered must be hashed with the [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to import plain text password.
Create a new user. Password provided must be hashed with the [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.

View file

@ -1 +1 @@
Create a new user. Password entered must be hashed with the [MD5](https://en.wikipedia.org/wiki/MD5) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to import plain text password.
Create a new user. Password provided must be hashed with the [MD5](https://en.wikipedia.org/wiki/MD5) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.

View file

@ -1 +1 @@
Create a new user. Password entered must be hashed with the [PHPass](https://www.openwall.com/phpass/) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to import plain text password.
Create a new user. Password provided must be hashed with the [PHPass](https://www.openwall.com/phpass/) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.

View file

@ -1 +1 @@
Create a new user. Password entered must be hashed with the [Scrypt Modified](https://gist.github.com/Meldiron/eecf84a0225eccb5a378d45bb27462cc) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to import plain text password.
Create a new user. Password provided must be hashed with the [Scrypt Modified](https://gist.github.com/Meldiron/eecf84a0225eccb5a378d45bb27462cc) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.

View file

@ -1 +1 @@
Create a new user. Password entered must be hashed with the [Scrypt](https://github.com/Tarsnap/scrypt) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to import plain text password.
Create a new user. Password provided must be hashed with the [Scrypt](https://github.com/Tarsnap/scrypt) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.

View file

@ -1 +1 @@
Create a new user. Password entered must be hashed with the [SHA](https://en.wikipedia.org/wiki/Secure_Hash_Algorithm) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint import plain text password.
Create a new user. Password provided must be hashed with the [SHA](https://en.wikipedia.org/wiki/Secure_Hash_Algorithm) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.

View file

@ -2,6 +2,6 @@ The Databases service allows you to create structured collections of documents,
All data returned by the Databases service are represented as structured JSON documents.
The Databases service can contain multiple databases, each database can contain multiple collections. A collection is a group of similarly structured documents. The accepted structure of documents is defined by [collection attributes](/docs/database#attributes). The collection attributes help you ensure all your user-submitted data is validated and stored according to the collection structure.
The Databases service can contain multiple databases, each database can contain multiple collections. A collection is a group of similarly structured documents. The accepted structure of documents is defined by [collection attributes](/docs/databases#attributes). The collection attributes help you ensure all your user-submitted data is validated and stored according to the collection structure.
Using Appwrite permissions architecture, you can assign read or write access to each collection or document in your project for either a specific user, team, user role, or even grant it with public access (`role:all`). You can learn more about [how Appwrite handles permissions and access control](/docs/permissions).

View file

@ -1,3 +1,3 @@
The Locale service allows you to customize your app based on your users' location. Using this service, you can get your users' location, IP address, list of countries and continents names, phone codes, currencies, and more.
The Locale service allows you to customize your app based on your users' location. Using this service, you can get your users' location, IP address, list of countries and continents names, phone codes, currencies, and more. Country codes returned follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard.
The user service supports multiple locales. This feature allows you to fetch countries and continents information in your app language. To switch locales, all you need to do is pass the 'X-Appwrite-Locale' header or set the 'setLocale' method using any of our available SDKs. [View here the list of available locales](https://github.com/appwrite/appwrite/blob/master/app/config/locale/codes.php).

1063
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -17,8 +17,8 @@
"gulp-less": "^5.0.0"
},
"dependencies": {
"chart.js": "^3.8.0",
"markdown-it": "^12.3.2",
"chart.js": "^3.8.2",
"markdown-it": "^13.0.1",
"pell": "^1.0.6",
"prismjs": "^1.28.0",
"turndown": "^7.1.1"

View file

@ -13,7 +13,7 @@
</extensions>
<testsuites>
<testsuite name="unit">
<directory>./tests/unit/</directory>
<directory>./tests/unit</directory>
</testsuite>
<testsuite name="e2e">
<file>./tests/e2e/Client.php</file>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,7 +1,7 @@
(function (exports, isomorphicFormData, crossFetch) {
'use strict';
/*! *****************************************************************************
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
@ -26,6 +26,45 @@
});
}
class Service {
constructor(client) {
this.client = client;
}
static flatten(data, prefix = '') {
let output = {};
for (const key in data) {
let value = data[key];
let finalKey = prefix ? `${prefix}[${key}]` : key;
if (Array.isArray(value)) {
output = Object.assign(output, this.flatten(value, finalKey));
}
else {
output[finalKey] = value;
}
}
return output;
}
}
Service.CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
class Query {
}
Query.equal = (attribute, value) => Query.addQuery(attribute, 'equal', value);
Query.notEqual = (attribute, value) => Query.addQuery(attribute, 'notEqual', value);
Query.lesser = (attribute, value) => Query.addQuery(attribute, 'lesser', value);
Query.lesserEqual = (attribute, value) => Query.addQuery(attribute, 'lesserEqual', value);
Query.greater = (attribute, value) => Query.addQuery(attribute, 'greater', value);
Query.greaterEqual = (attribute, value) => Query.addQuery(attribute, 'greaterEqual', value);
Query.search = (attribute, value) => Query.addQuery(attribute, 'search', value);
Query.addQuery = (attribute, oper, value) => value instanceof Array
? `${attribute}.${oper}(${value
.map((v) => Query.parseValues(v))
.join(',')})`
: `${attribute}.${oper}(${Query.parseValues(value)})`;
Query.parseValues = (value) => typeof value === 'string' || value instanceof String
? `"${value}"`
: `${value}`;
class AppwriteException extends Error {
constructor(message, code = 0, type = '', response = '') {
super(message);
@ -36,7 +75,7 @@
this.response = response;
}
}
class Appwrite {
class Client {
constructor() {
this.config = {
endpoint: 'https://HOSTNAME/v1',
@ -48,7 +87,7 @@
mode: '',
};
this.headers = {
'x-sdk-version': 'appwrite:web:5.0.0',
'x-sdk-version': 'appwrite:web:6.0.0',
'X-Appwrite-Response-Format': '0.15.0',
};
this.realtime = {
@ -6607,9 +6646,20 @@
? `"${value}"`
: `${value}`;
exports.Appwrite = Appwrite;
exports.Account = Account;
exports.AppwriteException = AppwriteException;
exports.Avatars = Avatars;
exports.Client = Client;
exports.Databases = Databases;
exports.Functions = Functions;
exports.Health = Health;
exports.Locale = Locale;
exports.Projects = Projects;
exports.Query = Query;
exports.Storage = Storage;
exports.Teams = Teams;
exports.Users = Users;
Object.defineProperty(exports, '__esModule', { value: true });
}(this.window = this.window || {}, null, window));
})(this.Appwrite = this.Appwrite || {}, null, window);

View file

@ -57,7 +57,7 @@ window.addEventListener("load", async () => {
const realtime = window.ls.container.get('realtime');
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
let current = {};
window.ls.container.get('console').subscribe(['project', 'console'], response => {
window.ls.container.get('console').client.subscribe(['project', 'console'], response => {
if (response.events.includes('stats.connections')) {
for (let project in response.payload) {
current[project] = response.payload[project] ?? 0;

View file

@ -2,16 +2,28 @@
"use strict";
window.ls.container.set('console', function (window) {
var sdk = new window.Appwrite();
var client = new Appwrite.Client();
var endpoint = window.location.origin + '/v1';
sdk
client
.setEndpoint(endpoint)
.setProject('console')
.setLocale(APP_ENV.LOCALE)
;
return sdk;
return {
client: client,
account: new Appwrite.Account(client),
avatars: new Appwrite.Avatars(client),
databases: new Appwrite.Databases(client),
functions: new Appwrite.Functions(client),
health: new Appwrite.Health(client),
locale: new Appwrite.Locale(client),
projects: new Appwrite.Projects(client),
storage: new Appwrite.Storage(client),
teams: new Appwrite.Teams(client),
users: new Appwrite.Users(client)
}
}, true);
})(window);

View file

@ -2,17 +2,28 @@
"use strict";
window.ls.container.set('sdk', function (window, router) {
var sdk = new window.Appwrite();
var client = new Appwrite.Client();
var endpoint = window.location.origin + '/v1';
sdk
client
.setEndpoint(endpoint)
.setProject(router.params.project || '')
.setLocale(APP_ENV.LOCALE)
.setMode('admin')
;
return sdk;
return {
client: client,
account: new Appwrite.Account(client),
avatars: new Appwrite.Avatars(client),
databases: new Appwrite.Databases(client),
functions: new Appwrite.Functions(client),
health: new Appwrite.Health(client),
locale: new Appwrite.Locale(client),
storage: new Appwrite.Storage(client),
teams: new Appwrite.Teams(client),
users: new Appwrite.Users(client)
}
}, false);
})(window);

View file

@ -26,6 +26,10 @@
"clientSecret": "oauth2Auth0ClientSecret",
"auth0Domain": "oauth2Auth0Domain"
},
"Authentik": {
"clientSecret": "oauth2AuthentikClientSecret",
"authentikDomain": "oauth2AuthentikDomain"
},
"Gitlab": {
"endpoint": "oauth2GitlabEndpoint",
"clientSecret": "oauth2GitlabClientSecret",

View file

@ -203,6 +203,12 @@
let params = [];
let match;
let indexOfArguments = functionAsString.indexOf('(');
if (indexOfArguments !== -1) {
functionAsString = functionAsString.slice(indexOfArguments, -1);
}
functionAsString = functionAsString.replaceAll('={}', "");
functionAsString = functionAsString.replaceAll('=[]', "");
functionAsString = functionAsString.replace(REGEX_COMMENTS, "");
@ -259,7 +265,7 @@
let args = getParams(target);
return target.apply(
target,
container.get(scope),
args.map(function(value) {
let result = getValue(value, prefix, data);

View file

@ -0,0 +1,227 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://goauthentik.io/docs/providers/oauth2/
class Authentik extends OAuth2
{
/**
* @var array
*/
protected array $scopes = [
'openid',
'profile',
'email',
'offline_access'
];
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'authentik';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://' . $this->getAuthentikDomain() . '/application/o/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
'scope' => \implode(' ', $this->getScopes()),
'response_type' => 'code'
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://' . $this->getAuthentikDomain() . '/application/o/token/',
$headers,
\http_build_query([
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'grant_type' => 'authorization_code'
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://' . $this->getAuthentikDomain() . '/application/o/token/',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'grant_type' => 'refresh_token'
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['sub'])) {
return $user['sub'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
}
/**
* Check if the User email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('GET', 'https://' . $this->getAuthentikDomain() . '/application/o/userinfo/', $headers);
$this->user = \json_decode($user, true);
}
return $this->user;
}
/**
* Extracts the Client Secret from the JSON stored in appSecret
*
* @return string
*/
protected function getClientSecret(): string
{
$secret = $this->getAppSecret();
return $secret['clientSecret'] ?? '';
}
/**
* Extracts the authentik Domain from the JSON stored in appSecret
*
* @return string
*/
protected function getAuthentikDomain(): string
{
$secret = $this->getAppSecret();
return $secret['authentikDomain'] ?? '';
}
/**
* Decode the JSON stored in appSecret
*
* @return array
*/
protected function getAppSecret(): array
{
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
throw new \Exception('Invalid secret');
}
return $secret;
}
}

View file

@ -0,0 +1,188 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://disqus.com/api/docs/auth/
class Disqus extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://disqus.com/api/';
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'read',
'email',
];
/**
* @return string
*/
public function getName(): string
{
return 'disqus';
}
/**
* @return string
*/
public function getLoginURL(): string
{
$url = $this->endpoint . 'oauth/2.0/authorize/?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback,
'scope' => \implode(',', $this->getScopes())
]);
return $url;
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . 'oauth/2.0/access_token/',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'authorization_code',
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'redirect_uri' => $this->callback,
'code' => $code,
'scope' => \implode(' ', $this->getScopes()),
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . 'oauth/2.0/access_token/?',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $token
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
$userId = $user['id'];
return $userId;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
$userEmail = $user['email'];
return $userEmail;
}
/**
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
// Look out for the change in their enpoint.
// It's in Beta so they may provide a parameter in the future.
// https://disqus.com/api/docs/users/details/
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
$username = $user['name'] ?? '';
return $username;
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request(
'GET',
$this->endpoint . '3.0/users/details.json?' . \http_build_query([
'access_token' => $accessToken,
'api_key' => $this->appID,
'api_secret' => $this->appSecret
]),
);
$this->user = \json_decode($user, true)['response'];
}
return $this->user;
}
}

View file

@ -80,7 +80,6 @@ class Linkedin extends OAuth2
])
), true);
}
return $this->tokens;
}
@ -107,7 +106,6 @@ class Linkedin extends OAuth2
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}

View file

@ -0,0 +1,199 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://developers.podio.com/doc/oauth-authorization
class Podio extends OAuth2
{
/**
* Endpoint used for initiating OAuth flow
*
* @var string
*/
private string $endpoint = 'https://podio.com/oauth';
/**
* Endpoint for communication with API server
*
* @var string
*/
private string $apiEndpoint = 'https://api.podio.com';
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = []; // No scopes required
/**
* @return string
*/
public function getName(): string
{
return 'podio';
}
/**
* @return string
*/
public function getLoginURL(): string
{
$url = $this->endpoint . '/authorize?' .
\http_build_query([
'client_id' => $this->appID,
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback
]);
return $url;
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->apiEndpoint . '/oauth/token',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $this->callback,
'client_id' => $this->appID,
'client_secret' => $this->appSecret
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
$this->apiEndpoint . '/oauth/token',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
return \strval($user['user_id']) ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['mail'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
$mails = $user['mails'];
$mainMailIndex = \array_search($user['mail'], \array_map(fn($m) => $m['mail'], $mails));
$mainMain = $mails[$mainMailIndex];
if ($mainMain['verified'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['name'] ?? '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = \json_decode($this->request(
'GET',
$this->apiEndpoint . '/user',
['Authorization: Bearer ' . \urlencode($accessToken)]
), true);
$profile = \json_decode($this->request(
'GET',
$this->apiEndpoint . '/user/profile',
['Authorization: Bearer ' . \urlencode($accessToken)]
), true);
$this->user = $user;
$this->user['name'] = $profile['name'];
}
return $this->user;
}
}

View file

@ -2,6 +2,8 @@
namespace Appwrite\Extend;
use Utopia\Config\Config;
class Exception extends \Exception
{
/**
@ -47,7 +49,7 @@ class Exception extends \Exception
public const GENERAL_ROUTE_NOT_FOUND = 'general_route_not_found';
public const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found';
public const GENERAL_SERVER_ERROR = 'general_server_error';
public const GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported';
public const GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported';
/** Users */
public const USER_COUNT_EXCEEDED = 'user_count_exceeded';
@ -69,6 +71,7 @@ class Exception extends \Exception
public const USER_AUTH_METHOD_UNSUPPORTED = 'user_auth_method_unsupported';
public const USER_PHONE_ALREADY_EXISTS = 'user_phone_already_exists';
public const USER_PHONE_NOT_FOUND = 'user_phone_not_found';
public const USER_MISSING_ID = 'user_missing_id';
/** Teams */
public const TEAM_NOT_FOUND = 'team_not_found';
@ -80,6 +83,7 @@ class Exception extends \Exception
/** Membership */
public const MEMBERSHIP_NOT_FOUND = 'membership_not_found';
public const MEMBERSHIP_ALREADY_CONFIRMED = 'membership_already_confirmed';
/** Avatars */
public const AVATAR_SET_NOT_FOUND = 'avatar_set_not_found';
@ -116,8 +120,8 @@ class Exception extends \Exception
public const EXECUTION_NOT_FOUND = 'execution_not_found';
/** Databases */
public const DATABASE_NOT_FOUND = 'database_not_found';
public const DATABASE_ALREADY_EXISTS = 'database_already_exists';
public const DATABASE_NOT_FOUND = 'database_not_found';
public const DATABASE_ALREADY_EXISTS = 'database_already_exists';
/** Collections */
public const COLLECTION_NOT_FOUND = 'collection_not_found';
@ -152,7 +156,6 @@ class Exception extends \Exception
public const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
public const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url';
public const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
public const PROJECT_MISSING_USER_ID = 'project_missing_user_id';
public const PROJECT_RESERVED_PROJECT = 'project_reserved_project';
public const PROJECT_KEY_EXPIRED = 'project_key_expired';
@ -170,14 +173,22 @@ class Exception extends \Exception
public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
protected $type = '';
private $type = '';
public function __construct(string $message, int $code = 0, string $type = Exception::GENERAL_UNKNOWN, \Throwable $previous = null)
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
{
$this->errors = Config::getParam('errors');
$this->type = $type;
parent::__construct($message, $code, $previous);
if (isset($this->errors[$type])) {
$this->code = $this->errors[$type]['code'];
$this->message = $this->errors[$type]['description'];
}
$this->message = $message ?? $this->message;
$this->code = $code ?? $this->code;
parent::__construct($this->message, $this->code, $previous);
}
/**

View file

@ -47,7 +47,8 @@ abstract class Migration
'0.14.2' => 'V13',
'0.15.0' => 'V14',
'0.15.1' => 'V14',
'0.15.2' => 'V14'
'0.15.2' => 'V14',
'0.15.3' => 'V14'
];
/**

View file

@ -217,7 +217,7 @@ class Response extends SwooleResponse
/**
* @var array
*/
protected $payload = [];
protected array $payload = [];
/**
* Response constructor.
@ -326,8 +326,7 @@ class Response extends SwooleResponse
// Verification
// Recovery
// Tests (keep last)
->setModel(new Mock())
;
->setModel(new Mock());
parent::__construct($response);
}
@ -417,12 +416,13 @@ class Response extends SwooleResponse
if ($model->isAny()) {
$this->payload = $document->getArrayCopy();
return $this->payload;
}
foreach ($model->getRules() as $key => $rule) {
if (!$document->isSet($key) && $rule['require']) { // do not set attribute in response if not required
if (!is_null($rule['default'])) {
if (!$document->isSet($key) && $rule['required']) { // do not set attribute in response if not required
if (\array_key_exists('default', $rule)) {
$document->setAttribute($key, $rule['default']);
} else {
throw new Exception('Model ' . $model->getName() . ' is missing response key: ' . $key);
@ -434,7 +434,7 @@ class Response extends SwooleResponse
throw new Exception($key . ' must be an array of type ' . $rule['type']);
}
foreach ($data[$key] as &$item) {
foreach ($data[$key] as $index => $item) {
if ($item instanceof Document) {
if (\is_array($rule['type'])) {
foreach ($rule['type'] as $type) {
@ -458,9 +458,13 @@ class Response extends SwooleResponse
throw new Exception('Missing model for rule: ' . $ruleType);
}
$item = $this->output($item, $ruleType);
$data[$key][$index] = $this->output($item, $ruleType);
}
}
} else {
if ($data[$key] instanceof Document) {
$data[$key] = $this->output($data[$key], $rule['type']);
}
}
$output[$key] = $data[$key];
@ -491,8 +495,7 @@ class Response extends SwooleResponse
$this
->setContentType(Response::CONTENT_TYPE_YAML)
->send(yaml_emit($data, YAML_UTF8_ENCODING))
;
->send(yaml_emit($data, YAML_UTF8_ENCODING));
}
/**

View file

@ -15,22 +15,28 @@ abstract class Model
/**
* @var bool
*/
protected $none = false;
protected bool $none = false;
/**
* @var bool
*/
protected $any = false;
protected bool $any = false;
/**
* @var bool
*/
protected $public = true;
protected bool $public = true;
/**
* @var array
*/
protected $rules = [];
protected array $rules = [];
/**
* @var array
*/
public array $conditions = [];
/**
* Filter Document Structure
@ -76,12 +82,10 @@ abstract class Model
protected function addRule(string $key, array $options): self
{
$this->rules[$key] = array_merge([
'require' => true,
'type' => '',
'required' => true,
'array' => false,
'description' => '',
'default' => null,
'example' => '',
'array' => false
'example' => ''
], $options);
return $this;
@ -108,7 +112,7 @@ abstract class Model
$list = [];
foreach ($this->rules as $key => $rule) {
if ($rule['require'] ?? false) {
if ($rule['required'] ?? false) {
$list[] = $key;
}
}

View file

@ -13,19 +13,19 @@ class AlgoArgon2 extends Model
$this
->addRule('memoryCost', [
'type' => self::TYPE_INTEGER,
'description' => 'Memory that may be used to compute hash.',
'description' => 'Memory used to compute hash.',
'default' => '',
'example' => 65536,
])
->addRule('timeCost', [
'type' => self::TYPE_INTEGER,
'description' => 'Amount of time it may take to compute hash.',
'description' => 'Amount of time consumed to compute hash',
'default' => '',
'example' => 4,
])
->addRule('threads', [
'type' => self::TYPE_INTEGER,
'description' => 'Number of threads to use to compute hash.',
'description' => 'Number of threads used to compute hash.',
'default' => '',
'example' => 3,
])

View file

@ -10,7 +10,7 @@ class Any extends Model
/**
* @var bool
*/
protected $any = true;
protected bool $any = true;
/**
* Get Name

View file

@ -39,7 +39,6 @@ class Attribute extends Model
'description' => 'Is attribute an array?',
'default' => false,
'example' => false,
'require' => false
])
;
}

View file

@ -28,9 +28,7 @@ class AttributeBoolean extends Attribute
'type' => self::TYPE_BOOLEAN,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => false,
'array' => false,
'require' => false,
'example' => false
])
;
}

View file

@ -27,8 +27,8 @@ class AttributeEmail extends Attribute
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_EMAIL,
'example' => APP_DATABASE_ATTRIBUTE_EMAIL,
'default' => 'email',
'example' => 'email',
'array' => false,
'require' => true,
])
@ -37,8 +37,6 @@ class AttributeEmail extends Attribute
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 'default@example.com',
'array' => false,
'require' => false,
])
;
}

View file

@ -30,13 +30,12 @@ class AttributeEnum extends Attribute
'default' => null,
'example' => 'element',
'array' => true,
'require' => true,
])
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_ENUM,
'example' => APP_DATABASE_ATTRIBUTE_ENUM,
'default' => 'enum',
'example' => 'enum',
'array' => false,
'require' => true,
])
@ -45,8 +44,6 @@ class AttributeEnum extends Attribute
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 'element',
'array' => false,
'require' => false,
])
;
}

View file

@ -29,24 +29,18 @@ class AttributeFloat extends Attribute
'description' => 'Minimum value to enforce for new documents.',
'default' => null,
'example' => 1.5,
'array' => false,
'require' => false,
])
->addRule('max', [
'type' => self::TYPE_FLOAT,
'description' => 'Maximum value to enforce for new documents.',
'default' => null,
'example' => 10.5,
'array' => false,
'require' => false,
])
->addRule('default', [
'type' => self::TYPE_FLOAT,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 2.5,
'array' => false,
'require' => false,
])
;
}

View file

@ -27,8 +27,8 @@ class AttributeIP extends Attribute
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_IP,
'example' => APP_DATABASE_ATTRIBUTE_IP,
'default' => 'ip',
'example' => 'ip',
'array' => false,
'require' => true,
])
@ -37,8 +37,6 @@ class AttributeIP extends Attribute
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => '192.0.2.0',
'array' => false,
'require' => false,
])
;
}

View file

@ -29,24 +29,18 @@ class AttributeInteger extends Attribute
'description' => 'Minimum value to enforce for new documents.',
'default' => null,
'example' => 1,
'array' => false,
'require' => false,
])
->addRule('max', [
'type' => self::TYPE_INTEGER,
'description' => 'Maximum value to enforce for new documents.',
'default' => null,
'example' => 10,
'array' => false,
'require' => false,
])
->addRule('default', [
'type' => self::TYPE_INTEGER,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 10,
'array' => false,
'require' => false,
])
;
}

View file

@ -23,8 +23,6 @@ class AttributeString extends Attribute
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 'default',
'array' => false,
'require' => false,
])
;
}

View file

@ -27,8 +27,8 @@ class AttributeURL extends Attribute
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_URL,
'example' => APP_DATABASE_ATTRIBUTE_URL,
'default' => 'url',
'example' => 'url',
'array' => false,
'required' => true,
])
@ -37,8 +37,6 @@ class AttributeURL extends Attribute
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'example' => 'http://example.com',
'array' => false,
'require' => false,
])
;
}

View file

@ -2,7 +2,6 @@
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class BaseList extends Model
@ -10,12 +9,12 @@ class BaseList extends Model
/**
* @var string
*/
protected $name = '';
protected string $name = '';
/**
* @var string
*/
protected $type = '';
protected string $type = '';
/**
* @param string $name

View file

@ -66,9 +66,9 @@ class Deployment extends Model
])
->addRule('status', [
'type' => self::TYPE_STRING,
'description' => 'The deployment status.',
'description' => 'The deployment status. Possible values are "processing", "building", "pending", "ready", and "failed".',
'default' => '',
'example' => 'enabled',
'example' => 'ready',
])
->addRule('buildStdout', [
'type' => self::TYPE_STRING,

View file

@ -10,7 +10,7 @@ class Domain extends Model
/**
* @var bool
*/
protected $public = false;
protected bool $public = false;
public function __construct()
{

View file

@ -9,7 +9,7 @@ class ErrorDev extends Error
/**
* @var bool
*/
protected $public = false;
protected bool $public = false;
public function __construct()
{

View file

@ -41,7 +41,6 @@ class Index extends Model
'default' => [],
'example' => [],
'array' => true,
'required' => false,
])
;
}

View file

@ -10,7 +10,7 @@ class Key extends Model
/**
* @var bool
*/
protected $public = false;
protected bool $public = false;
public function __construct()
{

View file

@ -16,7 +16,7 @@ class Metric extends Model
'default' => -1,
'example' => 1,
])
->addRule('timestamp', [
->addRule('date', [
'type' => self::TYPE_INTEGER,
'description' => 'The UNIX timestamp at which this metric was aggregated.',
'default' => 0,

View file

@ -10,7 +10,7 @@ class None extends Model
/**
* @var bool
*/
protected $none = true;
protected bool $none = true;
/**
* Get Name

View file

@ -10,7 +10,7 @@ class Platform extends Model
/**
* @var bool
*/
protected $public = false;
protected bool $public = false;
public function __construct()
{

View file

@ -6,11 +6,6 @@ use Appwrite\Utopia\Response;
class Preferences extends Any
{
/**
* @var bool
*/
protected $any = true;
/**
* Get Name
*

View file

@ -12,7 +12,7 @@ class Project extends Model
/**
* @var bool
*/
protected $public = false;
protected bool $public = false;
public function __construct()
{

View file

@ -10,7 +10,7 @@ class Webhook extends Model
/**
* @var bool
*/
protected $public = false;
protected bool $public = false;
public function __construct()
{

View file

@ -165,23 +165,23 @@ trait DatabasesBase
'array' => true,
]);
$this->assertEquals($title['headers']['status-code'], 201);
$this->assertEquals($title['headers']['status-code'], 202);
$this->assertEquals($title['body']['key'], 'title');
$this->assertEquals($title['body']['type'], 'string');
$this->assertEquals($title['body']['size'], 256);
$this->assertEquals($title['body']['required'], true);
$this->assertEquals($releaseYear['headers']['status-code'], 201);
$this->assertEquals($releaseYear['headers']['status-code'], 202);
$this->assertEquals($releaseYear['body']['key'], 'releaseYear');
$this->assertEquals($releaseYear['body']['type'], 'integer');
$this->assertEquals($releaseYear['body']['required'], true);
$this->assertEquals($duration['headers']['status-code'], 201);
$this->assertEquals($duration['headers']['status-code'], 202);
$this->assertEquals($duration['body']['key'], 'duration');
$this->assertEquals($duration['body']['type'], 'integer');
$this->assertEquals($duration['body']['required'], false);
$this->assertEquals($actors['headers']['status-code'], 201);
$this->assertEquals($actors['headers']['status-code'], 202);
$this->assertEquals($actors['body']['key'], 'actors');
$this->assertEquals($actors['body']['type'], 'string');
$this->assertEquals($actors['body']['size'], 256);
@ -318,7 +318,7 @@ trait DatabasesBase
'default' => true,
]);
$this->assertEquals(201, $string['headers']['status-code']);
$this->assertEquals(202, $string['headers']['status-code']);
$this->assertEquals('string', $string['body']['key']);
$this->assertEquals('string', $string['body']['type']);
$this->assertEquals(false, $string['body']['required']);
@ -326,7 +326,7 @@ trait DatabasesBase
$this->assertEquals(16, $string['body']['size']);
$this->assertEquals('default', $string['body']['default']);
$this->assertEquals(201, $email['headers']['status-code']);
$this->assertEquals(202, $email['headers']['status-code']);
$this->assertEquals('email', $email['body']['key']);
$this->assertEquals('string', $email['body']['type']);
$this->assertEquals(false, $email['body']['required']);
@ -334,7 +334,7 @@ trait DatabasesBase
$this->assertEquals('email', $email['body']['format']);
$this->assertEquals('default@example.com', $email['body']['default']);
$this->assertEquals(201, $enum['headers']['status-code']);
$this->assertEquals(202, $enum['headers']['status-code']);
$this->assertEquals('enum', $enum['body']['key']);
$this->assertEquals('string', $enum['body']['type']);
$this->assertEquals(false, $enum['body']['required']);
@ -344,7 +344,7 @@ trait DatabasesBase
$this->assertIsArray($enum['body']['elements']);
$this->assertEquals(['yes', 'no', 'maybe'], $enum['body']['elements']);
$this->assertEquals(201, $ip['headers']['status-code']);
$this->assertEquals(202, $ip['headers']['status-code']);
$this->assertEquals('ip', $ip['body']['key']);
$this->assertEquals('string', $ip['body']['type']);
$this->assertEquals(false, $ip['body']['required']);
@ -352,7 +352,7 @@ trait DatabasesBase
$this->assertEquals('ip', $ip['body']['format']);
$this->assertEquals('192.0.2.0', $ip['body']['default']);
$this->assertEquals(201, $url['headers']['status-code']);
$this->assertEquals(202, $url['headers']['status-code']);
$this->assertEquals('url', $url['body']['key']);
$this->assertEquals('string', $url['body']['type']);
$this->assertEquals(false, $url['body']['required']);
@ -360,7 +360,7 @@ trait DatabasesBase
$this->assertEquals('url', $url['body']['format']);
$this->assertEquals('http://example.com', $url['body']['default']);
$this->assertEquals(201, $integer['headers']['status-code']);
$this->assertEquals(202, $integer['headers']['status-code']);
$this->assertEquals('integer', $integer['body']['key']);
$this->assertEquals('integer', $integer['body']['type']);
$this->assertEquals(false, $integer['body']['required']);
@ -369,7 +369,7 @@ trait DatabasesBase
$this->assertEquals(5, $integer['body']['max']);
$this->assertEquals(3, $integer['body']['default']);
$this->assertEquals(201, $float['headers']['status-code']);
$this->assertEquals(202, $float['headers']['status-code']);
$this->assertEquals('float', $float['body']['key']);
$this->assertEquals('double', $float['body']['type']);
$this->assertEquals(false, $float['body']['required']);
@ -378,7 +378,7 @@ trait DatabasesBase
$this->assertEquals(5.5, $float['body']['max']);
$this->assertEquals(3.5, $float['body']['default']);
$this->assertEquals(201, $boolean['headers']['status-code']);
$this->assertEquals(202, $boolean['headers']['status-code']);
$this->assertEquals('boolean', $boolean['body']['key']);
$this->assertEquals('boolean', $boolean['body']['type']);
$this->assertEquals(false, $boolean['body']['required']);
@ -705,7 +705,7 @@ trait DatabasesBase
'attributes' => ['title'],
]);
$this->assertEquals(201, $titleIndex['headers']['status-code']);
$this->assertEquals(202, $titleIndex['headers']['status-code']);
$this->assertEquals('titleIndex', $titleIndex['body']['key']);
$this->assertEquals('fulltext', $titleIndex['body']['type']);
$this->assertCount(1, $titleIndex['body']['attributes']);
@ -721,7 +721,7 @@ trait DatabasesBase
'attributes' => ['releaseYear'],
]);
$this->assertEquals(201, $releaseYearIndex['headers']['status-code']);
$this->assertEquals(202, $releaseYearIndex['headers']['status-code']);
$this->assertEquals('releaseYear', $releaseYearIndex['body']['key']);
$this->assertEquals('key', $releaseYearIndex['body']['type']);
$this->assertCount(1, $releaseYearIndex['body']['attributes']);
@ -737,7 +737,7 @@ trait DatabasesBase
'attributes' => ['releaseYear', '$createdAt', '$updatedAt'],
]);
$this->assertEquals(201, $releaseWithDate['headers']['status-code']);
$this->assertEquals(202, $releaseWithDate['headers']['status-code']);
$this->assertEquals('releaseYearDated', $releaseWithDate['body']['key']);
$this->assertEquals('key', $releaseWithDate['body']['type']);
$this->assertCount(3, $releaseWithDate['body']['attributes']);
@ -1689,15 +1689,15 @@ trait DatabasesBase
'default' => 'NORTH'
]);
$this->assertEquals(201, $email['headers']['status-code']);
$this->assertEquals(201, $ip['headers']['status-code']);
$this->assertEquals(201, $url['headers']['status-code']);
$this->assertEquals(201, $range['headers']['status-code']);
$this->assertEquals(201, $floatRange['headers']['status-code']);
$this->assertEquals(201, $probability['headers']['status-code']);
$this->assertEquals(201, $upperBound['headers']['status-code']);
$this->assertEquals(201, $lowerBound['headers']['status-code']);
$this->assertEquals(201, $enum['headers']['status-code']);
$this->assertEquals(202, $email['headers']['status-code']);
$this->assertEquals(202, $ip['headers']['status-code']);
$this->assertEquals(202, $url['headers']['status-code']);
$this->assertEquals(202, $range['headers']['status-code']);
$this->assertEquals(202, $floatRange['headers']['status-code']);
$this->assertEquals(202, $probability['headers']['status-code']);
$this->assertEquals(202, $upperBound['headers']['status-code']);
$this->assertEquals(202, $lowerBound['headers']['status-code']);
$this->assertEquals(202, $enum['headers']['status-code']);
$this->assertEquals(400, $invalidRange['headers']['status-code']);
$this->assertEquals(400, $defaultArray['headers']['status-code']);
$this->assertEquals(400, $defaultRequired['headers']['status-code']);
@ -2144,7 +2144,7 @@ trait DatabasesBase
'required' => true,
]);
$this->assertEquals(201, $attribute['headers']['status-code'], 201);
$this->assertEquals(202, $attribute['headers']['status-code'], 202);
$this->assertEquals('attribute', $attribute['body']['key']);
// wait for db to add attribute
@ -2160,7 +2160,7 @@ trait DatabasesBase
'attributes' => [$attribute['body']['key']],
]);
$this->assertEquals(201, $index['headers']['status-code']);
$this->assertEquals(202, $index['headers']['status-code']);
$this->assertEquals('key_attribute', $index['body']['key']);
// wait for db to add attribute
@ -2292,7 +2292,7 @@ trait DatabasesBase
'attributes' => ['title'],
]);
$this->assertEquals($uniqueIndex['headers']['status-code'], 201);
$this->assertEquals($uniqueIndex['headers']['status-code'], 202);
sleep(2);
@ -2464,7 +2464,7 @@ trait DatabasesBase
'required' => true,
]);
$this->assertEquals($title['headers']['status-code'], 201);
$this->assertEquals($title['headers']['status-code'], 202);
// wait for database worker to create attributes
sleep(2);

View file

@ -63,7 +63,7 @@ class DatabasesCustomClientTest extends Scope
'size' => 255,
'required' => true,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals(202, $response['headers']['status-code']);
// Wait for database worker to finish creating attributes
sleep(2);

View file

@ -618,8 +618,8 @@ class DatabasesCustomServerTest extends Scope
'required' => true,
]);
$this->assertEquals(201, $attribute1['headers']['status-code']);
$this->assertEquals(201, $attribute2['headers']['status-code']);
$this->assertEquals(202, $attribute1['headers']['status-code']);
$this->assertEquals(202, $attribute2['headers']['status-code']);
$this->assertEquals('attribute1', $attribute1['body']['key']);
$this->assertEquals('attribute2', $attribute2['body']['key']);
@ -646,8 +646,8 @@ class DatabasesCustomServerTest extends Scope
'attributes' => ['attribute2'],
]);
$this->assertEquals(201, $index1['headers']['status-code']);
$this->assertEquals(201, $index2['headers']['status-code']);
$this->assertEquals(202, $index1['headers']['status-code']);
$this->assertEquals(202, $index2['headers']['status-code']);
$this->assertEquals('index1', $index1['body']['key']);
$this->assertEquals('index2', $index2['body']['key']);
@ -742,8 +742,8 @@ class DatabasesCustomServerTest extends Scope
'required' => true,
]);
$this->assertEquals(201, $attribute1['headers']['status-code']);
$this->assertEquals(201, $attribute2['headers']['status-code']);
$this->assertEquals(202, $attribute1['headers']['status-code']);
$this->assertEquals(202, $attribute2['headers']['status-code']);
$this->assertEquals('attribute1', $attribute1['body']['key']);
$this->assertEquals('attribute2', $attribute2['body']['key']);
@ -770,8 +770,8 @@ class DatabasesCustomServerTest extends Scope
'attributes' => ['attribute2'],
]);
$this->assertEquals(201, $index1['headers']['status-code']);
$this->assertEquals(201, $index2['headers']['status-code']);
$this->assertEquals(202, $index1['headers']['status-code']);
$this->assertEquals(202, $index2['headers']['status-code']);
$this->assertEquals('index1', $index1['body']['key']);
$this->assertEquals('index2', $index2['body']['key']);
@ -980,7 +980,7 @@ class DatabasesCustomServerTest extends Scope
'required' => true,
]);
$this->assertEquals($attribute['headers']['status-code'], 201);
$this->assertEquals($attribute['headers']['status-code'], 202);
}
sleep(5);
@ -1043,7 +1043,7 @@ class DatabasesCustomServerTest extends Scope
'required' => true,
]);
$this->assertEquals($attribute['headers']['status-code'], 201);
$this->assertEquals($attribute['headers']['status-code'], 202);
}
sleep(20);
@ -1080,7 +1080,7 @@ class DatabasesCustomServerTest extends Scope
'attributes' => ["attribute{$i}"],
]);
$this->assertEquals(201, $index['headers']['status-code']);
$this->assertEquals(202, $index['headers']['status-code']);
$this->assertEquals("key_attribute{$i}", $index['body']['key']);
}

View file

@ -77,7 +77,7 @@ class DatabasesPermissionsMemberTest extends Scope
'size' => 256,
'required' => true,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals(202, $response['headers']['status-code']);
$private = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
'collectionId' => 'unique()',
@ -95,7 +95,7 @@ class DatabasesPermissionsMemberTest extends Scope
'size' => 256,
'required' => true,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals(202, $response['headers']['status-code']);
sleep(2);

View file

@ -89,7 +89,7 @@ class FunctionsCustomClientTest extends Scope
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Wait for deployment to be built.
sleep(10);
@ -118,7 +118,7 @@ class FunctionsCustomClientTest extends Scope
'async' => true,
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $function['body']['$id'], [
@ -179,7 +179,7 @@ class FunctionsCustomClientTest extends Scope
// Wait for deployment to be built.
sleep(5);
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
$function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
@ -196,7 +196,7 @@ class FunctionsCustomClientTest extends Scope
'data' => 'foobar',
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@ -271,7 +271,7 @@ class FunctionsCustomClientTest extends Scope
'data' => 'foobar',
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
sleep(10);
@ -361,7 +361,7 @@ class FunctionsCustomClientTest extends Scope
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Wait for deployment to be built.
sleep(10);

View file

@ -292,7 +292,7 @@ class FunctionsCustomServerTest extends Scope
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$this->assertIsInt($deployment['body']['$createdAt']);
$this->assertEquals('index.php', $deployment['body']['entrypoint']);
@ -341,7 +341,7 @@ class FunctionsCustomServerTest extends Scope
}
@fclose($handle);
$this->assertEquals(201, $largeTag['headers']['status-code']);
$this->assertEquals(202, $largeTag['headers']['status-code']);
$this->assertNotEmpty($largeTag['body']['$id']);
$this->assertIsInt($largeTag['body']['$createdAt']);
$this->assertEquals('index.php', $largeTag['body']['entrypoint']);
@ -484,7 +484,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$this->assertNotEmpty($execution['body']['$id']);
$this->assertNotEmpty($execution['body']['functionId']);
$this->assertIsInt($execution['body']['$createdAt']);
@ -733,7 +733,7 @@ class FunctionsCustomServerTest extends Scope
'activate' => true,
]);
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(20);
@ -747,7 +747,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
sleep(10);
@ -818,7 +818,7 @@ class FunctionsCustomServerTest extends Scope
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(10);
@ -839,7 +839,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@ -930,7 +930,7 @@ class FunctionsCustomServerTest extends Scope
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(10);
@ -944,7 +944,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@ -1035,7 +1035,7 @@ class FunctionsCustomServerTest extends Scope
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(30);
@ -1049,7 +1049,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@ -1140,7 +1140,7 @@ class FunctionsCustomServerTest extends Scope
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(40);
@ -1154,7 +1154,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@ -1245,7 +1245,7 @@ class FunctionsCustomServerTest extends Scope
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(30);
@ -1259,7 +1259,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@ -1350,7 +1350,7 @@ class FunctionsCustomServerTest extends Scope
// ]);
// $deploymentId = $deployment['body']['$id'] ?? '';
// $this->assertEquals(201, $deployment['headers']['status-code']);
// $this->assertEquals(202, $deployment['headers']['status-code']);
// // Allow (slow) build step to run
// sleep(300);
@ -1364,7 +1364,7 @@ class FunctionsCustomServerTest extends Scope
// $executionId = $execution['body']['$id'] ?? '';
// $this->assertEquals(201, $execution['headers']['status-code']);
// $this->assertEquals(202, $execution['headers']['status-code']);
// $executionId = $execution['body']['$id'] ?? '';

Some files were not shown because too many files have changed in this diff Show more