diff --git a/.env b/.env index 581af6d978..1c260bc3fc 100644 --- a/.env +++ b/.env @@ -1,5 +1,6 @@ _APP_ENV=production _APP_ENV=development +_APP_LOCALE=en _APP_SYSTEM_EMAIL_NAME=Appwrite _APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io _APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io @@ -16,7 +17,7 @@ _APP_DB_PORT=3306 _APP_DB_SCHEMA=appwrite _APP_DB_USER=user _APP_DB_PASS=password -_APP_STORAGE_ANTIVIRUS=enabled +_APP_STORAGE_ANTIVIRUS=disabled _APP_STORAGE_ANTIVIRUS_HOST=clamav _APP_STORAGE_ANTIVIRUS_PORT=3310 _APP_INFLUXDB_HOST=influxdb diff --git a/CHANGES.md b/CHANGES.md index f06be2e2f7..e5e72ebfc9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,13 +5,23 @@ - Added Anonymous Login ([RFC-010](https://github.com/appwrite/rfc/blob/main/010-anonymous-login.md), #914) - Added new Environment Variable to enable or disable Anonymous Login - Added events for functions and executions (#971) +- Splited token & session models to become 2 different internal entities (#922) +- Added Dart 2.12 as a new Cloud Functions runtime (#989) +- ClamAV is now disabled by default to allow lower min requirments for Appwrite (#1064) +- Added a new env var named `_APP_LOCALE` that allow to change the default `en` locale value (#1056) +- Updated all the console bottom control to be consistent. Dropped the `+` icon (#1062) + +## Bugs + +- Fixed default value for HTTPS force option ## Breaking Changes - Only logged in users can execute functions (for guests, use anonymous login) - Only the user who has triggered the execution get access to the relevant execution logs +- Function execution env `APPWRITE_FUNCTION_EVENT_PAYLOAD` renamed to `APPWRITE_FUNCTION_EVENT_DATA` -# Version 0.7.2 (Not Released Yet) +# Version 0.7.2 ## Features diff --git a/Dockerfile b/Dockerfile index 7cd404b7ae..74dc9685e4 100755 --- a/Dockerfile +++ b/Dockerfile @@ -68,6 +68,7 @@ ARG VERSION=dev ENV _APP_SERVER=swoole \ _APP_ENV=production \ + _APP_LOCALE=en \ _APP_DOMAIN=localhost \ _APP_DOMAIN_TARGET=localhost \ _APP_HOME=https://appwrite.io \ diff --git a/README.md b/README.md index 84d47883ff..85cff2e6ff 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker microservices. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster. -Using Appwrite, you can easily integrate your app with user authentication & multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, schedule CRON tasks, and [more services](https://appwrite.io/docs). +Using Appwrite, you can easily integrate your app with user authentication & multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, and [more services](https://appwrite.io/docs). ![Appwrite](public/images/github.png) @@ -53,7 +53,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:0.7.2 + appwrite/appwrite:0.8.0 ``` ### Windows @@ -65,7 +65,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:0.7.2 + appwrite/appwrite:0.8.0 ``` #### PowerShell @@ -75,7 +75,7 @@ docker run -it --rm , --volume /var/run/docker.sock:/var/run/docker.sock , --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw , --entrypoint="install" , - appwrite/appwrite:0.7.2 + appwrite/appwrite:0.8.0 ``` Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-linux native hosts, the server might take a few minutes to start after installation completes. diff --git a/app/config/collections.php b/app/config/collections.php index 3307e7a12c..d023cae514 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -271,6 +271,16 @@ $collections = [ 'required' => true, 'array' => false, ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Sessions', + 'key' => 'sessions', + 'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT, + 'default' => [], + 'required' => false, + 'array' => true, + 'list' => [Database::SYSTEM_COLLECTION_SESSIONS], + ], [ '$collection' => Database::SYSTEM_COLLECTION_RULES, 'label' => 'Tokens', @@ -293,11 +303,11 @@ $collections = [ ], ], ], - Database::SYSTEM_COLLECTION_TOKENS => [ + Database::SYSTEM_COLLECTION_SESSIONS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - '$id' => Database::SYSTEM_COLLECTION_TOKENS, + '$id' => Database::SYSTEM_COLLECTION_SESSIONS, '$permissions' => ['read' => ['*']], - 'name' => 'Token', + 'name' => 'Session', 'structure' => true, 'rules' => [ [ @@ -306,16 +316,34 @@ $collections = [ 'key' => 'userId', 'type' => Database::SYSTEM_VAR_TYPE_TEXT, 'default' => null, + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Provider', + 'key' => 'provider', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Provider User Identifier', + 'key' => 'providerUid', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', 'required' => false, 'array' => false, ], [ '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Type', - 'key' => 'type', - 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, - 'default' => null, - 'required' => true, + 'label' => 'Provider Token', + 'key' => 'providerToken', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, 'array' => false, ], [ @@ -473,6 +501,69 @@ $collections = [ ], ], ], + Database::SYSTEM_COLLECTION_TOKENS => [ + '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + '$id' => Database::SYSTEM_COLLECTION_TOKENS, + '$permissions' => ['read' => ['*']], + 'name' => 'Token', + 'structure' => true, + 'rules' => [ + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'User ID', + 'key' => 'userId', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => null, + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Type', + 'key' => 'type', + 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + 'default' => null, + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Secret', + 'key' => 'secret', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Expire', + 'key' => 'expire', + 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + 'default' => 0, + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'User Agent', + 'key' => 'userAgent', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'IP', + 'key' => 'ip', + 'type' => Database::SYSTEM_VAR_TYPE_IP, + 'default' => '', + 'required' => true, + 'array' => false, + ], + ], + ], Database::SYSTEM_COLLECTION_MEMBERSHIPS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$id' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, @@ -1617,26 +1708,6 @@ foreach ($providers as $index => $provider) { 'array' => false, 'filter' => ['encrypt'], ]; - - $collections[Database::SYSTEM_COLLECTION_USERS]['rules'][] = [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'OAuth2 '.\ucfirst($index).' ID', - 'key' => 'oauth2'.\ucfirst($index), - 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - 'default' => '', - 'required' => false, - 'array' => false, - ]; - - $collections[Database::SYSTEM_COLLECTION_USERS]['rules'][] = [ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'OAuth2 '.\ucfirst($index).' Access Token', - 'key' => 'oauth2'.\ucfirst($index).'AccessToken', - 'type' => Database::SYSTEM_VAR_TYPE_TEXT, - 'default' => '', - 'required' => false, - 'array' => false, - ]; } return $collections; \ No newline at end of file diff --git a/app/config/environments.php b/app/config/environments.php index 9f1c5638e1..473da1b769 100644 --- a/app/config/environments.php +++ b/app/config/environments.php @@ -70,6 +70,15 @@ $environments = [ 'logo' => 'python.png', 'supports' => [System::X86, System::PPC, System::ARM], ], + 'python-3.9' => [ + 'name' => 'Python', + 'version' => '3.9', + 'base' => 'python:3.9-alpine', + 'image' => 'appwrite/env-python-3.9:1.0.0', + 'build' => '/usr/src/code/docker/environments/python-3.9', + 'logo' => 'python.png', + 'supports' => [System::X86, System::PPC, System::ARM], + ], 'deno-1.2' => [ 'name' => 'Deno', 'version' => '1.2', @@ -106,6 +115,15 @@ $environments = [ 'logo' => 'dart.png', 'supports' => [System::X86], ], + 'dart-2.12' => [ + 'name' => 'Dart', + 'version' => '2.12', + 'base' => 'google/dart:2.12', + 'image' => 'appwrite/env-dart-2.12:1.0.0', + 'build' => '/usr/src/code/docker/environments/dart-2.12', + 'logo' => 'dart.png', + 'supports' => [System::X86], + ], 'dotnet-3.1' => [ 'name' => '.NET', 'version' => '3.1', diff --git a/app/config/variables.php b/app/config/variables.php index 82baefb8f1..24df1cc8d5 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -15,6 +15,14 @@ return [ 'required' => false, 'question' => '', ], + [ + 'name' => '_APP_LOCALE', + 'description' => 'Set your Appwrite\'s locale. By default, the locale is set to \'en\'.', + 'introduction' => '', + 'default' => 'en', + 'required' => false, + 'question' => '', + ], [ 'name' => '_APP_OPTIONS_ABUSE', 'description' => 'Allows you to disable abuse checks and API rate limiting. By default, set to \'enabled\'. To cancel the abuse checking, set to \'disabled\'. It is not recommended to disable this check-in a production environment.', @@ -27,7 +35,7 @@ return [ 'name' => '_APP_OPTIONS_FORCE_HTTPS', 'description' => 'Allows you to force HTTPS connection to your API. This feature redirects any HTTP call to HTTPS and adds the \'Strict-Transport-Security\' header to all HTTP responses. By default, set to \'disabled\'. To enable, set to \'enabled\'. This feature will work only when your ports are set to default 80 and 443.', 'introduction' => '', - 'default' => 'enabled', + 'default' => 'disabled', 'required' => false, 'question' => '', ], @@ -309,9 +317,9 @@ return [ ], [ 'name' => '_APP_STORAGE_ANTIVIRUS', - 'description' => 'This variable allows you to disable the internal anti-virus scans. This value is set to \'enabled\' by default, to cancel the scans set the value to \'disabled\'. When disabled, it\'s recommended to turn off the ClamAV container for better resource usage.', + 'description' => 'This variable allows you to disable the internal anti-virus scans. This value is set to \'disabled\' by default, to enable the scans set the value to \'enabled\'. Before enabling, you must add the ClamAV service and depend on it on main Appwrite service.', 'introduction' => '', - 'default' => 'enabled', + 'default' => 'disabled', 'required' => false, 'question' => '', ], @@ -381,7 +389,7 @@ return [ 'name' => '_APP_FUNCTIONS_ENVS', 'description' => 'This option allows you to limit the available environments for cloud functions. This option is very useful for low-cost servers to safe disk space.\n\nTo enable/activate this option, pass a list of allowed environments separated by a comma.\n\nCurrently, supported environments are: ' . \implode(', ', \array_keys(Config::getParam('providers'))), 'introduction' => '0.7.0', - 'default' => 'node-14.5,deno-1.6,php-7.4,python-3.8,ruby-3.0,dotnet-5.0', + 'default' => 'node-14.5,deno-1.6,php-7.4,python-3.9,ruby-3.0,dotnet-5.0', 'required' => false, 'question' => '', ], diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 067fe58767..414432ccd8 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -6,10 +6,10 @@ use Utopia\Exception; use Utopia\Config\Config; use Utopia\Validator\Assoc; use Utopia\Validator\Text; -use Utopia\Validator\Email; +use Appwrite\Network\Validator\Email; use Utopia\Validator\WhiteList; -use Utopia\Validator\Host; -use Utopia\Validator\URL; +use Appwrite\Network\Validator\Host; +use Appwrite\Network\Validator\URL; use Utopia\Audit\Audit; use Utopia\Audit\Adapters\MySQL as AuditAdapter; use Appwrite\Auth\Auth; @@ -190,10 +190,11 @@ App::post('/v1/account/sessions') $secret = Auth::tokenGenerator(); $session = new Document(array_merge( [ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$collection' => Database::SYSTEM_COLLECTION_SESSIONS, '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]], 'userId' => $profile->getId(), - 'type' => Auth::TOKEN_TYPE_LOGIN, + 'provider' => Auth::SESSION_PROVIDER_EMAIL, + 'providerUid' => $email, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expiry, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -210,7 +211,7 @@ App::post('/v1/account/sessions') throw new Exception('Failed saving session to DB', 500); } - $profile->setAttribute('tokens', $session, Document::SET_TYPE_APPEND); + $profile->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); $profile = $projectDB->updateDocument($profile->getArrayCopy()); @@ -441,7 +442,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') throw new Exception('Missing ID from OAuth2 provider', 400); } - $current = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret); + $current = Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret); if ($current) { $projectDB->deleteDocument($current); //throw new Exception('User already logged in', 401); @@ -451,7 +452,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'limit' => 1, 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'oauth2'.\ucfirst($provider).'='.$oauth2ID, + 'sessions.provider='.$provider, + 'sessions.providerUid='.$oauth2ID ], ]) : $user; @@ -506,10 +508,12 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $secret = Auth::tokenGenerator(); $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $session = new Document(array_merge([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$collection' => Database::SYSTEM_COLLECTION_SESSIONS, '$permissions' => ['read' => ['user:'.$user['$id']], 'write' => ['user:'.$user['$id']]], 'userId' => $user->getId(), - 'type' => Auth::TOKEN_TYPE_LOGIN, + 'provider' => $provider, + 'providerUid' => $oauth2ID, + 'providerToken' => $accessToken, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expiry, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -527,10 +531,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } $user - ->setAttribute('oauth2'.\ucfirst($provider), $oauth2ID) - ->setAttribute('oauth2'.\ucfirst($provider).'AccessToken', $accessToken) ->setAttribute('status', Auth::USER_STATUS_ACTIVATED) - ->setAttribute('tokens', $session, Document::SET_TYPE_APPEND) + ->setAttribute('sessions', $session, Document::SET_TYPE_APPEND) ; Authorization::setRole('user:'.$user->getId()); @@ -648,10 +650,10 @@ App::post('/v1/account/sessions/anonymous') $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $session = new Document(array_merge( [ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$collection' => Database::SYSTEM_COLLECTION_SESSIONS, '$permissions' => ['read' => ['user:' . $user['$id']], 'write' => ['user:' . $user['$id']]], 'userId' => $user->getId(), - 'type' => Auth::TOKEN_TYPE_LOGIN, + 'provider' => Auth::SESSION_PROVIDER_ANONYMOUS, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expiry, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -663,7 +665,7 @@ App::post('/v1/account/sessions/anonymous') $detector->getDevice() )); - $user->setAttribute('tokens', $session, Document::SET_TYPE_APPEND); + $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); Authorization::setRole('user:'.$user->getId()); @@ -716,16 +718,18 @@ App::post('/v1/account/jwt') /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ - $tokens = $user->getAttribute('tokens', []); - $session = new Document(); + $sessions = $user->getAttribute('sessions', []); + $current = new Document(); - foreach ($tokens as $token) { /** @var Appwrite\Database\Document $token */ - if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too - $session = $token; + foreach ($sessions as $session) { + /** @var Appwrite\Database\Document $session */ + + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + $current = $session; } } - if($session->isEmpty()) { + if($current->isEmpty()) { throw new Exception('No valid session found', 401); } @@ -739,7 +743,7 @@ App::post('/v1/account/jwt') // 'scopes' => ['user'], // 'iss' => 'http://api.mysite.com', 'userId' => $user->getId(), - 'sessionId' => $session->getId(), + 'sessionId' => $current->getId(), ])]), Response::MODEL_JWT); }); @@ -804,22 +808,19 @@ App::get('/v1/account/sessions') /** @var Appwrite\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ - $tokens = $user->getAttribute('tokens', []); - $sessions = []; + $sessions = $user->getAttribute('sessions', []); $countries = $locale->getText('countries'); - $current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret); + $current = Auth::sessionVerify($sessions, Auth::$secret); - foreach ($tokens as $token) { /* @var $token Document */ - if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) { - continue; - } + foreach ($sessions as $key => $session) { + /** @var Document $session */ - $token->setAttribute('countryName', (isset($countries[strtoupper($token->getAttribute('countryCode'))])) - ? $countries[strtoupper($token->getAttribute('countryCode'))] + $session->setAttribute('countryName', (isset($countries[strtoupper($session->getAttribute('countryCode'))])) + ? $countries[strtoupper($session->getAttribute('countryCode'))] : $locale->getText('locale.country.unknown')); - $token->setAttribute('current', ($current == $token->getId()) ? true : false); + $session->setAttribute('current', ($current == $session->getId()) ? true : false); - $sessions[] = $token; + $sessions[$key] = $session; } $response->dynamic(new Document([ @@ -1147,7 +1148,7 @@ App::delete('/v1/account') ; $events - ->setParam('payload', $response->output($user, Response::MODEL_USER)) + ->setParam('eventData', $response->output($user, Response::MODEL_USER)) ; if (!Config::getParam('domainVerification')) { @@ -1192,14 +1193,16 @@ App::delete('/v1/account/sessions/:sessionId') $protocol = $request->getProtocol(); $sessionId = ($sessionId === 'current') - ? Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret) + ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) : $sessionId; - $tokens = $user->getAttribute('tokens', []); + $sessions = $user->getAttribute('sessions', []); - foreach ($tokens as $token) { /* @var $token Document */ - if (($sessionId == $token->getId()) && Auth::TOKEN_TYPE_LOGIN == $token->getAttribute('type')) { - if (!$projectDB->deleteDocument($token->getId())) { + foreach ($sessions as $session) { + /** @var Document $session */ + + if (($sessionId == $session->getId())) { + if (!$projectDB->deleteDocument($session->getId())) { throw new Exception('Failed to remove token from DB', 500); } @@ -1215,10 +1218,10 @@ App::delete('/v1/account/sessions/:sessionId') ; } - $token->setAttribute('current', false); + $session->setAttribute('current', false); - if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too - $token->setAttribute('current', true); + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + $session->setAttribute('current', true); $response ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) @@ -1227,7 +1230,7 @@ App::delete('/v1/account/sessions/:sessionId') } $events - ->setParam('payload', $response->output($token, Response::MODEL_SESSION)) + ->setParam('eventData', $response->output($session, Response::MODEL_SESSION)) ; return $response->noContent(); @@ -1264,10 +1267,12 @@ App::delete('/v1/account/sessions') /** @var Appwrite\Event\Event $events */ $protocol = $request->getProtocol(); - $tokens = $user->getAttribute('tokens', []); + $sessions = $user->getAttribute('sessions', []); - foreach ($tokens as $token) { /* @var $token Document */ - if (!$projectDB->deleteDocument($token->getId())) { + foreach ($sessions as $session) { + /** @var Document $session */ + + if (!$projectDB->deleteDocument($session->getId())) { throw new Exception('Failed to remove token from DB', 500); } @@ -1283,10 +1288,10 @@ App::delete('/v1/account/sessions') ; } - $token->setAttribute('current', false); + $session->setAttribute('current', false); - if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too - $token->setAttribute('current', true); + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + $session->setAttribute('current', true); $response ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) @@ -1295,9 +1300,9 @@ App::delete('/v1/account/sessions') } $events - ->setParam('payload', $response->output(new Document([ - 'sum' => count($tokens), - 'sessions' => $tokens + ->setParam('eventData', $response->output(new Document([ + 'sum' => count($sessions), + 'sessions' => $sessions ]), Response::MODEL_SESSION_LIST)) ; @@ -1420,7 +1425,7 @@ App::post('/v1/account/recovery') ; $events - ->setParam('payload', + ->setParam('eventData', $response->output($recovery->setAttribute('secret', $secret), Response::MODEL_TOKEN )) @@ -1623,7 +1628,7 @@ App::post('/v1/account/verification') ; $events - ->setParam('payload', + ->setParam('eventData', $response->output($verification->setAttribute('secret', $verificationSecret), Response::MODEL_TOKEN )) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index a094dc672a..b5c47c1987 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -14,7 +14,7 @@ use Utopia\Validator\Boolean; use Utopia\Validator\HexColor; use Utopia\Validator\Range; use Utopia\Validator\Text; -use Utopia\Validator\URL; +use Appwrite\Network\Validator\URL; use Utopia\Validator\WhiteList; $avatarCallback = function ($type, $code, $width, $height, $quality, $response) { diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 2c32ae981e..e86812e7e3 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -271,7 +271,7 @@ App::delete('/v1/database/collections/:collectionId') ; $events - ->setParam('payload', $response->output($collection, Response::MODEL_COLLECTION)) + ->setParam('eventData', $response->output($collection, Response::MODEL_COLLECTION)) ; $audits @@ -614,7 +614,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') } $events - ->setParam('payload', $response->output($document, Response::MODEL_ANY)) + ->setParam('eventData', $response->output($document, Response::MODEL_ANY)) ; $audits diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 63c99ffaee..5737714610 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -6,7 +6,7 @@ use Utopia\Validator\ArrayList; use Utopia\Validator\Boolean; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; -use Utopia\Validator\URL; +use Appwrite\Network\Validator\URL; use Utopia\Validator\Range; use Utopia\Config\Config; use Utopia\Domains\Domain; diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 820cc4e394..11c11f8e28 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -603,7 +603,7 @@ App::delete('/v1/storage/files/:fileId') ; $events - ->setParam('payload', $response->output($file, Response::MODEL_FILE)) + ->setParam('eventData', $response->output($file, Response::MODEL_FILE)) ; $response->noContent(); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index c4a9e4875d..e6ee69cb1b 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -3,9 +3,9 @@ use Utopia\App; use Utopia\Exception; use Utopia\Config\Config; -use Utopia\Validator\Email; +use Appwrite\Network\Validator\Email; use Utopia\Validator\Text; -use Utopia\Validator\Host; +use Appwrite\Network\Validator\Host; use Utopia\Validator\Range; use Utopia\Validator\ArrayList; use Utopia\Validator\WhiteList; @@ -243,7 +243,7 @@ App::delete('/v1/teams/:teamId') } $events - ->setParam('payload', $response->output($team, Response::MODEL_TEAM)) + ->setParam('eventData', $response->output($team, Response::MODEL_TEAM)) ; $response->noContent(); @@ -327,6 +327,7 @@ App::post('/v1/teams/:teamId/memberships') 'registration' => \time(), 'reset' => false, 'name' => $name, + 'sessions' => [], 'tokens' => [], ], ['email' => $email]); } catch (Duplicate $th) { @@ -595,10 +596,11 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $secret = Auth::tokenGenerator(); $session = new Document(array_merge([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$collection' => Database::SYSTEM_COLLECTION_SESSIONS, '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], 'userId' => $user->getId(), - 'type' => Auth::TOKEN_TYPE_LOGIN, + 'provider' => Auth::SESSION_PROVIDER_EMAIL, + 'providerUid' => $user->getAttribute('email'), 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expiry, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -606,7 +608,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', ], $detector->getOS(), $detector->getClient(), $detector->getDevice())); - $user->setAttribute('tokens', $session, Document::SET_TYPE_APPEND); + $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); Authorization::setRole('user:'.$userId); @@ -711,7 +713,7 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') ; $events - ->setParam('payload', $response->output($membership, Response::MODEL_MEMBERSHIP)) + ->setParam('eventData', $response->output($membership, Response::MODEL_MEMBERSHIP)) ; $response->noContent(); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 958dfc1488..44b64bd948 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -4,7 +4,7 @@ use Utopia\App; use Utopia\Exception; use Utopia\Validator\Assoc; use Utopia\Validator\WhiteList; -use Utopia\Validator\Email; +use Appwrite\Network\Validator\Email; use Utopia\Validator\Text; use Utopia\Validator\Range; use Utopia\Audit\Audit; @@ -196,21 +196,18 @@ App::get('/v1/users/:userId/sessions') throw new Exception('User not found', 404); } - $tokens = $user->getAttribute('tokens', []); - $sessions = []; + $sessions = $user->getAttribute('sessions', []); $countries = $locale->getText('countries'); - foreach ($tokens as $token) { /* @var $token Document */ - if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) { - continue; - } + foreach ($sessions as $key => $session) { + /** @var Document $session */ - $token->setAttribute('countryName', (isset($countries[strtoupper($token->getAttribute('countryCode'))])) - ? $countries[strtoupper($token->getAttribute('countryCode'))] + $session->setAttribute('countryName', (isset($countries[strtoupper($session->getAttribute('countryCode'))])) + ? $countries[strtoupper($session->getAttribute('countryCode'))] : $locale->getText('locale.country.unknown')); - $token->setAttribute('current', false); + $session->setAttribute('current', false); - $sessions[] = $token; + $sessions[$key] = $session; } $response->dynamic(new Document([ @@ -434,16 +431,18 @@ App::delete('/v1/users/:userId/sessions/:sessionId') throw new Exception('User not found', 404); } - $tokens = $user->getAttribute('tokens', []); + $sessions = $user->getAttribute('sessions', []); - foreach ($tokens as $token) { /* @var $token Document */ - if ($sessionId == $token->getId()) { - if (!$projectDB->deleteDocument($token->getId())) { + foreach ($sessions as $session) { + /** @var Document $session */ + + if ($sessionId == $session->getId()) { + if (!$projectDB->deleteDocument($session->getId())) { throw new Exception('Failed to remove token from DB', 500); } $events - ->setParam('payload', $response->output($user, Response::MODEL_USER)) + ->setParam('eventData', $response->output($user, Response::MODEL_USER)) ; } } @@ -478,16 +477,18 @@ App::delete('/v1/users/:userId/sessions') throw new Exception('User not found', 404); } - $tokens = $user->getAttribute('tokens', []); + $sessions = $user->getAttribute('sessions', []); - foreach ($tokens as $token) { /* @var $token Document */ - if (!$projectDB->deleteDocument($token->getId())) { + foreach ($sessions as $session) { + /** @var Document $session */ + + if (!$projectDB->deleteDocument($session->getId())) { throw new Exception('Failed to remove token from DB', 500); } } $events - ->setParam('payload', $response->output($user, Response::MODEL_USER)) + ->setParam('eventData', $response->output($user, Response::MODEL_USER)) ; // TODO : Response filter implementation @@ -547,7 +548,7 @@ App::delete('/v1/users/:userId') ; $events - ->setParam('payload', $response->output($user, Response::MODEL_USER)) + ->setParam('eventData', $response->output($user, Response::MODEL_USER)) ; // TODO : Response filter implementation diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 8dc0c097c7..ffb5198c0d 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -78,7 +78,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e ->setParam('projectId', $project->getId()) ->setParam('userId', $user->getId()) ->setParam('event', $route->getLabel('event', '')) - ->setParam('payload', []) + ->setParam('eventData', []) ->setParam('functionId', null) ->setParam('executionId', null) ->setParam('trigger', 'event') @@ -123,8 +123,8 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits /** @var bool $mode */ if (!empty($events->getParam('event'))) { - if(empty($events->getParam('payload'))) { - $events->setParam('payload', $response->getPayload()); + if(empty($events->getParam('eventData'))) { + $events->setParam('eventData', $response->getPayload()); } $webhooks = clone $events; diff --git a/app/init.php b/app/init.php index 2aa5fb0206..6d26b566f0 100644 --- a/app/init.php +++ b/app/init.php @@ -40,7 +40,7 @@ const APP_MODE_DEFAULT = 'default'; const APP_MODE_ADMIN = 'admin'; const APP_PAGING_LIMIT = 12; const APP_CACHE_BUSTER = 145; -const APP_VERSION_STABLE = '0.7.2'; +const APP_VERSION_STABLE = '0.8.0'; const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; const APP_STORAGE_CACHE = '/storage/cache'; @@ -313,7 +313,7 @@ App::setResource('layout', function($locale) { }, ['locale']); App::setResource('locale', function() { - return new Locale('en'); + return new Locale(App::getEnv('_APP_LOCALE', 'en')); }); // Queues @@ -419,7 +419,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response if (empty($user->getId()) // Check a document has been found in the DB || Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document - || !Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)) { // Validate user has valid login token + || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)) { // Validate user has valid login token $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); } diff --git a/app/views/console/database/collection.phtml b/app/views/console/database/collection.phtml index 45d3287cf5..f10e97e1fa 100644 --- a/app/views/console/database/collection.phtml +++ b/app/views/console/database/collection.phtml @@ -36,9 +36,6 @@ $maxCells = 10;
-
+
+ + + Add Document +
  • diff --git a/app/views/console/database/index.phtml b/app/views/console/database/index.phtml index 8ec46a13ad..2760a1ac10 100644 --- a/app/views/console/database/index.phtml +++ b/app/views/console/database/index.phtml @@ -12,40 +12,6 @@
  • Collections

    - -
    -
    +
    + + +