1
0
Fork 0
mirror of synced 2024-06-26 18:20:43 +12:00

Merge branch '0.16.x' of github.com:appwrite/appwrite into feat-file-cache-cleanup

 Conflicts:
	composer.json
	composer.lock
This commit is contained in:
shimon 2022-08-17 11:16:09 +03:00
commit 9c68cff317
89 changed files with 4800 additions and 1801 deletions

4
.env
View file

@ -56,8 +56,8 @@ _APP_SMTP_PORT=1025
_APP_SMTP_SECURE=
_APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
_APP_PHONE_PROVIDER=phone://mock
_APP_PHONE_FROM=+123456789
_APP_SMS_PROVIDER=sms://mock
_APP_SMS_FROM=+123456789
_APP_STORAGE_LIMIT=30000000
_APP_STORAGE_PREVIEW_LIMIT=20000000
_APP_FUNCTIONS_SIZE_LIMIT=30000000

View file

@ -123,6 +123,33 @@ RUN \
./configure && \
make && make install
# Rust Extensions Compile Image
FROM php:8.0.18-cli as rust_compile
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH=/root/.cargo/bin:$PATH
RUN apt-get update && apt-get install musl-tools build-essential clang-11 git -y
RUN rustup target add $(uname -m)-unknown-linux-musl
# Install ZigBuild for easier cross-compilation
RUN curl https://ziglang.org/builds/zig-linux-$(uname -m)-0.10.0-dev.2674+d980c6a38.tar.xz --output /tmp/zig.tar.xz
RUN tar -xf /tmp/zig.tar.xz -C /tmp/ && cp -r /tmp/zig-linux-$(uname -m)-0.10.0-dev.2674+d980c6a38 /tmp/zig/
ENV PATH=/tmp/zig:$PATH
RUN cargo install cargo-zigbuild
ENV RUSTFLAGS="-C target-feature=-crt-static"
FROM rust_compile as scrypt
WORKDIR /usr/local/lib/php/extensions/
RUN \
git clone --depth 1 https://github.com/appwrite/php-scrypt.git && \
cd php-scrypt && \
cargo zigbuild --workspace --all-targets --target $(uname -m)-unknown-linux-musl --release && \
mv target/$(uname -m)-unknown-linux-musl/release/libphp_scrypt.so target/libphp_scrypt.so
FROM php:8.0.18-cli-alpine3.15 as final
LABEL maintainer="team@appwrite.io"
@ -193,8 +220,8 @@ ENV _APP_SERVER=swoole \
_APP_SMTP_SECURE= \
_APP_SMTP_USERNAME= \
_APP_SMTP_PASSWORD= \
_APP_PHONE_PROVIDER= \
_APP_PHONE_FROM= \
_APP_SMS_PROVIDER= \
_APP_SMS_FROM= \
_APP_FUNCTIONS_SIZE_LIMIT=30000000 \
_APP_FUNCTIONS_TIMEOUT=900 \
_APP_FUNCTIONS_CONTAINERS=10 \
@ -263,6 +290,7 @@ COPY --from=imagick /usr/local/lib/php/extensions/no-debug-non-zts-20200930/imag
COPY --from=yaml /usr/local/lib/php/extensions/no-debug-non-zts-20200930/yaml.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
COPY --from=maxmind /usr/local/lib/php/extensions/no-debug-non-zts-20200930/maxminddb.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
COPY --from=mongodb /usr/local/lib/php/extensions/no-debug-non-zts-20200930/mongodb.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
COPY --from=scrypt /usr/local/lib/php/extensions/php-scrypt/target/libphp_scrypt.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
# Add Source Code
COPY ./app /usr/src/code/app
@ -319,6 +347,7 @@ RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini
RUN echo extension=imagick.so >> /usr/local/etc/php/conf.d/imagick.ini
RUN echo extension=yaml.so >> /usr/local/etc/php/conf.d/yaml.ini
RUN echo extension=maxminddb.so >> /usr/local/etc/php/conf.d/maxminddb.ini
RUN echo extension=libphp_scrypt.so >> /usr/local/etc/php/conf.d/libphp_scrypt.ini
RUN if [ "$DEBUG" == "true" ]; then printf "zend_extension=yasd \nyasd.debug_mode=remote \nyasd.init_file=/usr/local/dev/yasd_init.php \nyasd.remote_port=9005 \nyasd.log_level=-1" >> /usr/local/etc/php/conf.d/yasd.ini; fi
RUN if [ "$DEBUG" == "true" ]; then echo "opcache.enable=0" >> /usr/local/etc/php/conf.d/appwrite.ini; fi

View file

@ -1,5 +1,6 @@
<?php
use Appwrite\Auth\Auth;
use Utopia\Config\Config;
use Utopia\Database\Database;
@ -1158,8 +1159,30 @@ $collections = [
'required' => false,
'default' => null,
'array' => false,
'filters' => ['encrypt'],
],
[
'$id' => 'hash', // Hashing algorithm used to hash the password
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
'signed' => true,
'required' => false,
'default' => Auth::DEFAULT_ALGO,
'array' => false,
'filters' => [],
],
[
'$id' => 'hashOptions', // Configuration of hashing algorithm
'type' => Database::VAR_STRING,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => Auth::DEFAULT_ALGO_OPTIONS,
'array' => false,
'filters' => ['json'],
],
[
'$id' => 'passwordUpdate',
'type' => Database::VAR_INTEGER,
@ -2386,6 +2409,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'stdout',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 1000000,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'statusCode',
'type' => Database::VAR_INTEGER,

View file

@ -50,7 +50,7 @@ return [
],
Exception::GENERAL_PHONE_DISABLED => [
'name' => Exception::GENERAL_PHONE_DISABLED,
'description' => 'Phone provider is not configured. Please check the _APP_PHONE_PROVIDER environment variable of your Appwrite server.',
'description' => 'Phone provider is not configured. Please check the _APP_SMS_PROVIDER environment variable of your Appwrite server.',
'code' => 503,
],
Exception::GENERAL_ARGUMENT_INVALID => [
@ -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

@ -7,7 +7,7 @@
use Utopia\App;
use Appwrite\Runtimes\Runtimes;
$runtimes = new Runtimes('v1');
$runtimes = new Runtimes('v2');
$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -394,8 +394,8 @@ return [
'description' => '',
'variables' => [
[
'name' => '_APP_PHONE_PROVIDER',
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'phone://[USER]:[SECRET]@[PROVIDER]'. \n\nAvailable providers are twilio, text-magic and telesign.",
'name' => '_APP_SMS_PROVIDER',
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'. \n\nAvailable providers are twilio, text-magic and telesign.",
'introduction' => '0.15.0',
'default' => '',
'required' => false,
@ -403,7 +403,7 @@ return [
'filter' => ''
],
[
'name' => '_APP_PHONE_FROM',
'name' => '_APP_SMS_FROM',
'description' => 'Phone number used for sending out messages. Must start with a leading \'+\' and maximum of 15 digits without spaces (+123456789).',
'introduction' => '0.15.0',
'default' => '',

View file

@ -2,9 +2,9 @@
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Phone;
use Appwrite\SMS\Adapter\Mock;
use Appwrite\Auth\Validator\Password;
use Appwrite\Auth\Validator\Phone as ValidatorPhone;
use Appwrite\Auth\Validator\Phone;
use Appwrite\Detector\Detector;
use Appwrite\Event\Event;
use Appwrite\Event\Mail;
@ -53,7 +53,7 @@ App::post('/v1/account')
->label('sdk.description', '/docs/references/account/create.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->label('abuse-limit', 10)
->param('userId', '', new CustomId(), 'Unique 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('email', '', new Email(), 'User email.')
@ -67,18 +67,17 @@ App::post('/v1/account')
->inject('usage')
->inject('events')
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
$email = \strtolower($email);
if ('console' === $project->getId()) {
$whitelistEmails = $project->getAttribute('authWhitelistEmails');
$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 +87,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);
}
}
@ -101,7 +100,9 @@ App::post('/v1/account')
'email' => $email,
'emailVerification' => false,
'status' => true,
'password' => Auth::passwordHash($password),
'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
'passwordUpdate' => \time(),
'registration' => \time(),
'reset' => false,
@ -113,7 +114,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);
@ -129,7 +130,7 @@ App::post('/v1/account')
$events->setParam('userId', $user->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::post('/v1/account/sessions/email')
@ -166,12 +167,12 @@ App::post('/v1/account/sessions/email')
$profile = $dbForProject->findOne('users', [
new Query('email', Query::TYPE_EQUAL, [$email])]);
if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) {
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); // Wrong password or username
if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) {
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'));
@ -198,6 +199,15 @@ App::post('/v1/account/sessions/email')
Authorization::setRole('user:' . $profile->getId());
// Re-hash if not using recommended algo
if ($profile->getAttribute('hash') !== Auth::DEFAULT_ALGO) {
$profile
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
$dbForProject->updateDocument('users', $profile->getId(), $profile);
}
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$read', ['user:' . $profile->getId()])
->setAttribute('$write', ['user:' . $profile->getId()]));
@ -276,13 +286,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)) {
@ -388,7 +398,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);
@ -397,18 +407,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;
@ -422,7 +432,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);
@ -432,7 +442,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', []);
@ -470,7 +480,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);
}
}
@ -483,7 +493,9 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'email' => $email,
'emailVerification' => true,
'status' => true, // Email should already be authenticated by OAuth2 provider
'password' => Auth::passwordHash(Auth::passwordGenerator()),
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
'passwordUpdate' => 0,
'registration' => \time(),
'reset' => false,
@ -495,13 +507,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
@ -619,7 +631,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();
@ -635,7 +647,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);
}
}
@ -649,6 +661,8 @@ App::post('/v1/account/sessions/magic-url')
'emailVerification' => false,
'status' => true,
'password' => null,
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
'passwordUpdate' => 0,
'registration' => \time(),
'reset' => false,
@ -750,13 +764,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'));
@ -802,7 +816,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());
@ -849,7 +863,7 @@ App::post('/v1/account/sessions/phone')
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
->param('userId', '', new CustomId(), 'Unique 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('number', '', new ValidatorPhone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->inject('request')
->inject('response')
->inject('project')
@ -857,17 +871,16 @@ App::post('/v1/account/sessions/phone')
->inject('audits')
->inject('events')
->inject('messaging')
->inject('phone')
->action(function (string $userId, string $number, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Event $events, EventPhone $messaging, Phone $phone) {
if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) {
throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED);
->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(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$user = $dbForProject->findOne('users', [new Query('phone', Query::TYPE_EQUAL, [$number])]);
$user = $dbForProject->findOne('users', [new Query('phone', Query::TYPE_EQUAL, [$phone])]);
if (!$user) {
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
@ -876,7 +889,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);
}
}
@ -887,7 +900,7 @@ App::post('/v1/account/sessions/phone')
'$read' => ['role:all'],
'$write' => ['user:' . $userId],
'email' => null,
'phone' => $number,
'phone' => $phone,
'emailVerification' => false,
'phoneVerification' => false,
'status' => true,
@ -899,11 +912,11 @@ App::post('/v1/account/sessions/phone')
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $number])
'search' => implode(' ', [$userId, $phone])
])));
}
$secret = $phone->generateSecretDigits();
$secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$digits : Auth::codeGenerator();
$expire = \time() + Auth::TOKEN_EXPIRATION_PHONE;
@ -927,7 +940,7 @@ App::post('/v1/account/sessions/phone')
$dbForProject->deleteCachedDocument('users', $user->getId());
$messaging
->setRecipient($number)
->setRecipient($phone)
->setMessage($secret)
->trigger();
@ -980,13 +993,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'));
@ -1030,7 +1043,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());
@ -1092,11 +1105,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;
@ -1105,7 +1118,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);
}
}
@ -1118,6 +1131,8 @@ App::post('/v1/account/sessions/anonymous')
'emailVerification' => false,
'status' => true,
'password' => null,
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
'passwordUpdate' => 0,
'registration' => \time(),
'reset' => false,
@ -1222,7 +1237,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.
@ -1248,15 +1263,13 @@ App::get('/v1/account')
->label('sdk.description', '/docs/references/account/get.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->inject('response')
->inject('user')
->inject('usage')
->action(function (Response $response, Document $user, Stats $usage) {
$usage->setParam('users.read', 1);
$response->dynamic($user, Response::MODEL_USER);
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::get('/v1/account/prefs')
@ -1417,7 +1430,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')
@ -1431,7 +1444,7 @@ App::patch('/v1/account/name')
->label('sdk.description', '/docs/references/account/update-name.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->param('name', '', new Text(128), 'User name. Max length: 128 chars.')
->inject('response')
->inject('user')
@ -1453,7 +1466,7 @@ App::patch('/v1/account/name')
$usage->setParam('users.update', 1);
$events->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_USER);
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::patch('/v1/account/password')
@ -1467,7 +1480,7 @@ App::patch('/v1/account/password')
->label('sdk.description', '/docs/references/account/update-password.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true)
->inject('response')
@ -1477,19 +1490,16 @@ App::patch('/v1/account/password')
->inject('usage')
->inject('events')
->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'))) { // Double check user password
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
}
$user = $dbForProject->updateDocument(
'users',
$user->getId(),
$user
->setAttribute('password', Auth::passwordHash($password))
->setAttribute('passwordUpdate', \time())
);
$user = $dbForProject->updateDocument('users', $user->getId(), $user
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
->setAttribute('passwordUpdate', \time()));
$audits
->setResource('user/' . $user->getId())
@ -1499,7 +1509,7 @@ App::patch('/v1/account/password')
$usage->setParam('users.update', 1);
$events->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_USER);
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::patch('/v1/account/email')
@ -1513,7 +1523,7 @@ App::patch('/v1/account/email')
->label('sdk.description', '/docs/references/account/update-email.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->param('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->inject('response')
@ -1523,20 +1533,21 @@ App::patch('/v1/account/email')
->inject('usage')
->inject('events')
->action(function (string $email, string $password, Response $response, Document $user, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
$isAnonymousUser = Auth::isAnonymousUser($user); // Check if request is from an anonymous account for converting
if (
!$isAnonymousUser &&
!Auth::passwordVerify($password, $user->getAttribute('password'))
!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);
$user
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password) : $user->getAttribute('password', ''))
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS) : $user->getAttribute('password', ''))
->setAttribute('hash', $isAnonymousUser ? Auth::DEFAULT_ALGO : $user->getAttribute('hash', ''))
->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', ''), $email, $user->getAttribute('phone', '')]));
@ -1544,7 +1555,7 @@ App::patch('/v1/account/email')
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
@ -1555,7 +1566,7 @@ App::patch('/v1/account/email')
$usage->setParam('users.update', 1);
$events->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_USER);
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::patch('/v1/account/phone')
@ -1569,8 +1580,8 @@ App::patch('/v1/account/phone')
->label('sdk.description', '/docs/references/account/update-phone.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('number', '', new ValidatorPhone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->inject('response')
->inject('user')
@ -1584,9 +1595,9 @@ App::patch('/v1/account/phone')
if (
!$isAnonymousUser &&
!Auth::passwordVerify($password, $user->getAttribute('password'))
!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
@ -1597,7 +1608,7 @@ App::patch('/v1/account/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
@ -1608,7 +1619,7 @@ App::patch('/v1/account/phone')
$usage->setParam('users.update', 1);
$events->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_USER);
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::patch('/v1/account/prefs')
@ -1622,7 +1633,7 @@ App::patch('/v1/account/prefs')
->label('sdk.description', '/docs/references/account/update-prefs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->param('prefs', [], new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
->inject('user')
@ -1638,7 +1649,7 @@ App::patch('/v1/account/prefs')
$usage->setParam('users.update', 1);
$events->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_USER);
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::patch('/v1/account/status')
@ -1652,7 +1663,7 @@ App::patch('/v1/account/status')
->label('sdk.description', '/docs/references/account/update-status.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->inject('request')
->inject('response')
->inject('user')
@ -1666,11 +1677,11 @@ App::patch('/v1/account/status')
$audits
->setResource('user/' . $user->getId())
->setPayload($response->output($user, Response::MODEL_USER));
->setPayload($response->output($user, Response::MODEL_ACCOUNT));
$events
->setParam('userId', $user->getId())
->setPayload($response->output($user, Response::MODEL_USER));
->setPayload($response->output($user, Response::MODEL_ACCOUNT));
if (!Config::getParam('domainVerification')) {
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
@ -1678,7 +1689,7 @@ App::patch('/v1/account/status')
$usage->setParam('users.delete', 1);
$response->dynamic($user, Response::MODEL_USER);
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::delete('/v1/account/sessions/:sessionId')
@ -1755,7 +1766,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')
@ -1809,7 +1820,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, '', [], []);
@ -1842,7 +1853,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')
@ -1941,7 +1952,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();
@ -1955,11 +1966,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;
@ -2041,28 +2052,29 @@ App::put('/v1/account/recovery')
->inject('usage')
->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());
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
->setAttribute('password', Auth::passwordHash($password))
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
->setAttribute('passwordUpdate', \time())
->setAttribute('emailVerification', true));
@ -2115,7 +2127,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();
@ -2204,14 +2216,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());
@ -2255,21 +2267,20 @@ App::post('/v1/account/verification/phone')
->label('abuse-key', 'userId:{userId}')
->inject('request')
->inject('response')
->inject('phone')
->inject('user')
->inject('dbForProject')
->inject('audits')
->inject('events')
->inject('usage')
->inject('messaging')
->action(function (Request $request, Response $response, Phone $phone, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage, EventPhone $messaging) {
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage, EventPhone $messaging) {
if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) {
throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED);
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(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();
@ -2278,7 +2289,7 @@ App::post('/v1/account/verification/phone')
$verificationSecret = Auth::tokenGenerator();
$secret = $phone->generateSecretDigits();
$secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$digits : Auth::codeGenerator();
$expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM;
$verification = new Document([
@ -2352,13 +2363,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

@ -23,15 +23,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';
@ -39,7 +39,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);
}
$image = new Image(\file_get_contents($path));
@ -138,19 +138,19 @@ App::get('/v1/avatars/image')
$type = 'png';
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);
@ -189,7 +189,7 @@ App::get('/v1/avatars/favicon')
$type = 'png';
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();
@ -211,7 +211,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();
@ -269,7 +269,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');
}
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
@ -281,7 +281,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());
@ -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([
@ -993,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([
@ -1133,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
@ -1203,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
@ -1214,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([
@ -1304,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');
@ -1355,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
@ -1421,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
@ -1519,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', [
@ -1535,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
@ -1581,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'];
@ -1590,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
@ -1612,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);
@ -1667,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');
@ -1710,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');
@ -1724,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], [
@ -1764,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
@ -1844,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');
}
/**
@ -1865,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);
}
}
@ -1873,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);
}
}
@ -1888,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) . ')');
}
}
}
@ -1908,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
@ -1966,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.
@ -1977,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);
}
}
@ -1985,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);
}
}
@ -1993,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;
@ -2002,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());
}
}
@ -2020,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.");
}
}
@ -2074,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.
@ -2083,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);
}
}
@ -2091,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);
}
}
@ -2108,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
@ -2146,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);
@ -2244,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.
@ -2253,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);
}
}
@ -2261,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));
@ -2271,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);
@ -2299,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) . ')');
}
}
}
@ -2324,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
@ -2380,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.
@ -2389,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);
}
}
@ -2397,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);
}
}
@ -2409,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') {
@ -2700,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
@ -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);
@ -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();
@ -945,6 +945,7 @@ App::post('/v1/functions/:functionId/executions')
$execution->setAttribute('status', $executionResponse['status']);
$execution->setAttribute('statusCode', $executionResponse['statusCode']);
$execution->setAttribute('response', $executionResponse['response']);
$execution->setAttribute('stdout', $executionResponse['stdout']);
$execution->setAttribute('stderr', $executionResponse['stderr']);
$execution->setAttribute('time', $executionResponse['time']);
} catch (\Throwable $th) {
@ -965,6 +966,14 @@ App::post('/v1/functions/:functionId/executions')
->setParam('functionStatus', $execution->getAttribute('status', ''))
->setParam('functionExecutionTime', $execution->getAttribute('time') * 1000); // ms
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (!$isPrivilegedUser && !$isAppUser) {
$execution->setAttribute('stdout', '');
$execution->setAttribute('stderr', '');
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($execution, Response::MODEL_EXECUTION);
@ -994,14 +1003,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.");
}
}
@ -1016,6 +1025,17 @@ App::get('/v1/functions/:functionId/executions')
$results = $dbForProject->find('executions', $queries, $limit, $offset, [], [Database::ORDER_DESC], $cursorExecution ?? null, $cursorDirection);
$total = $dbForProject->count('executions', $queries, APP_LIMIT_COUNT);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (!$isPrivilegedUser && !$isAppUser) {
$results = array_map(function ($execution) {
$execution->setAttribute('stdout', '');
$execution->setAttribute('stderr', '');
return $execution;
}, $results);
}
$response->dynamic(new Document([
'executions' => $results,
'total' => $total,
@ -1042,17 +1062,25 @@ 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);
}
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (!$isPrivilegedUser && !$isAppUser) {
$execution->setAttribute('stdout', '');
$execution->setAttribute('stderr', '');
}
$response->dynamic($execution, Response::MODEL_EXECUTION);
@ -1082,21 +1110,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

@ -36,7 +36,7 @@ App::init()
->inject('project')
->action(function (Document $project) {
if ($project->getId() !== 'console') {
throw new Exception('Access to this API is forbidden.', 401, Exception::GENERAL_ACCESS_FORBIDDEN);
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN);
}
});
@ -70,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', []);
@ -82,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([
@ -185,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.");
}
}
@ -222,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);
@ -249,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 = [];
@ -366,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
@ -405,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', []);
@ -437,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', []);
@ -468,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', []);
@ -503,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', []);
@ -531,14 +531,14 @@ App::delete('/v1/projects/:projectId')
->inject('deletes')
->action(function (string $projectId, string $password, Response $response, Document $user, Database $dbForConsole, Delete $deletes) {
if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
if (!Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
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
@ -547,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();
@ -583,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);
@ -629,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', [
@ -661,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', [
@ -670,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);
@ -701,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);
@ -712,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
@ -749,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', [
@ -758,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)));
@ -787,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', [
@ -796,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());
@ -829,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([
@ -870,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', [
@ -902,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', [
@ -911,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);
@ -939,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', [
@ -948,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
@ -982,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', [
@ -991,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());
@ -1025,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([
@ -1067,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', [
@ -1099,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', [
@ -1108,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);
@ -1136,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', [
@ -1145,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
@ -1180,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', [
@ -1189,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);
@ -1220,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', [
@ -1229,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);
@ -1280,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', [
@ -1312,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', [
@ -1321,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);
@ -1346,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', [
@ -1355,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) {
@ -1371,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);
}
@ -1406,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', [
@ -1415,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

@ -72,7 +72,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 = [];
@ -121,7 +121,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
@ -167,7 +167,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.");
}
}
@ -199,7 +199,7 @@ App::get('/v1/storage/buckets/:bucketId')
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$usage->setParam('storage.buckets.read', 1);
@ -238,7 +238,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
@ -296,11 +296,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
@ -360,7 +360,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
@ -368,7 +368,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);
}
}
@ -381,12 +381,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);
}
}
}
@ -401,12 +401,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
@ -425,7 +425,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) {
@ -445,18 +445,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
@ -483,7 +483,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 ?? [];
@ -497,7 +497,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);
}
}
@ -521,7 +521,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');
}
}
@ -588,9 +588,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
@ -640,9 +640,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);
}
}
@ -694,14 +694,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);
}
}
@ -719,7 +719,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.");
}
}
@ -772,14 +772,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);
}
}
@ -790,7 +790,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
@ -839,7 +839,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));
@ -848,14 +848,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');
}
}
@ -875,7 +875,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');
@ -902,7 +902,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);
}
if (empty($output)) {
@ -998,14 +998,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);
}
}
@ -1016,13 +1016,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
@ -1050,7 +1050,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
@ -1137,14 +1137,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);
}
}
@ -1157,13 +1157,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();
@ -1196,7 +1196,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
@ -1296,12 +1296,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);
}
}
}
@ -1310,14 +1310,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);
}
}
@ -1328,7 +1328,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
@ -1388,14 +1388,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);
}
}
@ -1406,7 +1406,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;
@ -1431,10 +1431,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());
@ -1583,7 +1583,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.');
}
}
@ -333,7 +333,9 @@ App::post('/v1/teams/:teamId/memberships')
'email' => $email,
'emailVerification' => false,
'status' => true,
'password' => Auth::passwordHash(Auth::passwordGenerator()),
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
/**
* Set the password update time to 0 for users created using
* team invite and OAuth to allow password updates without an
@ -350,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();
@ -383,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));
@ -393,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);
}
}
@ -458,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.");
}
}
@ -531,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'));
@ -576,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());
@ -594,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');
}
/**
@ -652,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()) {
@ -678,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
@ -778,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());
@ -851,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

@ -28,6 +28,56 @@ use Utopia\Validator\Text;
use Utopia\Validator\Range;
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 $phone, string $name, Database $dbForProject, Stats $usage, Event $events): Document
{
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
if (!empty($email)) {
$email = \strtolower($email);
}
try {
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
$user = $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$read' => ['role:all'],
'$write' => ['user:' . $userId],
'email' => $email,
'emailVerification' => false,
'phone' => $phone,
'phoneVerification' => false,
'status' => true,
'password' => (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null,
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions,
'passwordUpdate' => (!empty($password)) ? \time() : 0,
'registration' => \time(),
'reset' => false,
'name' => $name,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $phone, $name])
]));
} catch (Duplicate $th) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
$usage
->setParam('users.create', 1)
;
$events
->setParam('userId', $user->getId())
;
return $user;
}
App::post('/v1/users')
->desc('Create User')
@ -42,48 +92,228 @@ App::post('/v1/users')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new CustomId(), 'User 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('email', null, new Email(), 'User email.', true)
->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('password', null, new Password(), 'Plain text user password. Must be at least 8 chars.', true)
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('events')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) {
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $dbForProject, $usage, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/bcrypt')
->desc('Create User with Bcrypt Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createBcryptUser')
->label('sdk.description', '/docs/references/users/create-bcrypt-user.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new CustomId(), 'User 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('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->param('password', '', new Password(), 'User password hashed using Bcrypt.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('events')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) {
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $dbForProject, $usage, $events);
$email = \strtolower($email);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
});
try {
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
$user = $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$read' => ['role:all'],
'$write' => ['user:' . $userId],
'email' => $email,
'emailVerification' => false,
'status' => true,
'password' => Auth::passwordHash($password),
'passwordUpdate' => \time(),
'registration' => \time(),
'reset' => false,
'name' => $name,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name])
]));
} catch (Duplicate $th) {
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
App::post('/v1/users/md5')
->desc('Create User with MD5 Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createMD5User')
->label('sdk.description', '/docs/references/users/create-md5-user.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new CustomId(), 'User 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('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password hashed using MD5.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('events')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) {
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $dbForProject, $usage, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/argon2')
->desc('Create User with Argon2 Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createArgon2User')
->label('sdk.description', '/docs/references/users/create-argon2-user.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new CustomId(), 'User 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('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Argon2.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('events')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) {
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $dbForProject, $usage, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/sha')
->desc('Create User with SHA Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createSHAUser')
->label('sdk.description', '/docs/references/users/create-sha-user.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new CustomId(), 'User 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('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password hashed using SHA.')
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('events')
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) {
$options = '{}';
if (!empty($passwordVersion)) {
$options = '{"version":"' . $passwordVersion . '"}';
}
$usage
->setParam('users.create', 1)
;
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $dbForProject, $usage, $events);
$events
->setParam('userId', $user->getId())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/phpass')
->desc('Create User with PHPass Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createPHPassUser')
->label('sdk.description', '/docs/references/users/create-phpass-user.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new CustomId(), 'User 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('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password hashed using PHPass.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('events')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) {
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $dbForProject, $usage, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/scrypt')
->desc('Create User with Scrypt Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createScryptUser')
->label('sdk.description', '/docs/references/users/create-scrypt-user.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new CustomId(), 'User 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('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Scrypt.')
->param('passwordSalt', '', new Text(128), 'Optional salt used to hash password.')
->param('passwordCpu', '', new Integer(), 'Optional CPU cost used to hash password.')
->param('passwordMemory', '', new Integer(), 'Optional memory cost used to hash password.')
->param('passwordParallel', '', new Integer(), 'Optional parallelization cost used to hash password.')
->param('passwordLength', '', new Integer(), 'Optional hash length used to hash password.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('events')
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) {
$options = [
'salt' => $passwordSalt,
'costCpu' => $passwordCpu,
'costMemory' => $passwordMemory,
'costParallel' => $passwordParallel,
'length' => $passwordLength
];
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $dbForProject, $usage, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/scrypt-modified')
->desc('Create User with Scrypt Modified Password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createScryptModifiedUser')
->label('sdk.description', '/docs/references/users/create-scrypt-modified-user.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new CustomId(), 'User 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('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Scrypt Modified.')
->param('passwordSalt', '', new Text(128), 'Salt used to hash password.')
->param('passwordSaltSeparator', '', new Text(128), 'Salt separator used to hash password.')
->param('passwordSignerKey', '', new Text(128), 'Signer key used to hash password.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('events')
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) {
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $dbForProject, $usage, $events);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
@ -115,7 +345,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.");
}
}
@ -155,7 +385,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
@ -184,7 +414,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());
@ -216,7 +446,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', []);
@ -259,7 +489,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) {
@ -303,7 +533,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);
@ -384,7 +614,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));
@ -423,7 +653,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));
@ -462,7 +692,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));
@ -501,7 +731,7 @@ 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
@ -541,15 +771,16 @@ App::patch('/v1/users/:userId/password')
->inject('audits')
->inject('events')
->action(function (string $userId, string $password, Response $response, Database $dbForProject, EventAudit $audits, Event $events) {
$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('password', Auth::passwordHash($password))
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
->setAttribute('passwordUpdate', \time());
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
@ -588,7 +819,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);
@ -602,7 +833,7 @@ App::patch('/v1/users/:userId/email')
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);
}
@ -640,7 +871,7 @@ 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
@ -652,7 +883,7 @@ App::patch('/v1/users/:userId/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);
}
@ -690,7 +921,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));
@ -728,13 +959,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());
@ -775,7 +1006,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', []);
@ -822,7 +1053,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

@ -131,11 +131,11 @@ App::init()
}
if ($project->isEmpty()) {
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
throw new AppwriteException(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);
throw new AppwriteException(AppwriteException::PROJECT_UNKNOWN);
}
$referrer = $request->getReferer();
@ -206,7 +206,7 @@ App::init()
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);
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP.');
}
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
@ -239,7 +239,7 @@ App::init()
&& $route->getLabel('origin', false) !== '*'
&& empty($request->getHeader('x-appwrite-key', ''))
) {
throw new AppwriteException($originValidator->getDescription(), 403, AppwriteException::GENERAL_UNKNOWN_ORIGIN);
throw new AppwriteException(AppwriteException::GENERAL_UNKNOWN_ORIGIN, $originValidator->getDescription());
}
/*
@ -295,7 +295,7 @@ App::init()
$expire = $key->getAttribute('expire', 0);
if (!empty($expire) && $expire < \time()) {
throw new AppwriteException('Project key expired', 401, AppwriteException:: PROJECT_KEY_EXPIRED);
throw new AppwriteException(AppwriteException:: PROJECT_KEY_EXPIRED);
}
Authorization::setRole('role:' . Auth::USER_ROLE_APP);
@ -316,24 +316,24 @@ App::init()
&& !$project->getAttribute('services', [])[$service]
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
) {
throw new AppwriteException('Service is disabled', 503, AppwriteException::GENERAL_SERVICE_DISABLED);
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('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
}
throw new AppwriteException($user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')', 401, AppwriteException::GENERAL_UNAUTHORIZED_SCOPE);
throw new AppwriteException(AppwriteException::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')');
}
if (false === $user->getAttribute('status')) { // Account is blocked
throw new AppwriteException('Invalid credentials. User is blocked', 401, AppwriteException::USER_BLOCKED);
throw new AppwriteException(AppwriteException::USER_BLOCKED);
}
if ($user->getAttribute('reset')) {
throw new AppwriteException('Password reset is required', 412, AppwriteException::USER_PASSWORD_RESET_REQUIRED);
throw new AppwriteException(AppwriteException::USER_PASSWORD_RESET_REQUIRED);
}
});
@ -445,7 +445,7 @@ App::error()
/** Handle Utopia Errors */
if ($error instanceof Utopia\Exception) {
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
switch ($code) {
case 400:
$error->setType(AppwriteException::GENERAL_ARGUMENT_INVALID);
@ -458,7 +458,7 @@ App::error()
/** Wrap all exceptions inside Appwrite\Extend\Exception */
if (!($error instanceof AppwriteException)) {
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
}
switch ($code) { // Don't show 500 errors!
@ -601,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

@ -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([
@ -571,7 +571,7 @@ App::shutdown()
$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);
throw new Exception(Exception::GENERAL_MOCK, 'Failed to read results', 500);
}
$result[$route->getMethod() . ':' . $route->getPath()] = true;
@ -579,7 +579,7 @@ App::shutdown()
$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);
throw new Exception(Exception::GENERAL_MOCK, 'Failed to save results', 500);
}
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);

View file

@ -67,7 +67,7 @@ App::init()
$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);
throw new Exception(Exception::PROJECT_UNKNOWN);
}
/*
@ -116,7 +116,7 @@ App::init()
&& $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);
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED);
}
}
@ -199,36 +199,36 @@ App::init()
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);
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('Magic URL authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
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('Anonymous authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
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('Invites authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
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('JWT authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'JWT authentication is disabled for this project');
}
break;
default:
throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route');
break;
}
});

View file

@ -512,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

@ -488,6 +488,7 @@ App::post('/v1/execution')
$executionStart = \microtime(true);
$stdout = '';
$stderr = '';
$res = '';
$statusCode = 0;
$errNo = -1;
$executorResponse = '';
@ -515,6 +516,7 @@ App::post('/v1/execution')
]);
$executorResponse = \curl_exec($ch);
$executorResponse = json_decode($executorResponse, true);
$statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);
@ -538,13 +540,19 @@ App::post('/v1/execution')
switch (true) {
case $statusCode >= 500:
$stderr = $executorResponse ?? 'Internal Runtime error.';
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Internal Runtime error.';
$stdout = ($executorResponse ?? [])['stdout'] ?? 'Internal Runtime error.';
break;
case $statusCode >= 100:
$stdout = $executorResponse;
$stdout = $executorResponse['stdout'];
$res = $executorResponse['response'];
if (is_array($res)) {
$res = json_encode($res, JSON_UNESCAPED_UNICODE);
}
break;
default:
$stderr = $executorResponse ?? 'Execution failed.';
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Execution failed.';
$stdout = ($executorResponse ?? [])['stdout'] ?? '';
break;
}
@ -557,7 +565,8 @@ App::post('/v1/execution')
$execution = [
'status' => $functionStatus,
'statusCode' => $statusCode,
'response' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
'response' => \mb_strcut($res, 0, 1000000), // Limit to 1MB
'stdout' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
'time' => $executionTime,
];
@ -648,7 +657,7 @@ $http->on('start', function ($http) {
/**
* Warmup: make sure images are ready to run fast 🚀
*/
$runtimes = new Runtimes('v1');
$runtimes = new Runtimes('v2');
$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));
$runtimes = $runtimes->getAll(true, $allowList);
foreach ($runtimes as $runtime) {

View file

@ -23,12 +23,12 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Extend\Exception;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Phone\Mock;
use Appwrite\Auth\Phone\Telesign;
use Appwrite\Auth\Phone\TextMagic;
use Appwrite\Auth\Phone\Twilio;
use Appwrite\Auth\Phone\Msg91;
use Appwrite\Auth\Phone\Vonage;
use Appwrite\SMS\Adapter\Mock;
use Appwrite\SMS\Adapter\Telesign;
use Appwrite\SMS\Adapter\TextMagic;
use Appwrite\SMS\Adapter\Twilio;
use Appwrite\SMS\Adapter\Msg91;
use Appwrite\SMS\Adapter\Vonage;
use Appwrite\DSN\DSN;
use Appwrite\Event\Audit;
use Appwrite\Event\Database as EventDatabase;
@ -450,7 +450,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);
@ -820,7 +820,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'] ?? '';
@ -984,8 +984,8 @@ App::setResource('geodb', function ($register) {
return $register->get('geodb');
}, ['register']);
App::setResource('phone', function () {
$dsn = new DSN(App::getEnv('_APP_PHONE_PROVIDER'));
App::setResource('sms', function () {
$dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
$user = $dsn->getUser();
$secret = $dsn->getPassword();

View file

@ -433,7 +433,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_USER);
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([
'type' => 'connected',
@ -548,7 +548,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
$user = $response->output($user, Response::MODEL_USER);
$user = $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([
'type' => 'response',
'data' => [

View file

@ -392,10 +392,10 @@ sort($patterns);
<tr>
<th width="30"></th>
<th width="160">Created</th>
<th width="150">Status</th>
<th width="120">Trigger</th>
<th width="80">Runtime</th>
<th></th>
<th width="100">Status</th>
<th width="80">Trigger</th>
<th width="60">Runtime</th>
<th width=""></th>
</tr>
</thead>
<tbody data-ls-loop="project-function-executions.executions" data-ls-as="execution">
@ -416,29 +416,44 @@ sort($patterns);
<td data-title="Trigger: ">
<span data-ls-bind="{{execution.trigger}}"></span>
</td>
<td data-title="Runtime: ">
<td data-title="Time: ">
<span data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-ls-bind="{{execution.time|seconds2hum}}"></span>
<span data-ls-if="{{execution.status}} === 'waiting' || {{execution.status}} === 'processing'">-</span>
</td>
<td data-title="">
<div data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-title="">
<div data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-title="" style="display: flex;">
<button class="desktops-only pull-end link margin-start text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Stderr</button>
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Stdout</button>
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-response-{{execution.$id}}">Response</button>
<button class="desktops-only pull-end link margin-start text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Errors</button>
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Output</button>
<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-response-{{execution.$id}}">Response</button>
<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Stdout</button>
<button class="phones-only-inline tablets-only-inline link text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Stderr</button>
<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Output</button>
<button class="phones-only-inline tablets-only-inline link text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Errors</button>
<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-stdout-{{execution.$id}}">
<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-response-{{execution.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>STDOUT</h1>
<h1>RESPONSE</h1>
<div class="margin-bottom ide" data-ls-if="({{execution.response.length}})">
<pre data-ls-bind="{{execution.response}}"></pre>
</div>
<div class="margin-bottom" data-ls-if="(!{{execution.response.length}})">
<p>No Response was logged.</p>
</div>
</div>
<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-stdout-{{execution.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>STDOUT</h1>
<div class="margin-bottom ide" data-ls-if="({{execution.stdout.length}})">
<pre data-ls-bind="{{execution.stdout}}"></pre>
</div>
<div class="margin-bottom" data-ls-if="(!{{execution.stdout.length}})">
<p>No output was logged.</p>
</div>
</div>

View file

@ -149,8 +149,8 @@ services:
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_PHONE_PROVIDER
- _APP_PHONE_SECRET
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
appwrite-realtime:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@ -514,8 +514,8 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_PHONE_PROVIDER
- _APP_PHONE_FROM
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@ -595,7 +595,7 @@ services:
- _APP_REDIS_PASS
mariadb:
image: mariadb:10.8.3 # fix issues when upgrading using: mysql_upgrade -u root -p
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
restart: unless-stopped

View file

@ -293,6 +293,7 @@ class FunctionsV1 extends Worker
->setAttribute('status', $executionResponse['status'])
->setAttribute('statusCode', $executionResponse['statusCode'])
->setAttribute('response', $executionResponse['response'])
->setAttribute('stdout', $executionResponse['stdout'])
->setAttribute('stderr', $executionResponse['stderr'])
->setAttribute('time', $executionResponse['time']);
} catch (\Throwable $th) {

View file

@ -1,12 +1,12 @@
<?php
use Appwrite\Auth\Phone;
use Appwrite\Auth\Phone\Mock;
use Appwrite\Auth\Phone\Telesign;
use Appwrite\Auth\Phone\TextMagic;
use Appwrite\Auth\Phone\Twilio;
use Appwrite\Auth\Phone\Msg91;
use Appwrite\Auth\Phone\Vonage;
use Appwrite\Auth\SMS;
use Appwrite\SMS\Adapter\Mock;
use Appwrite\SMS\Adapter\Telesign;
use Appwrite\SMS\Adapter\TextMagic;
use Appwrite\SMS\Adapter\Twilio;
use Appwrite\SMS\Adapter\Msg91;
use Appwrite\SMS\Adapter\Vonage;
use Appwrite\DSN\DSN;
use Appwrite\Resque\Worker;
use Utopia\App;
@ -19,7 +19,7 @@ Console::success(APP_NAME . ' messaging worker v1 has started' . "\n");
class MessagingV1 extends Worker
{
protected ?Phone $phone = null;
protected ?SMS $sms = null;
protected ?string $from = null;
public function getName(): string
@ -29,11 +29,11 @@ class MessagingV1 extends Worker
public function init(): void
{
$dsn = new DSN(App::getEnv('_APP_PHONE_PROVIDER'));
$dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
$user = $dsn->getUser();
$secret = $dsn->getPassword();
$this->phone = match ($dsn->getHost()) {
$this->sms = match ($dsn->getHost()) {
'mock' => new Mock('', ''), // used for tests
'twilio' => new Twilio($user, $secret),
'text-magic' => new TextMagic($user, $secret),
@ -43,12 +43,12 @@ class MessagingV1 extends Worker
default => null
};
$this->from = App::getEnv('_APP_PHONE_FROM');
$this->from = App::getEnv('_APP_SMS_FROM');
}
public function run(): void
{
if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) {
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
Console::info('Skipped sms processing. No Phone provider has been set.');
return;
}
@ -62,7 +62,7 @@ class MessagingV1 extends Worker
$message = $this->args['message'];
try {
$this->phone->send($this->from, $recipient, $message);
$this->sms->send($this->from, $recipient, $message);
} catch (\Exception $error) {
throw new Exception('Error sending message: ' . $error->getMessage(), 500);
}

View file

@ -42,8 +42,8 @@
"ext-zlib": "*",
"ext-sockets": "*",
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.10.*",
"utopia-php/framework": "0.21.*",
"appwrite/php-runtimes": "0.11.*",
"utopia-php/framework": "0.20.*",
"utopia-php/logger": "0.3.*",
"utopia-php/abuse": "0.7.*",
"utopia-php/analytics": "0.2.*",

20
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": "b54c2126173a52cc14bb2d04aec44fb6",
"content-hash": "bbe0a04899feee1909b5924a2131dbee",
"packages": [
{
"name": "adhocore/jwt",
@ -115,11 +115,11 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.10.0",
"version": "0.11.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/runtimes.git",
"reference": "09874846c6bdb7be58c97b12323d2b35ec995409"
"reference": "547fc026e11c0946846a8ac690898f5bf53be101"
},
"require": {
"php": ">=8.0",
@ -154,7 +154,7 @@
"php",
"runtimes"
],
"time": "2022-06-28T05:26:20+00:00"
"time": "2022-08-15T14:03:36+00:00"
},
{
"name": "chillerlan/php-qrcode",
@ -2169,16 +2169,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.21.0",
"version": "0.20.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/framework.git",
"reference": "5aa5431788460a782065e42b0e8a35e7f139af2f"
"reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/5aa5431788460a782065e42b0e8a35e7f139af2f",
"reference": "5aa5431788460a782065e42b0e8a35e7f139af2f",
"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.21.0"
"source": "https://github.com/utopia-php/framework/tree/0.20.0"
},
"time": "2022-08-12T11:37:21+00:00"
"time": "2022-07-30T09:55:28+00:00"
},
{
"name": "utopia-php/image",

View file

@ -173,8 +173,8 @@ services:
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_PHONE_PROVIDER
- _APP_PHONE_SECRET
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
appwrite-realtime:
entrypoint: realtime
@ -541,8 +541,8 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_PHONE_PROVIDER
- _APP_PHONE_FROM
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@ -634,7 +634,7 @@ services:
- _APP_REDIS_PASS
mariadb:
image: mariadb:10.8.3 # fix issues when upgrading using: mysql_upgrade -u root -p
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
networks:

View file

@ -0,0 +1 @@
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

@ -0,0 +1 @@
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

@ -0,0 +1 @@
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

@ -0,0 +1 @@
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

@ -0,0 +1 @@
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

@ -0,0 +1 @@
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

@ -0,0 +1 @@
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.

2539
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -866,6 +866,79 @@ if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createArgon2User(userId,email,password,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/argon2';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createBcryptUser(userId,email,password,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/bcrypt';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createMD5User(userId,email,password,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/md5';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createPHPassUser(userId,email,password,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/phpass';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createScryptUser(userId,email,password,passwordSalt,passwordCpu,passwordMemory,passwordParallel,passwordLength,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
if(typeof passwordSalt==='undefined'){throw new AppwriteException('Missing required parameter: "passwordSalt"');}
if(typeof passwordCpu==='undefined'){throw new AppwriteException('Missing required parameter: "passwordCpu"');}
if(typeof passwordMemory==='undefined'){throw new AppwriteException('Missing required parameter: "passwordMemory"');}
if(typeof passwordParallel==='undefined'){throw new AppwriteException('Missing required parameter: "passwordParallel"');}
if(typeof passwordLength==='undefined'){throw new AppwriteException('Missing required parameter: "passwordLength"');}
let path='/users/scrypt';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof passwordSalt!=='undefined'){payload['passwordSalt']=passwordSalt;}
if(typeof passwordCpu!=='undefined'){payload['passwordCpu']=passwordCpu;}
if(typeof passwordMemory!=='undefined'){payload['passwordMemory']=passwordMemory;}
if(typeof passwordParallel!=='undefined'){payload['passwordParallel']=passwordParallel;}
if(typeof passwordLength!=='undefined'){payload['passwordLength']=passwordLength;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createScryptModifiedUser(userId,email,password,passwordSalt,passwordSaltSeparator,passwordSignerKey,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
if(typeof passwordSalt==='undefined'){throw new AppwriteException('Missing required parameter: "passwordSalt"');}
if(typeof passwordSaltSeparator==='undefined'){throw new AppwriteException('Missing required parameter: "passwordSaltSeparator"');}
if(typeof passwordSignerKey==='undefined'){throw new AppwriteException('Missing required parameter: "passwordSignerKey"');}
let path='/users/scrypt-modified';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof passwordSalt!=='undefined'){payload['passwordSalt']=passwordSalt;}
if(typeof passwordSaltSeparator!=='undefined'){payload['passwordSaltSeparator']=passwordSaltSeparator;}
if(typeof passwordSignerKey!=='undefined'){payload['passwordSignerKey']=passwordSignerKey;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createSHAUser(userId,email,password,passwordVersion,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/sha';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof passwordVersion!=='undefined'){payload['passwordVersion']=passwordVersion;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
getUsage(range,provider){return __awaiter(this,void 0,void 0,function*(){let path='/users/usage';let payload={};if(typeof range!=='undefined'){payload['range']=range;}
if(typeof provider!=='undefined'){payload['provider']=provider;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}

View file

@ -866,6 +866,79 @@ if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createArgon2User(userId,email,password,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/argon2';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createBcryptUser(userId,email,password,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/bcrypt';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createMD5User(userId,email,password,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/md5';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createPHPassUser(userId,email,password,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/phpass';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createScryptUser(userId,email,password,passwordSalt,passwordCpu,passwordMemory,passwordParallel,passwordLength,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
if(typeof passwordSalt==='undefined'){throw new AppwriteException('Missing required parameter: "passwordSalt"');}
if(typeof passwordCpu==='undefined'){throw new AppwriteException('Missing required parameter: "passwordCpu"');}
if(typeof passwordMemory==='undefined'){throw new AppwriteException('Missing required parameter: "passwordMemory"');}
if(typeof passwordParallel==='undefined'){throw new AppwriteException('Missing required parameter: "passwordParallel"');}
if(typeof passwordLength==='undefined'){throw new AppwriteException('Missing required parameter: "passwordLength"');}
let path='/users/scrypt';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof passwordSalt!=='undefined'){payload['passwordSalt']=passwordSalt;}
if(typeof passwordCpu!=='undefined'){payload['passwordCpu']=passwordCpu;}
if(typeof passwordMemory!=='undefined'){payload['passwordMemory']=passwordMemory;}
if(typeof passwordParallel!=='undefined'){payload['passwordParallel']=passwordParallel;}
if(typeof passwordLength!=='undefined'){payload['passwordLength']=passwordLength;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createScryptModifiedUser(userId,email,password,passwordSalt,passwordSaltSeparator,passwordSignerKey,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
if(typeof passwordSalt==='undefined'){throw new AppwriteException('Missing required parameter: "passwordSalt"');}
if(typeof passwordSaltSeparator==='undefined'){throw new AppwriteException('Missing required parameter: "passwordSaltSeparator"');}
if(typeof passwordSignerKey==='undefined'){throw new AppwriteException('Missing required parameter: "passwordSignerKey"');}
let path='/users/scrypt-modified';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof passwordSalt!=='undefined'){payload['passwordSalt']=passwordSalt;}
if(typeof passwordSaltSeparator!=='undefined'){payload['passwordSaltSeparator']=passwordSaltSeparator;}
if(typeof passwordSignerKey!=='undefined'){payload['passwordSignerKey']=passwordSignerKey;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
createSHAUser(userId,email,password,passwordVersion,name){return __awaiter(this,void 0,void 0,function*(){if(typeof userId==='undefined'){throw new AppwriteException('Missing required parameter: "userId"');}
if(typeof email==='undefined'){throw new AppwriteException('Missing required parameter: "email"');}
if(typeof password==='undefined'){throw new AppwriteException('Missing required parameter: "password"');}
let path='/users/sha';let payload={};if(typeof userId!=='undefined'){payload['userId']=userId;}
if(typeof email!=='undefined'){payload['email']=email;}
if(typeof password!=='undefined'){payload['password']=password;}
if(typeof passwordVersion!=='undefined'){payload['passwordVersion']=passwordVersion;}
if(typeof name!=='undefined'){payload['name']=name;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
getUsage(range,provider){return __awaiter(this,void 0,void 0,function*(){let path='/users/usage';let payload={};if(typeof range!=='undefined'){payload['range']=range;}
if(typeof provider!=='undefined'){payload['provider']=provider;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}

View file

@ -695,7 +695,7 @@
* stored as is, and replaces any previous value. The maximum allowed prefs
* size is 64kB and throws error if exceeded.
*
* @param {Partial<Preferences>} prefs
* @param {object} prefs
* @throws {AppwriteException}
* @returns {Promise}
*/
@ -1459,7 +1459,8 @@
*
* 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.
* 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
@ -1860,7 +1861,7 @@
*
* 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
* integration](/docs/server/databases#databasesCreateCollection) API or
* directly from your database console.
*
* @param {string} databaseId
@ -2588,7 +2589,7 @@
*
* 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
* integration](/docs/server/databases#databasesCreateCollection) API or
* directly from your database console.
*
* @param {string} databaseId
@ -5289,8 +5290,8 @@
*
* 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.
* 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)
@ -6186,6 +6187,388 @@
}, payload);
});
}
/**
* Create User with Argon2 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.
*
* @param {string} userId
* @param {string} email
* @param {string} password
* @param {string} name
* @throws {AppwriteException}
* @returns {Promise}
*/
createArgon2User(userId, email, password, name) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof userId === 'undefined') {
throw new AppwriteException('Missing required parameter: "userId"');
}
if (typeof email === 'undefined') {
throw new AppwriteException('Missing required parameter: "email"');
}
if (typeof password === 'undefined') {
throw new AppwriteException('Missing required parameter: "password"');
}
let path = '/users/argon2';
let payload = {};
if (typeof userId !== 'undefined') {
payload['userId'] = userId;
}
if (typeof email !== 'undefined') {
payload['email'] = email;
}
if (typeof password !== 'undefined') {
payload['password'] = password;
}
if (typeof name !== 'undefined') {
payload['name'] = name;
}
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('post', uri, {
'content-type': 'application/json',
}, payload);
});
}
/**
* Create User with Bcrypt 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.
*
* @param {string} userId
* @param {string} email
* @param {string} password
* @param {string} name
* @throws {AppwriteException}
* @returns {Promise}
*/
createBcryptUser(userId, email, password, name) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof userId === 'undefined') {
throw new AppwriteException('Missing required parameter: "userId"');
}
if (typeof email === 'undefined') {
throw new AppwriteException('Missing required parameter: "email"');
}
if (typeof password === 'undefined') {
throw new AppwriteException('Missing required parameter: "password"');
}
let path = '/users/bcrypt';
let payload = {};
if (typeof userId !== 'undefined') {
payload['userId'] = userId;
}
if (typeof email !== 'undefined') {
payload['email'] = email;
}
if (typeof password !== 'undefined') {
payload['password'] = password;
}
if (typeof name !== 'undefined') {
payload['name'] = name;
}
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('post', uri, {
'content-type': 'application/json',
}, payload);
});
}
/**
* Create User with MD5 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.
*
* @param {string} userId
* @param {string} email
* @param {string} password
* @param {string} name
* @throws {AppwriteException}
* @returns {Promise}
*/
createMD5User(userId, email, password, name) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof userId === 'undefined') {
throw new AppwriteException('Missing required parameter: "userId"');
}
if (typeof email === 'undefined') {
throw new AppwriteException('Missing required parameter: "email"');
}
if (typeof password === 'undefined') {
throw new AppwriteException('Missing required parameter: "password"');
}
let path = '/users/md5';
let payload = {};
if (typeof userId !== 'undefined') {
payload['userId'] = userId;
}
if (typeof email !== 'undefined') {
payload['email'] = email;
}
if (typeof password !== 'undefined') {
payload['password'] = password;
}
if (typeof name !== 'undefined') {
payload['name'] = name;
}
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('post', uri, {
'content-type': 'application/json',
}, payload);
});
}
/**
* Create User with PHPass 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.
*
* @param {string} userId
* @param {string} email
* @param {string} password
* @param {string} name
* @throws {AppwriteException}
* @returns {Promise}
*/
createPHPassUser(userId, email, password, name) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof userId === 'undefined') {
throw new AppwriteException('Missing required parameter: "userId"');
}
if (typeof email === 'undefined') {
throw new AppwriteException('Missing required parameter: "email"');
}
if (typeof password === 'undefined') {
throw new AppwriteException('Missing required parameter: "password"');
}
let path = '/users/phpass';
let payload = {};
if (typeof userId !== 'undefined') {
payload['userId'] = userId;
}
if (typeof email !== 'undefined') {
payload['email'] = email;
}
if (typeof password !== 'undefined') {
payload['password'] = password;
}
if (typeof name !== 'undefined') {
payload['name'] = name;
}
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('post', uri, {
'content-type': 'application/json',
}, payload);
});
}
/**
* Create User with Scrypt 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.
*
* @param {string} userId
* @param {string} email
* @param {string} password
* @param {string} passwordSalt
* @param {number} passwordCpu
* @param {number} passwordMemory
* @param {number} passwordParallel
* @param {number} passwordLength
* @param {string} name
* @throws {AppwriteException}
* @returns {Promise}
*/
createScryptUser(userId, email, password, passwordSalt, passwordCpu, passwordMemory, passwordParallel, passwordLength, name) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof userId === 'undefined') {
throw new AppwriteException('Missing required parameter: "userId"');
}
if (typeof email === 'undefined') {
throw new AppwriteException('Missing required parameter: "email"');
}
if (typeof password === 'undefined') {
throw new AppwriteException('Missing required parameter: "password"');
}
if (typeof passwordSalt === 'undefined') {
throw new AppwriteException('Missing required parameter: "passwordSalt"');
}
if (typeof passwordCpu === 'undefined') {
throw new AppwriteException('Missing required parameter: "passwordCpu"');
}
if (typeof passwordMemory === 'undefined') {
throw new AppwriteException('Missing required parameter: "passwordMemory"');
}
if (typeof passwordParallel === 'undefined') {
throw new AppwriteException('Missing required parameter: "passwordParallel"');
}
if (typeof passwordLength === 'undefined') {
throw new AppwriteException('Missing required parameter: "passwordLength"');
}
let path = '/users/scrypt';
let payload = {};
if (typeof userId !== 'undefined') {
payload['userId'] = userId;
}
if (typeof email !== 'undefined') {
payload['email'] = email;
}
if (typeof password !== 'undefined') {
payload['password'] = password;
}
if (typeof passwordSalt !== 'undefined') {
payload['passwordSalt'] = passwordSalt;
}
if (typeof passwordCpu !== 'undefined') {
payload['passwordCpu'] = passwordCpu;
}
if (typeof passwordMemory !== 'undefined') {
payload['passwordMemory'] = passwordMemory;
}
if (typeof passwordParallel !== 'undefined') {
payload['passwordParallel'] = passwordParallel;
}
if (typeof passwordLength !== 'undefined') {
payload['passwordLength'] = passwordLength;
}
if (typeof name !== 'undefined') {
payload['name'] = name;
}
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('post', uri, {
'content-type': 'application/json',
}, payload);
});
}
/**
* Create User with Scrypt Modified 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.
*
* @param {string} userId
* @param {string} email
* @param {string} password
* @param {string} passwordSalt
* @param {string} passwordSaltSeparator
* @param {string} passwordSignerKey
* @param {string} name
* @throws {AppwriteException}
* @returns {Promise}
*/
createScryptModifiedUser(userId, email, password, passwordSalt, passwordSaltSeparator, passwordSignerKey, name) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof userId === 'undefined') {
throw new AppwriteException('Missing required parameter: "userId"');
}
if (typeof email === 'undefined') {
throw new AppwriteException('Missing required parameter: "email"');
}
if (typeof password === 'undefined') {
throw new AppwriteException('Missing required parameter: "password"');
}
if (typeof passwordSalt === 'undefined') {
throw new AppwriteException('Missing required parameter: "passwordSalt"');
}
if (typeof passwordSaltSeparator === 'undefined') {
throw new AppwriteException('Missing required parameter: "passwordSaltSeparator"');
}
if (typeof passwordSignerKey === 'undefined') {
throw new AppwriteException('Missing required parameter: "passwordSignerKey"');
}
let path = '/users/scrypt-modified';
let payload = {};
if (typeof userId !== 'undefined') {
payload['userId'] = userId;
}
if (typeof email !== 'undefined') {
payload['email'] = email;
}
if (typeof password !== 'undefined') {
payload['password'] = password;
}
if (typeof passwordSalt !== 'undefined') {
payload['passwordSalt'] = passwordSalt;
}
if (typeof passwordSaltSeparator !== 'undefined') {
payload['passwordSaltSeparator'] = passwordSaltSeparator;
}
if (typeof passwordSignerKey !== 'undefined') {
payload['passwordSignerKey'] = passwordSignerKey;
}
if (typeof name !== 'undefined') {
payload['name'] = name;
}
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('post', uri, {
'content-type': 'application/json',
}, payload);
});
}
/**
* Create User with SHA 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.
*
* @param {string} userId
* @param {string} email
* @param {string} password
* @param {string} passwordVersion
* @param {string} name
* @throws {AppwriteException}
* @returns {Promise}
*/
createSHAUser(userId, email, password, passwordVersion, name) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof userId === 'undefined') {
throw new AppwriteException('Missing required parameter: "userId"');
}
if (typeof email === 'undefined') {
throw new AppwriteException('Missing required parameter: "email"');
}
if (typeof password === 'undefined') {
throw new AppwriteException('Missing required parameter: "password"');
}
let path = '/users/sha';
let payload = {};
if (typeof userId !== 'undefined') {
payload['userId'] = userId;
}
if (typeof email !== 'undefined') {
payload['email'] = email;
}
if (typeof password !== 'undefined') {
payload['password'] = password;
}
if (typeof passwordVersion !== 'undefined') {
payload['passwordVersion'] = passwordVersion;
}
if (typeof name !== 'undefined') {
payload['name'] = name;
}
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('post', uri, {
'content-type': 'application/json',
}, payload);
});
}
/**
* Get usage stats for the users API
*

View file

@ -2,11 +2,32 @@
namespace Appwrite\Auth;
use Appwrite\Auth\Hash\Argon2;
use Appwrite\Auth\Hash\Bcrypt;
use Appwrite\Auth\Hash\Md5;
use Appwrite\Auth\Hash\Phpass;
use Appwrite\Auth\Hash\Scrypt;
use Appwrite\Auth\Hash\Scryptmodified;
use Appwrite\Auth\Hash\Sha;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
class Auth
{
public const SUPPORTED_ALGOS = [
'argon2',
'bcrypt',
'md5',
'sha',
'phpass',
'scrypt',
'scryptMod',
'plaintext'
];
public const DEFAULT_ALGO = 'argon2';
public const DEFAULT_ALGO_OPTIONS = ['memoryCost' => 2048, 'timeCost' => 4, 'threads' => 3];
/**
* User Roles.
*/
@ -133,26 +154,98 @@ class Auth
*
* One way string hashing for user passwords
*
* @param $string
* @param string $string
* @param string $algo hashing algorithm to use
* @param array $options algo-specific options
*
* @return bool|string|null
*/
public static function passwordHash($string)
public static function passwordHash(string $string, string $algo, array $options = [])
{
return \password_hash($string, PASSWORD_BCRYPT, array('cost' => 8));
// Plain text not supported, just an alias. Switch to recommended algo
if ($algo === 'plaintext') {
$algo = Auth::DEFAULT_ALGO;
$options = Auth::DEFAULT_ALGO_OPTIONS;
}
if (!\in_array($algo, Auth::SUPPORTED_ALGOS)) {
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
}
switch ($algo) {
case 'argon2':
$hasher = new Argon2($options);
return $hasher->hash($string);
case 'bcrypt':
$hasher = new Bcrypt($options);
return $hasher->hash($string);
case 'md5':
$hasher = new Md5($options);
return $hasher->hash($string);
case 'sha':
$hasher = new Sha($options);
return $hasher->hash($string);
case 'phpass':
$hasher = new Phpass($options);
return $hasher->hash($string);
case 'scrypt':
$hasher = new Scrypt($options);
return $hasher->hash($string);
case 'scryptMod':
$hasher = new Scryptmodified($options);
return $hasher->hash($string);
default:
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
}
}
/**
* Password verify.
*
* @param $plain
* @param $hash
* @param string $plain
* @param string $hash
* @param string $algo hashing algorithm used to hash
* @param array $options algo-specific options
*
* @return bool
*/
public static function passwordVerify($plain, $hash)
public static function passwordVerify(string $plain, string $hash, string $algo, array $options = [])
{
return \password_verify($plain, $hash);
// Plain text not supported, just an alias. Switch to recommended algo
if ($algo === 'plaintext') {
$algo = Auth::DEFAULT_ALGO;
$options = Auth::DEFAULT_ALGO_OPTIONS;
}
if (!\in_array($algo, Auth::SUPPORTED_ALGOS)) {
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
}
switch ($algo) {
case 'argon2':
$hasher = new Argon2($options);
return $hasher->verify($plain, $hash);
case 'bcrypt':
$hasher = new Bcrypt($options);
return $hasher->verify($plain, $hash);
case 'md5':
$hasher = new Md5($options);
return $hasher->verify($plain, $hash);
case 'sha':
$hasher = new Sha($options);
return $hasher->verify($plain, $hash);
case 'phpass':
$hasher = new Phpass($options);
return $hasher->verify($plain, $hash);
case 'scrypt':
$hasher = new Scrypt($options);
return $hasher->verify($plain, $hash);
case 'scryptMod':
$hasher = new Scryptmodified($options);
return $hasher->verify($plain, $hash);
default:
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
}
}
/**
@ -163,8 +256,6 @@ class Auth
* @param int $length
*
* @return string
*
* @throws \Exception
*/
public static function passwordGenerator(int $length = 20): string
{
@ -179,14 +270,32 @@ class Auth
* @param int $length
*
* @return string
*
* @throws \Exception
*/
public static function tokenGenerator(int $length = 128): string
{
return \bin2hex(\random_bytes($length));
}
/**
* Code Generator.
*
* Generate random code string
*
* @param int $length
*
* @return string
*/
public static function codeGenerator(int $length = 6): string
{
$value = '';
for ($i = 0; $i < $length; $i++) {
$value .= random_int(0, 9);
}
return $value;
}
/**
* Verify token and check that its not expired.
*

View file

@ -0,0 +1,62 @@
<?php
namespace Appwrite\Auth;
abstract class Hash
{
/**
* @var array $options Hashing-algo specific options
*/
protected array $options = [];
/**
* @param array $options Hashing-algo specific options
*/
public function __construct(array $options = [])
{
$this->setOptions($options);
}
/**
* Set hashing algo options
*
* @param array $options Hashing-algo specific options
*/
public function setOptions(array $options): self
{
$this->options = \array_merge([], $this->getDefaultOptions(), $options);
return $this;
}
/**
* Get hashing algo options
*
* @return array $options Hashing-algo specific options
*/
public function getOptions(): array
{
return $this->options;
}
/**
* @param string $password Input password to hash
*
* @return string hash
*/
abstract public function hash(string $password): string;
/**
* @param string $password Input password to validate
* @param string $hash Hash to verify password against
*
* @return boolean true if password matches hash
*/
abstract public function verify(string $password, string $hash): bool;
/**
* Get default options for specific hashing algo
*
* @return array options named array
*/
abstract public function getDefaultOptions(): array;
}

View file

@ -0,0 +1,47 @@
<?php
namespace Appwrite\Auth\Hash;
use Appwrite\Auth\Hash;
/*
* Argon2 accepted options:
* int threads
* int time_cost
* int memory_cost
*
* Reference: https://www.php.net/manual/en/function.password-hash.php#example-983
*/
class Argon2 extends Hash
{
/**
* @param string $password Input password to hash
*
* @return string hash
*/
public function hash(string $password): string
{
return \password_hash($password, PASSWORD_ARGON2ID, $this->getOptions());
}
/**
* @param string $password Input password to validate
* @param string $hash Hash to verify password against
*
* @return boolean true if password matches hash
*/
public function verify(string $password, string $hash): bool
{
return \password_verify($password, $hash);
}
/**
* Get default options for specific hashing algo
*
* @return array options named array
*/
public function getDefaultOptions(): array
{
return ['memory_cost' => 65536, 'time_cost' => 4, 'threads' => 3];
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Appwrite\Auth\Hash;
use Appwrite\Auth\Hash;
/*
* Bcrypt accepted options:
* int cost
* string? salt; auto-generated if empty
*
* Reference: https://www.php.net/manual/en/password.constants.php
*/
class Bcrypt extends Hash
{
/**
* @param string $password Input password to hash
*
* @return string hash
*/
public function hash(string $password): string
{
return \password_hash($password, PASSWORD_BCRYPT, $this->getOptions());
}
/**
* @param string $password Input password to validate
* @param string $hash Hash to verify password against
*
* @return boolean true if password matches hash
*/
public function verify(string $password, string $hash): bool
{
return \password_verify($password, $hash);
}
/**
* Get default options for specific hashing algo
*
* @return array options named array
*/
public function getDefaultOptions(): array
{
return [ 'cost' => 8 ];
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Appwrite\Auth\Hash;
use Appwrite\Auth\Hash;
/*
* MD5 does not accept any options.
*
* Reference: https://www.php.net/manual/en/function.md5.php
*/
class Md5 extends Hash
{
/**
* @param string $password Input password to hash
*
* @return string hash
*/
public function hash(string $password): string
{
return \md5($password);
}
/**
* @param string $password Input password to validate
* @param string $hash Hash to verify password against
*
* @return boolean true if password matches hash
*/
public function verify(string $password, string $hash): bool
{
return $this->hash($password) === $hash;
}
/**
* Get default options for specific hashing algo
*
* @return array options named array
*/
public function getDefaultOptions(): array
{
return [];
}
}

View file

@ -0,0 +1,290 @@
<?php
/**
* Portable PHP password hashing framework.
* source Version 0.5 / genuine.
* Written by Solar Designer <solar at openwall.com> in 2004-2017 and placed in
* the public domain. Revised in subsequent years, still public domain.
* There's absolutely no warranty.
* The homepage URL for the source framework is: http://www.openwall.com/phpass/
* Please be sure to update the Version line if you edit this file in any way.
* It is suggested that you leave the main version number intact, but indicate
* your project name (after the slash) and add your own revision information.
* Please do not change the "private" password hashing method implemented in
* here, thereby making your hashes incompatible. However, if you must, please
* change the hash type identifier (the "$P$") to something different.
* Obviously, since this code is in the public domain, the above are not
* requirements (there can be none), but merely suggestions.
*
* @author Solar Designer <solar@openwall.com>
* @copyright Copyright (C) 2017 All rights reserved.
* @license http://www.opensource.org/licenses/mit-license.html MIT License; see LICENSE.txt
*/
namespace Appwrite\Auth\Hash;
use Appwrite\Auth\Hash;
/*
* PHPass accepted options:
* int iteration_count_log2; The Logarithmic cost value used when generating hash values indicating the number of rounds used to generate hashes
* string portable_hashes
* string random_state; The cached random state
*
* Reference: https://github.com/photodude/phpass
*/
class Phpass extends Hash
{
/**
* Alphabet used in itoa64 conversions.
*
* @var string
* @since 0.1.0
*/
protected string $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
/**
* Get default options for specific hashing algo
*
* @return array options named array
*/
public function getDefaultOptions(): array
{
$randomState = \microtime();
if (\function_exists('getmypid')) {
$randomState .= getmypid();
}
return ['iteration_count_log2' => 8, 'portable_hashes' => false, 'random_state' => $randomState];
}
/**
* @param string $password Input password to hash
*
* @return string hash
*/
public function hash(string $password): string
{
$options = $this->getDefaultOptions();
$random = '';
if (CRYPT_BLOWFISH === 1 && !$options['portable_hashes']) {
$random = $this->getRandomBytes(16, $options);
$hash = crypt($password, $this->gensaltBlowfish($random, $options));
if (strlen($hash) === 60) {
return $hash;
}
}
if (strlen($random) < 6) {
$random = $this->getRandomBytes(6, $options);
}
$hash = $this->cryptPrivate($password, $this->gensaltPrivate($random, $options));
if (strlen($hash) === 34) {
return $hash;
}
/**
* Returning '*' on error is safe here, but would _not_ be safe
* in a crypt(3)-like function used _both_ for generating new
* hashes and for validating passwords against existing hashes.
*/
return '*';
}
/**
* @param string $password Input password to validate
* @param string $hash Hash to verify password against
*
* @return boolean true if password matches hash
*/
public function verify(string $password, string $hash): bool
{
$verificationHash = $this->cryptPrivate($password, $hash);
if ($verificationHash[0] === '*') {
$verificationHash = crypt($password, $hash);
}
/**
* This is not constant-time. In order to keep the code simple,
* for timing safety we currently rely on the salts being
* unpredictable, which they are at least in the non-fallback
* cases (that is, when we use /dev/urandom and bcrypt).
*/
return $hash === $verificationHash;
}
/**
* @param int $count
*
* @return String $output
* @since 0.1.0
* @throws Exception Thows an Exception if the $count parameter is not a positive integer.
*/
protected function getRandomBytes(int $count, array $options): string
{
if (!is_int($count) || $count < 1) {
throw new \Exception('Argument count must be a positive integer');
}
$output = '';
if (@is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) {
$output = fread($fh, $count);
fclose($fh);
}
if (strlen($output) < $count) {
$output = '';
for ($i = 0; $i < $count; $i += 16) {
$options['iteration_count_log2'] = md5(microtime() . $options['iteration_count_log2']);
$output .= md5($options['iteration_count_log2'], true);
}
$output = substr($output, 0, $count);
}
return $output;
}
/**
* @param String $input
* @param int $count
*
* @return String $output
* @since 0.1.0
* @throws Exception Thows an Exception if the $count parameter is not a positive integer.
*/
protected function encode64($input, $count)
{
if (!is_int($count) || $count < 1) {
throw new \Exception('Argument count must be a positive integer');
}
$output = '';
$i = 0;
do {
$value = ord($input[$i++]);
$output .= $this->itoa64[$value & 0x3f];
if ($i < $count) {
$value |= ord($input[$i]) << 8;
}
$output .= $this->itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count) {
break;
}
if ($i < $count) {
$value |= ord($input[$i]) << 16;
}
$output .= $this->itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count) {
break;
}
$output .= $this->itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
/**
* @param String $input
*
* @return String $output
* @since 0.1.0
*/
private function gensaltPrivate($input, $options)
{
$output = '$P$';
$output .= $this->itoa64[min($options['iteration_count_log2'] + ((PHP_VERSION >= '5') ? 5 : 3), 30)];
$output .= $this->encode64($input, 6);
return $output;
}
/**
* @param String $password
* @param String $setting
*
* @return String $output
* @since 0.1.0
*/
private function cryptPrivate($password, $setting)
{
$output = '*0';
if (substr($setting, 0, 2) === $output) {
$output = '*1';
}
$id = substr($setting, 0, 3);
// We use "$P$", phpBB3 uses "$H$" for the same thing
if ($id !== '$P$' && $id !== '$H$') {
return $output;
}
$count_log2 = strpos($this->itoa64, $setting[3]);
if ($count_log2 < 7 || $count_log2 > 30) {
return $output;
}
$count = 1 << $count_log2;
$salt = substr($setting, 4, 8);
if (strlen($salt) !== 8) {
return $output;
}
/**
* We were kind of forced to use MD5 here since it's the only
* cryptographic primitive that was available in all versions of PHP
* in use. To implement our own low-level crypto in PHP
* would have result in much worse performance and
* consequently in lower iteration counts and hashes that are
* quicker to crack (by non-PHP code).
*/
$hash = md5($salt . $password, true);
do {
$hash = md5($hash . $password, true);
} while (--$count);
$output = substr($setting, 0, 12);
$output .= $this->encode64($hash, 16);
return $output;
}
/**
* @param String $input
*
* @return String $output
* @since 0.1.0
*/
private function gensaltBlowfish($input, $options)
{
/**
* This one needs to use a different order of characters and a
* different encoding scheme from the one in encode64() above.
* We care because the last character in our encoded string will
* only represent 2 bits. While two known implementations of
* bcrypt will happily accept and correct a salt string which
* has the 4 unused bits set to non-zero, we do not want to take
* chances and we also do not want to waste an additional byte
* of entropy.
*/
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$output = '$2a$';
$output .= chr(ord('0') + $options['iteration_count_log2'] / 10);
$output .= chr(ord('0') + $options['iteration_count_log2'] % 10);
$output .= '$';
$i = 0;
do {
$c1 = ord($input[$i++]);
$output .= $itoa64[$c1 >> 2];
$c1 = ($c1 & 0x03) << 4;
if ($i >= 16) {
$output .= $itoa64[$c1];
break;
}
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 4;
$output .= $itoa64[$c1];
$c1 = ($c2 & 0x0f) << 2;
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 6;
$output .= $itoa64[$c1];
$output .= $itoa64[$c2 & 0x3f];
} while (1);
return $output;
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Appwrite\Auth\Hash;
use Appwrite\Auth\Hash;
/*
* Scrypt accepted options:
* string? salt; auto-generated if empty
* int costCpu
* int costMemory
* int costParallel
* int length
*
* Reference: https://github.com/DomBlack/php-scrypt/blob/master/scrypt.php#L112-L116
*/
class Scrypt extends Hash
{
/**
* @param string $password Input password to hash
*
* @return string hash
*/
public function hash(string $password): string
{
$options = $this->getOptions();
return \scrypt($password, $options['salt'], $options['costCpu'], $options['costMemory'], $options['costParallel'], $options['length']);
}
/**
* @param string $password Input password to validate
* @param string $hash Hash to verify password against
*
* @return boolean true if password matches hash
*/
public function verify(string $password, string $hash): bool
{
return $hash === $this->hash($password);
}
/**
* Get default options for specific hashing algo
*
* @return array options named array
*/
public function getDefaultOptions(): array
{
return [ 'costCpu' => 8, 'costMemory' => 14, 'costParallel' => 1, 'length' => 64 ];
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Appwrite\Auth\Hash;
use Appwrite\Auth\Hash;
/*
* This is Scrypt hash with some additional steps added by Google.
*
* string salt
* string saltSeparator
* strin signerKey
*
* Reference: https://github.com/DomBlack/php-scrypt/blob/master/scrypt.php#L112-L116
*/
class Scryptmodified extends Hash
{
/**
* @param string $password Input password to hash
*
* @return string hash
*/
public function hash(string $password): string
{
$options = $this->getOptions();
$derivedKeyBytes = $this->generateDerivedKey($password);
$signerKeyBytes = \base64_decode($options['signerKey']);
$hashedPassword = $this->hashKeys($signerKeyBytes, $derivedKeyBytes);
return \base64_encode($hashedPassword);
}
/**
* @param string $password Input password to validate
* @param string $hash Hash to verify password against
*
* @return boolean true if password matches hash
*/
public function verify(string $password, string $hash): bool
{
return $this->hash($password) === $hash;
}
/**
* Get default options for specific hashing algo
*
* @return array options named array
*/
public function getDefaultOptions(): array
{
return [ ];
}
private function generateDerivedKey(string $password)
{
$options = $this->getOptions();
$saltBytes = \base64_decode($options['salt']);
$saltSeparatorBytes = \base64_decode($options['saltSeparator']);
$derivedKey = \scrypt(\utf8_encode($password), $saltBytes . $saltSeparatorBytes, 16384, 8, 1, 64);
$derivedKey = \hex2bin($derivedKey);
return $derivedKey;
}
private function hashKeys($signerKeyBytes, $derivedKeyBytes): string
{
$key = \substr($derivedKeyBytes, 0, 32);
$iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
$hash = \openssl_encrypt($signerKeyBytes, 'aes-256-ctr', $key, OPENSSL_RAW_DATA, $iv);
return $hash;
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Appwrite\Auth\Hash;
use Appwrite\Auth\Hash;
/*
* SHA accepted options:
* string? version. Allowed:
* - Version 1: sha1
* - Version 2: sha224, sha256, sha384, sha512/224, sha512/256, sha512
* - Version 3: sha3-224, sha3-256, sha3-384, sha3-512
*
* Reference: https://www.php.net/manual/en/function.hash-algos.php
*/
class Sha extends Hash
{
/**
* @param string $password Input password to hash
*
* @return string hash
*/
public function hash(string $password): string
{
$algo = $this->getOptions()['version'];
return \hash($algo, $password);
}
/**
* @param string $password Input password to validate
* @param string $hash Hash to verify password against
*
* @return boolean true if password matches hash
*/
public function verify(string $password, string $hash): bool
{
return $this->hash($password) === $hash;
}
/**
* Get default options for specific hashing algo
*
* @return array options named array
*/
public function getDefaultOptions(): array
{
return [ 'version' => 'sha3-512' ];
}
}

View file

@ -1,33 +0,0 @@
<?php
namespace Appwrite\Auth\Phone;
use Appwrite\Auth\Phone;
class Mock extends Phone
{
/**
* @var string
*/
public static string $defaultDigits = '123456';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
return;
}
/**
* @param int $digits
* @return string
*/
public function generateSecretDigits(int $digits = 6): string
{
return self::$defaultDigits;
}
}

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

@ -1,10 +1,8 @@
<?php
namespace Appwrite\Auth;
namespace Appwrite\SMS;
use Appwrite\Extend\Exception;
abstract class Phone
abstract class Adapter
{
/**
* @var string
@ -69,20 +67,9 @@ abstract class Phone
\curl_close($ch);
if ($code >= 400) {
throw new Exception($response);
throw new \Exception($response);
}
return $response;
}
/**
* Generate 6 random digits for phone verification.
*
* @param int $digits
* @return string
*/
public function generateSecretDigits(int $digits = 6): string
{
return substr(str_shuffle("0123456789"), 0, $digits);
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Appwrite\SMS\Adapter;
use Appwrite\SMS\Adapter;
class Mock extends Adapter
{
/**
* @var string
*/
public static string $digits = '123456';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
return;
}
}

View file

@ -1,13 +1,13 @@
<?php
namespace Appwrite\Auth\Phone;
namespace Appwrite\SMS\Adapter;
use Appwrite\Auth\Phone;
use Appwrite\SMS\Adapter;
// Reference Material
// https://docs.msg91.com/p/tf9GTextN/e/Irz7-x1PK/MSG91
class Msg91 extends Phone
class Msg91 extends Adapter
{
/**
* @var string
@ -16,10 +16,10 @@ class Msg91 extends Phone
/**
* For Flow based sending SMS sender ID should not be set in flow
* In environment _APP_PHONE_PROVIDER format is 'phone://[senderID]:[authKey]@msg91'.
* _APP_PHONE_FROM value is flow ID created in Msg91
* Eg. _APP_PHONE_PROVIDER = phone://DINESH:5e1e93cad6fc054d8e759a5b@msg91
* _APP_PHONE_FROM = 3968636f704b303135323339
* In environment _APP_SMS_PROVIDER format is 'sms://[senderID]:[authKey]@msg91'.
* _APP_SMS_FROM value is flow ID created in Msg91
* Eg. _APP_SMS_PROVIDER = sms://DINESH:5e1e93cad6fc054d8e759a5b@msg91
* _APP_SMS_FROM = 3968636f704b303135323339
* @param string $from-> utilized from for flow id
* @param string $to
* @param string $message

View file

@ -1,13 +1,13 @@
<?php
namespace Appwrite\Auth\Phone;
namespace Appwrite\SMS\Adapter;
use Appwrite\Auth\Phone;
use Appwrite\SMS\Adapter;
// Reference Material
// https://developer.telesign.com/enterprise/docs/sms-api-send-an-sms
class Telesign extends Phone
class Telesign extends Adapter
{
/**
* @var string

View file

@ -1,13 +1,13 @@
<?php
namespace Appwrite\Auth\Phone;
namespace Appwrite\SMS\Adapter;
use Appwrite\Auth\Phone;
use Appwrite\SMS\Adapter;
// Reference Material
// https://www.textmagic.com/docs/api/start/
class TextMagic extends Phone
class TextMagic extends Adapter
{
/**
* @var string

View file

@ -1,13 +1,13 @@
<?php
namespace Appwrite\Auth\Phone;
namespace Appwrite\SMS\Adapter;
use Appwrite\Auth\Phone;
use Appwrite\SMS\Adapter;
// Reference Material
// https://www.twilio.com/docs/sms/api
class Twilio extends Phone
class Twilio extends Adapter
{
/**
* @var string

View file

@ -1,13 +1,13 @@
<?php
namespace Appwrite\Auth\Phone;
namespace Appwrite\SMS\Adapter;
use Appwrite\Auth\Phone;
use Appwrite\SMS\Adapter;
// Reference Material
// https://developer.vonage.com/api/sms
class Vonage extends Phone
class Vonage extends Adapter
{
/**
* @var string

View file

@ -17,15 +17,32 @@ class OpenAPI3 extends Format
protected function getNestedModels(Model $model, array &$usedModels): void
{
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float', 'double'])
) {
$usedModels[] = $rule['type'];
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
return;
if (!in_array($model->getType(), $usedModels)) {
continue;
}
if (\is_array($rule['type'])) {
foreach ($rule['type'] as $ruleType) {
if (!in_array($ruleType, ['string', 'integer', 'boolean', 'json', 'float', 'double'])) {
$usedModels[] = $ruleType;
foreach ($this->models as $m) {
if ($m->getType() === $ruleType) {
$this->getNestedModels($m, $usedModels);
continue;
}
}
}
}
} else {
if (!in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float', 'double'])) {
$usedModels[] = $rule['type'];
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
continue;
}
}
}
}

View file

@ -17,15 +17,32 @@ class Swagger2 extends Format
protected function getNestedModels(Model $model, array &$usedModels): void
{
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float', 'double'])
) {
$usedModels[] = $rule['type'];
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
return;
if (!in_array($model->getType(), $usedModels)) {
continue;
}
if (\is_array($rule['type'])) {
foreach ($rule['type'] as $ruleType) {
if (!in_array($ruleType, ['string', 'integer', 'boolean', 'json', 'float'])) {
$usedModels[] = $ruleType;
foreach ($this->models as $m) {
if ($m->getType() === $ruleType) {
$this->getNestedModels($m, $usedModels);
continue;
}
}
}
}
} else {
if (!in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])) {
$usedModels[] = $rule['type'];
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
continue;
}
}
}
}

View file

@ -8,6 +8,14 @@ use Swoole\Http\Response as SwooleHTTPResponse;
use Utopia\Database\Document;
use Appwrite\Utopia\Response\Filter;
use Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response\Model\Account;
use Appwrite\Utopia\Response\Model\AlgoArgon2;
use Appwrite\Utopia\Response\Model\AlgoBcrypt;
use Appwrite\Utopia\Response\Model\AlgoMd5;
use Appwrite\Utopia\Response\Model\AlgoPhpass;
use Appwrite\Utopia\Response\Model\AlgoScrypt;
use Appwrite\Utopia\Response\Model\AlgoScryptModified;
use Appwrite\Utopia\Response\Model\AlgoSha;
use Appwrite\Utopia\Response\Model\None;
use Appwrite\Utopia\Response\Model\Any;
use Appwrite\Utopia\Response\Model\Attribute;
@ -118,6 +126,7 @@ class Response extends SwooleResponse
public const MODEL_ATTRIBUTE_URL = 'attributeUrl';
// Users
public const MODEL_ACCOUNT = 'account';
public const MODEL_USER = 'user';
public const MODEL_USER_LIST = 'userList';
public const MODEL_SESSION = 'session';
@ -126,6 +135,15 @@ class Response extends SwooleResponse
public const MODEL_JWT = 'jwt';
public const MODEL_PREFERENCES = 'preferences';
// Users password algos
public const MODEL_ALGO_MD5 = 'algoMd5';
public const MODEL_ALGO_SHA = 'algoSha';
public const MODEL_ALGO_SCRYPT = 'algoScrypt';
public const MODEL_ALGO_SCRYPT_MODIFIED = 'algoScryptModified';
public const MODEL_ALGO_BCRYPT = 'algoBcrypt';
public const MODEL_ALGO_ARGON2 = 'algoArgon2';
public const MODEL_ALGO_PHPASS = 'algoPhpass';
// Storage
public const MODEL_FILE = 'file';
public const MODEL_FILE_LIST = 'fileList';
@ -259,6 +277,14 @@ class Response extends SwooleResponse
->setModel(new ModelDocument())
->setModel(new Log())
->setModel(new User())
->setModel(new AlgoMd5())
->setModel(new AlgoSha())
->setModel(new AlgoPhpass())
->setModel(new AlgoBcrypt())
->setModel(new AlgoScrypt())
->setModel(new AlgoScryptModified())
->setModel(new AlgoArgon2())
->setModel(new Account())
->setModel(new Preferences())
->setModel(new Session())
->setModel(new Token())

View file

@ -91,6 +91,22 @@ abstract class Model
return $this;
}
/**
* Delete an existing Rule
* If rule exists, it will be removed
*
* @param string $key
* @param array $options
*/
protected function removeRule(string $key): self
{
if (isset($this->rules[$key])) {
unset($this->rules[$key]);
}
return $this;
}
public function getRequired()
{
$list = [];

View file

@ -0,0 +1,38 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class Account extends User
{
public function __construct()
{
parent::__construct();
$this
->removeRule('password')
->removeRule('hash')
->removeRule('hashOptions');
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Account';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ACCOUNT;
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class AlgoArgon2 extends Model
{
public function __construct()
{
// No options if imported. If hashed by Appwrite, following configuration is available:
$this
->addRule('memoryCost', [
'type' => self::TYPE_INTEGER,
'description' => 'Memory used to compute hash.',
'default' => '',
'example' => 65536,
])
->addRule('timeCost', [
'type' => self::TYPE_INTEGER,
'description' => 'Amount of time consumed to compute hash',
'default' => '',
'example' => 4,
])
->addRule('threads', [
'type' => self::TYPE_INTEGER,
'description' => 'Number of threads used to compute hash.',
'default' => '',
'example' => 3,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AlgoArgon2';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ALGO_ARGON2;
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class AlgoBcrypt extends Model
{
public function __construct()
{
// No options, because this can only be imported, and verifying doesnt require any configuration
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AlgoBcrypt';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ALGO_BCRYPT;
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class AlgoMd5 extends Model
{
public function __construct()
{
// No options, because this can only be imported, and verifying doesnt require any configuration
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AlgoMD5';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ALGO_MD5;
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class AlgoPhpass extends Model
{
public function __construct()
{
// No options, because this can only be imported, and verifying doesnt require any configuration
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AlgoPHPass';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ALGO_PHPASS;
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class AlgoScrypt extends Model
{
public function __construct()
{
$this
->addRule('costCpu', [
'type' => self::TYPE_INTEGER,
'description' => 'CPU complexity of computed hash.',
'default' => '',
'example' => 8,
])
->addRule('costMemory', [
'type' => self::TYPE_INTEGER,
'description' => 'Memory complexity of computed hash.',
'default' => '',
'example' => 14,
])
->addRule('costParallel', [
'type' => self::TYPE_INTEGER,
'description' => 'Parallelization of computed hash.',
'default' => '',
'example' => 1,
])
->addRule('length', [
'type' => self::TYPE_INTEGER,
'description' => 'Length used to compute hash.',
'default' => '',
'example' => 1,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AlgoScrypt';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ALGO_SCRYPT;
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class AlgoScryptModified extends Model
{
public function __construct()
{
$this
->addRule('salt', [
'type' => self::TYPE_STRING,
'description' => 'Salt used to compute hash.',
'default' => '',
'example' => 'UxLMreBr6tYyjQ==',
])
->addRule('saltSeparator', [
'type' => self::TYPE_STRING,
'description' => 'Separator used to compute hash.',
'default' => '',
'example' => 'Bw==',
])
->addRule('signerKey', [
'type' => self::TYPE_STRING,
'description' => 'Key used to compute hash.',
'default' => '',
'example' => 'XyEKE9RcTDeLEsL/RjwPDBv/RqDl8fb3gpYEOQaPihbxf1ZAtSOHCjuAAa7Q3oHpCYhXSN9tizHgVOwn6krflQ==',
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AlgoScryptModified';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ALGO_SCRYPT_MODIFIED;
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class AlgoSha extends Model
{
public function __construct()
{
// No options, because this can only be imported, and verifying doesnt require any configuration
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AlgoSHA';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ALGO_SHA;
}
}

View file

@ -29,8 +29,7 @@ class AttributeEmail extends Attribute
'description' => 'String format.',
'default' => 'email',
'example' => 'email',
'array' => false,
'require' => true,
'required' => true,
])
->addRule('default', [
'type' => self::TYPE_STRING,

View file

@ -36,8 +36,7 @@ class AttributeEnum extends Attribute
'description' => 'String format.',
'default' => 'enum',
'example' => 'enum',
'array' => false,
'require' => true,
'required' => true,
])
->addRule('default', [
'type' => self::TYPE_STRING,

View file

@ -29,8 +29,7 @@ class AttributeIP extends Attribute
'description' => 'String format.',
'default' => 'ip',
'example' => 'ip',
'array' => false,
'require' => true,
'required' => true,
])
->addRule('default', [
'type' => self::TYPE_STRING,

View file

@ -29,7 +29,6 @@ class AttributeURL extends Attribute
'description' => 'String format.',
'default' => 'url',
'example' => 'url',
'array' => false,
'required' => true,
])
->addRule('default', [

View file

@ -65,9 +65,15 @@ class Execution extends Model
'default' => '',
'example' => '',
])
->addRule('stdout', [
'type' => self::TYPE_STRING,
'description' => 'The script stdout output string. Logs the last 4,000 characters of the execution stdout output. This will return an empty string unless the response is returned using an API key or as part of a webhook payload.',
'default' => '',
'example' => '',
])
->addRule('stderr', [
'type' => self::TYPE_STRING,
'description' => 'The script stderr output string. Logs the last 4,000 characters of the execution stderr output',
'description' => 'The script stderr output string. Logs the last 4,000 characters of the execution stderr output. This will return an empty string unless the response is returned using an API key or as part of a webhook payload.',
'default' => '',
'example' => '',
])

View file

@ -35,6 +35,33 @@ class User extends Model
'default' => '',
'example' => 'John Doe',
])
->addRule('password', [
'type' => self::TYPE_STRING,
'description' => 'Hashed user password.',
'default' => '',
'example' => '$argon2id$v=19$m=2048,t=4,p=3$aUZjLnliVWRINmFNTWMudg$5S+x+7uA31xFnrHFT47yFwcJeaP0w92L/4LdgrVRXxE',
])
->addRule('hash', [
'type' => self::TYPE_STRING,
'description' => 'Password hashing algorithm.',
'default' => '',
'example' => 'argon2',
])
->addRule('hashOptions', [
'type' => [
Response::MODEL_ALGO_ARGON2,
Response::MODEL_ALGO_SCRYPT,
Response::MODEL_ALGO_SCRYPT_MODIFIED,
Response::MODEL_ALGO_BCRYPT,
Response::MODEL_ALGO_PHPASS,
Response::MODEL_ALGO_SHA,
Response::MODEL_ALGO_MD5, // keep least secure at the bottom. this order will be used in docs
],
'description' => 'Password hashing algorithm configuration.',
'default' => [],
'example' => new \stdClass(),
'array' => false,
])
->addRule('registration', [
'type' => self::TYPE_INTEGER,
'description' => 'User registration date in Unix timestamp.',

View file

@ -2,7 +2,7 @@
namespace Tests\E2E\Services\Account;
use Appwrite\Auth\Phone\Mock;
use Appwrite\SMS\Adapter\Mock;
use Tests\E2E\Client;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\ProjectCustom;
@ -690,7 +690,7 @@ class AccountCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'userId' => 'unique()',
'number' => $number,
'phone' => $number,
]);
$this->assertEquals(201, $response['headers']['status-code']);
@ -713,7 +713,7 @@ class AccountCustomClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
$data['token'] = Mock::$defaultDigits;
$data['token'] = Mock::$digits;
$data['id'] = $userId;
$data['number'] = $number;
@ -869,7 +869,7 @@ class AccountCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'number' => $newPhone,
'phone' => $newPhone,
'password' => 'new-password'
]);
@ -949,7 +949,7 @@ class AccountCustomClientTest extends Scope
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'userId' => $id,
'secret' => Mock::$defaultDigits,
'secret' => Mock::$digits,
]);
$this->assertEquals(200, $response['headers']['status-code']);
@ -964,7 +964,7 @@ class AccountCustomClientTest extends Scope
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'userId' => 'ewewe',
'secret' => Mock::$defaultDigits,
'secret' => Mock::$digits,
]);
$this->assertEquals(404, $response['headers']['status-code']);

View file

@ -397,6 +397,9 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']);
$this->assertNotEmpty($output['APPWRITE_FUNCTION_JWT']);
$this->assertEquals($projectId, $output['APPWRITE_FUNCTION_PROJECT_ID']);
// Client should never see logs and errors
$this->assertEmpty($execution['body']['stdout']);
$this->assertEmpty($execution['body']['stderr']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [

View file

@ -866,6 +866,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals('', $output['APPWRITE_FUNCTION_USER_ID']);
$this->assertEmpty($output['APPWRITE_FUNCTION_JWT']);
$this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']);
$this->assertStringContainsString('Amazing Function Log', $executions['body']['stdout']);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
@ -893,7 +894,7 @@ class FunctionsCustomServerTest extends Scope
public function testCreateCustomNodeExecution()
{
$name = 'node-17.0';
$name = 'node-18.0';
$folder = 'node';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
$this->packageCode($folder);
@ -964,7 +965,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
$this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
$this->assertEquals('Node.js', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('17.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('18.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT_DATA']);
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);

View file

@ -55,14 +55,313 @@ trait UsersBase
$this->assertEquals(true, $res['body']['status']);
$this->assertGreaterThan(0, $res['body']['registration']);
/**
* Test Create with hashed passwords
*/
$res = $this->client->call(Client::METHOD_POST, '/users/md5', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'md5',
'email' => 'md5@appwrite.io',
'password' => '144fa7eaa4904e8ee120651997f70dcc', // appwrite
'name' => 'MD5 User',
]);
$res = $this->client->call(Client::METHOD_POST, '/users/bcrypt', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'bcrypt',
'email' => 'bcrypt@appwrite.io',
'password' => '$2a$15$xX/myGbFU.ZSKHSi6EHdBOySTdYm8QxBLXmOPHrYMwV0mHRBBSBOq', // appwrite (15 rounds)
'name' => 'Bcrypt User',
]);
$this->assertEquals($res['headers']['status-code'], 201);
$this->assertEquals($res['body']['password'], '$2a$15$xX/myGbFU.ZSKHSi6EHdBOySTdYm8QxBLXmOPHrYMwV0mHRBBSBOq');
$this->assertEquals($res['body']['hash'], 'bcrypt');
$res = $this->client->call(Client::METHOD_POST, '/users/argon2', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'argon2',
'email' => 'argon2@appwrite.io',
'password' => '$argon2i$v=19$m=20,t=3,p=2$YXBwd3JpdGU$A/54i238ed09ZR4NwlACU5XnkjNBZU9QeOEuhjLiexI', // appwrite (salt appwrite, parallel 2, memory 20, iterations 3, length 32)
'name' => 'Argon2 User',
]);
$this->assertEquals($res['headers']['status-code'], 201);
$this->assertEquals($res['body']['password'], '$argon2i$v=19$m=20,t=3,p=2$YXBwd3JpdGU$A/54i238ed09ZR4NwlACU5XnkjNBZU9QeOEuhjLiexI');
$this->assertEquals($res['body']['hash'], 'argon2');
$res = $this->client->call(Client::METHOD_POST, '/users/sha', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'sha512',
'email' => 'sha512@appwrite.io',
'password' => '4243da0a694e8a2f727c8060fe0507c8fa01ca68146c76d2c190805b638c20c6bf6ba04e21f11ae138785d0bff63c416e6f87badbffad37f6dee50094cc38c70', // appwrite (sha512)
'name' => 'SHA512 User',
'passwordVersion' => 'sha512'
]);
$this->assertEquals($res['headers']['status-code'], 201);
$this->assertEquals($res['body']['password'], '4243da0a694e8a2f727c8060fe0507c8fa01ca68146c76d2c190805b638c20c6bf6ba04e21f11ae138785d0bff63c416e6f87badbffad37f6dee50094cc38c70');
$this->assertEquals($res['body']['hash'], 'sha');
$this->assertEquals($res['body']['hashOptions']['version'], 'sha512');
$res = $this->client->call(Client::METHOD_POST, '/users/scrypt', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'scrypt',
'email' => 'scrypt@appwrite.io',
'password' => '3fdef49701bc4cfaacd551fe017283513284b4731e6945c263246ef948d3cf63b5d269c31fd697246085111a428245e24a4ddc6b64c687bc60a8910dbafc1d5b', // appwrite (salt appwrite, cpu 16384, memory 13, parallel 2, length 64)
'name' => 'Scrypt User',
'passwordSalt' => 'appwrite',
'passwordCpu' => 16384,
'passwordMemory' => 13,
'passwordParallel' => 2,
'passwordLength' => 64
]);
$this->assertEquals($res['headers']['status-code'], 201);
$this->assertEquals($res['body']['password'], '3fdef49701bc4cfaacd551fe017283513284b4731e6945c263246ef948d3cf63b5d269c31fd697246085111a428245e24a4ddc6b64c687bc60a8910dbafc1d5b');
$this->assertEquals($res['body']['hash'], 'scrypt');
$this->assertEquals($res['body']['hashOptions']['salt'], 'appwrite');
$this->assertEquals($res['body']['hashOptions']['costCpu'], 16384);
$this->assertEquals($res['body']['hashOptions']['costMemory'], 13);
$this->assertEquals($res['body']['hashOptions']['costParallel'], 2);
$this->assertEquals($res['body']['hashOptions']['length'], 64);
$res = $this->client->call(Client::METHOD_POST, '/users/phpass', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'phpass',
'email' => 'phpass@appwrite.io',
'password' => '$P$Br387rwferoKN7uwHZqNMu98q3U8RO.',
'name' => 'PHPass User',
]);
$this->assertEquals($res['headers']['status-code'], 201);
$this->assertEquals($res['body']['password'], '$P$Br387rwferoKN7uwHZqNMu98q3U8RO.');
$res = $this->client->call(Client::METHOD_POST, '/users/scrypt-modified', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'scrypt-modified',
'email' => 'scrypt-modified@appwrite.io',
'password' => 'UlM7JiXRcQhzAGlaonpSqNSLIz475WMddOgLjej5De9vxTy48K6WtqlEzrRFeK4t0COfMhWCb8wuMHgxOFCHFQ==', // appwrite
'name' => 'Scrypt Modified User',
'passwordSalt' => 'UxLMreBr6tYyjQ==',
'passwordSaltSeparator' => 'Bw==',
'passwordSignerKey' => 'XyEKE9RcTDeLEsL/RjwPDBv/RqDl8fb3gpYEOQaPihbxf1ZAtSOHCjuAAa7Q3oHpCYhXSN9tizHgVOwn6krflQ==',
]);
$this->assertEquals($res['headers']['status-code'], 201);
$this->assertEquals($res['body']['password'], 'UlM7JiXRcQhzAGlaonpSqNSLIz475WMddOgLjej5De9vxTy48K6WtqlEzrRFeK4t0COfMhWCb8wuMHgxOFCHFQ==');
$this->assertEquals($res['body']['hash'], 'scryptMod');
$this->assertEquals($res['body']['hashOptions']['salt'], 'UxLMreBr6tYyjQ==');
$this->assertEquals($res['body']['hashOptions']['signerKey'], 'XyEKE9RcTDeLEsL/RjwPDBv/RqDl8fb3gpYEOQaPihbxf1ZAtSOHCjuAAa7Q3oHpCYhXSN9tizHgVOwn6krflQ==');
$this->assertEquals($res['body']['hashOptions']['saltSeparator'], 'Bw==');
return ['userId' => $body['$id']];
}
/**
* Tries to login into all accounts created with hashed password. Ensures hash veifying logic.
*
* @depends testCreateUser
*/
public function testCreateUserSessionHashed(array $data): void
{
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'md5@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'md5');
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'bcrypt@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'bcrypt');
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'argon2@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'argon2');
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'sha512@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'sha512');
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'scrypt@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'scrypt');
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'phpass@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'phpass');
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'scrypt-modified@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'scrypt-modified');
}
/**
* Tests all optional parameters of createUser (email, phone, anonymous..)
*
* @depends testCreateUser
*/
public function testCreateUserTypes(array $data): void
{
/**
* Test for SUCCESS
*/
// Email + password
$response = $this->client->call(Client::METHOD_POST, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'unique()',
'email' => 'emailuser@appwrite.io',
'password' => 'emailUserPassword',
]);
$this->assertNotEmpty($response['body']['email']);
$this->assertNotEmpty($response['body']['password']);
$this->assertEmpty($response['body']['phone']);
// Phone
$response = $this->client->call(Client::METHOD_POST, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'unique()',
'phone' => '+123456789012',
]);
$this->assertEmpty($response['body']['email']);
$this->assertEmpty($response['body']['password']);
$this->assertNotEmpty($response['body']['phone']);
// Anonymous
$response = $this->client->call(Client::METHOD_POST, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'unique()',
]);
$this->assertEmpty($response['body']['email']);
$this->assertEmpty($response['body']['password']);
$this->assertEmpty($response['body']['phone']);
// Email-only
$response = $this->client->call(Client::METHOD_POST, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'unique()',
'email' => 'emailonlyuser@appwrite.io',
]);
$this->assertNotEmpty($response['body']['email']);
$this->assertEmpty($response['body']['password']);
$this->assertEmpty($response['body']['phone']);
// Password-only
$response = $this->client->call(Client::METHOD_POST, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'unique()',
'password' => 'passwordOnlyUser',
]);
$this->assertEmpty($response['body']['email']);
$this->assertNotEmpty($response['body']['password']);
$this->assertEmpty($response['body']['phone']);
// Password and phone
$response = $this->client->call(Client::METHOD_POST, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'unique()',
'password' => 'passwordOnlyUser',
'phone' => '+123456789013',
]);
$this->assertEmpty($response['body']['email']);
$this->assertNotEmpty($response['body']['password']);
$this->assertNotEmpty($response['body']['phone']);
}
/**
* @depends testCreateUser
*/
public function testListUsers(array $data): void
{
$totalUsers = 15;
/**
* Test for SUCCESS listUsers
*/
@ -74,7 +373,7 @@ trait UsersBase
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(2, $response['body']['users']);
$this->assertCount($totalUsers, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $data['userId']);
$this->assertEquals($response['body']['users'][1]['$id'], 'user1');
@ -89,7 +388,7 @@ trait UsersBase
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertCount($totalUsers - 1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], 'user1');
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([

View file

@ -1,6 +1,8 @@
<?php
return function ($request, $response) {
\var_dump("Amazing Function Log"); // We test logs (stdout) visibility with this
$response->json([
'APPWRITE_FUNCTION_ID' => $request['env']['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $request['env']['APPWRITE_FUNCTION_NAME'],

View file

@ -44,12 +44,137 @@ class AuthTest extends TestCase
public function testPassword(): void
{
$secret = 'secret';
$static = '$2y$08$PDbMtV18J1KOBI9tIYabBuyUwBrtXPGhLxCy9pWP6xkldVOKLrLKy';
$dynamic = Auth::passwordHash($secret);
/*
General tests, using pre-defined hashes generated by online tools
*/
$this->assertEquals(Auth::passwordVerify($secret, $dynamic), true);
$this->assertEquals(Auth::passwordVerify($secret, $static), true);
// Bcrypt - Version Y
$plain = 'secret';
$hash = '$2y$08$PDbMtV18J1KOBI9tIYabBuyUwBrtXPGhLxCy9pWP6xkldVOKLrLKy';
$generatedHash = Auth::passwordHash($plain, 'bcrypt');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt'));
// Bcrypt - Version A
$plain = 'test123';
$hash = '$2a$12$3f2ZaARQ1AmhtQWx2nmQpuXcWfTj1YV2/Hl54e8uKxIzJe3IfwLiu';
$generatedHash = Auth::passwordHash($plain, 'bcrypt');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt'));
// Bcrypt - Cost 5
$plain = 'hello-world';
$hash = '$2a$05$IjrtSz6SN7UJ6Sh3l.b5jODEvEG2LMJTPAHIaLWRvlWx7if3VMkFO';
$generatedHash = Auth::passwordHash($plain, 'bcrypt');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt'));
// Bcrypt - Cost 15
$plain = 'super-secret-password';
$hash = '$2a$15$DS0ZzbsFZYumH/E4Qj5oeOHnBcM3nCCsCA2m4Goigat/0iMVQC4Na';
$generatedHash = Auth::passwordHash($plain, 'bcrypt');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt'));
// MD5 - Short
$plain = 'appwrite';
$hash = '144fa7eaa4904e8ee120651997f70dcc';
$generatedHash = Auth::passwordHash($plain, 'md5');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md5'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'md5'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'md5'));
// MD5 - Long
$plain = 'AppwriteIsAwesomeBackendAsAServiceThatIsAlsoOpenSourced';
$hash = '8410e96cf7ac64e0b84c3f8517a82616';
$generatedHash = Auth::passwordHash($plain, 'md5');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md5'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'md5'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'md5'));
// PHPass
$plain = 'pass123';
$hash = '$P$BVKPmJBZuLch27D4oiMRTEykGLQ9tX0';
$generatedHash = Auth::passwordHash($plain, 'phpass');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'phpass'));
// SHA
$plain = 'developersAreAwesome!';
$hash = '2455118438cb125354b89bb5888346e9bd23355462c40df393fab514bf2220b5a08e4e2d7b85d7327595a450d0ac965cc6661152a46a157c66d681bed20a4735';
$generatedHash = Auth::passwordHash($plain, 'sha');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'sha'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'sha'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'sha'));
// Argon2
$plain = 'safe-argon-password';
$hash = '$argon2id$v=19$m=2048,t=3,p=4$MWc5NWRmc2QxZzU2$41mp7rSgBZ49YxLbbxIac7aRaxfp5/e1G45ckwnK0g8';
$generatedHash = Auth::passwordHash($plain, 'argon2');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'argon2'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'argon2'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'argon2'));
// Scrypt
$plain = 'some-scrypt-password';
$hash = 'b448ad7ba88b653b5b56b8053a06806724932d0751988bc9cd0ef7ff059e8ba8a020e1913b7069a650d3f99a1559aba0221f2c277826919513a054e76e339028';
$generatedHash = Auth::passwordHash($plain, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]);
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]));
$this->assertEquals(false, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-wrong-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]));
$this->assertEquals(false, Auth::passwordVerify($plain, $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 10, 'costParallel' => 2]));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'scrypt', [ 'salt' => 'some-salt', 'length' => 64, 'costCpu' => 16384, 'costMemory' => 12, 'costParallel' => 2]));
// ScryptModified tested are in provider-specific tests below
/*
Provider-specific tests, ensuring functionality of specific use-cases
*/
// Provider #1 (Database)
$plain = 'example-password';
$hash = '$2a$10$3bIGRWUes86CICsuchGLj.e.BqdCdg2/1Ud9LvBhJr0j7Dze8PBdS';
$generatedHash = Auth::passwordHash($plain, 'bcrypt');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'bcrypt'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'bcrypt'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'bcrypt'));
// Provider #2 (Blog)
$plain = 'your-password';
$hash = '$P$BkiNDJTpAWXtpaMhEUhUdrv7M0I1g6.';
$generatedHash = Auth::passwordHash($plain, 'phpass');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'phpass'));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'phpass'));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'phpass'));
// Provider #2 (Google)
$plain = 'users-password';
$hash = 'EPKgfALpS9Tvgr/y1ki7ubY4AEGJeWL3teakrnmOacN4XGiyD00lkzEHgqCQ71wGxoi/zb7Y9a4orOtvMV3/Jw==';
$salt = '56dFqW+kswqktw==';
$saltSeparator = 'Bw==';
$signerKey = 'XyEKE9RcTDeLEsL/RjwPDBv/RqDl8fb3gpYEOQaPihbxf1ZAtSOHCjuAAa7Q3oHpCYhXSN9tizHgVOwn6krflQ==';
$options = [ 'salt' => $salt, 'saltSeparator' => $saltSeparator, 'signerKey' => $signerKey ];
$generatedHash = Auth::passwordHash($plain, 'scryptMod', $options);
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scryptMod', $options));
$this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scryptMod', $options));
$this->assertEquals(false, Auth::passwordVerify('wrongPassword', $hash, 'scryptMod', $options));
}
public function testUnknownAlgo()
{
$this->expectExceptionMessage('Hashing algorithm \'md8\' is not supported.');
// Bcrypt - Cost 5
$plain = 'whatIsMd8?!?';
$generatedHash = Auth::passwordHash($plain, 'md8');
$this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'md8'));
}
public function testPasswordGenerator(): void
@ -64,6 +189,14 @@ class AuthTest extends TestCase
$this->assertEquals(\mb_strlen(Auth::tokenGenerator(5)), 10);
}
public function testCodeGenerator(): void
{
$this->assertEquals(6, \strlen(Auth::codeGenerator()));
$this->assertEquals(\mb_strlen(Auth::codeGenerator(256)), 256);
$this->assertEquals(\mb_strlen(Auth::codeGenerator(10)), 10);
$this->assertTrue(is_numeric(Auth::codeGenerator(5)));
}
public function testSessionVerify(): void
{
$secret = 'secret1';