diff --git a/.env b/.env
index 436098cbe3..07fb4a7a60 100644
--- a/.env
+++ b/.env
@@ -3,6 +3,7 @@ _APP_ENV=development
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io
+_APP_SYSTEM_RESPONSE_FORMAT=
_APP_OPTIONS_ABUSE=disabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
@@ -15,6 +16,9 @@ _APP_DB_PORT=3306
_APP_DB_SCHEMA=appwrite
_APP_DB_USER=user
_APP_DB_PASS=password
+_APP_STORAGE_ANTIVIRUS=enabled
+_APP_STORAGE_ANTIVIRUS_HOST=clamav
+_APP_STORAGE_ANTIVIRUS_PORT=3310
_APP_INFLUXDB_HOST=influxdb
_APP_INFLUXDB_PORT=8086
_APP_STATSD_HOST=telegraf
@@ -33,3 +37,4 @@ _APP_FUNCTIONS_MEMORY_SWAP=128
_APP_MAINTENANCE_EXECUTION_LOG_RETENTION=60
_APP_MAINTENANCE_ABUSE_LOG_RETENTION=60
_APP_MAINTENANCE_AUDIT_LOG_RETENTION=60
+_APP_USAGE_STATS=enabled
diff --git a/build.sh b/.travis-ci/build.sh
similarity index 100%
rename from build.sh
rename to .travis-ci/build.sh
diff --git a/.travis-ci/deploy.sh b/.travis-ci/deploy.sh
new file mode 100644
index 0000000000..b4c132022a
--- /dev/null
+++ b/.travis-ci/deploy.sh
@@ -0,0 +1 @@
+echo 'Nothing to deploy right now.'
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index ea3d9573ba..8406222a16 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,11 @@
+dist: xenial
+
arch:
- amd64
os: linux
-language: minimal
+language: shell
notifications:
email:
@@ -13,26 +15,22 @@ services:
- docker
before_install:
- - curl -fsSL https://get.docker.com | sh
- - echo '{"experimental":"enabled"}' | sudo tee /etc/docker/daemon.json
- - mkdir -p $HOME/.docker
- - echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json
- - sudo service docker start
-
-# cache:
-# directories:
-# - docker_images
-
-# before_install:
-# - docker load -i docker_images/images.tar || true
-
-# before_cache:
-# - docker save -o docker_images/images.tar appwrite_appwrite
+- curl -fsSL https://get.docker.com | sh
+- echo '{"experimental":"enabled"}' | sudo tee /etc/docker/daemon.json
+- mkdir -p $HOME/.docker
+- echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json
+- sudo service docker start
+- >
+ if [ ! -z "${DOCKERHUB_PULL_USERNAME:-}" ]; then
+ echo "${DOCKERHUB_PULL_PASSWORD}" | docker login --username "${DOCKERHUB_PULL_USERNAME}" --password-stdin
+ fi
+- docker --version
+- docker buildx create --use
+- chmod -R u+x ./.travis-ci
install:
-- docker --version
- docker-compose up -d
-- sleep 90
+- sleep 10
script:
- docker ps
@@ -40,3 +38,11 @@ script:
- docker-compose exec appwrite doctor
- docker-compose exec appwrite vars
- docker-compose exec appwrite test
+
+deploy:
+ - provider: script
+ edge: true
+ script: ./.travis-ci/deploy.sh
+ on:
+ repo: appwrite/appwrite
+ branch: deploy
diff --git a/CHANGES.md b/CHANGES.md
index 6185144417..1418fb4532 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,37 +2,22 @@
## Features
-- Improved Webhooks and New System Events - [Learn more]()
-- New QR code generator library (@PedroCisnerosSantana - [#475](https://github.com/appwrite/appwrite/issues/475))
+- Improved Webhooks and added new system events - [Learn more]()
- Added response to /locale/languages API with a list of languages (@TorstenDittmann ,[#351](https://github.com/appwrite/appwrite/issues/351))
-- Added API response payload structure info and examples to the docs site ([#381](https://github.com/appwrite/appwrite/issues/381))
-- Added Google Fonts to Appwrite for offline availability
- Added a new route in the Avatars API to get user initials avatar ([#386](https://github.com/appwrite/appwrite/issues/386))
-- Added option to delete team from the console ([#380](https://github.com/appwrite/appwrite/issues/380))
-- Added option to view team members from the console ([#378](https://github.com/appwrite/appwrite/issues/378))
-- Add option to assign new team members to a team from the console and the API ([#379](https://github.com/appwrite/appwrite/issues/379))
+- Added API response payload structure info and examples to the docs site ([#381](https://github.com/appwrite/appwrite/issues/381))
- Added support for Brotli compression (@PedroCisnerosSantana, @Rohitub222, [#310](https://github.com/appwrite/appwrite/issues/310))
-- Added Select All Checkbox for on Console API key Scopes Screen ([#477](https://github.com/appwrite/appwrite/issues/477))
-- Added pagination and search for team memberships route ([#387](https://github.com/appwrite/appwrite/issues/387))
-- UI performance & accessibility improvements ([#406](https://github.com/appwrite/appwrite/pull/406))
-- Added option to delete user from the console (@PineappleIOnic - #538)
-- Created lazy deletion of data worker ([#521](https://github.com/appwrite/appwrite/issues/521))
-- All emails are now sent asynchronously for improved performance (@TorstenDittmann ,[#402](https://github.com/appwrite/appwrite/pull/402))
-- Updated grid for OAuth2 providers list in the console ([#413](https://github.com/appwrite/appwrite/issues/413))
-- Upgraded Redis Resque queue library to version 1.3.6 ([#319](https://github.com/appwrite/appwrite/issues/319))
+- New deletion worker ([#521](https://github.com/appwrite/appwrite/issues/521))
+- New maintenance worker - cleaning up system logs and other optimizations ([#766](https://github.com/appwrite/appwrite/pull/766))
+- New email worker - all emails are now sent asynchronously for improved performance (@TorstenDittmann ,[#402](https://github.com/appwrite/appwrite/pull/402))
- Moved all Appwrite container logs to STDOUT & STDERR ([#389](https://github.com/appwrite/appwrite/issues/389))
-- New UI micro-interactions and CSS fixes (@AnatoleLucet)
- New Doctor CLI to debug the Appwrite server ([#415](https://github.com/appwrite/appwrite/issues/415))
- Added container names to docker-compose.yml (@drandell)
-- Upgraded ClamAV container image to version 1.0.11 ([#412](https://github.com/appwrite/appwrite/issues/412))
- Optimised function execution by using fully-qualified function calls
- Added support for boolean 'true' and 'false' in query strings alongside 1 and 0
-- Added pagination for projects list on the console home page.
- Updated storage calculation to match IEC standards
- Now using Alpine as base Docker image
-- Upgraded device detctor to version 3.12.6
-- Upgraded MariaDB to version 10.5.5
-- Switch standard ports to 95xx prefix ([#780](https://github.com/appwrite/appwrite/pull/780))
+- Switch standard dev ports to 95xx prefix ([#780](https://github.com/appwrite/appwrite/pull/780))
- User & Team name max length is now 128 chars and not 100 for better API consistency
- Collection name max length is now 128 chars and not 256 for better API consistency
- Project name max length is now 128 chars and not 100 for better API consistency
@@ -40,19 +25,49 @@
- API Key name max length is now 128 chars and not 256 for better API consistency
- Task name max length is now 128 chars and not 256 for better API consistency
- Platform name max length is now 128 chars and not 256 for better API consistency
-- Webhooks payloads are now exactly the same as any of the API response objects
+- Webhooks payloads are now exactly the same as any of the API response objects, documentation added
- Added new locale: Marathi -mr (@spielers)
- New and consistent response format for all API object + new response examples in the docs
- Removed user roles attribute from user object (can be fetched from /v1/teams/memberships) **
- Removed type attribute from session object response (used only internally)
- ** - might be changed before merging to master
+ - Added fallback option to 0.6 format for backward compatibility with any changes (@christyjacob4 [#772](https://github.com/appwrite/appwrite/pull/772))
+- Added option to disable mail sending by setting an empty SMTP host value ([#730](https://github.com/appwrite/appwrite/issues/730))
+- Upgraded installation script ([#490](https://github.com/appwrite/appwrite/issues/490))
+- Added new environment variables for ClamAV hostname and port ([#780](https://github.com/appwrite/appwrite/pull/780))
+- New OAuth adapter for Box.com (@armino-dev - [#420](https://github.com/appwrite/appwrite/issues/410))
+- New OAuth adapter for PayPal sandbox (@armino-dev - [#420](https://github.com/appwrite/appwrite/issues/410))
+- Introducing new permssion types: role:guest, role:member, role:app
+- Disabled rate-limits on server side integrations
+
+### User Interface
+
+- Updated grid for OAuth2 providers list in the console ([#413](https://github.com/appwrite/appwrite/issues/413))
+- Added Google Fonts to Appwrite for offline availability
+- Added option to delete user from the console (@PineappleIOnic - [#538](https://github.com/appwrite/appwrite/issues/538))
+- Added option to delete team from the console ([#380](https://github.com/appwrite/appwrite/issues/380))
+- Added option to view team members from the console ([#378](https://github.com/appwrite/appwrite/issues/378))
+- Add option to assign new team members to a team from the console and the API ([#379](https://github.com/appwrite/appwrite/issues/379))
+- Added Select All Checkbox for on Console API key Scopes Screen ([#477](https://github.com/appwrite/appwrite/issues/477))
+- Added pagination and search for team memberships route ([#387](https://github.com/appwrite/appwrite/issues/387))
+- Added pagination for projects list on the console home page.
+- UI performance & accessibility improvements ([#406](https://github.com/appwrite/appwrite/pull/406))
+- New UI micro-interactions and CSS fixes (@AnatoleLucet)
+- Added toggle to hide/show secret keys and passwords inside the dashboard (@kodumbeats, [#535](https://github.com/appwrite/appwrite/issues/535))
+
+### Upgrades
+
+- Upgraded QR codes generator library (@PedroCisnerosSantana - [#475](https://github.com/appwrite/appwrite/issues/475))
- Upgraded Traefik image to version 2.3
+- Upgraded MariaDB to version 10.5.5
- Upgraded Redis Docker image to version 6.0 (alpine)
- Upgraded Influxdb Docker image to version 1.8 (alpine)
-- Added option to disable mail sending by setting empty SMTP host
-- Upgraded installation script ([#490](https://github.com/appwrite/appwrite/issues/490))
+- Upgraded Redis Resque queue library to version 1.3.6 ([#319](https://github.com/appwrite/appwrite/issues/319))
+- Upgraded ClamAV container image to version 1.0.11 ([#412](https://github.com/appwrite/appwrite/issues/412))
+- Upgraded device detctor to version 3.12.6
## Breaking Changes (Read before upgrading!)
+
- **Deprecated** `first` and `last` query params for documents list route in the database API
- **Deprecated** Deprectaed Pubjabi Translations ('pn')
- **Deprecated** `PATCH /account/prefs` is now updating the prefs payload and not just merging it
@@ -89,14 +104,8 @@
- Fixed Bug when trying to overwrite OAuth cookie in the Flutter SDK
- Fixed OAuth redirect when using the self-hosted instance default success URL ([#454](https://github.com/appwrite/appwrite/issues/454))
- Fixed bug denying authentication with Github OAuth provider
-- New OAuth adapter for Box.com
-- New OAuth adapter for PayPal sandbox
- Fixed a bug making read permission overwrite write permission in some cases
-## Breaking Changes
-- **Deprecated** `first` and `last` query params for documents list route in the database API
-- **Deprecated** Deprecated Punjabi Translations ('pn')
-
## Security
- Access to Health API now requires authentication with an API Key with access to `health.read` scope allowed
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0f9f433926..e3448a5dc9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -72,7 +72,23 @@ cd appwrite
docker-compose up -d
```
-After finishing the installation process, you can start writing and editing code. To compile new CSS and JS distribution files, use 'less' and 'build' tasks using gulp as a task manager.
+### Code Autocompletion
+
+To get proper autocompletion for all the different functions and classes in the codebase, you'll need to install Appwrite dependencies on your local machine. You can easily do that with PHP's package manager, [Composer](https://getcomposer.org/). If you don't have Composer installed, you can use the Docker Hub image to get the same result:
+
+```bash
+docker run --rm --interactive --tty \
+ --volume $PWD:/app \
+ composer install
+```
+
+### User Interface
+
+Appwrite uses an internal micro-framework called Litespeed.js to build simple UI components in vanilla JS and [less](http://lesscss.org/) for compiling CSS code. To apply any of your changes to the UI, use the `gulp build` or `gulp less` commands, and restart the Appwrite main container to load the new static files to memory using `docker-compose restart appwrite`.
+
+### Get Started
+
+After finishing the installation process, you can start writing and editing code.
## Architecture
@@ -216,7 +232,7 @@ For us to find the right balance, please open an issue explaining your ideas bef
This will allow the Appwrite community to have sufficient discussion about the new feature value and how it fits in the product roadmap and vision.
-This is also important for the Appwrite lead developers to be able to give technical input and different emphasis regarding the feature design and architecture.
+This is also important for the Appwrite lead developers to be able to give technical input and different emphasis regarding the feature design and architecture. Some bigger features might need to go through our [RFC process](https://github.com/appwrite/rfc).
## Build
@@ -243,12 +259,29 @@ bash ./docker/environments/build.sh
## Tests
-To run tests manually, use the Appwrite Docker CLI from your terminal:
+To run all tests manually, use the Appwrite Docker CLI from your terminal:
```bash
docker-compose exec appwrite test
```
+To run unit tests use:
+
+```bash
+docker-compose exec appwrite test /usr/src/code/tests/unit
+```
+
+To run end-2-end tests use:
+
+```bash
+docker-compose exec appwrite test /usr/src/code/tests/e2e
+```
+
+To run end-2-end tests for a spcific service use:
+
+```bash
+docker-compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName]
+```
## Benchmarking
You can use WRK Docker image to benchmark the server performance. Benchmarking is extremely useful when you want to compare how the server behaves before and after a change has been applied. Replace [APPWRITE_HOSTNAME_OR_IP] with your Appwrite server hostname or IP. Note that localhost is not accessible from inside the WRK container.
diff --git a/Dockerfile b/Dockerfile
index fb578a6f1a..0017d68dcb 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -77,6 +77,8 @@ ENV _APP_SERVER=swoole \
_APP_OPENSSL_KEY_V1=your-secret-key \
_APP_STORAGE_LIMIT=10000000 \
_APP_STORAGE_ANTIVIRUS=enabled \
+ _APP_STORAGE_ANTIVIRUS_HOST=clamav \
+ _APP_STORAGE_ANTIVIRUS_PORT=3310 \
_APP_REDIS_HOST=redis \
_APP_REDIS_PORT=6379 \
_APP_DB_HOST=mariadb \
@@ -97,6 +99,7 @@ ENV _APP_SERVER=swoole \
_APP_FUNCTIONS_MEMORY_SWAP=128 \
_APP_SETUP=self-hosted \
_APP_VERSION=$VERSION \
+ _APP_USAGE_STATS=enabled \
# 1 Day = 86400 s
_APP_MAINTENANCE_INTERVAL=86400
#ENV _APP_SMTP_SECURE ''
diff --git a/Dockerfile.nginx b/Dockerfile.nginx
deleted file mode 100644
index b7acd23a7f..0000000000
--- a/Dockerfile.nginx
+++ /dev/null
@@ -1,185 +0,0 @@
-FROM ubuntu:18.04 AS builder
-
-LABEL maintainer="team@appwrite.io"
-
-ARG TESTING=false
-
-ENV TZ=Asia/Tel_Aviv \
- DEBIAN_FRONTEND=noninteractive \
- PHP_VERSION=7.4 \
- PHP_REDIS_VERSION=5.2.1
-
-RUN \
- apt-get update && \
- apt-get install -y --no-install-recommends --no-install-suggests ca-certificates software-properties-common wget git openssl && \
- LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php && \
- apt-get update && \
- apt-get install -y --no-install-recommends --no-install-suggests make php$PHP_VERSION php$PHP_VERSION-dev zip unzip php$PHP_VERSION-zip && \
- # Redis Extension
- wget -q https://github.com/phpredis/phpredis/archive/$PHP_REDIS_VERSION.tar.gz && \
- tar -xf $PHP_REDIS_VERSION.tar.gz && \
- cd phpredis-$PHP_REDIS_VERSION && \
- phpize$PHP_VERSION && \
- ./configure && \
- make && \
- # Composer
- wget https://getcomposer.org/composer.phar && \
- chmod +x ./composer.phar && \
- mv ./composer.phar /usr/bin/composer && \
- #Brotli
- cd / && \
- git clone https://github.com/eustas/ngx_brotli.git && \
- cd ngx_brotli && git submodule update --init && cd ..
-
-WORKDIR /usr/local/src/
-
-# Updating PHP Dependencies and Auto-loading...
-
-ENV TESTING=$TESTING
-
-COPY composer.* /usr/local/src/
-
-RUN composer update --ignore-platform-reqs --optimize-autoloader \
- --no-plugins --no-scripts --prefer-dist \
- `if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
-
-FROM ubuntu:18.04
-LABEL maintainer="team@appwrite.io"
-
-ARG VERSION=dev
-
-ENV TZ=Asia/Tel_Aviv \
- DEBIAN_FRONTEND=noninteractive \
- PHP_VERSION=7.4 \
- _APP_SERVER=nginx \
- _APP_ENV=production \
- _APP_DOMAIN=localhost \
- _APP_DOMAIN_TARGET=localhost \
- _APP_HOME=https://appwrite.io \
- _APP_EDITION=community \
- _APP_OPTIONS_ABUSE=enabled \
- _APP_OPTIONS_FORCE_HTTPS=disabled \
- _APP_OPENSSL_KEY_V1=your-secret-key \
- _APP_STORAGE_LIMIT=10000000 \
- _APP_STORAGE_ANTIVIRUS=enabled \
- _APP_REDIS_HOST=redis \
- _APP_REDIS_PORT=6379 \
- _APP_DB_HOST=mariadb \
- _APP_DB_PORT=3306 \
- _APP_DB_USER=root \
- _APP_DB_PASS=password \
- _APP_DB_SCHEMA=appwrite \
- _APP_INFLUXDB_HOST=influxdb \
- _APP_INFLUXDB_PORT=8086 \
- _APP_STATSD_HOST=telegraf \
- _APP_STATSD_PORT=8125 \
- _APP_SMTP_HOST=smtp \
- _APP_SMTP_PORT=25 \
- _APP_SETUP=self-hosted \
- _APP_VERSION=$VERSION
-#ENV _APP_SMTP_SECURE ''
-#ENV _APP_SMTP_USERNAME ''
-#ENV _APP_SMTP_PASSWORD ''
-
-COPY --from=builder /phpredis-5.2.1/modules/redis.so /usr/lib/php/20190902/
-COPY --from=builder /phpredis-5.2.1/modules/redis.so /usr/lib/php/20190902/
-COPY --from=builder /ngx_brotli /ngx_brotli
-
-RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
-
-RUN \
- apt-get update && \
- apt-get install -y --no-install-recommends --no-install-suggests wget ca-certificates software-properties-common build-essential libpcre3-dev zlib1g-dev libssl-dev openssl gnupg htop supervisor && \
- LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php && \
- add-apt-repository universe && \
- add-apt-repository ppa:certbot/certbot && \
- apt-get update && \
- apt-get install -y --no-install-recommends --no-install-suggests php$PHP_VERSION php$PHP_VERSION-fpm \
- php$PHP_VERSION-mysqlnd php$PHP_VERSION-curl php$PHP_VERSION-imagick php$PHP_VERSION-mbstring php$PHP_VERSION-dom certbot && \
- # Nginx
- wget http://nginx.org/download/nginx-1.19.0.tar.gz && \
- tar -xzvf nginx-1.19.0.tar.gz && rm nginx-1.19.0.tar.gz && \
- cd nginx-1.19.0 && \
- ./configure --prefix=/usr/share/nginx \
- --sbin-path=/usr/sbin/nginx \
- --modules-path=/usr/lib/nginx/modules \
- --conf-path=/etc/nginx/nginx.conf \
- --error-log-path=/var/log/nginx/error.log \
- --http-log-path=/var/log/nginx/access.log \
- --pid-path=/run/nginx.pid \
- --lock-path=/var/lock/nginx.lock \
- --user=www-data \
- --group=www-data \
- --build=Ubuntu \
- --with-http_gzip_static_module \
- --with-http_ssl_module \
- --with-http_v2_module \
- --add-module=/ngx_brotli && \
- make && \
- make install && \
- rm -rf ../nginx-1.19.0 && \
- # Redis Extension
- echo extension=redis.so >> /etc/php/$PHP_VERSION/fpm/conf.d/redis.ini && \
- echo extension=redis.so >> /etc/php/$PHP_VERSION/cli/conf.d/redis.ini && \
- # Cleanup
- cd ../ && \
- apt-get purge -y --auto-remove wget software-properties-common build-essential libpcre3-dev zlib1g-dev libssl-dev gnupg && \
- apt-get clean && \
- rm -rf /ngx_brotli && \
- rm -rf /var/lib/apt/lists/*
-
-# Set Upload Limit (default to 100MB)
-RUN echo "upload_max_filesize = ${_APP_STORAGE_LIMIT}" >> /etc/php/$PHP_VERSION/fpm/conf.d/appwrite.ini
-RUN echo "post_max_size = ${_APP_STORAGE_LIMIT}" >> /etc/php/$PHP_VERSION/fpm/conf.d/appwrite.ini
-RUN echo "opcache.preload_user=www-data" >> /etc/php/$PHP_VERSION/fpm/conf.d/appwrite.ini
-RUN echo "opcache.preload=/usr/src/code/app/preload.php" >> /etc/php/$PHP_VERSION/fpm/conf.d/appwrite.ini
-RUN echo "opcache.enable_cli = 1" >> /etc/php/$PHP_VERSION/fpm/conf.d/appwrite.ini
-
-# Add logs file
-RUN echo "" >> /var/log/appwrite.log
-
-# Nginx Configuration (with self-signed ssl certificates)
-COPY ./docker/nginx.conf.template /etc/nginx/nginx.conf.template
-COPY ./docker/ssl/cert.pem /etc/nginx/ssl/cert.pem
-COPY ./docker/ssl/key.pem /etc/nginx/ssl/key.pem
-
-# PHP Configuration
-RUN mkdir -p /var/run/php
-COPY ./docker/www.conf /etc/php/$PHP_VERSION/fpm/pool.d/www.conf
-
-# Add PHP Source Code
-COPY ./app /usr/src/code/app
-COPY ./bin /usr/local/bin
-COPY ./docs /usr/src/code/docs
-COPY ./public /usr/src/code/public
-COPY ./src /usr/src/code/src
-COPY --from=builder /usr/local/src/vendor /usr/src/code/vendor
-
-RUN mkdir -p /storage/uploads && \
- mkdir -p /storage/cache && \
- mkdir -p /storage/config && \
- mkdir -p /storage/certificates && \
- mkdir -p /storage/functions && \
- chown -Rf www-data.www-data /storage/uploads && chmod -Rf 0755 /storage/uploads && \
- chown -Rf www-data.www-data /storage/cache && chmod -Rf 0755 /storage/cache && \
- chown -Rf www-data.www-data /storage/config && chmod -Rf 0755 /storage/config && \
- chown -Rf www-data.www-data /storage/certificates && chmod -Rf 0755 /storage/certificates && \
- chown -Rf www-data.www-data /storage/functions && chmod -Rf 0755 /storage/functions
-
-# Supervisord Conf
-COPY ./docker/supervisord.conf /etc/supervisord.conf
-
-# Executables
-RUN chmod +x /usr/local/bin/start
-RUN chmod +x /usr/local/bin/doctor
-RUN chmod +x /usr/local/bin/migrate
-RUN chmod +x /usr/local/bin/test
-
-# Letsencrypt Permissions
-RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
-
-EXPOSE 80
-
-WORKDIR /usr/src/code
-
-CMD ["/bin/bash", "/usr/local/bin/start"]
diff --git a/README.md b/README.md
index 42b37d53d6..7ece1f7cbc 100644
--- a/README.md
+++ b/README.md
@@ -8,11 +8,11 @@
-[](https://appwrite.io/discord)
-[](https://hub.docker.com/r/appwrite/appwrite)
-[](https://travis-ci.com/appwrite/appwrite)
-[](https://twitter.com/appwrite_io)
-[](https://stackshare.io/appwrite)
+[](https://appwrite.io/discord)
+[](https://hub.docker.com/r/appwrite/appwrite)
+[](https://travis-ci.com/appwrite/appwrite)
+[](https://twitter.com/appwrite_io)
+[](https://stackshare.io/appwrite)
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.
@@ -104,7 +104,7 @@ Getting started with Appwrite is as easy as creating a new project, choosing you
* [**Teams**](https://appwrite.io/docs/client/teams) - Manage and group users in teams. Manage memberships, invites, and user roles within a team.
* [**Database**](https://appwrite.io/docs/client/database) - Manage database collections and documents. Read, create, update, and delete documents and filter lists of documents collections using an advanced filter with graph-like capabilities.
* [**Storage**](https://appwrite.io/docs/client/storage) - Manage storage files. Read, create, delete, and preview files. Manipulate the preview of your files to fit your app perfectly. All files are scanned by ClamAV and stored in a secure and encrypted way.
-* [**Functions**](https://appwrite.io/docs/server/functions) - Customize your Appwrite server by executing your custom code in an isolate environment, you can trigger your code on any Appwrite system event, manually or using a CRON schedule.
+* [**Functions**](https://appwrite.io/docs/server/functions) - Customize your Appwrite server by executing your custom code in a secure, isolated environment. You can trigger your code on any Appwrite system event, manually or using a CRON schedule.
* [**Locale**](https://appwrite.io/docs/client/locale) - Track your user's location, and manage your app locale-based data.
* [**Avatars**](https://appwrite.io/docs/client/avatars) - Manage your users' avatars, countries' flags, browser icons, credit card symbols, and generate QR codes.
@@ -115,17 +115,18 @@ For the complete API documentation, visit [https://appwrite.io/docs](https://app
Below is a list of currently supported platforms and languages. If you wish to help us add support to your platform of choice, you can go over to our [SDK Generator](https://github.com/appwrite/sdk-generator) project and view our [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md).
#### Client
-* ✅ [Web](https://github.com/appwrite/sdk-for-js) (Maintained by the Appwrite Team)
-* ✅ [Flutter](https://github.com/appwrite/sdk-for-flutter) (Maintained by the Appwrite Team)
+* ✅ [Web](https://github.com/appwrite/sdk-for-js) (Maintained by the Appwrite Team)
+* ✅ [Flutter](https://github.com/appwrite/sdk-for-flutter) (Maintained by the Appwrite Team)
#### Server
-* ✅ [NodeJS](https://github.com/appwrite/sdk-for-node) (Maintained by the Appwrite Team)
-* ✅ [PHP](https://github.com/appwrite/sdk-for-php) (Maintained by the Appwrite Team)
-* ✅ [Deno](https://github.com/appwrite/sdk-for-deno) - **Beta** (Maintained by the Appwrite Team)
-* ✅ [Ruby](https://github.com/appwrite/sdk-for-ruby) - **Beta** (Maintained by the Appwrite Team)
-* ✅ [Python](https://github.com/appwrite/sdk-for-python) - **Beta** (Maintained by the Appwrite Team)
-* ✅ [Go](https://github.com/appwrite/sdk-for-go) **Work in progress** (Maintained by the Appwrite Team)
-* ✅ [Dart](https://github.com/appwrite/sdk-for-dart) **Work in progress** (Maintained by the Appwrite Team)
+* ✅ [NodeJS](https://github.com/appwrite/sdk-for-node) (Maintained by the Appwrite Team)
+* ✅ [PHP](https://github.com/appwrite/sdk-for-php) (Maintained by the Appwrite Team)
+* ✅ [Deno](https://github.com/appwrite/sdk-for-deno) - **Beta** (Maintained by the Appwrite Team)
+* ✅ [Ruby](https://github.com/appwrite/sdk-for-ruby) - **Beta** (Maintained by the Appwrite Team)
+* ✅ [Python](https://github.com/appwrite/sdk-for-python) - **Beta** (Maintained by the Appwrite Team)
+* ✅ [.NET](https://github.com/appwrite/sdk-for-dotnet) - **Experimental** (Maintained by the Appwrite Team)
+* ✅ [Dart](https://github.com/appwrite/sdk-for-dart) **Experimental** (Maintained by the Appwrite Team)
+* ✅ [Go](https://github.com/appwrite/sdk-for-go) **Work in progress** (Maintained by the Appwrite Team)
Looking for more SDKs? - Help us by contributing a pull request to our [SDK Generator](https://github.com/appwrite/sdk-generator)!
diff --git a/app/config/environments.php b/app/config/environments.php
index 11773eed4f..3e483829b7 100644
--- a/app/config/environments.php
+++ b/app/config/environments.php
@@ -1,11 +1,22 @@
[
+ 'node-14.5' => [
'name' => 'Node.js',
'version' => '14.5',
'base' => 'node:14.5-alpine',
'image' => 'appwrite/env-node-14.5:1.0.0',
+ 'build' => '/usr/src/code/docker/environments/node-14.5',
+ 'logo' => 'node.png',
+ ],
+ 'node-15.5' => [
+ 'name' => 'Node.js',
+ 'version' => '15.5',
+ 'base' => 'node:15.5-alpine',
+ 'image' => 'appwrite/env-node-15.5:1.0.0',
+ 'build' => '/usr/src/code/docker/environments/node-15.5',
'logo' => 'node.png',
],
'php-7.4' => [
@@ -13,6 +24,7 @@ return [
'version' => '7.4',
'base' => 'php:7.4-cli-alpine',
'image' => 'appwrite/env-php-7.4:1.0.0',
+ 'build' => '/usr/src/code/docker/environments/php-7.4',
'logo' => 'php.png',
],
'php-8.0' => [
@@ -20,6 +32,7 @@ return [
'version' => '8.0',
'base' => 'php:8.0-cli-alpine',
'image' => 'appwrite/env-php-8.0:1.0.0',
+ 'build' => '/usr/src/code/docker/environments/php-8.0',
'logo' => 'php.png',
],
'ruby-2.7' => [
@@ -27,6 +40,15 @@ return [
'version' => '2.7',
'base' => 'ruby:2.7-alpine',
'image' => 'appwrite/env-ruby-2.7:1.0.2',
+ 'build' => '/usr/src/code/docker/environments/ruby-2.7',
+ 'logo' => 'ruby.png',
+ ],
+ 'ruby-3.0' => [
+ 'name' => 'Ruby',
+ 'version' => '3.0',
+ 'base' => 'ruby:3.0-alpine',
+ 'image' => 'appwrite/env-ruby-3.0:1.0.0',
+ 'build' => '/usr/src/code/docker/environments/ruby-3.0',
'logo' => 'ruby.png',
],
'python-3.8' => [
@@ -34,6 +56,7 @@ return [
'version' => '3.8',
'base' => 'python:3.8-alpine',
'image' => 'appwrite/env-python-3.8:1.0.0',
+ 'build' => '/usr/src/code/docker/environments/python-3.8',
'logo' => 'python.png',
],
'deno-1.2' => [
@@ -41,6 +64,7 @@ return [
'version' => '1.2',
'base' => 'hayd/deno:alpine-1.2.0',
'image' => 'appwrite/env-deno-1.2:1.0.0',
+ 'build' => '/usr/src/code/docker/environments/deno-1.2',
'logo' => 'deno.png',
],
'deno-1.5' => [
@@ -48,6 +72,15 @@ return [
'version' => '1.5',
'base' => 'hayd/deno:alpine-1.5.0',
'image' => 'appwrite/env-deno-1.5:1.0.0',
+ 'build' => '/usr/src/code/docker/environments/deno-1.5',
+ 'logo' => 'deno.png',
+ ],
+ 'deno-1.6' => [
+ 'name' => 'Deno',
+ 'version' => '1.6',
+ 'base' => 'hayd/deno:alpine-1.6.0',
+ 'image' => 'appwrite/env-deno-1.6:1.0.0',
+ 'build' => '/usr/src/code/docker/environments/deno-1.6',
'logo' => 'deno.png',
],
// 'dart-2.8' => [
@@ -55,6 +88,7 @@ return [
// 'version' => '2.8',
// 'base' => 'google/dart:2.8',
// 'image' => 'appwrite/env-dart:2.8',
+ // 'build' => '/usr/src/code/docker/environments/dart-2.8',
// 'logo' => 'dart.png',
// ],
];
\ No newline at end of file
diff --git a/app/config/platforms.php b/app/config/platforms.php
index c9a3f25b59..3a45f7e7c1 100644
--- a/app/config/platforms.php
+++ b/app/config/platforms.php
@@ -96,6 +96,7 @@ return [
// 'enabled' => false,
// 'dev' => false,
// 'beta' => false,
+ // 'dev' => false,
// 'family' => APP_PLATFORM_CLIENT,
// 'prism' => 'java',
// 'source' => false,
@@ -250,17 +251,33 @@ return [
'gitRepoName' => 'sdk-for-java',
'gitUserName' => 'appwrite',
],
+ [
+ 'key' => 'dotnet',
+ 'name' => '.NET',
+ 'version' => '0.0.3',
+ 'url' => 'https://github.com/appwrite/sdk-for-dotnet',
+ 'package' => 'https://www.nuget.org/packages/Appwrite',
+ 'enabled' => true,
+ 'beta' => true,
+ 'dev' => true,
+ 'family' => APP_PLATFORM_SERVER,
+ 'prism' => 'csharp',
+ 'source' => \realpath(__DIR__ . '/../sdks/server-dotnet'),
+ 'gitUrl' => 'git@github.com:appwrite/sdk-for-dotnet.git',
+ 'gitRepoName' => 'sdk-for-dotnet',
+ 'gitUserName' => 'appwrite',
+ ],
[
'key' => 'dart',
'name' => 'Dart',
- 'version' => '0.0.1',
+ 'version' => '0.1.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
- 'package' => '',
- 'enabled' => false,
+ 'package' => 'https://pub.dev/packages/dart_appwrite',
+ 'enabled' => true,
'beta' => true,
- 'dev' => false,
+ 'dev' => true,
'family' => APP_PLATFORM_SERVER,
- 'prism' => 'java',
+ 'prism' => 'dart',
'source' => \realpath(__DIR__ . '/../sdks/server-dart'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-dart.git',
'gitRepoName' => 'sdk-for-dart',
diff --git a/app/config/roles.php b/app/config/roles.php
index 8dfaf78eb4..78dd24ad45 100644
--- a/app/config/roles.php
+++ b/app/config/roles.php
@@ -1,15 +1,8 @@
[
+ Auth::USER_ROLE_GUEST => [
'label' => 'Guest',
'scopes' => [
'public',
@@ -71,23 +64,23 @@ return [
'execution.write',
],
],
- ROLE_MEMBER => [
+ Auth::USER_ROLE_MEMBER => [
'label' => 'Member',
- 'scopes' => \array_merge($logged, []),
+ 'scopes' => \array_merge($member, []),
],
- ROLE_ADMIN => [
+ Auth::USER_ROLE_ADMIN => [
'label' => 'Admin',
'scopes' => \array_merge($admins, []),
],
- ROLE_DEVELOPER => [
+ Auth::USER_ROLE_DEVELOPER => [
'label' => 'Developer',
'scopes' => \array_merge($admins, []),
],
- ROLE_OWNER => [
+ Auth::USER_ROLE_OWNER => [
'label' => 'Owner',
- 'scopes' => \array_merge($logged, $admins, []),
+ 'scopes' => \array_merge($member, $admins, []),
],
- ROLE_APP => [
+ Auth::USER_ROLE_APP => [
'label' => 'Application',
'scopes' => ['health.read'],
],
diff --git a/app/config/variables.php b/app/config/variables.php
index 0893dfd252..6215b18f7c 100644
--- a/app/config/variables.php
+++ b/app/config/variables.php
@@ -2,177 +2,369 @@
return [
[
- 'name' => '_APP_ENV',
- 'default' => 'production',
- 'required' => false,
- 'question' => '',
+ 'category' => 'General',
+ 'description' => '',
+ 'variables' => [
+ [
+ 'name' => '_APP_ENV',
+ 'description' => 'Set your server running environment. By default, the var is set to \'development\'. When deploying to production, change it to: \'production\'.',
+ 'introduction' => '',
+ 'default' => 'production',
+ '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.',
+ 'introduction' => '',
+ 'default' => 'enabled',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ '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',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_OPENSSL_KEY_V1',
+ 'description' => 'This is your server private secret key that is used to encrypt all sensitive data on your server. Appwrite server encrypts all secret data on your server like webhooks, HTTP passwords, user sessions, and storage files. The var is not set by default, if you wish to take advantage of Appwrite encryption capabilities you should change it and make sure to **keep it a secret and have a backup for it**.',
+ 'introduction' => '',
+ 'default' => 'your-secret-key',
+ 'required' => true,
+ 'question' => 'Choose a secret API key, make sure to make a backup of your key in a secure location',
+ ],
+ [
+ 'name' => '_APP_DOMAIN',
+ 'description' => 'Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is \'localhost\'.',
+ 'introduction' => '',
+ 'default' => 'localhost',
+ 'required' => true,
+ 'question' => 'Enter your Appwrite hostname',
+ ],
+ [
+ 'name' => '_APP_DOMAIN_TARGET',
+ 'description' => 'A DNS A record hostname to serve as a CNAME target for your Appwrite custom domains. You can use the same value as used for the Appwrite \'_APP_DOMAIN\' variable. The default value is \'localhost\'.',
+ 'introduction' => '',
+ 'default' => 'localhost',
+ 'required' => true,
+ 'question' => "Enter a DNS A record hostname to serve as a CNAME for your custom domains.\nYou can use the same value as used for the Appwrite hostname.",
+ ],
+ [
+ 'name' => '_APP_CONSOLE_WHITELIST_EMAILS',
+ 'description' => 'This option allows you to limit creation of users to Appwrite console. This option is very useful for small teams or sole developers. To enable it, pass a list of allowed email addresses separated by a comma.',
+ 'introduction' => '',
+ 'default' => '',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_CONSOLE_WHITELIST_DOMAINS',
+ 'description' => "This option allows you to limit creation of users to Appwrite console for users sharing the same email domains. This option is very useful for team working with company emails domain.\n\nTo enable this option, pass a list of allowed email domains separated by a comma.",
+ 'introduction' => '',
+ 'default' => '',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_CONSOLE_WHITELIST_IPS',
+ 'description' => "This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP.\n\nTo enable/activate this option, pass a list of allowed IP addresses separated by a comma.",
+ 'introduction' => '',
+ 'default' => '',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_SYSTEM_EMAIL_NAME',
+ 'description' => 'This is the sender name value that will appear on email messages sent to developers from the Appwrite console. The default value is: \'Appwrite\'. You can use url encoded strings for spaces and special chars.',
+ 'introduction' => '0.7.0',
+ 'default' => 'Appwrite',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_SYSTEM_EMAIL_ADDRESS',
+ 'description' => 'This is the sender email address that will appear on email messages sent to developers from the Appwrite console. The default value is \'team@appwrite.io\'. You should choose an email address that is allowed to be used from your SMTP server to avoid the server email ending in the users\' SPAM folders.',
+ 'introduction' => '0.7.0',
+ 'default' => 'team@appwrite.io',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_SYSTEM_RESPONSE_FORMAT',
+ 'description' => 'Use this environment variable to set the default Appwrite HTTP response format to support an older version of Appwrite. This option is useful to overcome breaking changes between versions. You can also use the `X-Appwrite-Response-Format` HTTP request header to overwrite the response for a specific request. This variable accepts any valid Appwrite version. To use the current version format, leave the value of the variable empty.',
+ 'introduction' => '0.7.0',
+ 'default' => '',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_USAGE_STATS',
+ 'description' => 'This variable allows you to disable the collection and displaying of usage stats. This value is set to \'enabled\' by default, to disable the usage stats set the value to \'disabled\'. When disabled, it\'s recommended to turn off the Worker Usage, Influxdb and Telegraf containers for better resource usage.',
+ 'introduction' => '0.7.0',
+ 'default' => 'enabled',
+ 'required' => false,
+ 'question' => '',
+ ],
+ ],
],
[
- 'name' => '_APP_OPTIONS_ABUSE',
- 'default' => 'enabled',
- 'required' => false,
- 'question' => '',
+ 'category' => 'Redis',
+ 'description' => 'Appwrite uses a Redis server for managing cache, queues and scheduled tasks. The Redis env vars are used to allow Appwrite server to connect to the Redis container.',
+ 'variables' => [
+ [
+ 'name' => '_APP_REDIS_HOST',
+ 'description' => 'Redis server hostname address. Default value is: \'redis\'.',
+ 'introduction' => '',
+ 'default' => 'redis',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_REDIS_PORT',
+ 'description' => 'Redis server TCP port. Default value is: \'6379\'.',
+ 'introduction' => '',
+ 'default' => '6379',
+ 'required' => false,
+ 'question' => '',
+ ],
+ ],
],
[
- 'name' => '_APP_OPTIONS_FORCE_HTTPS',
- 'default' => 'enabled',
- 'required' => false,
- 'question' => '',
+ 'category' => 'MariaDB',
+ 'description' => 'Appwrite is using a MariaDB server for managing persistent database data. The MariaDB env vars are used to allow Appwrite server to connect to the MariaDB container.',
+ 'variables' => [
+ [
+ 'name' => '_APP_DB_HOST',
+ 'description' => 'MariaDB server host name address. Default value is: \'mariadb\'.',
+ 'introduction' => '',
+ 'default' => 'mariadb',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_DB_PORT',
+ 'description' => 'MariaDB server TCP port. Default value is: \'3306\'.',
+ 'introduction' => '',
+ 'default' => '3306',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_DB_SCHEMA',
+ 'description' => 'MariaDB server database schema. Default value is: \'appwrite\'.',
+ 'introduction' => '',
+ 'default' => 'appwrite',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_DB_USER',
+ 'description' => 'MariaDB server user name. Default value is: \'root\'.',
+ 'introduction' => '',
+ 'default' => 'user',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_DB_PASS',
+ 'description' => 'MariaDB server user password. Default value is: \'password\'.',
+ 'introduction' => '',
+ 'default' => 'password',
+ 'required' => false,
+ 'question' => '',
+ ],
+ ],
],
[
- 'name' => '_APP_OPENSSL_KEY_V1',
- 'default' => 'your-secret-key',
- 'required' => true,
- 'question' => 'Choose a secret API key, make sure to make a backup of your key in a secure location',
+ 'category' => 'InfluxDB',
+ 'description' => 'Appwrite uses an InfluxDB server for managing time-series data and server stats. The InfluxDB env vars are used to allow Appwrite server to connect to the InfluxDB container.',
+ 'variables' => [
+ [
+ 'name' => '_APP_INFLUXDB_HOST',
+ 'description' => 'InfluxDB server host name address. Default value is: \'influxdb\'.',
+ 'introduction' => '',
+ 'default' => 'influxdb',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_INFLUXDB_PORT',
+ 'description' => 'InfluxDB server TCP port. Default value is: \'8086\'.',
+ 'introduction' => '',
+ 'default' => '8086',
+ 'required' => false,
+ 'question' => '',
+ ],
+ ],
],
[
- 'name' => '_APP_DOMAIN',
- 'default' => 'localhost',
- 'required' => true,
- 'question' => 'Enter your Appwrite hostname',
+ 'category' => 'StatsD',
+ 'description' => 'Appwrite uses a StatsD server for aggregating and sending stats data over a fast UDP connection. The StatsD env vars are used to allow Appwrite server to connect to the StatsD container.',
+ 'variables' => [
+ [
+ 'name' => '_APP_STATSD_HOST',
+ 'description' => 'StatsD server host name address. Default value is: \'telegraf\'.',
+ 'introduction' => '',
+ 'default' => 'telegraf',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_STATSD_PORT',
+ 'description' => 'StatsD server TCP port. Default value is: \'8125\'.',
+ 'introduction' => '',
+ 'default' => '8125',
+ 'required' => false,
+ 'question' => '',
+ ],
+ ],
],
[
- 'name' => '_APP_DOMAIN_TARGET',
- 'default' => 'localhost',
- 'required' => true,
- 'question' => "Enter a DNS A record hostname to serve as a CNAME for your custom domains.\nYou can use the same value as used for the Appwrite hostname.",
+ 'category' => 'SMTP',
+ 'description' => "Appwrite is using an SMTP server for emailing your projects users and server admins. The SMTP env vars are used to allow Appwrite server to connect to the SMTP container.\n\nIf running in production, it might be easier to use a 3rd party SMTP server as it might be a little more difficult to set up a production SMTP server that will not send all your emails into your user's SPAM folder.",
+ 'variables' => [
+ [
+ 'name' => '_APP_SMTP_HOST',
+ 'description' => 'SMTP server host name address. Default value is: \'smtp\'. Pass an empty string to disable all mail sending from the server.',
+ 'introduction' => '',
+ 'default' => 'smtp',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_SMTP_PORT',
+ 'description' => 'SMTP server TCP port. Default value is: \'25\'.',
+ 'introduction' => '',
+ 'default' => '25',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_SMTP_SECURE',
+ 'description' => 'SMTP secure connection protocol. Empty by default, change to \'tls\' if running on a secure connection.',
+ 'introduction' => '',
+ 'default' => '',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_SMTP_USERNAME',
+ 'description' => 'SMTP server user name. Empty by default.',
+ 'introduction' => '',
+ 'default' => '',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_SMTP_PASSWORD',
+ 'description' => 'SMTP server user password. Empty by default.',
+ 'introduction' => '',
+ 'default' => '',
+ 'required' => false,
+ 'question' => '',
+ ],
+ ],
],
[
- 'name' => '_APP_REDIS_HOST',
- 'default' => 'redis',
- 'required' => false,
- 'question' => '',
+ 'category' => 'Storage',
+ 'description' => '',
+ 'variables' => [
+ [
+ 'name' => '_APP_STORAGE_LIMIT',
+ 'description' => 'Maximun file size allowed for file upload. The default value is 10MB limitation. You should pass your size limit value in bytes.',
+ 'introduction' => '0.7.0',
+ 'default' => '10000000',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ '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.',
+ 'introduction' => '',
+ 'default' => 'enabled',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_STORAGE_ANTIVIRUS_HOST',
+ 'description' => 'ClamAV server host name address. Default value is: \'clamav\'.',
+ 'introduction' => '0.7.0',
+ 'default' => 'clamav',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_STORAGE_ANTIVIRUS_PORT',
+ 'description' => 'ClamAV server TCP port. Default value is: \'3310\'.',
+ 'introduction' => '0.7.0',
+ 'default' => '3310',
+ 'required' => false,
+ 'question' => '',
+ ],
+ ],
],
[
- 'name' => '_APP_REDIS_PORT',
- 'default' => '6379',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_DB_HOST',
- 'default' => 'mariadb',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_DB_PORT',
- 'default' => '3306',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_DB_SCHEMA',
- 'default' => 'appwrite',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_DB_USER',
- 'default' => 'user',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_DB_PASS',
- 'default' => 'password',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_INFLUXDB_HOST',
- 'default' => 'influxdb',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_INFLUXDB_PORT',
- 'default' => '8086',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_STATSD_HOST',
- 'default' => 'telegraf',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_STATSD_PORT',
- 'default' => '8125',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_SMTP_HOST',
- 'default' => 'smtp',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_SMTP_PORT',
- 'default' => '25',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_SMTP_SECURE',
- 'default' => '',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_SMTP_USERNAME',
- 'default' => '',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_SMTP_PASSWORD',
- 'default' => '',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_STORAGE_LIMIT',
- 'default' => '10000000',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_FUNCTIONS_TIMEOUT',
- 'default' => '900',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_FUNCTIONS_CONTAINERS',
- 'default' => '10',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_FUNCTIONS_CPUS',
- 'default' => '1',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_FUNCTIONS_MEMORY',
- 'default' => '128',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_FUNCTIONS_MEMORY_SWAP',
- 'default' => '128',
- 'required' => false,
- 'question' => '',
- ],
- [
- 'name' => '_APP_MAINTENANCE_INTERVAL',
- 'default' => '86400',
- 'required' => false,
- 'question' => '',
+ 'category' => 'Functions',
+ 'description' => '',
+ 'variables' => [
+ [
+ 'name' => '_APP_FUNCTIONS_TIMEOUT',
+ 'description' => 'The maximum number of seconds allowed as a timeout value when creating a new function. The default value is 900 seconds.',
+ 'introduction' => '0.7.0',
+ 'default' => '900',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_FUNCTIONS_CONTAINERS',
+ 'description' => 'The maximum number of containers Appwrite is allowed to keep alive in the background for function environments. Running containers allow faster execution time as there is no need to recreate each container every time a function gets executed. The default value is 10.',
+ 'introduction' => '0.7.0',
+ 'default' => '10',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_FUNCTIONS_CPUS',
+ 'description' => 'The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is 1.',
+ 'introduction' => '0.7.0',
+ 'default' => '1',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_FUNCTIONS_MEMORY',
+ 'description' => 'The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is 128.',
+ 'introduction' => '0.7.0',
+ 'default' => '128',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_FUNCTIONS_MEMORY_SWAP',
+ 'description' => 'The maximum amount of swap memory a single cloud function is allowed to use in megabytes. The default value is 128.',
+ 'introduction' => '0.7.0',
+ 'default' => '128',
+ 'required' => false,
+ 'question' => '',
+ ],
+ ],
+ [
+ 'category' => 'Maintenance',
+ 'description' => '',
+ 'variables' => [
+ [
+ 'name' => '_APP_MAINTENANCE_INTERVAL',
+ 'description' => 'Interval value containing the number of seconds that the Appwrite maintenance process should wait before executing system cleanups and optimizations. The default value is 86400 seconds (1 day).',
+ 'introduction' => '0.7.0',
+ 'default' => '86400',
+ 'required' => false,
+ 'question' => '',
+ ],
+ ],
+ ],
],
];
\ No newline at end of file
diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php
index fd06fc0eaf..9e366abbaf 100644
--- a/app/controllers/api/account.php
+++ b/app/controllers/api/account.php
@@ -1,5 +1,6 @@
desc('Create Account JWT')
+ ->groups(['api', 'account'])
+ ->label('scope', 'account')
+ ->label('sdk.platform', [APP_PLATFORM_CLIENT])
+ ->label('sdk.namespace', 'account')
+ ->label('sdk.method', 'createJWT')
+ ->label('sdk.description', '/docs/references/account/create-jwt.md')
+ ->label('abuse-limit', 10)
+ ->label('abuse-key', 'url:{url},userId:{param-userId}')
+ ->inject('response')
+ ->inject('user')
+ ->action(function ($response, $user) {
+ /** @var Appwrite\Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+
+ $tokens = $user->getAttribute('tokens', []);
+ $session = 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;
+ }
+ }
+
+ if($session->isEmpty()) {
+ throw new Exception('No valid session found', 401);
+ }
+
+ $jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->dynamic(new Document(['jwt' => $jwt->encode([
+ // 'uid' => 1,
+ // 'aud' => 'http://site.com',
+ // 'scopes' => ['user'],
+ // 'iss' => 'http://api.mysite.com',
+ 'userId' => $user->getId(),
+ 'sessionId' => $session->getId(),
+ ])]), Response::MODEL_JWT);
+ });
+
App::get('/v1/account')
->desc('Get Account')
->groups(['api', 'account'])
@@ -674,7 +718,7 @@ App::get('/v1/account/prefs')
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $user */
- $prefs = $user->getAttribute('prefs', new \stdClass);
+ $prefs = $user->getAttribute('prefs', new \stdClass());
$response->dynamic(new Document($prefs), Response::MODEL_ANY);
});
diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php
index 1059c6e1c7..fe2b53f71f 100644
--- a/app/controllers/api/avatars.php
+++ b/app/controllers/api/avatars.php
@@ -383,8 +383,9 @@ App::get('/v1/avatars/qr')
$download = ($download === '1' || $download === 'true' || $download === 1 || $download === true);
$options = new QROptions([
- 'quietzone' => $size,
- 'outputType' => QRCode::OUTPUT_IMAGICK
+ 'addQuietzone' => true,
+ 'quietzoneSize' => $margin,
+ 'outputType' => QRCode::OUTPUT_IMAGICK,
]);
$qrcode = new QRCode($options);
@@ -393,10 +394,14 @@ App::get('/v1/avatars/qr')
$response->addHeader('Content-Disposition', 'attachment; filename="qr.png"');
}
+ $resize = new Resize($qrcode->render($text));
+
+ $resize->crop((int) $size, (int) $size);
+
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
->setContentType('image/png')
- ->send($qrcode->render($text))
+ ->send($resize->output('png', 9))
;
});
diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php
index 1a21dfb02c..0572b8137c 100644
--- a/app/controllers/api/functions.php
+++ b/app/controllers/api/functions.php
@@ -156,96 +156,100 @@ App::get('/v1/functions/:functionId/usage')
if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
throw new Exception('Function not found', 404);
}
-
- $period = [
- '24h' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('-24 hours')),
- 'end' => DateTime::createFromFormat('U', \strtotime('+1 hour')),
- 'group' => '30m',
- ],
- '7d' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('-7 days')),
- 'end' => DateTime::createFromFormat('U', \strtotime('now')),
- 'group' => '1d',
- ],
- '30d' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')),
- 'end' => DateTime::createFromFormat('U', \strtotime('now')),
- 'group' => '1d',
- ],
- '90d' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')),
- 'end' => DateTime::createFromFormat('U', \strtotime('now')),
- 'group' => '1d',
- ],
- ];
-
- $client = $register->get('influxdb');
-
- $executions = [];
- $failures = [];
- $compute = [];
-
- if ($client) {
- $start = $period[$range]['start']->format(DateTime::RFC3339);
- $end = $period[$range]['end']->format(DateTime::RFC3339);
- $database = $client->selectDB('telegraf');
-
- // Executions
- $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
- $points = $result->getPoints();
-
- foreach ($points as $point) {
- $executions[] = [
- 'value' => (!empty($point['value'])) ? $point['value'] : 0,
- 'date' => \strtotime($point['time']),
- ];
- }
-
- // Failures
- $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' AND "functionStatus"=\'failed\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
- $points = $result->getPoints();
-
- foreach ($points as $point) {
- $failures[] = [
- 'value' => (!empty($point['value'])) ? $point['value'] : 0,
- 'date' => \strtotime($point['time']),
- ];
- }
-
- // Compute
- $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_time" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
- $points = $result->getPoints();
-
- foreach ($points as $point) {
- $compute[] = [
- 'value' => round((!empty($point['value'])) ? $point['value'] / 1000 : 0, 2), // minutes
- 'date' => \strtotime($point['time']),
- ];
+
+ if(App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
+ $period = [
+ '24h' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('-24 hours')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('+1 hour')),
+ 'group' => '30m',
+ ],
+ '7d' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('-7 days')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('now')),
+ 'group' => '1d',
+ ],
+ '30d' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('now')),
+ 'group' => '1d',
+ ],
+ '90d' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('now')),
+ 'group' => '1d',
+ ],
+ ];
+
+ $client = $register->get('influxdb');
+
+ $executions = [];
+ $failures = [];
+ $compute = [];
+
+ if ($client) {
+ $start = $period[$range]['start']->format(DateTime::RFC3339);
+ $end = $period[$range]['end']->format(DateTime::RFC3339);
+ $database = $client->selectDB('telegraf');
+
+ // Executions
+ $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
+ $points = $result->getPoints();
+
+ foreach ($points as $point) {
+ $executions[] = [
+ 'value' => (!empty($point['value'])) ? $point['value'] : 0,
+ 'date' => \strtotime($point['time']),
+ ];
+ }
+
+ // Failures
+ $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' AND "functionStatus"=\'failed\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
+ $points = $result->getPoints();
+
+ foreach ($points as $point) {
+ $failures[] = [
+ 'value' => (!empty($point['value'])) ? $point['value'] : 0,
+ 'date' => \strtotime($point['time']),
+ ];
+ }
+
+ // Compute
+ $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_time" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
+ $points = $result->getPoints();
+
+ foreach ($points as $point) {
+ $compute[] = [
+ 'value' => round((!empty($point['value'])) ? $point['value'] / 1000 : 0, 2), // minutes
+ 'date' => \strtotime($point['time']),
+ ];
+ }
}
+
+ $response->json([
+ 'range' => $range,
+ 'executions' => [
+ 'data' => $executions,
+ 'total' => \array_sum(\array_map(function ($item) {
+ return $item['value'];
+ }, $executions)),
+ ],
+ 'failures' => [
+ 'data' => $failures,
+ 'total' => \array_sum(\array_map(function ($item) {
+ return $item['value'];
+ }, $failures)),
+ ],
+ 'compute' => [
+ 'data' => $compute,
+ 'total' => \array_sum(\array_map(function ($item) {
+ return $item['value'];
+ }, $compute)),
+ ],
+ ]);
+ } else {
+ $response->json([]);
}
-
- $response->json([
- 'range' => $range,
- 'executions' => [
- 'data' => $executions,
- 'total' => \array_sum(\array_map(function ($item) {
- return $item['value'];
- }, $executions)),
- ],
- 'failures' => [
- 'data' => $failures,
- 'total' => \array_sum(\array_map(function ($item) {
- return $item['value'];
- }, $failures)),
- ],
- 'compute' => [
- 'data' => $compute,
- 'total' => \array_sum(\array_map(function ($item) {
- return $item['value'];
- }, $compute)),
- ],
- ]);
});
App::put('/v1/functions/:functionId')
@@ -629,6 +633,8 @@ App::post('/v1/functions/:functionId/executions')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_EXECUTION)
+ ->label('abuse-limit', 60)
+ ->label('abuse-time', 60)
->param('functionId', '', new UID(), 'Function unique ID.')
// ->param('async', 1, new Range(0, 1), 'Execute code asynchronously. Pass 1 for true, 0 for false. Default value is 1.', true)
->inject('response')
diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php
index 90efef16c5..21e9f2c94a 100644
--- a/app/controllers/api/health.php
+++ b/app/controllers/api/health.php
@@ -257,7 +257,8 @@ App::get('/v1/health/anti-virus')
throw new Exception('Anitvirus is disabled');
}
- $antiVirus = new Network('clamav', 3310);
+ $antiVirus = new Network(App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
+ (int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310));
$response->json([
'status' => (@$antiVirus->ping()) ? 'online' : 'offline',
@@ -286,10 +287,6 @@ App::get('/v1/health/stats') // Currently only used internally
$response
->json([
- 'server' => [
- 'name' => 'nginx',
- 'version' => \shell_exec('nginx -v 2>&1'),
- ],
'storage' => [
'used' => Storage::human($device->getDirectorySize($device->getRoot().'/')),
'partitionTotal' => Storage::human($device->getPartitionTotalSpace()),
diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php
index 74a26dd48a..0b22e40614 100644
--- a/app/controllers/api/projects.php
+++ b/app/controllers/api/projects.php
@@ -176,74 +176,82 @@ App::get('/v1/projects/:projectId/usage')
throw new Exception('Project not found', 404);
}
- $period = [
- '24h' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('-24 hours')),
- 'end' => DateTime::createFromFormat('U', \strtotime('+1 hour')),
- 'group' => '30m',
- ],
- '7d' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('-7 days')),
- 'end' => DateTime::createFromFormat('U', \strtotime('now')),
- 'group' => '1d',
- ],
- '30d' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')),
- 'end' => DateTime::createFromFormat('U', \strtotime('now')),
- 'group' => '1d',
- ],
- '90d' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')),
- 'end' => DateTime::createFromFormat('U', \strtotime('now')),
- 'group' => '1d',
- ],
- ];
+ if(App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
- $client = $register->get('influxdb');
-
- $requests = [];
- $network = [];
- $functions = [];
-
- if ($client) {
- $start = $period[$range]['start']->format(DateTime::RFC3339);
- $end = $period[$range]['end']->format(DateTime::RFC3339);
- $database = $client->selectDB('telegraf');
-
- // Requests
- $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
- $points = $result->getPoints();
-
- foreach ($points as $point) {
- $requests[] = [
- 'value' => (!empty($point['value'])) ? $point['value'] : 0,
- 'date' => \strtotime($point['time']),
- ];
- }
-
- // Network
- $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
- $points = $result->getPoints();
-
- foreach ($points as $point) {
- $network[] = [
- 'value' => (!empty($point['value'])) ? $point['value'] : 0,
- 'date' => \strtotime($point['time']),
- ];
- }
-
- // Functions
- $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
- $points = $result->getPoints();
-
- foreach ($points as $point) {
- $functions[] = [
- 'value' => (!empty($point['value'])) ? $point['value'] : 0,
- 'date' => \strtotime($point['time']),
- ];
+ $period = [
+ '24h' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('-24 hours')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('+1 hour')),
+ 'group' => '30m',
+ ],
+ '7d' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('-7 days')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('now')),
+ 'group' => '1d',
+ ],
+ '30d' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('now')),
+ 'group' => '1d',
+ ],
+ '90d' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('now')),
+ 'group' => '1d',
+ ],
+ ];
+
+ $client = $register->get('influxdb');
+
+ $requests = [];
+ $network = [];
+ $functions = [];
+
+ if ($client) {
+ $start = $period[$range]['start']->format(DateTime::RFC3339);
+ $end = $period[$range]['end']->format(DateTime::RFC3339);
+ $database = $client->selectDB('telegraf');
+
+ // Requests
+ $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
+ $points = $result->getPoints();
+
+ foreach ($points as $point) {
+ $requests[] = [
+ 'value' => (!empty($point['value'])) ? $point['value'] : 0,
+ 'date' => \strtotime($point['time']),
+ ];
+ }
+
+ // Network
+ $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
+ $points = $result->getPoints();
+
+ foreach ($points as $point) {
+ $network[] = [
+ 'value' => (!empty($point['value'])) ? $point['value'] : 0,
+ 'date' => \strtotime($point['time']),
+ ];
+ }
+
+ // Functions
+ $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
+ $points = $result->getPoints();
+
+ foreach ($points as $point) {
+ $functions[] = [
+ 'value' => (!empty($point['value'])) ? $point['value'] : 0,
+ 'date' => \strtotime($point['time']),
+ ];
+ }
}
+ } else {
+ $requests = [];
+ $network = [];
+ $functions = [];
}
+
// Users
$projectDB->getCollection([
diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php
index b7628e320b..b70fe9f950 100644
--- a/app/controllers/api/storage.php
+++ b/app/controllers/api/storage.php
@@ -96,7 +96,8 @@ App::post('/v1/storage/files')
$mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled
- $antiVirus = new Network('clamav', 3310);
+ $antiVirus = new Network(App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
+ (int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310));
if (!$antiVirus->fileScan($path)) {
$device->delete($path);
diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php
index 06dc87a42b..76e20aad98 100644
--- a/app/controllers/api/users.php
+++ b/app/controllers/api/users.php
@@ -165,7 +165,7 @@ App::get('/v1/users/:userId/prefs')
throw new Exception('User not found', 404);
}
- $prefs = $user->getAttribute('prefs', '');
+ $prefs = $user->getAttribute('prefs', new \stdClass());
$response->dynamic(new Document($prefs), Response::MODEL_ANY);
});
@@ -418,7 +418,6 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_NONE)
- ->label('abuse-limit', 100)
->param('userId', '', new UID(), 'User unique ID.')
->param('sessionId', null, new UID(), 'User unique session ID.')
->inject('response')
@@ -449,6 +448,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
}
}
+ // TODO : Response filter implementation
$response->noContent();
});
@@ -464,7 +464,6 @@ App::delete('/v1/users/:userId/sessions')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_NONE)
- ->label('abuse-limit', 100)
->param('userId', '', new UID(), 'User unique ID.')
->inject('response')
->inject('projectDB')
@@ -492,6 +491,7 @@ App::delete('/v1/users/:userId/sessions')
->setParam('payload', $response->output($user, Response::MODEL_USER))
;
+ // TODO : Response filter implementation
$response->noContent();
});
@@ -507,7 +507,6 @@ App::delete('/v1/users/:userId')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_NONE)
- ->label('abuse-limit', 100)
->param('userId', '', function () {return new UID();}, 'User unique ID.')
->inject('response')
->inject('projectDB')
@@ -553,5 +552,6 @@ App::delete('/v1/users/:userId')
->setParam('payload', $response->output($user, Response::MODEL_USER))
;
+ // TODO : Response filter implementation
$response->noContent();
});
diff --git a/app/controllers/general.php b/app/controllers/general.php
index d77b3d5607..445563301d 100644
--- a/app/controllers/general.php
+++ b/app/controllers/general.php
@@ -16,25 +16,20 @@ use Appwrite\Database\Validator\Authorization;
use Appwrite\Network\Validator\Origin;
use Appwrite\Storage\Device\Local;
use Appwrite\Storage\Storage;
+use Appwrite\Utopia\Response\Filters\V06;
use Utopia\CLI\Console;
Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
-App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $events, $audits, $usage, $deletes, $clients) {
+App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $clients) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $console */
/** @var Appwrite\Database\Document $project */
/** @var Appwrite\Database\Document $user */
/** @var Utopia\Locale\Locale $locale */
- /** @var Appwrite\Event\Event $events */
- /** @var Appwrite\Event\Event $audits */
- /** @var Appwrite\Event\Event $usage */
- /** @var Appwrite\Event\Event $deletes */
- /** @var Appwrite\Event\Event $functions */
-
/** @var bool $mode */
/** @var array $clients */
@@ -50,20 +45,17 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
throw new Exception('Missing or unknown project ID', 400);
}
- $console->setAttribute('platforms', [ // Allways allow current host
- '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS,
- 'name' => 'Current Host',
- 'type' => 'web',
- 'hostname' => $request->getHostname(),
- ], Document::SET_TYPE_APPEND);
-
$referrer = $request->getReferer();
$origin = \parse_url($request->getOrigin($referrer), PHP_URL_HOST);
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()).'://'.((\in_array($origin, $clients))
- ? $origin : 'localhost') . (!empty($port) ? ':'.$port : '');
+ ? $origin : 'localhost').(!empty($port) ? ':'.$port : '');
+
+ $refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible
+ ? $refDomain
+ : (!empty($protocol) ? $protocol : $request->getProtocol()).'://'.$origin.(!empty($port) ? ':'.$port : '');
$selfDomain = new Domain($request->getHostname());
$endDomain = new Domain((string)$origin);
@@ -94,6 +86,22 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
Storage::setDevice('files', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId()));
Storage::setDevice('functions', new Local(APP_STORAGE_FUNCTIONS.'/app-'.$project->getId()));
+ /*
+ * Response format
+ */
+ $responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
+ if ($responseFormat) {
+ switch($responseFormat) {
+ case version_compare ($responseFormat , '0.6.2', '<=') :
+ Response::setFilter(new V06());
+ break;
+ default:
+ throw new Exception('No filter available for response format : '.$responseFormat, 400);
+ }
+ } else {
+ Response::setFilter(null);
+ }
+
/*
* Security Headers
*
@@ -106,15 +114,13 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
}
$response->addHeader('Strict-Transport-Security', 'max-age='.(60 * 60 * 24 * 126)); // 126 days
- }
+ }
$response
->addHeader('Server', 'Appwrite')
- ->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url='.\urlencode($request->getURI()))
- //->addHeader('X-Frame-Options', ($refDomain == 'http://localhost') ? 'SAMEORIGIN' : 'ALLOW-FROM ' . $refDomain)
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
- ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-SDK-Version, Cache-Control, Expires, Pragma')
+ ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-SDK-Version, Cache-Control, Expires, Pragma')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $refDomain)
->addHeader('Access-Control-Allow-Credentials', 'true')
@@ -123,7 +129,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
/*
* Validate Client Domain - Check to avoid CSRF attack
* Adding Appwrite API domains to allow XDOMAIN communication
- * Skip this check for non-web platforms which are not requiredto send an origin header
+ * Skip this check for non-web platforms which are not required to send an origin header
*/
$origin = $request->getOrigin($request->getReferer(''));
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
@@ -162,27 +168,31 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
$roles = Config::getParam('roles', []);
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
-
- // Check if given key match project API keys
- $key = $project->search('secret', $request->getHeader('x-appwrite-key', ''), $project->getAttribute('keys', []));
-
- /*
- * Try app auth when we have project key and no user
- * Mock user to app and grant API key scopes in addition to default app scopes
- */
- if (null !== $key && $user->isEmpty()) {
- $user = new Document([
- '$id' => '',
- 'status' => Auth::USER_STATUS_ACTIVATED,
- 'email' => 'app.'.$project->getId().'@service.'.$request->getHostname(),
- 'password' => '',
- 'name' => $project->getAttribute('name', 'Untitled'),
- ]);
- $role = Auth::USER_ROLE_APP;
- $scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
+ $authKey = $request->getHeader('x-appwrite-key', '');
- Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
+ if (!empty($authKey)) { // API Key authentication
+ // Check if given key match project API keys
+ $key = $project->search('secret', $authKey, $project->getAttribute('keys', []));
+
+ /*
+ * Try app auth when we have project key and no user
+ * Mock user to app and grant API key scopes in addition to default app scopes
+ */
+ if ($key && $user->isEmpty()) {
+ $user = new Document([
+ '$id' => '',
+ 'status' => Auth::USER_STATUS_ACTIVATED,
+ 'email' => 'app.'.$project->getId().'@service.'.$request->getHostname(),
+ 'password' => '',
+ 'name' => $project->getAttribute('name', 'Untitled'),
+ ]);
+
+ $role = Auth::USER_ROLE_APP;
+ $scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
+
+ Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
+ }
}
if ($user->getId()) {
@@ -219,99 +229,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
throw new Exception('Password reset is required', 412);
}
- /*
- * Background Jobs
- */
-
- $events
- ->setParam('projectId', $project->getId())
- ->setParam('userId', $user->getId())
- ->setParam('event', $route->getLabel('event', ''))
- ->setParam('payload', [])
- ->setParam('functionId', null)
- ->setParam('executionId', null)
- ->setParam('trigger', 'event')
- ;
-
- $audits
- ->setParam('projectId', $project->getId())
- ->setParam('userId', $user->getId())
- ->setParam('event', '')
- ->setParam('resource', '')
- ->setParam('userAgent', $request->getUserAgent(''))
- ->setParam('ip', $request->getIP())
- ->setParam('data', [])
- ;
-
- $usage
- ->setParam('projectId', $project->getId())
- ->setParam('httpRequest', 1)
- ->setParam('httpUrl', $request->getHostname().$request->getURI())
- ->setParam('httpMethod', $request->getMethod())
- ->setParam('networkRequestSize', 0)
- ->setParam('networkResponseSize', 0)
- ->setParam('storage', 0)
- ;
-
- $deletes
- ->setParam('projectId', $project->getId())
- ;
-
-}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'events', 'audits', 'usage', 'deletes', 'clients']);
-
-App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $mode) {
- /** @var Utopia\App $utopia */
- /** @var Utopia\Swoole\Request $request */
- /** @var Appwrite\Utopia\Response $response */
- /** @var Appwrite\Database\Document $project */
- /** @var Appwrite\Event\Event $events */
- /** @var Appwrite\Event\Event $audits */
- /** @var Appwrite\Event\Event $usage */
- /** @var Appwrite\Event\Event $deletes */
- /** @var Appwrite\Event\Event $functions */
- /** @var bool $mode */
-
- if (!empty($events->getParam('event'))) {
- if(empty($events->getParam('payload'))) {
- $events->setParam('payload', $response->getPayload());
- }
-
- $webhooks = clone $events;
- $functions = clone $events;
-
- $webhooks
- ->setQueue('v1-webhooks')
- ->setClass('WebhooksV1')
- ->trigger();
-
- $functions
- ->setQueue('v1-functions')
- ->setClass('FunctionsV1')
- ->trigger();
- }
-
- if (!empty($audits->getParam('event'))) {
- $audits->trigger();
- }
-
- if (!empty($deletes->getParam('type')) && !empty($deletes->getParam('document'))) {
- $deletes->trigger();
- }
-
- $route = $utopia->match($request);
-
- if ($project->getId()
- && $mode !== APP_MODE_ADMIN //TODO: add check to make sure user is admin
- && !empty($route->getLabel('sdk.namespace', null))) { // Don't calculate console usage on admin mode
-
- $usage
- ->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
- ->setParam('networkResponseSize', $response->getSize())
- ->trigger()
- ;
- }
-
-}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'mode']);
+}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'clients']);
App::options(function ($request, $response) {
/** @var Utopia\Swoole\Request $request */
@@ -322,7 +240,7 @@ App::options(function ($request, $response) {
$response
->addHeader('Server', 'Appwrite')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
- ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-SDK-Version, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
+ ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-SDK-Version, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $origin)
->addHeader('Access-Control-Allow-Credentials', 'true')
@@ -341,8 +259,11 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) {
$template = ($route) ? $route->getLabel('error', null) : null;
if (php_sapi_name() === 'cli') {
- Console::error('[Error] Method: '.$route->getMethod());
- Console::error('[Error] URL: '.$route->getURL());
+ if($route) {
+ Console::error('[Error] Method: '.$route->getMethod());
+ Console::error('[Error] URL: '.$route->getURL());
+ }
+
Console::error('[Error] Type: '.get_class($error));
Console::error('[Error] Message: '.$error->getMessage());
Console::error('[Error] File: '.$error->getFile());
diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php
index d64c46a2cd..bc2707f249 100644
--- a/app/controllers/shared/api.php
+++ b/app/controllers/shared/api.php
@@ -1,17 +1,29 @@
getId()));
+ Storage::setDevice('functions', new Local(APP_STORAGE_FUNCTIONS.'/app-'.$project->getId()));
$route = $utopia->match($request);
@@ -49,7 +61,105 @@ App::init(function ($utopia, $request, $response, $project, $user, $register) {
;
}
- if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
+ $isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles);
+ $isAppUser = Auth::isAppUser(Authorization::$roles);
+
+ if (($abuse->check() // Route is rate-limited
+ && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not diabled
+ && (!$isAppUser && !$isPreviliggedUser)) // User is not an admin or API key
+ {
throw new Exception('Too many requests', 429);
}
-}, ['utopia', 'request', 'response', 'project', 'user', 'register'], 'api');
\ No newline at end of file
+
+ /*
+ * Background Jobs
+ */
+ $events
+ ->setParam('projectId', $project->getId())
+ ->setParam('userId', $user->getId())
+ ->setParam('event', $route->getLabel('event', ''))
+ ->setParam('payload', [])
+ ->setParam('functionId', null)
+ ->setParam('executionId', null)
+ ->setParam('trigger', 'event')
+ ;
+
+ $audits
+ ->setParam('projectId', $project->getId())
+ ->setParam('userId', $user->getId())
+ ->setParam('event', '')
+ ->setParam('resource', '')
+ ->setParam('userAgent', $request->getUserAgent(''))
+ ->setParam('ip', $request->getIP())
+ ->setParam('data', [])
+ ;
+
+ $usage
+ ->setParam('projectId', $project->getId())
+ ->setParam('httpRequest', 1)
+ ->setParam('httpUrl', $request->getHostname().$request->getURI())
+ ->setParam('httpMethod', $request->getMethod())
+ ->setParam('networkRequestSize', 0)
+ ->setParam('networkResponseSize', 0)
+ ->setParam('storage', 0)
+ ;
+
+ $deletes
+ ->setParam('projectId', $project->getId())
+ ;
+
+}, ['utopia', 'request', 'response', 'project', 'user', 'register', 'events', 'audits', 'usage', 'deletes'], 'api');
+
+App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $mode) {
+ /** @var Utopia\App $utopia */
+ /** @var Utopia\Swoole\Request $request */
+ /** @var Appwrite\Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Event\Event $events */
+ /** @var Appwrite\Event\Event $audits */
+ /** @var Appwrite\Event\Event $usage */
+ /** @var Appwrite\Event\Event $deletes */
+ /** @var Appwrite\Event\Event $functions */
+ /** @var bool $mode */
+
+ if (!empty($events->getParam('event'))) {
+ if(empty($events->getParam('payload'))) {
+ $events->setParam('payload', $response->getPayload());
+ }
+
+ $webhooks = clone $events;
+ $functions = clone $events;
+
+ $webhooks
+ ->setQueue('v1-webhooks')
+ ->setClass('WebhooksV1')
+ ->trigger();
+
+ $functions
+ ->setQueue('v1-functions')
+ ->setClass('FunctionsV1')
+ ->trigger();
+ }
+
+ if (!empty($audits->getParam('event'))) {
+ $audits->trigger();
+ }
+
+ if (!empty($deletes->getParam('type')) && !empty($deletes->getParam('document'))) {
+ $deletes->trigger();
+ }
+
+ $route = $utopia->match($request);
+ if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
+ && $project->getId()
+ && $mode !== APP_MODE_ADMIN //TODO: add check to make sure user is admin
+ && !empty($route->getLabel('sdk.namespace', null))) { // Don't calculate console usage on admin mode
+
+ $usage
+ ->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
+ ->setParam('networkResponseSize', $response->getSize())
+ ->trigger()
+ ;
+ }
+
+}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'mode'], 'api');
diff --git a/app/controllers/shared/web.php b/app/controllers/shared/web.php
index 42d2a18c55..43eaf74d09 100644
--- a/app/controllers/shared/web.php
+++ b/app/controllers/shared/web.php
@@ -37,6 +37,7 @@ App::init(function ($utopia, $request, $response, $layout) {
->addHeader('Cache-Control', 'public, max-age='.$time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
+ ->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url='.\urlencode($request->getURI()))
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
;
diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php
index bddc8ea8bb..b8fc5a621e 100644
--- a/app/controllers/web/console.php
+++ b/app/controllers/web/console.php
@@ -122,7 +122,8 @@ App::get('/console/home')
/** @var Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/home/index.phtml');
-
+ $page
+ ->setParam('usageStatsEnabled',App::getEnv('_APP_USAGE_STATS','enabled') == 'enabled');
$layout
->setParam('title', APP_NAME.' - Console')
->setParam('body', $page);
@@ -390,6 +391,7 @@ App::get('/console/functions/function')
->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
->setParam('timeout', (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900))
+ ->setParam('usageStatsEnabled',App::getEnv('_APP_USAGE_STATS','enabled') == 'enabled');
;
$layout
diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php
index 5b4e9b7314..8dc9769860 100644
--- a/app/controllers/web/home.php
+++ b/app/controllers/web/home.php
@@ -191,7 +191,7 @@ App::get('/error/:code')
$layout
->setParam('title', 'Error'.' - '.APP_NAME)
->setParam('body', $page);
- }, ['']);
+ });
App::get('/specs/:format')
->groups(['web', 'home'])
diff --git a/app/http.php b/app/http.php
index 2197201d85..74fb950825 100644
--- a/app/http.php
+++ b/app/http.php
@@ -20,7 +20,7 @@ ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
-$http = new Server("0.0.0.0", 80);
+$http = new Server("0.0.0.0", App::getEnv('PORT', 80));
$payloadSize = max(4000000 /* 4mb */, App::getEnv('_APP_STORAGE_LIMIT', 10000000 /* 10mb */));
@@ -48,6 +48,7 @@ $http->on('AfterReload', function($serv, $workerId) {
});
$http->on('start', function (Server $http) use ($payloadSize) {
+
Console::success('Server started succefully (max payload is '.number_format($payloadSize).' bytes)');
Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
diff --git a/app/init.php b/app/init.php
index 887cac9b23..977efd94d6 100644
--- a/app/init.php
+++ b/app/init.php
@@ -11,6 +11,8 @@ if (\file_exists(__DIR__.'/../vendor/autoload.php')) {
require_once __DIR__.'/../vendor/autoload.php';
}
+use Ahc\Jwt\JWT;
+use Ahc\Jwt\JWTException;
use Appwrite\Auth\Auth;
use Appwrite\Database\Database;
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
@@ -319,7 +321,14 @@ App::setResource('deletes', function($register) {
}, ['register']);
// Test Mock
-App::setResource('clients', function($console, $project) {
+App::setResource('clients', function($request, $console, $project) {
+ $console->setAttribute('platforms', [ // Allways allow current host
+ '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS,
+ 'name' => 'Current Host',
+ 'type' => 'web',
+ 'hostname' => $request->getHostname(),
+ ], Document::SET_TYPE_APPEND);
+
/**
* Get All verified client URLs for both console and current projects
* + Filter for duplicated entries
@@ -345,7 +354,7 @@ App::setResource('clients', function($console, $project) {
}))));
return $clients;
-}, ['console', 'project']);
+}, ['request', 'console', 'project']);
App::setResource('user', function($mode, $project, $console, $request, $response, $projectDB, $consoleDB) {
/** @var Utopia\Swoole\Request $request */
@@ -405,6 +414,29 @@ App::setResource('user', function($mode, $project, $console, $request, $response
}
}
+ $authJWT = $request->getHeader('x-appwrite-jwt', '');
+
+ if (!empty($authJWT)) { // JWT authentication
+ $jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
+
+ try {
+ $payload = $jwt->decode($authJWT);
+ } catch (JWTException $error) {
+ throw new Exception('Failed to verify JWT. '.$error->getMessage(), 401);
+ }
+
+ $jwtUserId = $payload['userId'] ?? '';
+ $jwtSessionId = $payload['sessionId'] ?? '';
+
+ if($jwtUserId && $jwtSessionId) {
+ $user = $projectDB->getDocument($jwtUserId);
+ }
+
+ if (empty($user->search('$id', $jwtSessionId, $user->getAttribute('tokens')))) { // Match JWT to active token
+ $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]);
+ }
+ }
+
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'projectDB', 'consoleDB']);
diff --git a/app/tasks/doctor.php b/app/tasks/doctor.php
index 70a7546b2a..e3985c8e75 100644
--- a/app/tasks/doctor.php
+++ b/app/tasks/doctor.php
@@ -113,7 +113,8 @@ $cli
if(App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled
try {
- $antiVirus = new Network('clamav', 3310);
+ $antiVirus = new Network(App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
+ (int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310));
if((@$antiVirus->ping())) {
Console::success('AntiVirus...........connected 👍');
diff --git a/app/tasks/install.php b/app/tasks/install.php
index b28391d6d4..8ca090c772 100644
--- a/app/tasks/install.php
+++ b/app/tasks/install.php
@@ -30,10 +30,17 @@ $cli
* 5. Run docker-compose up -d - DONE
* 6. Run data migration
*/
- $vars = Config::getParam('variables');
+ $config = Config::getParam('variables');
$path = '/usr/src/code/appwrite';
$defaultHTTPPort = '80';
$defaultHTTPSPort = '443';
+ $vars = [];
+
+ foreach($config as $category) {
+ foreach($category['variables'] ?? [] as $var) {
+ $vars[] = $var;
+ }
+ }
Console::success('Starting Appwrite installation...');
diff --git a/app/tasks/maintenance.php b/app/tasks/maintenance.php
index f8786bcac8..34c34cfb83 100644
--- a/app/tasks/maintenance.php
+++ b/app/tasks/maintenance.php
@@ -4,23 +4,13 @@ global $cli;
require_once __DIR__.'/../init.php';
-use Appwrite\Database\Database;
-use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
-use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Event\Event;
use Utopia\App;
use Utopia\CLI\Console;
-use Utopia\Config\Config;
-// TODO: Think of a better way to access consoleDB
-function getConsoleDB() {
- global $register;
- $consoleDB = new Database();
- $consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register));
- $consoleDB->setNamespace('app_console'); // Main DB
- $consoleDB->setMocks(Config::getParam('collections', []));
- return $consoleDB;
-}
+Console::title('Maintenance V1');
+
+Console::success(APP_NAME.' maintenance process v1 has started');
function notifyDeleteExecutionLogs()
{
@@ -51,17 +41,14 @@ $cli
->action(function () {
// # of days in seconds (1 day = 86400s)
$interval = (int) App::getEnv('_APP_MAINTENANCE_INTERVAL', '86400');
- //Convert Seconds to microseconds
- $intervalMicroseconds = $interval * 1000000;
- $consoleDB = getConsoleDB();
-
- Console::loop(function() use ($consoleDB, $interval){
- Console::info("[ MAINTENANCE TASK ] Notifying deletes workers every {$interval} seconds");
+ Console::loop(function() use ($interval){
+ $time = date('d-m-Y H:i:s', time());
+ Console::info("[{$time}] Notifying deletes workers every {$interval} seconds");
notifyDeleteExecutionLogs();
notifyDeleteAbuseLogs($interval);
notifyDeleteAuditLogs($interval);
- }, $intervalMicroseconds);
+ }, $interval);
});
\ No newline at end of file
diff --git a/app/tasks/sdks.php b/app/tasks/sdks.php
index 857b4de7b5..50d2601150 100644
--- a/app/tasks/sdks.php
+++ b/app/tasks/sdks.php
@@ -11,6 +11,7 @@ use Appwrite\SDK\Language\Python;
use Appwrite\SDK\Language\Ruby;
use Appwrite\SDK\Language\Dart;
use Appwrite\SDK\Language\Deno;
+use Appwrite\SDK\Language\DotNet;
use Appwrite\SDK\Language\Flutter;
use Appwrite\SDK\Language\Go;
use Appwrite\SDK\Language\Java;
@@ -39,7 +40,6 @@ $cli
$git = (Console::confirm('Should we use git push? (yes/no)') == 'yes');
$production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false;
$message = ($git) ? Console::confirm('Please enter your commit message:') : '';
- $warning = '**This SDK is compatible with Appwrite server version ' . $version . '. For older versions, please check previous releases.**';
if(!in_array($version, ['0.6.2', '0.7.0'])) {
throw new Exception('Unknown version given');
@@ -60,6 +60,7 @@ $cli
$spec = file_get_contents(__DIR__.'/../config/specs/'.$version.'.'.$language['family'].'.json');
+ $cover = 'https://appwrite.io/images/github.png';
$result = \realpath(__DIR__.'/..').'/sdks/'.$key.'-'.$language['key'];
$resultExamples = \realpath(__DIR__.'/../..').'/docs/examples/'.$version.'/'.$key.'-'.$language['key'];
$target = \realpath(__DIR__.'/..').'/sdks/git/'.$language['key'].'/';
@@ -69,9 +70,9 @@ $cli
$examples = ($examples) ? \file_get_contents($examples) : '';
$changelog = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/CHANGELOG.md');
$changelog = ($changelog) ? \file_get_contents($changelog) : '# Change Log';
- $warning = ($language['beta']) ? '**This SDK is compatible with Appwrite server version ' . $version . '. For older versions, please check previous releases.**' : '';
+ $warning = '**This SDK is compatible with Appwrite server version ' . $version . '. For older versions, please check [previous releases]('.$language['url'].'/releases).**';
$license = 'BSD-3-Clause';
- $licenseContent = 'Copyright (c) 2019 Appwrite (https://appwrite.io) and individual contributors.
+ $licenseContent = 'Copyright (c) ' . date('Y') . ' Appwrite (https://appwrite.io) and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
@@ -122,6 +123,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
break;
case 'dart':
$config = new Dart();
+ $config->setPackageName('dart_appwrite');
break;
case 'go':
$config = new Go();
@@ -132,6 +134,10 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
case 'swift':
$config = new Swift();
break;
+ case 'dotnet':
+ $cover = '';
+ $config = new DotNet();
+ break;
default:
throw new Exception('Language "'.$language['key'].'" not supported');
break;
@@ -154,7 +160,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
->setGitRepo($language['gitUrl'])
->setGitRepoName($language['gitRepoName'])
->setGitUserName($language['gitUserName'])
- ->setLogo('https://appwrite.io/images/github.png')
+ ->setLogo($cover)
->setURL('https://appwrite.io')
->setShareText('Appwrite is a backend as a service for building web or mobile apps')
->setShareURL('http://appwrite.io')
diff --git a/app/tasks/vars.php b/app/tasks/vars.php
index ed4a329735..6c02348222 100644
--- a/app/tasks/vars.php
+++ b/app/tasks/vars.php
@@ -10,9 +10,16 @@ $cli
->task('vars')
->desc('List all the server environment variables')
->action(function () {
- $variables = Config::getParam('variables', []);
+ $config = Config::getParam('variables', []);
+ $vars = [];
- foreach ($variables as $key => $value) {
+ foreach($config as $category) {
+ foreach($category['variables'] ?? [] as $var) {
+ $vars[] = $var;
+ }
+ }
+
+ foreach ($vars as $key => $value) {
Console::log('- '.$value['name'].'='.App::getEnv($value['name'], ''));
}
});
\ No newline at end of file
diff --git a/app/views/console/account/index.phtml b/app/views/console/account/index.phtml
index 84a18521de..3f1e7decc9 100644
--- a/app/views/console/account/index.phtml
+++ b/app/views/console/account/index.phtml
@@ -29,6 +29,8 @@