diff --git a/.env b/.env index 581af6d97..1c260bc3f 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/Dockerfile b/Dockerfile index 7cd404b7a..74dc9685e 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/app/config/environments.php b/app/config/environments.php index 9f1c5638e..473da1b76 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 8aa226b8b..24df1cc8d 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.', @@ -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 8ff3e6970..414432ccd 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; diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index a094dc672..b5c47c198 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/projects.php b/app/controllers/api/projects.php index 63c99ffae..573771461 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/teams.php b/app/controllers/api/teams.php index eb6d691db..e6ee69cb1 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; diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 3f6a82d9c..44b64bd94 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; diff --git a/app/init.php b/app/init.php index b6dd2432f..6d26b566f 100644 --- a/app/init.php +++ b/app/init.php @@ -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 diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index 1c0c98108..cbe2bb531 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -52,10 +52,11 @@ services: depends_on: - mariadb - redis - - clamav +# - clamav - influxdb environment: - _APP_ENV + - _APP_LOCALE - _APP_CONSOLE_WHITELIST_EMAILS - _APP_CONSOLE_WHITELIST_IPS - _APP_SYSTEM_EMAIL_NAME @@ -351,14 +352,14 @@ services: volumes: - appwrite-redis:/data:rw - clamav: - image: appwrite/clamav:1.2.0 - container_name: appwrite-clamav - restart: unless-stopped - networks: - - appwrite - volumes: - - appwrite-uploads:/storage/uploads +# clamav: +# image: appwrite/clamav:1.2.0 +# container_name: appwrite-clamav +# restart: unless-stopped +# networks: +# - appwrite +# volumes: +# - appwrite-uploads:/storage/uploads influxdb: image: influxdb:1.8-alpine diff --git a/docker-compose.yml b/docker-compose.yml index c4ad29c64..994270785 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,10 +71,11 @@ services: depends_on: - mariadb - redis - - clamav + # - clamav - influxdb environment: - _APP_ENV + - _APP_LOCALE - _APP_SYSTEM_EMAIL_NAME - _APP_SYSTEM_EMAIL_ADDRESS - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS @@ -413,13 +414,13 @@ services: volumes: - appwrite-redis:/data:rw - clamav: - image: appwrite/clamav:1.2.0 - container_name: appwrite-clamav - networks: - - appwrite - volumes: - - appwrite-uploads:/storage/uploads + # clamav: + # image: appwrite/clamav:1.2.0 + # container_name: appwrite-clamav + # networks: + # - appwrite + # volumes: + # - appwrite-uploads:/storage/uploads influxdb: image: influxdb:1.8-alpine diff --git a/docker/environments/build.sh b/docker/environments/build.sh index 1543c5814..6dffb1761 100644 --- a/docker/environments/build.sh +++ b/docker/environments/build.sh @@ -24,6 +24,9 @@ docker buildx build --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 echo 'Python 3.8...' docker buildx build --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/ppc64le -t appwrite/env-python-3.8:1.0.0 ./docker/environments/python-3.8/ --push +echo 'Python 3.9...' +docker buildx build --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/ppc64le -t appwrite/env-python-3.9:1.0.0 ./docker/environments/python-3.9/ --push + echo 'Ruby 2.7...' docker buildx build --platform linux/amd64,linux/arm64,linux/386,linux/ppc64le -t appwrite/env-ruby-2.7:1.0.2 ./docker/environments/ruby-2.7/ --push @@ -33,6 +36,9 @@ docker buildx build --platform linux/amd64,linux/arm64,linux/386,linux/ppc64le - echo 'Dart 2.10...' docker buildx build --platform linux/amd64 -t appwrite/env-dart-2.10:1.0.0 ./docker/environments/dart-2.10/ --push +echo 'Dart 2.12...' +docker buildx build --platform linux/amd64 -t appwrite/env-dart-2.12:1.0.0 ./docker/environments/dart-2.12/ --push + echo '.NET 3.1...' docker buildx build --platform linux/amd64,linux/arm64 -t appwrite/env-dotnet-3.1:1.0.0 ./docker/environments/dotnet-3.1/ --push diff --git a/docker/environments/dart-2.12/Dockerfile b/docker/environments/dart-2.12/Dockerfile new file mode 100644 index 000000000..06740803a --- /dev/null +++ b/docker/environments/dart-2.12/Dockerfile @@ -0,0 +1,9 @@ +FROM google/dart:2.12 + +LABEL maintainer="team@appwrite.io" + +RUN apt-get update -y && apt-get install -y tar + +WORKDIR /usr/local/src/ + +ENV PUB_CACHE=/usr/local/src/.appwrite \ No newline at end of file diff --git a/docker/environments/python-3.9/Dockerfile b/docker/environments/python-3.9/Dockerfile new file mode 100644 index 000000000..94b7b8ceb --- /dev/null +++ b/docker/environments/python-3.9/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.9-alpine + +LABEL maintainer="team@appwrite.io" + +RUN apk add tar + +RUN mkdir /usr/local/src + +WORKDIR /usr/local/src/ + +ENV PYTHONPATH "${PYTHONPATH}:/usr/local/src/.appwrite" \ No newline at end of file diff --git a/docs/sdks/dart/GETTING_STARTED.md b/docs/sdks/dart/GETTING_STARTED.md index 56297a2e5..5318c8ce1 100644 --- a/docs/sdks/dart/GETTING_STARTED.md +++ b/docs/sdks/dart/GETTING_STARTED.md @@ -11,6 +11,7 @@ void main() async { .setEndpoint('http://[HOSTNAME_OR_IP]/v1') // Make sure your endpoint is accessible .setProject('5ff3379a01d25') // Your project ID .setKey('cd868c7af8bdc893b4...93b7535db89') + .setSelfSigned() // Use only on dev mode with a self-signed SSL cert Users users = Users(client); diff --git a/docs/sdks/deno/GETTING_STARTED.md b/docs/sdks/deno/GETTING_STARTED.md index bfe40468b..b8f851a4b 100644 --- a/docs/sdks/deno/GETTING_STARTED.md +++ b/docs/sdks/deno/GETTING_STARTED.md @@ -10,6 +10,7 @@ client .setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint .setProject('5df5acd0d48c2') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key + .setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; ``` @@ -41,6 +42,7 @@ client .setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint .setProject('5df5acd0d48c2') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key + .setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; let promise = users.create('email@example.com', 'password'); diff --git a/docs/sdks/dotnet/GETTING_STARTED.md b/docs/sdks/dotnet/GETTING_STARTED.md index 66dca9ff3..4363c6820 100644 --- a/docs/sdks/dotnet/GETTING_STARTED.md +++ b/docs/sdks/dotnet/GETTING_STARTED.md @@ -14,6 +14,7 @@ static async Task Main(string[] args) .setEndpoint('http://[HOSTNAME_OR_IP]/v1') // Make sure your endpoint is accessible .setProject('5ff3379a01d25') // Your project ID .setKey('cd868c7af8bdc893b4...93b7535db89') + .setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; var users = Users(client); diff --git a/docs/sdks/flutter/GETTING_STARTED.md b/docs/sdks/flutter/GETTING_STARTED.md index 78845f3e1..9af9720d8 100644 --- a/docs/sdks/flutter/GETTING_STARTED.md +++ b/docs/sdks/flutter/GETTING_STARTED.md @@ -58,7 +58,7 @@ Client client = Client(); client .setEndpoint('https://localhost/v1') // Your Appwrite Endpoint .setProject('5e8cf4f46b5e8') // Your project ID - .setSelfSigned() // Remove in production + .setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; ``` @@ -91,7 +91,7 @@ Client client = Client(); client .setEndpoint('https://localhost/v1') // Your Appwrite Endpoint .setProject('5e8cf4f46b5e8') // Your project ID - .setSelfSigned() // Remove in production + .setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; diff --git a/docs/sdks/nodejs/GETTING_STARTED.md b/docs/sdks/nodejs/GETTING_STARTED.md index 1053f2814..b2ce5f091 100644 --- a/docs/sdks/nodejs/GETTING_STARTED.md +++ b/docs/sdks/nodejs/GETTING_STARTED.md @@ -12,6 +12,7 @@ client .setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint .setProject('5df5acd0d48c2') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key + .setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; ``` @@ -40,6 +41,7 @@ client .setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint .setProject('5df5acd0d48c2') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key + .setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; let users = new sdk.Users(client); diff --git a/docs/sdks/php/GETTING_STARTED.md b/docs/sdks/php/GETTING_STARTED.md index b92bb90e9..a7ceb6137 100644 --- a/docs/sdks/php/GETTING_STARTED.md +++ b/docs/sdks/php/GETTING_STARTED.md @@ -10,6 +10,7 @@ $client ->setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint ->setProject('5df5acd0d48c2') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key + ->setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; ``` @@ -33,6 +34,7 @@ $client ->setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint ->setProject('5df5acd0d48c2') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key + ->setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; $users = new Users($client); diff --git a/docs/sdks/python/GETTING_STARTED.md b/docs/sdks/python/GETTING_STARTED.md index b513f01da..81c795450 100644 --- a/docs/sdks/python/GETTING_STARTED.md +++ b/docs/sdks/python/GETTING_STARTED.md @@ -13,6 +13,7 @@ client = Client() .set_endpoint('https://[HOSTNAME_OR_IP]/v1') # Your API Endpoint .set_project('5df5acd0d48c2') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key + .set_self_signed() # Use only on dev mode with a self-signed SSL cert ) ``` @@ -36,6 +37,7 @@ client = Client() .set_endpoint('https://[HOSTNAME_OR_IP]/v1') # Your API Endpoint .set_project('5df5acd0d48c2') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key + .set_self_signed() # Use only on dev mode with a self-signed SSL cert ) users = Users(client) diff --git a/docs/sdks/ruby/GETTING_STARTED.md b/docs/sdks/ruby/GETTING_STARTED.md index 23de47c1a..acabf1c0a 100644 --- a/docs/sdks/ruby/GETTING_STARTED.md +++ b/docs/sdks/ruby/GETTING_STARTED.md @@ -12,6 +12,7 @@ client .set_endpoint(ENV["APPWRITE_ENDPOINT"]) # Your API Endpoint .set_project(ENV["APPWRITE_PROJECT"]) # Your project ID .set_key(ENV["APPWRITE_SECRET"]) # Your secret API key + .setSelfSigned() # Use only on dev mode with a self-signed SSL cert ; ``` @@ -34,6 +35,7 @@ client .set_endpoint(ENV["APPWRITE_ENDPOINT"]) # Your API Endpoint .set_project(ENV["APPWRITE_PROJECT"]) # Your project ID .set_key(ENV["APPWRITE_SECRET"]) # Your secret API key + .setSelfSigned() # Use only on dev mode with a self-signed SSL cert ; users = Appwrite::Users.new(client); diff --git a/docs/sdks/web/GETTING_STARTED.md b/docs/sdks/web/GETTING_STARTED.md index c8eee4999..f8908ae9d 100644 --- a/docs/sdks/web/GETTING_STARTED.md +++ b/docs/sdks/web/GETTING_STARTED.md @@ -15,6 +15,7 @@ const appwrite = new Appwrite(); appwrite .setEndpoint('http://localhost/v1') // Your Appwrite Endpoint .setProject('455x34dfkj') // Your project ID + .setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; ``` @@ -41,6 +42,7 @@ const appwrite = new Appwrite(); appwrite .setEndpoint('http://localhost/v1') // Your Appwrite Endpoint .setProject('455x34dfkj') + .setSelfSigned() // Use only on dev mode with a self-signed SSL cert ; // Register User diff --git a/docs/tutorials/running-in-production.md b/docs/tutorials/running-in-production.md deleted file mode 100644 index 2ed69d0ef..000000000 --- a/docs/tutorials/running-in-production.md +++ /dev/null @@ -1,45 +0,0 @@ -# Running in Production - -This tutorial will cover some basic concepts and best practices for running a production Appwrite server. This tutorial assumes you have some basic knowledge of Docker and Docker Compose command-line tools. - -## Error Reporting - -By default, Appwrite installation comes with error debugging turned on, We do this to help new users solve issues and report problems while still in development mode. - -In production, it is highly recommended to turn error reporting off. To do so, you have to change the Appwrite container environment variable **_APP_ENV** value from **development** to **production**. - -## Enable Encryption - -By default, the Appwrite setup doesn’t come with a uniquely generated encryption key. This key is used to store your files and sensitive data like webhook passwords or API keys in a safe way. To take advantage of this feature, you must generate a unique key and set it as the value of the **_APP_OPENSSL_KEY_V1** environment variable. - -Make sure to keep this key in a safe place and never make it publicly accessible. There are many [online resources]([https://www.freecodecamp.org/news/how-to-securely-store-api-keys-4ff3ea19ebda/](https://www.freecodecamp.org/news/how-to-securely-store-api-keys-4ff3ea19ebda/)) with methods of keeping your secret keys safe in your servers. - -## Limit Access to your Console - -By default, anyone can signup for your Appwrite server, create projects, and use your computing power. While this is great for testing around or running your Appwrite service in a network isolated environment, it is highly not recommended for public production use. - -We are providing three different methods to limit access to your Appwrite console. You can either set a list of [IPs]([https://github.com/appwrite/appwrite/blob/master/docs/tutorials/environment-variables.md#_app_console_whitelist_ips](https://github.com/appwrite/appwrite/blob/master/docs/tutorials/environment-variables.md#_app_console_whitelist_ips)), [email address]([https://github.com/appwrite/appwrite/blob/master/docs/tutorials/environment-variables.md#_app_console_whitelist_emails](https://github.com/appwrite/appwrite/blob/master/docs/tutorials/environment-variables.md#_app_console_whitelist_emails)) or [email domains]([https://github.com/appwrite/appwrite/blob/master/docs/tutorials/environment-variables.md#_app_console_whitelist_domains](https://github.com/appwrite/appwrite/blob/master/docs/tutorials/environment-variables.md#_app_console_whitelist_domains)) which users are allowed to signup from. You can choose one or multiple restriction methods to apply. - -## Scaling - -Appwrite was built with scalability in mind. Appwrite can potentially scale horizontally infinitely with no known limitations. - -Appwrite uses a few containers to run, where each container has its job. Most of the Appwrite containers are stateless, and in order to scale them, all you need is run multiple instances of them and setup a load balancer in front of them. - -If you decide to set up a load balancer for a specific container, make sure that the containers that are trying to communicate with it are accessing it through a load balancer and not directly. All connections between Appwrite different containers are set using Docker environment variables. - -There are three Appwrite containers that do keep their state are the MariaDB, Redis, and InfluxDB containers that are used for storing data, cache, and stats (in this order). To scale them out, all you need to do is set up a standard cluster (same as you would with any other app using these technologies) according to your needs and performance. - -## Sending Emails - -Sending emails is hard. There are a lot of SPAM rules and configurations to master in order to set a functional SMTP server. The SMTP server that comes packaged with Appwrite is great for development but needs some work done to function well against SPAM filters. You can find some guidelines in this [tutorial]([https://www.digitalocean.com/community/tutorials/how-to-use-an-spf-record-to-prevent-spoofing-improve-e-mail-reliability](https://www.digitalocean.com/community/tutorials/how-to-use-an-spf-record-to-prevent-spoofing-improve-e-mail-reliability)). - -Another **easier option** is to use an ‘SMTP as a service’ product like [Sendgrid]([https://sendgrid.com/](https://sendgrid.com/)) or [Mailgun]([https://www.mailgun.com/](https://www.mailgun.com/)). You can change Appwrite SMTP settings and credentials to any 3rd party provider you like who support SMTP integration using our [Docker environment variables]([https://github.com/appwrite/appwrite/blob/master/docs/tutorials/environment-variables.md#smtp](https://github.com/appwrite/appwrite/blob/master/docs/tutorials/environment-variables.md#smtp)). Most services offer a decent free tier to get started with. - -## Backups - -Backups are highly recommended for any production environment. Currently, there is not built-in script we provide to do this automatically. To be able to backup your Appwrite server data, stats, and files you will need to do the following. - -1. Create a script to backups and restore your MariaDB Appwrite schema. Note that trying to backup MariaDB using a docker volume backup can result in a corrupted copy of your data. It is recommended to use MariaDB or MySQL built-in tools for this. -2. Create a script to backups and restore your InfluxDB stats. If you don’t care much about your server stats, you can skip this. -3. Create a script to backup Appwrite storage volume. There are many [online resources]([https://blog.ssdnodes.com/blog/docker-backup-volumes/](https://blog.ssdnodes.com/blog/docker-backup-volumes/)) explaining different ways to backup a docker volume. When running on multiple servers, it is very recommended to use an attachable storage point. Some cloud providers offer integrated backups to such attachable mount like GCP, AWS, DigitalOcean, and the list continues. diff --git a/src/Appwrite/Network/Validator/Email.php b/src/Appwrite/Network/Validator/Email.php new file mode 100644 index 000000000..ca9ba1d53 --- /dev/null +++ b/src/Appwrite/Network/Validator/Email.php @@ -0,0 +1,56 @@ +whitelist = $whitelist; + } + + /** + * Get Description + * + * Returns validator description + * + * @return string + */ + public function getDescription() + { + return 'URL host must be one of: ' . \implode(', ', $this->whitelist); + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType() + { + return 'string'; + } + + /** + * Is valid + * + * Validation will pass when $value starts with one of the given hosts + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + $urlValidator = new URL(); + + if (!$urlValidator->isValid($value)) { + return false; + } + + if (\in_array(\parse_url($value, PHP_URL_HOST), $this->whitelist)) { + return true; + } + + return false; + } +} diff --git a/src/Appwrite/Network/Validator/IP.php b/src/Appwrite/Network/Validator/IP.php new file mode 100644 index 000000000..523147eb2 --- /dev/null +++ b/src/Appwrite/Network/Validator/IP.php @@ -0,0 +1,102 @@ +type = $type; + } + + /** + * Get Description + * + * Returns validator description + * + * @return string + */ + public function getDescription() + { + return 'Value must be a valid IP address'; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType() + { + return 'string'; + } + + /** + * Is valid + * + * Validation will pass when $value is valid IP address. + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + switch ($this->type) { + case self::ALL: + if (\filter_var($value, FILTER_VALIDATE_IP)) { + return true; + } + break; + + case self::V4: + if (\filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return true; + } + break; + + case self::V6: + if (\filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + return true; + } + break; + + default: + return false; + break; + } + + return false; + } +} diff --git a/src/Appwrite/Network/Validator/URL.php b/src/Appwrite/Network/Validator/URL.php new file mode 100644 index 000000000..bd1546e11 --- /dev/null +++ b/src/Appwrite/Network/Validator/URL.php @@ -0,0 +1,56 @@ + 'python main.py', 'timeout' => 15, ], + [ + 'language' => 'Python', + 'version' => '3.9', + 'name' => 'python-3.9', + 'code' => $functions.'/python.tar.gz', + 'command' => 'python main.py', + 'timeout' => 15, + ], [ 'language' => 'Node.js', 'version' => '14.5', @@ -541,6 +549,14 @@ class FunctionsCustomServerTest extends Scope 'command' => 'dart main.dart', 'timeout' => 15, ], + [ + 'language' => 'Dart', + 'version' => '2.12', + 'name' => 'dart-2.12', + 'code' => $functions.'/dart.tar.gz', + 'command' => 'dart main.dart', + 'timeout' => 15, + ], [ 'language' => '.NET', 'version' => '3.1', diff --git a/tests/resources/docker/docker-compose.yml b/tests/resources/docker/docker-compose.yml index 9f4074c54..7a931600c 100644 --- a/tests/resources/docker/docker-compose.yml +++ b/tests/resources/docker/docker-compose.yml @@ -62,7 +62,7 @@ services: depends_on: - mariadb - redis - - clamav + # - clamav - influxdb environment: - _APP_ENV @@ -81,6 +81,7 @@ services: - _APP_USAGE_STATS - _APP_INFLUXDB_HOST - _APP_INFLUXDB_PORT + - _APP_STORAGE_ANTIVIRUS=disabled - _APP_STORAGE_LIMIT - _APP_FUNCTIONS_TIMEOUT - _APP_FUNCTIONS_CONTAINERS @@ -326,14 +327,14 @@ services: volumes: - appwrite-redis:/data:rw - clamav: - image: appwrite/clamav:1.2.0 - container_name: appwrite-clamav - restart: unless-stopped - networks: - - appwrite - volumes: - - appwrite-uploads:/storage/uploads + # clamav: + # image: appwrite/clamav:1.2.0 + # container_name: appwrite-clamav + # restart: unless-stopped + # networks: + # - appwrite + # volumes: + # - appwrite-uploads:/storage/uploads influxdb: image: influxdb:1.6 diff --git a/tests/unit/Docker/ComposeTest.php b/tests/unit/Docker/ComposeTest.php index c484fc17e..79fbdd9f9 100644 --- a/tests/unit/Docker/ComposeTest.php +++ b/tests/unit/Docker/ComposeTest.php @@ -36,7 +36,7 @@ class ComposeTest extends TestCase public function testServices() { - $this->assertCount(17, $this->object->getServices()); + $this->assertCount(16, $this->object->getServices()); $this->assertEquals('appwrite-telegraf', $this->object->getService('telegraf')->getContainerName()); $this->assertEquals('appwrite', $this->object->getService('appwrite')->getContainerName()); $this->assertEquals('', $this->object->getService('appwrite')->getImageVersion()); diff --git a/tests/unit/Network/Validators/EmailTest.php b/tests/unit/Network/Validators/EmailTest.php new file mode 100755 index 000000000..5a7bf0f96 --- /dev/null +++ b/tests/unit/Network/Validators/EmailTest.php @@ -0,0 +1,71 @@ + + * @version 1.0 RC4 + * @license The MIT License (MIT) + */ + +namespace Appwrite\Network\Validator; + +use PHPUnit\Framework\TestCase; + +class EmailTest extends TestCase +{ + /** + * @var Email + */ + protected $email = null; + + public function setUp():void + { + $this->email = new Email(); + } + + public function tearDown():void + { + $this->email = null; + } + + public function testIsValid() + { + // Assertions + $this->assertEquals(true, $this->email->isValid('email@domain.com')); + $this->assertEquals(true, $this->email->isValid('firstname.lastname@domain.com')); + $this->assertEquals(true, $this->email->isValid('email@subdomain.domain.com')); + $this->assertEquals(true, $this->email->isValid('firstname+lastname@domain.com')); + $this->assertEquals(true, $this->email->isValid('email@[123.123.123.123]')); + $this->assertEquals(true, $this->email->isValid('"email"@domain.com')); + $this->assertEquals(true, $this->email->isValid('1234567890@domain.com')); + $this->assertEquals(true, $this->email->isValid('email@domain-one.com')); + $this->assertEquals(true, $this->email->isValid('_______@domain.com')); + $this->assertEquals(true, $this->email->isValid('email@domain.name')); + $this->assertEquals(true, $this->email->isValid('email@domain.co.jp')); + $this->assertEquals(true, $this->email->isValid('firstname-lastname@domain.com')); + $this->assertEquals(false, $this->email->isValid(false)); + $this->assertEquals(false, $this->email->isValid(['string', 'string'])); + $this->assertEquals(false, $this->email->isValid(1)); + $this->assertEquals(false, $this->email->isValid(1.2)); + $this->assertEquals(false, $this->email->isValid('plainaddress')); // Missing @ sign and domain + $this->assertEquals(false, $this->email->isValid('@domain.com')); // Missing username + $this->assertEquals(false, $this->email->isValid('#@%^%#$@#$@#.com')); // Garbage + $this->assertEquals(false, $this->email->isValid('Joe Smith ')); // Encoded html within email is invalid + $this->assertEquals(false, $this->email->isValid('email.domain.com')); // Missing @ + $this->assertEquals(false, $this->email->isValid('email@domain@domain.com')); // Two @ sign + $this->assertEquals(false, $this->email->isValid('.email@domain.com')); // Leading dot in address is not allowed + $this->assertEquals(false, $this->email->isValid('email.@domain.com')); // Trailing dot in address is not allowed + $this->assertEquals(false, $this->email->isValid('email..email@domain.com')); // Multiple dots + $this->assertEquals(false, $this->email->isValid('あいうえお@domain.com')); // Unicode char as address + $this->assertEquals(false, $this->email->isValid('email@domain.com (Joe Smith)')); // Text followed email is not allowed + $this->assertEquals(false, $this->email->isValid('email@domain')); // Missing top level domain (.com/.net/.org/etc) + $this->assertEquals(false, $this->email->isValid('email@-domain.com')); // Leading dash in front of domain is invalid + $this->assertEquals(false, $this->email->isValid('email@111.222.333.44444')); // Invalid IP format + $this->assertEquals(false, $this->email->isValid('email@domain..com')); // Multiple dot in the domain portion is invalid + $this->assertEquals($this->email->getType(), 'string'); + } +} diff --git a/tests/unit/Network/Validators/HostTest.php b/tests/unit/Network/Validators/HostTest.php new file mode 100755 index 000000000..724ad7415 --- /dev/null +++ b/tests/unit/Network/Validators/HostTest.php @@ -0,0 +1,45 @@ + + * @version 1.0 RC4 + * @license The MIT License (MIT) + */ + +namespace Appwrite\Network\Validator; + +use PHPUnit\Framework\TestCase; + +class HostTest extends TestCase +{ + /** + * @var Host + */ + protected $host = null; + + public function setUp():void + { + $this->host = new Host(['appwrite.io', 'subdomain.appwrite.test', 'localhost']); + } + + public function tearDown():void + { + $this->host = null; + } + + public function testIsValid() + { + // Assertions + $this->assertEquals($this->host->isValid('https://appwrite.io/link'), true); + $this->assertEquals($this->host->isValid('https://localhost'), true); + $this->assertEquals($this->host->isValid('localhost'), false); + $this->assertEquals($this->host->isValid('http://subdomain.appwrite.test/path'), true); + $this->assertEquals($this->host->isValid('http://test.subdomain.appwrite.test/path'), false); + $this->assertEquals($this->host->getType(), 'string'); + } +} diff --git a/tests/unit/Network/Validators/IPTest.php b/tests/unit/Network/Validators/IPTest.php new file mode 100755 index 000000000..8ebd12dd0 --- /dev/null +++ b/tests/unit/Network/Validators/IPTest.php @@ -0,0 +1,81 @@ + + * @version 1.0 RC4 + * @license The MIT License (MIT) + */ + +namespace Appwrite\Network\Validator; + +use PHPUnit\Framework\TestCase; + +class IPTest extends TestCase +{ + public function tearDown():void + { + $this->validator = null; + } + + public function testIsValidIP() + { + $validator = new IP(); + + // Assertions + $this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true); + $this->assertEquals($validator->isValid('109.67.204.101'), true); + $this->assertEquals($validator->isValid(23.5), false); + $this->assertEquals($validator->isValid('23.5'), false); + $this->assertEquals($validator->isValid(null), false); + $this->assertEquals($validator->isValid(true), false); + $this->assertEquals($validator->isValid(false), false); + $this->assertEquals($validator->getType(), 'string'); + } + + public function testIsValidIPALL() + { + $validator = new IP(IP::ALL); + + // Assertions + $this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true); + $this->assertEquals($validator->isValid('109.67.204.101'), true); + $this->assertEquals($validator->isValid(23.5), false); + $this->assertEquals($validator->isValid('23.5'), false); + $this->assertEquals($validator->isValid(null), false); + $this->assertEquals($validator->isValid(true), false); + $this->assertEquals($validator->isValid(false), false); + } + + public function testIsValidIPV4() + { + $validator = new IP(IP::V4); + + // Assertions + $this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), false); + $this->assertEquals($validator->isValid('109.67.204.101'), true); + $this->assertEquals($validator->isValid(23.5), false); + $this->assertEquals($validator->isValid('23.5'), false); + $this->assertEquals($validator->isValid(null), false); + $this->assertEquals($validator->isValid(true), false); + $this->assertEquals($validator->isValid(false), false); + } + + public function testIsValidIPV6() + { + $validator = new IP(IP::V6); + + // Assertions + $this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true); + $this->assertEquals($validator->isValid('109.67.204.101'), false); + $this->assertEquals($validator->isValid(23.5), false); + $this->assertEquals($validator->isValid('23.5'), false); + $this->assertEquals($validator->isValid(null), false); + $this->assertEquals($validator->isValid(true), false); + $this->assertEquals($validator->isValid(false), false); + } +} diff --git a/tests/unit/Network/Validators/URLTest.php b/tests/unit/Network/Validators/URLTest.php new file mode 100755 index 000000000..458637da6 --- /dev/null +++ b/tests/unit/Network/Validators/URLTest.php @@ -0,0 +1,48 @@ + + * @version 1.0 RC4 + * @license The MIT License (MIT) + */ + +namespace Appwrite\Network\Validator; + +use PHPUnit\Framework\TestCase; + +class URLTest extends TestCase +{ + /** + * @var Domain + */ + protected $url = null; + + public function setUp():void + { + $this->url = new URL(); + } + + public function tearDown():void + { + $this->url = null; + } + + public function testIsValid() + { + // Assertions + $this->assertEquals(true, $this->url->isValid('http://example.com')); + $this->assertEquals(true, $this->url->isValid('https://example.com')); + $this->assertEquals(true, $this->url->isValid('htts://example.com')); // does not validate protocol + $this->assertEquals(false, $this->url->isValid('example.com')); // though, requires some kind of protocol + $this->assertEquals(false, $this->url->isValid('http:/example.com')); + $this->assertEquals(true, $this->url->isValid('http://exa-mple.com')); + $this->assertEquals(false, $this->url->isValid('htt@s://example.com')); + $this->assertEquals(true, $this->url->isValid('http://www.example.com/foo%2\u00c2\u00a9zbar')); + $this->assertEquals(true, $this->url->isValid('http://www.example.com/?q=%3Casdf%3E')); + } +}