Merge branch '0.8.x' of github.com:appwrite/appwrite into feat-god-user

This commit is contained in:
Eldad Fux
2021-04-23 23:33:04 +03:00
1809 changed files with 6187 additions and 2152 deletions
+3 -2
View File
@@ -3,6 +3,7 @@ _APP_ENV=development
_APP_CONSOLE_WHITELIST_GOD=disabled
_APP_CONSOLE_WHITELIST_EMAILS=
_APP_CONSOLE_WHITELIST_IPS=
_APP_LOCALE=en
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io
@@ -19,7 +20,7 @@ _APP_DB_PORT=3306
_APP_DB_SCHEMA=appwrite
_APP_DB_USER=user
_APP_DB_PASS=password
_APP_STORAGE_ANTIVIRUS=enabled
_APP_STORAGE_ANTIVIRUS=disabled
_APP_STORAGE_ANTIVIRUS_HOST=clamav
_APP_STORAGE_ANTIVIRUS_PORT=3310
_APP_INFLUXDB_HOST=influxdb
@@ -27,7 +28,7 @@ _APP_INFLUXDB_PORT=8086
_APP_STATSD_HOST=telegraf
_APP_STATSD_PORT=8125
_APP_SMTP_HOST=maildev
_APP_SMTP_PORT=25
_APP_SMTP_PORT=1025
_APP_SMTP_SECURE=
_APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
+1 -6
View File
@@ -15,12 +15,7 @@ then
exit 1
fi
echo "Updating git repository $1 / $2"
git fetch origin
git reset --hard origin/master
if test $(find "./app/db/DBIP/dbip-country-lite-2020-01.mmdb" -mmin +259200)
if test $(find "./app/db/DBIP/dbip-country-lite-2021-02.mmdb" -mmin +259200)
then
printf "${RED}GEO country DB has not been updated for more than 6 months. Go to https://db-ip.com/db/download/ip-to-country-lite to download a newer version${NC}\n"
fi
+3 -4
View File
@@ -2,6 +2,7 @@ dist: xenial
arch:
- amd64
- arm64
os: linux
@@ -10,9 +11,6 @@ language: shell
notifications:
email:
- team@appwrite.io
services:
- docker
before_install:
- curl -fsSL https://get.docker.com | sh
@@ -27,6 +25,7 @@ before_install:
- docker --version
- docker buildx create --use
- chmod -R u+x ./.travis-ci
- export COMPOSE_INTERACTIVE_NO_CLI=1
install:
- docker-compose up -d
@@ -38,7 +37,7 @@ script:
- docker-compose logs appwrite-worker-functions
- docker-compose exec appwrite doctor
- docker-compose exec appwrite vars
- docker-compose exec appwrite test
- docker-compose exec appwrite test --debug
deploy:
- provider: script
+71 -5
View File
@@ -1,15 +1,71 @@
# Version 0.8.0 (Not Released Yet)
- Anonymous login
# Version 0.7.1 (Not Released Yet)
## Features
- Added Anonymous Login ([RFC-010](https://github.com/appwrite/rfc/blob/main/010-anonymous-login.md), #914)
- Added events for functions and executions (#971)
- Added JWT support
- Added ARM support
- Splited token & session models to become 2 different internal entities (#922)
- Added Dart 2.12 as a new Cloud Functions runtime (#989)
- Added option to disable email/password
- Added option to disable anonymous login (need to merge and apply changed)
- Added option to disable JWT auth
- Added option to disable team invites
- Option to limit number of users (good for app launches + god account PR)
- Added 2 new endpoints to the projects API to allow new settings
- Enabled 501 errors (Not Implemented) from the error handler
- Added Python 3.9 as a new Cloud Functions runtime
- Added Deno 1.8 as a new Cloud Functions runtime (#989)
- ClamAV is now disabled by default to allow lower min requirments for Appwrite (#1064)
- Added a new env var named `_APP_LOCALE` that allow to change the default `en` locale value (#1056)
- Updated all the console bottom control to be consistent. Dropped the `+` icon (#1062)
## Bugs
- Fixed default value for HTTPS force option
- Fixed form array casting in dashboard
- Fixed collection document rule form in dashboard
## Breaking Changes (Read before upgrading!)
- Rename `deleteuser` to `delete` on Users Api
- Only logged in users can execute functions (for guests, use anonymous login)
- Only the user who has triggered the execution get access to the relevant execution logs
- Function execution env `APPWRITE_FUNCTION_EVENT_PAYLOAD` renamed to `APPWRITE_FUNCTION_EVENT_DATA`
- Introdcues rate limits for:
- Team invite (10 requests in every 60 minutes per IP address)
# Version 0.7.2
## Features
- When creating new resources from the client API, the current user gets both read & write permissions by default. (#1007)
- Added timestamp to errors logs on the HTTP API container (#1002)
- Added verbose tests output on the terminal and CI (#1006)
## Upgrades
- Upgraded utopia-php/abuse to version 0.4.0
- Upgraded utopia-php/analytics to version 0.2.0
## Bugs
- Fixed certificates worker error on successful operations (#1010)
- Fixed head requests not responding (#998)
- Fixed bug when using auth credential for the Redis container (#993)
- Fixed server warning logs on 3** redirect endpoints (#1013)
# Version 0.7.1
## Features
- Better error logs on appwrite certificates worker
- Added option for Redis authentication
- Force adding a security email on setup
- Better error logs on appwrite cretificates worker## Upgrades
- SMTP is now disabled by default, no dummy SMTP is included in setup
- Added a new endpoint that returns the server and SDKs latest versions numbers #941
- Custom data strings, userId, and JWT available for cloud functions #967
## Upgrades
@@ -21,10 +77,20 @@
- Upgraded influxdb/influxdb-php lib to version 1.15.2
- Upgraded phpmailer/phpmailer lib to version 6.3.0
- Upgraded adhocore/jwt lib to version 1.1.2
- Upgraded domnikl/statsd to slickdeals/statsd version 3.0
## Bug Fixes
- Updated missing storage env vars
- Fixed a bug, that added a wrong timzone offset to user log timestamps
- Fixed a bug, that Response format header was not added in the access-control-allow-header list.
- Fixed a bug where countryName is unknown on sessions (#933)
- Added missing event users.update.prefs (#952)
- Fixed bug not allowing to reset document permissions (#977)
## Security
- Fixed an XSS vulnerability in the Appwrite console
# Version 0.7.0
+33 -4
View File
@@ -90,6 +90,35 @@ Appwrite uses an internal micro-framework called Litespeed.js to build simple UI
After finishing the installation process, you can start writing and editing code.
#### Advanced Topics
We love to create issues that are good for beginners and label them as `good first issue` or `hacktoberfest`, but some more advanced topics might require extra knowledge. Below is a list of links you can use to learn more about some of the more advance topics that will help you master the Appwrite codebase.
##### Tools and Libs
- [Docker](https://www.docker.com/get-started)
- [PHP FIG](https://www.php-fig.org/) - [PSR-1](https://www.php-fig.org/psr/psr-1/) and [PSR-4](https://www.php-fig.org/psr/psr-4/)
- [PHP Swoole](https://www.swoole.co.uk/)
Learn more at our [Technology Stack](## Technology Stack) section.
##### Network and Protocols
- [OSI Model](https://en.wikipedia.org/wiki/OSI_model)
- [TCP vs UDP](https://www.guru99.com/tcp-vs-udp-understanding-the-difference.html#:~:text=TCP%20is%20a%20connection%2Doriented,speed%20of%20UDP%20is%20faster&text=TCP%20does%20error%20checking%20and,but%20it%20discards%20erroneous%20packets.)
- [HTTP](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol)
- [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer)
- [GraphQL](https://en.wikipedia.org/wiki/GraphQL)
- [gRPC](https://en.wikipedia.org/wiki/GRPC)
##### Architecture
- [Microservices vs Monolithic](https://www.mulesoft.com/resources/api/microservices-vs-monolithic#:~:text=Microservices%20architecture%20vs%20monolithic%20architecture&text=A%20monolithic%20application%20is%20built%20as%20a%20single%20unit.&text=To%20make%20any%20alterations%20to,formally%20with%20business%2Doriented%20APIs.)
- [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) - Appwrite console architecture
##### Security
- [Appwrite Auth and ACL](https://github.com/appwrite/appwrite/blob/0.7.x/docs/specs/authentication.drawio.svg)
- [OAuth](https://en.wikipedia.org/wiki/OAuth)
- [Encryption](https://medium.com/searchencrypt/what-is-encryption-how-does-it-work-e8f20e340537#:~:text=Encryption%20is%20a%20process%20that,%2C%20or%20decrypt%2C%20the%20information.)
- [Hashing](https://searchsqlserver.techtarget.com/definition/hashing#:~:text=Hashing%20is%20the%20transformation%20of,it%20using%20the%20original%20value.)
## Architecture
Appwrite's current structure is a combination of both [Monolithic](https://en.wikipedia.org/wiki/Monolithic_application) and [Microservice](https://en.wikipedia.org/wiki/Microservices) architectures, but our final goal, as we grow, is to be using only microservices.
@@ -114,7 +143,6 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
├── docs # Docs and tutorials
│ ├── examples
│ ├── references
│ ├── sdks
│ ├── services
│ ├── specs
│ └── tutorials
@@ -128,13 +156,14 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
│ └── Appwrite
│ ├── Auth
│ ├── Database
│ ├── Detector
│ ├── Docker
│ ├── Event
│ ├── Extend
│ ├── Migration
│ ├── Network
│ ├── OpenSSL
│ ├── Resize
│ ├── Storage
│ ├── Specification
│ ├── Task
│ ├── Template
│ ├── URL
@@ -336,7 +365,7 @@ From time to time, our team will add tutorials that will help contributors find
* [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md)
* [Appwrite Environment Variables](./docs/tutorials/environment-variables.md)
* [Running in Production](./docs/tutorials/running-in-production.md)
* [Adding Storage Adapter](./docs/tutorials/add-storage-adapter.md)
## Other Ways to Help
+1
View File
@@ -68,6 +68,7 @@ ARG VERSION=dev
ENV _APP_SERVER=swoole \
_APP_ENV=production \
_APP_LOCALE=en \
_APP_DOMAIN=localhost \
_APP_DOMAIN_TARGET=localhost \
_APP_HOME=https://appwrite.io \
+8 -7
View File
@@ -16,7 +16,7 @@
Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker<nobr> microservices. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.
Using Appwrite, you can easily integrate your app with user authentication & multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, schedule CRON tasks, and [more services](https://appwrite.io/docs).
Using Appwrite, you can easily integrate your app with user authentication & multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, and [more services](https://appwrite.io/docs).
![Appwrite](public/images/github.png)
@@ -53,7 +53,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:0.7.0
appwrite/appwrite:0.8.0
```
### Windows
@@ -65,7 +65,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:0.7.0
appwrite/appwrite:0.8.0
```
#### PowerShell
@@ -75,13 +75,13 @@ docker run -it --rm ,
--volume /var/run/docker.sock:/var/run/docker.sock ,
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
--entrypoint="install" ,
appwrite/appwrite:0.7.0
appwrite/appwrite:0.8.0
```
Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-linux native hosts, the server might take a few minutes to start after installation completes.
For advanced production and custom installation, check out our Docker [environment variables](docs/tutorials/environment-variables.md) docs. You can also use our public [docker-compose.yml](https://appwrite.io/docker-compose.yml) file to manually set up and environment.
For advanced production and custom installation, check out our Docker [environment variables](https://appwrite.io/docs/environment-variables) docs. You can also use our public [docker-compose.yml](https://gist.github.com/eldadfux/977869ff6bdd7312adfd4e629ee15cc5#file-docker-compose-yml) file to manually set up an environment.
### Upgrade from an Older Version
@@ -94,6 +94,7 @@ Getting started with Appwrite is as easy as creating a new project, choosing you
* [Getting Started for Web](https://appwrite.io/docs/getting-started-for-web)
* [Getting Started for Flutter](https://appwrite.io/docs/getting-started-for-flutter)
* [Getting Started for Server](https://appwrite.io/docs/getting-started-for-server)
* [Getting Started for CLI](https://appwrite.io/docs/command-line)
* Getting Started for Android (Coming soon...)
* Getting Started for iOS (Coming soon...)
@@ -132,13 +133,13 @@ Looking for more SDKs? - Help us by contributing a pull request to our [SDK Gene
## Contributing
All code contributions - including those of people having commit access - must go through a pull request and approved by a core developer before being merged. This is to ensure proper review of all the code.
All code contributions - including those of people having commit access - must go through a pull request and be approved by a core developer before being merged. This is to ensure a proper review of all the code.
We truly ❤️ pull requests! If you wish to help, you can learn more about how you can contribute to this project in the [contribution guide](CONTRIBUTING.md).
## Security
For security issues, kindly email us at [security@appwrite.io](mailto:security@appwrite.io) instead of posting a public issue in GitHub.
For security issues, kindly email us at [security@appwrite.io](mailto:security@appwrite.io) instead of posting a public issue on GitHub.
## Follow Us
+42
View File
@@ -0,0 +1,42 @@
<?php
// Auth methods
return [
'email-password' => [
'name' => 'Email/Password',
'key' => 'usersAuthEmailPassword',
'icon' => '/images/users/email-password.png',
'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateSession',
'enabled' => true,
],
'anonymous' => [
'name' => 'Anonymous',
'key' => 'usersAuthAnonymous',
'icon' => '/images/users/anonymous.png',
'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateAnonymousSession',
'enabled' => true,
],
'invites' => [
'name' => 'Invites',
'key' => 'usersAuthInvites',
'icon' => '/images/users/invites.png',
'docs' => 'https://appwrite.io/docs/client/teams?sdk=web#teamsCreateMembership',
'enabled' => true,
],
'jwt' => [
'name' => 'JWT',
'key' => 'usersAuthJWT',
'icon' => '/images/users/jwt.png',
'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateJWT',
'enabled' => true,
],
'phone' => [
'name' => 'Phone',
'key' => 'usersAuthPhone',
'icon' => '/images/users/phone.png',
'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreatePhoneSession',
'docs' => '',
'enabled' => false,
],
];
+117 -26
View File
@@ -5,6 +5,7 @@ use Utopia\Config\Config;
use Appwrite\Database\Database;
$providers = Config::getParam('providers', []);
$auth = Config::getParam('auth', []);
$collections = [
'console' => [
@@ -205,7 +206,7 @@ $collections = [
'key' => 'email',
'type' => Database::SYSTEM_VAR_TYPE_EMAIL,
'default' => '',
'required' => true,
'required' => false,
'array' => false,
],
[
@@ -223,7 +224,7 @@ $collections = [
'key' => 'password',
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => '',
'required' => true,
'required' => false,
'array' => false,
],
[
@@ -272,6 +273,16 @@ $collections = [
'required' => true,
'array' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'Sessions',
'key' => 'sessions',
'type' => Database::SYSTEM_VAR_TYPE_DOCUMENT,
'default' => [],
'required' => false,
'array' => true,
'list' => [Database::SYSTEM_COLLECTION_SESSIONS],
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'Tokens',
@@ -294,11 +305,11 @@ $collections = [
],
],
],
Database::SYSTEM_COLLECTION_TOKENS => [
Database::SYSTEM_COLLECTION_SESSIONS => [
'$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS,
'$id' => Database::SYSTEM_COLLECTION_TOKENS,
'$id' => Database::SYSTEM_COLLECTION_SESSIONS,
'$permissions' => ['read' => ['*']],
'name' => 'Token',
'name' => 'Session',
'structure' => true,
'rules' => [
[
@@ -307,16 +318,34 @@ $collections = [
'key' => 'userId',
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => null,
'required' => true,
'array' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'Provider',
'key' => 'provider',
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => '',
'required' => true,
'array' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'Provider User Identifier',
'key' => 'providerUid',
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => '',
'required' => false,
'array' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'Type',
'key' => 'type',
'type' => Database::SYSTEM_VAR_TYPE_NUMERIC,
'default' => null,
'required' => true,
'label' => 'Provider Token',
'key' => 'providerToken',
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => '',
'required' => false,
'array' => false,
],
[
@@ -474,6 +503,69 @@ $collections = [
],
],
],
Database::SYSTEM_COLLECTION_TOKENS => [
'$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS,
'$id' => Database::SYSTEM_COLLECTION_TOKENS,
'$permissions' => ['read' => ['*']],
'name' => 'Token',
'structure' => true,
'rules' => [
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'User ID',
'key' => 'userId',
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => null,
'required' => false,
'array' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'Type',
'key' => 'type',
'type' => Database::SYSTEM_VAR_TYPE_NUMERIC,
'default' => null,
'required' => true,
'array' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'Secret',
'key' => 'secret',
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => '',
'required' => true,
'array' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'Expire',
'key' => 'expire',
'type' => Database::SYSTEM_VAR_TYPE_NUMERIC,
'default' => 0,
'required' => true,
'array' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'User Agent',
'key' => 'userAgent',
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => '',
'required' => true,
'array' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'IP',
'key' => 'ip',
'type' => Database::SYSTEM_VAR_TYPE_IP,
'default' => '',
'required' => true,
'array' => false,
],
],
],
Database::SYSTEM_COLLECTION_MEMBERSHIPS => [
'$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS,
'$id' => Database::SYSTEM_COLLECTION_MEMBERSHIPS,
@@ -680,6 +772,14 @@ $collections = [
'default' => '',
'required' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'Max users allowed',
'key' => 'usersAuthLimit',
'type' => Database::SYSTEM_VAR_TYPE_NUMERIC,
'default' => 0,
'required' => false,
],
[
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'Webhooks',
@@ -1618,25 +1718,16 @@ foreach ($providers as $index => $provider) {
'array' => false,
'filter' => ['encrypt'],
];
}
$collections[Database::SYSTEM_COLLECTION_USERS]['rules'][] = [
foreach ($auth as $index => $method) {
$collections[Database::SYSTEM_COLLECTION_PROJECTS]['rules'][] = [
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'OAuth2 '.\ucfirst($index).' ID',
'key' => 'oauth2'.\ucfirst($index),
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => '',
'label' => $method['name'] || '',
'key' => $method['key'] || '',
'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN,
'default' => true,
'required' => false,
'array' => false,
];
$collections[Database::SYSTEM_COLLECTION_USERS]['rules'][] = [
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'label' => 'OAuth2 '.\ucfirst($index).' Access Token',
'key' => 'oauth2'.\ucfirst($index).'AccessToken',
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => '',
'required' => false,
'array' => false,
];
}
+31 -4
View File
@@ -70,6 +70,15 @@ $environments = [
'logo' => 'python.png',
'supports' => [System::X86, System::PPC, System::ARM],
],
'python-3.9' => [
'name' => 'Python',
'version' => '3.9',
'base' => 'python:3.9-alpine',
'image' => 'appwrite/env-python-3.9:1.0.0',
'build' => '/usr/src/code/docker/environments/python-3.9',
'logo' => 'python.png',
'supports' => [System::X86, System::PPC, System::ARM],
],
'deno-1.2' => [
'name' => 'Deno',
'version' => '1.2',
@@ -77,7 +86,7 @@ $environments = [
'image' => 'appwrite/env-deno-1.2:1.0.0',
'build' => '/usr/src/code/docker/environments/deno-1.2',
'logo' => 'deno.png',
'supports' => [System::X86, System::PPC, System::ARM],
'supports' => [System::X86],
],
'deno-1.5' => [
'name' => 'Deno',
@@ -86,7 +95,7 @@ $environments = [
'image' => 'appwrite/env-deno-1.5:1.0.0',
'build' => '/usr/src/code/docker/environments/deno-1.5',
'logo' => 'deno.png',
'supports' => [System::X86, System::PPC, System::ARM],
'supports' => [System::X86],
],
'deno-1.6' => [
'name' => 'Deno',
@@ -95,7 +104,16 @@ $environments = [
'image' => 'appwrite/env-deno-1.6:1.0.0',
'build' => '/usr/src/code/docker/environments/deno-1.6',
'logo' => 'deno.png',
'supports' => [System::X86, System::PPC, System::ARM],
'supports' => [System::X86],
],
'deno-1.8' => [
'name' => 'Deno',
'version' => '1.8',
'base' => 'hayd/deno:alpine-1.8.2',
'image' => 'appwrite/env-deno-1.8:1.0.0',
'build' => '/usr/src/code/docker/environments/deno-1.8',
'logo' => 'deno.png',
'supports' => [System::X86],
],
'dart-2.10' => [
'name' => 'Dart',
@@ -106,6 +124,15 @@ $environments = [
'logo' => 'dart.png',
'supports' => [System::X86],
],
'dart-2.12' => [
'name' => 'Dart',
'version' => '2.12',
'base' => 'google/dart:2.12',
'image' => 'appwrite/env-dart-2.12:1.0.0',
'build' => '/usr/src/code/docker/environments/dart-2.12',
'logo' => 'dart.png',
'supports' => [System::X86],
],
'dotnet-3.1' => [
'name' => '.NET',
'version' => '3.1',
@@ -113,7 +140,7 @@ $environments = [
'image' => 'appwrite/env-dotnet-3.1:1.0.0',
'build' => '/usr/src/code/docker/environments/dotnet-3.1',
'logo' => 'dotnet.png',
'supports' => [System::X86, System::ARM],
'supports' => [System::X86],
],
'dotnet-5.0' => [
'name' => '.NET',
+49 -4
View File
@@ -84,19 +84,59 @@ return [
],
'database.documents.create' => [
'description' => 'This event triggers when a database document is created.',
'model' => Response::MODEL_ANY,
'model' => Response::MODEL_DOCUMENT,
'note' => '',
],
'database.documents.update' => [
'description' => 'This event triggers when a database document is updated.',
'model' => Response::MODEL_ANY,
'model' => Response::MODEL_DOCUMENT,
'note' => '',
],
'database.documents.delete' => [
'description' => 'This event triggers when a database document is deleted.',
'model' => Response::MODEL_ANY,
'model' => Response::MODEL_DOCUMENT,
'note' => '',
],
'functions.create' => [
'description' => 'This event triggers when a function is created.',
'model' => Response::MODEL_FUNCTION,
'note' => 'version >= 0.7',
],
'functions.update' => [
'description' => 'This event triggers when a function is updated.',
'model' => Response::MODEL_FUNCTION,
'note' => 'version >= 0.7',
],
'functions.delete' => [
'description' => 'This event triggers when a function is deleted.',
'model' => Response::MODEL_ANY,
'note' => 'version >= 0.7',
],
'functions.tags.create' => [
'description' => 'This event triggers when a function tag is created.',
'model' => Response::MODEL_TAG,
'note' => 'version >= 0.7',
],
'functions.tags.update' => [
'description' => 'This event triggers when a function tag is updated.',
'model' => Response::MODEL_FUNCTION,
'note' => 'version >= 0.7',
],
'functions.tags.delete' => [
'description' => 'This event triggers when a function tag is deleted.',
'model' => Response::MODEL_ANY,
'note' => 'version >= 0.7',
],
'functions.executions.create' => [
'description' => 'This event triggers when a function execution is created.',
'model' => Response::MODEL_EXECUTION,
'note' => 'version >= 0.7',
],
'functions.executions.update' => [
'description' => 'This event triggers when a function execution is updated.',
'model' => Response::MODEL_EXECUTION,
'note' => 'version >= 0.7',
],
'storage.files.create' => [
'description' => 'This event triggers when a storage file is created.',
'model' => Response::MODEL_FILE,
@@ -117,6 +157,11 @@ return [
'model' => Response::MODEL_USER,
'note' => 'version >= 0.7',
],
'users.update.prefs' => [
'description' => 'This event triggers when a user preference is updated from the users API.',
'model' => Response::MODEL_ANY,
'note' => 'version >= 0.7',
],
'users.update.status' => [
'description' => 'This event triggers when a user status is updated from the users API.',
'model' => Response::MODEL_USER,
@@ -162,4 +207,4 @@ return [
'model' => Response::MODEL_MEMBERSHIP,
'note' => 'version >= 0.7',
],
];
];
+7 -7
View File
@@ -32,7 +32,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '0.3.0',
'version' => '0.4.0',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -165,7 +165,7 @@ return [
[
'key' => 'deno',
'name' => 'Deno',
'version' => '0.1.0',
'version' => '0.1.1',
'url' => 'https://github.com/appwrite/sdk-for-deno',
'package' => 'https://deno.land/x/appwrite',
'enabled' => true,
@@ -199,7 +199,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '0.1.0',
'version' => '0.1.1',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -216,7 +216,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '2.0.0',
'version' => '2.0.2',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -284,12 +284,12 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '0.2.0',
'version' => '0.4.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
'beta' => true,
'dev' => true,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'prism' => 'dart',
@@ -301,7 +301,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '0.5.0',
'version' => '0.6.0',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://github.com/appwrite/sdk-for-cli',
'enabled' => true,
+28 -2
View File
@@ -6,6 +6,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.amazon.com/apps-and-games/services-and-apis',
'icon' => 'icon-amazon',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -15,6 +16,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.apple.com/',
'icon' => 'icon-apple',
'enabled' => true,
'sandbox' => false,
'form' => 'apple.phtml', // Perperation for adding ability to customized OAuth UI forms, currently handled hardcoded.
'beta' => true,
'mock' => false,
@@ -24,6 +26,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.atlassian.com/bitbucket',
'icon' => 'icon-bitbucket',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -33,6 +36,7 @@ return [ // Ordered by ABC.
'developers' => 'https://dev.bitly.com/v4_documentation.html',
'icon' => 'icon-bitly',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false
@@ -42,6 +46,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.box.com/reference/',
'icon' => 'icon-box',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false
@@ -51,6 +56,7 @@ return [ // Ordered by ABC.
'developers' => 'https://discordapp.com/developers/docs/topics/oauth2',
'icon' => 'icon-discord',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -60,6 +66,7 @@ return [ // Ordered by ABC.
'developers' => 'https://www.dropbox.com/developers/documentation',
'icon' => 'icon-dropbox',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -69,6 +76,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developers.facebook.com/',
'icon' => 'icon-facebook',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -78,6 +86,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.github.com/',
'icon' => 'icon-github-circled',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -87,6 +96,7 @@ return [ // Ordered by ABC.
'developers' => 'https://docs.gitlab.com/ee/api/',
'icon' => 'icon-gitlab',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -96,6 +106,7 @@ return [ // Ordered by ABC.
'developers' => 'https://support.google.com/googleapi/answer/6158849',
'icon' => 'icon-google',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -105,6 +116,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.linkedin.com/',
'icon' => 'icon-linkedin',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -114,6 +126,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.microsoft.com/en-us/',
'icon' => 'icon-windows',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -123,15 +136,17 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.paypal.com/docs/api/overview/',
'icon' => 'icon-paypal',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false
],
'paypalSandbox' => [
'name' => 'PayPal (Sandbox)',
'name' => 'PayPal',
'developers' => 'https://developer.paypal.com/docs/api/overview/',
'icon' => 'icon-paypal',
'enabled' => true,
'sandbox' => true,
'form' => false,
'beta' => false,
'mock' => false
@@ -141,6 +156,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.salesforce.com/docs/',
'icon' => 'icon-salesforce',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -150,6 +166,7 @@ return [ // Ordered by ABC.
'developers' => 'https://api.slack.com/',
'icon' => 'icon-slack',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -159,6 +176,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.spotify.com/documentation/general/guides/authorization-guide/',
'icon' => 'icon-spotify',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -168,15 +186,17 @@ return [ // Ordered by ABC.
'developers' => 'https://developers.tradeshift.com/docs/api',
'icon' => 'icon-tradeshift',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
],
'tradeshiftBox' => [
'name' => 'Tradeshift Sandbox',
'name' => 'Tradeshift',
'developers' => 'https://developers.tradeshift.com/docs/api',
'icon' => 'icon-tradeshiftbox',
'enabled' => true,
'sandbox' => true,
'form' => false,
'beta' => false,
'mock' => false,
@@ -186,6 +206,7 @@ return [ // Ordered by ABC.
'developers' => 'https://dev.twitch.tv/docs/authentication',
'icon' => 'icon-twitch',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -195,6 +216,7 @@ return [ // Ordered by ABC.
'developers' => 'https://vk.com/dev',
'icon' => 'icon-vk',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -204,6 +226,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.yahoo.com/oauth2/guide/flows_authcode/',
'icon' => 'icon-yahoo',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -213,6 +236,7 @@ return [ // Ordered by ABC.
'developers' => 'https://tech.yandex.com/oauth/',
'icon' => 'icon-yandex',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
@@ -238,6 +262,7 @@ return [ // Ordered by ABC.
'developers' => 'https://developer.wordpress.com/docs/oauth2/',
'icon' => 'icon-wordpress',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false
@@ -248,6 +273,7 @@ return [ // Ordered by ABC.
'developers' => 'https://appwrite.io',
'icon' => 'icon-appwrite',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => true,
-2
View File
@@ -60,8 +60,6 @@ return [
'files.read',
'locale.read',
'avatars.read',
'execution.read',
'execution.write',
],
],
Auth::USER_ROLE_MEMBER => [
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+18 -10
View File
@@ -15,6 +15,14 @@ return [
'required' => false,
'question' => '',
],
[
'name' => '_APP_LOCALE',
'description' => 'Set your Appwrite\'s locale. By default, the locale is set to \'en\'.',
'introduction' => '',
'default' => 'en',
'required' => false,
'question' => '',
],
[
'name' => '_APP_OPTIONS_ABUSE',
'description' => 'Allows you to disable abuse checks and API rate limiting. By default, set to \'enabled\'. To cancel the abuse checking, set to \'disabled\'. It is not recommended to disable this check-in a production environment.',
@@ -25,9 +33,9 @@ return [
],
[
'name' => '_APP_OPTIONS_FORCE_HTTPS',
'description' => 'Allows you to force HTTPS connection to your API. This feature redirects any HTTP call to HTTPS and adds the \'Strict-Transport-Security\' header to all HTTP responses. By default, set to \'disabled\'. To enable, set to \'enabled\'. This feature will work only when your ports are set to default 80 and 443.',
'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 \'enabled\'. To disable, set to \'disabled\'. This feature will work only when your ports are set to default 80 and 443.',
'introduction' => '',
'default' => 'enabled',
'default' => 'disabled',
'required' => false,
'question' => '',
],
@@ -115,8 +123,8 @@ return [
'name' => '_APP_SYSTEM_SECURITY_EMAIL_ADDRESS',
'description' => 'This is the email address used to issue SSL certificates for custom domains or the user agent in your webhooks payload.',
'introduction' => '0.7.0',
'default' => '',
'required' => true,
'default' => 'certs@appwrite.io',
'required' => false,
'question' => '',
],
[
@@ -126,7 +134,7 @@ return [
'default' => 'enabled',
'required' => false,
'question' => '',
],
]
],
],
[
@@ -151,7 +159,7 @@ return [
],
[
'name' => '_APP_REDIS_USER',
'description' => 'Redis server user.',
'description' => 'Redis server user. This is an optional variable. Default value is an empty string.',
'introduction' => '0.7',
'default' => '',
'required' => false,
@@ -159,7 +167,7 @@ return [
],
[
'name' => '_APP_REDIS_PASS',
'description' => 'Redis server password.',
'description' => 'Redis server password. This is an optional variable. Default value is an empty string.',
'introduction' => '0.7',
'default' => '',
'required' => false,
@@ -317,9 +325,9 @@ return [
],
[
'name' => '_APP_STORAGE_ANTIVIRUS',
'description' => 'This variable allows you to disable the internal anti-virus scans. This value is set to \'enabled\' by default, to cancel the scans set the value to \'disabled\'. When disabled, it\'s recommended to turn off the ClamAV container for better resource usage.',
'description' => 'This variable allows you to disable the internal anti-virus scans. This value is set to \'disabled\' by default, to enable the scans set the value to \'enabled\'. Before enabling, you must add the ClamAV service and depend on it on main Appwrite service.',
'introduction' => '',
'default' => 'enabled',
'default' => 'disabled',
'required' => false,
'question' => '',
],
@@ -389,7 +397,7 @@ return [
'name' => '_APP_FUNCTIONS_ENVS',
'description' => 'This option allows you to limit the available environments for cloud functions. This option is very useful for low-cost servers to safe disk space.\n\nTo enable/activate this option, pass a list of allowed environments separated by a comma.\n\nCurrently, supported environments are: ' . \implode(', ', \array_keys(Config::getParam('providers'))),
'introduction' => '0.7.0',
'default' => 'node-14.5,deno-1.6,php-7.4,python-3.8,ruby-3.0,dotnet-5.0',
'default' => 'node-14.5,deno-1.8,php-7.4,python-3.9,ruby-3.0,dotnet-5.0',
'required' => false,
'question' => '',
],
+293 -90
View File
@@ -6,10 +6,10 @@ use Utopia\Exception;
use Utopia\Config\Config;
use Utopia\Validator\Assoc;
use Utopia\Validator\Text;
use Utopia\Validator\Email;
use Appwrite\Network\Validator\Email;
use Utopia\Validator\WhiteList;
use Utopia\Validator\Host;
use Utopia\Validator\URL;
use Appwrite\Network\Validator\Host;
use Appwrite\Network\Validator\URL;
use Utopia\Audit\Audit;
use Utopia\Audit\Adapters\MySQL as AuditAdapter;
use Appwrite\Auth\Auth;
@@ -31,10 +31,11 @@ $oauthDefaultFailure = App::getEnv('_APP_HOME').'/auth/oauth2/failure';
App::post('/v1/account')
->desc('Create Account')
->groups(['api', 'account'])
->groups(['api', 'account', 'auth'])
->label('event', 'account.create')
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('auth.type', 'emailPassword')
->label('sdk.auth', [])
->label('sdk.namespace', 'account')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/account/create.md')
@@ -90,6 +91,22 @@ App::post('/v1/account')
}
}
$limit = $project->getAttribute('usersAuthLimit', 0);
if ($limit !== 0) {
$projectDB->getCollection([ // Count users
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_USERS,
],
]);
$sum = $projectDB->getSum();
if($sum >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501);
}
}
$profile = $projectDB->getCollectionFirst([ // Get user by email address
'limit' => 1,
'filters' => [
@@ -124,7 +141,7 @@ App::post('/v1/account')
throw new Exception('Account already exists', 409);
}
Authorization::enable();
Authorization::reset();
Authorization::unsetRole('role:'.Auth::USER_ROLE_GUEST);
Authorization::setRole('user:'.$user->getId());
@@ -148,10 +165,11 @@ App::post('/v1/account')
App::post('/v1/account/sessions')
->desc('Create Account Session')
->groups(['api', 'account'])
->groups(['api', 'account', 'auth'])
->label('event', 'account.sessions.create')
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('auth.type', 'emailPassword')
->label('sdk.auth', [])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createSession')
->label('sdk.description', '/docs/references/account/create-session.md')
@@ -205,10 +223,11 @@ App::post('/v1/account/sessions')
$secret = Auth::tokenGenerator();
$session = new Document(array_merge(
[
'$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$collection' => Database::SYSTEM_COLLECTION_SESSIONS,
'$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]],
'userId' => $profile->getId(),
'type' => Auth::TOKEN_TYPE_LOGIN,
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $email,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expiry,
'userAgent' => $request->getUserAgent('UNKNOWN'),
@@ -225,7 +244,7 @@ App::post('/v1/account/sessions')
throw new Exception('Failed saving session to DB', 500);
}
$profile->setAttribute('tokens', $session, Document::SET_TYPE_APPEND);
$profile->setAttribute('sessions', $session, Document::SET_TYPE_APPEND);
$profile = $projectDB->updateDocument($profile->getArrayCopy());
@@ -251,9 +270,11 @@ App::post('/v1/account/sessions')
->setStatusCode(Response::STATUS_CODE_CREATED)
;
$countries = $locale->getText('countries');
$session
->setAttribute('current', true)
->setAttribute('countryName', (isset($countries[$session->getAttribute('countryCode')])) ? $countries[$session->getAttribute('countryCode')] : $locale->getText('locale.country.unknown'))
->setAttribute('countryName', (isset($countries[strtoupper($session->getAttribute('countryCode'))])) ? $countries[strtoupper($session->getAttribute('countryCode'))] : $locale->getText('locale.country.unknown'))
;
$response->dynamic($session, Response::MODEL_SESSION);
@@ -264,12 +285,12 @@ App::get('/v1/account/sessions/oauth2/:provider')
->groups(['api', 'account'])
->label('error', __DIR__.'/../../views/general/error.phtml')
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createOAuth2Session')
->label('sdk.description', '/docs/references/account/create-session-oauth2.md')
->label('sdk.response.code', 301)
->label('sdk.response.type', 'text/html')
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
->label('sdk.methodType', 'webAuth')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
@@ -454,7 +475,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
throw new Exception('Missing ID from OAuth2 provider', 400);
}
$current = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
$current = Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret);
if ($current) {
$projectDB->deleteDocument($current); //throw new Exception('User already logged in', 401);
@@ -464,7 +485,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'limit' => 1,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_USERS,
'oauth2'.\ucfirst($provider).'='.$oauth2ID,
'sessions.provider='.$provider,
'sessions.providerUid='.$oauth2ID
],
]) : $user;
@@ -480,7 +502,23 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
],
]);
if (!$user || empty($user->getId())) { // Last option -> create user alone, generate random password
if (!$user || empty($user->getId())) { // Last option -> create the user, generate random password
$limit = $project->getAttribute('usersAuthLimit', 0);
if ($limit !== 0) {
$projectDB->getCollection([ // Count users
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_USERS,
],
]);
$sum = $projectDB->getSum();
if($sum >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501);
}
}
Authorization::disable();
try {
@@ -500,7 +538,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
throw new Exception('Account already exists', 409);
}
Authorization::enable();
Authorization::reset();
if (false === $user) {
throw new Exception('Failed saving user to DB', 500);
@@ -519,10 +557,12 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$secret = Auth::tokenGenerator();
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$session = new Document(array_merge([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$collection' => Database::SYSTEM_COLLECTION_SESSIONS,
'$permissions' => ['read' => ['user:'.$user['$id']], 'write' => ['user:'.$user['$id']]],
'userId' => $user->getId(),
'type' => Auth::TOKEN_TYPE_LOGIN,
'provider' => $provider,
'providerUid' => $oauth2ID,
'providerToken' => $accessToken,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expiry,
'userAgent' => $request->getUserAgent('UNKNOWN'),
@@ -530,11 +570,18 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
$isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password'));
if ($isAnonymousUser) {
$user
->setAttribute('name', $oauth2->getUserName($accessToken))
->setAttribute('email', $oauth2->getUserEmail($accessToken))
;
}
$user
->setAttribute('oauth2'.\ucfirst($provider), $oauth2ID)
->setAttribute('oauth2'.\ucfirst($provider).'AccessToken', $accessToken)
->setAttribute('status', Auth::USER_STATUS_ACTIVATED)
->setAttribute('tokens', $session, Document::SET_TYPE_APPEND)
->setAttribute('sessions', $session, Document::SET_TYPE_APPEND)
;
Authorization::setRole('user:'.$user->getId());
@@ -579,12 +626,156 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
;
});
App::post('/v1/account/sessions/anonymous')
->desc('Create Anonymous Session')
->groups(['api', 'account', 'auth'])
->label('event', 'account.sessions.create')
->label('scope', 'public')
->label('auth.type', 'anonymous')
->label('sdk.auth', [])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createAnonymousSession')
->label('sdk.description', '/docs/references/account/create-session-anonymous.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SESSION)
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->inject('request')
->inject('response')
->inject('locale')
->inject('user')
->inject('project')
->inject('projectDB')
->inject('geodb')
->inject('audits')
->action(function ($request, $response, $locale, $user, $project, $projectDB, $geodb, $audits) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Database\Document $user */
/** @var Appwrite\Database\Document $project */
/** @var Appwrite\Database\Database $projectDB */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
$protocol = $request->getProtocol();
if ('console' === $project->getId()) {
throw new Exception('Failed to create anonymous user.', 401);
}
if ($user->getId()) {
throw new Exception('Cannot create an anonymous user when logged in.', 401);
}
$limit = $project->getAttribute('usersAuthLimit', 0);
if ($limit !== 0) {
$projectDB->getCollection([ // Count users
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_USERS,
],
]);
$sum = $projectDB->getSum();
if($sum >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501);
}
}
Authorization::disable();
try {
$user = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_USERS,
'$permissions' => [
'read' => ['*'],
'write' => ['user:{self}']
],
'email' => null,
'emailVerification' => false,
'status' => Auth::USER_STATUS_UNACTIVATED,
'password' => null,
'passwordUpdate' => \time(),
'registration' => \time(),
'reset' => false,
'name' => null
]);
} catch (Exception $th) {
throw new Exception('Failed saving user to DB', 500);
}
Authorization::reset();
if (false === $user) {
throw new Exception('Failed saving user to DB', 500);
}
// Create session token
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$secret = Auth::tokenGenerator();
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$session = new Document(array_merge(
[
'$collection' => Database::SYSTEM_COLLECTION_SESSIONS,
'$permissions' => ['read' => ['user:' . $user['$id']], 'write' => ['user:' . $user['$id']]],
'userId' => $user->getId(),
'provider' => Auth::SESSION_PROVIDER_ANONYMOUS,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expiry,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
],
$detector->getOS(),
$detector->getClient(),
$detector->getDevice()
));
$user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND);
Authorization::setRole('user:'.$user->getId());
$user = $projectDB->updateDocument($user->getArrayCopy());
if (false === $user) {
throw new Exception('Failed saving user to DB', 500);
}
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.sessions.create')
->setParam('resource', 'users/'.$user->getId())
;
if (!Config::getParam('domainVerification')) {
$response
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
;
}
$response
->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
->setStatusCode(Response::STATUS_CODE_CREATED)
;
$session
->setAttribute('current', true)
->setAttribute('countryName', (isset($countries[$session->getAttribute('countryCode')])) ? $countries[$session->getAttribute('countryCode')] : $locale->getText('locale.country.unknown'))
;
$response->dynamic($session, Response::MODEL_SESSION);
});
App::post('/v1/account/jwt')
->desc('Create Account JWT')
->groups(['api', 'account'])
->groups(['api', 'account', 'auth'])
->label('scope', 'account')
->label('docs', false) // Hidden for now - private beta
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('auth.type', 'jwt')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createJWT')
->label('sdk.description', '/docs/references/account/create-jwt.md')
@@ -596,16 +787,18 @@ App::post('/v1/account/jwt')
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $user */
$tokens = $user->getAttribute('tokens', []);
$session = new Document();
$sessions = $user->getAttribute('sessions', []);
$current = new Document();
foreach ($tokens as $token) { /** @var Appwrite\Database\Document $token */
if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$session = $token;
foreach ($sessions as $session) {
/** @var Appwrite\Database\Document $session */
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$current = $session;
}
}
if($session->isEmpty()) {
if($current->isEmpty()) {
throw new Exception('No valid session found', 401);
}
@@ -619,7 +812,7 @@ App::post('/v1/account/jwt')
// 'scopes' => ['user'],
// 'iss' => 'http://api.mysite.com',
'userId' => $user->getId(),
'sessionId' => $session->getId(),
'sessionId' => $current->getId(),
])]), Response::MODEL_JWT);
});
@@ -627,7 +820,7 @@ App::get('/v1/account')
->desc('Get Account')
->groups(['api', 'account'])
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/account/get.md')
@@ -647,7 +840,7 @@ App::get('/v1/account/prefs')
->desc('Get Account Preferences')
->groups(['api', 'account'])
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getPrefs')
->label('sdk.description', '/docs/references/account/get-prefs.md')
@@ -669,7 +862,7 @@ App::get('/v1/account/sessions')
->desc('Get Account Sessions')
->groups(['api', 'account'])
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getSessions')
->label('sdk.description', '/docs/references/account/get-sessions.md')
@@ -684,22 +877,19 @@ App::get('/v1/account/sessions')
/** @var Appwrite\Database\Document $user */
/** @var Utopia\Locale\Locale $locale */
$tokens = $user->getAttribute('tokens', []);
$sessions = [];
$sessions = $user->getAttribute('sessions', []);
$countries = $locale->getText('countries');
$current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
$current = Auth::sessionVerify($sessions, Auth::$secret);
foreach ($tokens as $token) { /* @var $token Document */
if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) {
continue;
}
foreach ($sessions as $key => $session) {
/** @var Document $session */
$token->setAttribute('countryName', (isset($countries[$token->getAttribute('contryCode')]))
? $countries[$token->getAttribute('contryCode')]
$session->setAttribute('countryName', (isset($countries[strtoupper($session->getAttribute('countryCode'))]))
? $countries[strtoupper($session->getAttribute('countryCode'))]
: $locale->getText('locale.country.unknown'));
$token->setAttribute('current', ($current == $token->getId()) ? true : false);
$session->setAttribute('current', ($current == $session->getId()) ? true : false);
$sessions[] = $token;
$sessions[$key] = $session;
}
$response->dynamic(new Document([
@@ -712,7 +902,7 @@ App::get('/v1/account/logs')
->desc('Get Account Logs')
->groups(['api', 'account'])
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getLogs')
->label('sdk.description', '/docs/references/account/get-logs.md')
@@ -789,7 +979,7 @@ App::patch('/v1/account/name')
->groups(['api', 'account'])
->label('event', 'account.update.name')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateName')
->label('sdk.description', '/docs/references/account/update-name.md')
@@ -829,7 +1019,7 @@ App::patch('/v1/account/password')
->groups(['api', 'account'])
->label('event', 'account.update.password')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updatePassword')
->label('sdk.description', '/docs/references/account/update-password.md')
@@ -874,7 +1064,7 @@ App::patch('/v1/account/email')
->groups(['api', 'account'])
->label('event', 'account.update.email')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateEmail')
->label('sdk.description', '/docs/references/account/update-email.md')
@@ -893,7 +1083,12 @@ App::patch('/v1/account/email')
/** @var Appwrite\Database\Database $projectDB */
/** @var Appwrite\Event\Event $audits */
if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
$isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password')); // Check if request is from an anonymous account for converting
if (
!$isAnonymousUser &&
!Auth::passwordVerify($password, $user->getAttribute('password'))
) { // Double check user password
throw new Exception('Invalid credentials', 401);
}
@@ -911,10 +1106,14 @@ App::patch('/v1/account/email')
// TODO after this user needs to confirm mail again
$user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
'email' => $email,
'emailVerification' => false,
]));
$user = $projectDB->updateDocument(\array_merge(
$user->getArrayCopy(),
($isAnonymousUser ? [ 'password' => Auth::passwordHash($password) ] : []),
[
'email' => $email,
'emailVerification' => false,
]
));
if (false === $user) {
throw new Exception('Failed saving user to DB', 500);
@@ -934,7 +1133,7 @@ App::patch('/v1/account/prefs')
->groups(['api', 'account'])
->label('event', 'account.update.prefs')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updatePrefs')
->label('sdk.description', '/docs/references/account/update-prefs.md')
@@ -973,7 +1172,7 @@ App::delete('/v1/account')
->groups(['api', 'account'])
->label('event', 'account.delete')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/account/delete.md')
@@ -1018,7 +1217,7 @@ App::delete('/v1/account')
;
$events
->setParam('payload', $response->output($user, Response::MODEL_USER))
->setParam('eventData', $response->output($user, Response::MODEL_USER))
;
if (!Config::getParam('domainVerification')) {
@@ -1039,7 +1238,7 @@ App::delete('/v1/account/sessions/:sessionId')
->groups(['api', 'account'])
->label('scope', 'account')
->label('event', 'account.sessions.delete')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'deleteSession')
->label('sdk.description', '/docs/references/account/delete-session.md')
@@ -1063,14 +1262,16 @@ App::delete('/v1/account/sessions/:sessionId')
$protocol = $request->getProtocol();
$sessionId = ($sessionId === 'current')
? Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)
? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret)
: $sessionId;
$tokens = $user->getAttribute('tokens', []);
$sessions = $user->getAttribute('sessions', []);
foreach ($tokens as $token) { /* @var $token Document */
if (($sessionId == $token->getId()) && Auth::TOKEN_TYPE_LOGIN == $token->getAttribute('type')) {
if (!$projectDB->deleteDocument($token->getId())) {
foreach ($sessions as $session) {
/** @var Document $session */
if (($sessionId == $session->getId())) {
if (!$projectDB->deleteDocument($session->getId())) {
throw new Exception('Failed to remove token from DB', 500);
}
@@ -1086,10 +1287,10 @@ App::delete('/v1/account/sessions/:sessionId')
;
}
$token->setAttribute('current', false);
$session->setAttribute('current', false);
if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$token->setAttribute('current', true);
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$session->setAttribute('current', true);
$response
->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
@@ -1098,7 +1299,7 @@ App::delete('/v1/account/sessions/:sessionId')
}
$events
->setParam('payload', $response->output($token, Response::MODEL_SESSION))
->setParam('eventData', $response->output($session, Response::MODEL_SESSION))
;
return $response->noContent();
@@ -1113,7 +1314,7 @@ App::delete('/v1/account/sessions')
->groups(['api', 'account'])
->label('scope', 'account')
->label('event', 'account.sessions.delete')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'deleteSessions')
->label('sdk.description', '/docs/references/account/delete-sessions.md')
@@ -1135,10 +1336,12 @@ App::delete('/v1/account/sessions')
/** @var Appwrite\Event\Event $events */
$protocol = $request->getProtocol();
$tokens = $user->getAttribute('tokens', []);
$sessions = $user->getAttribute('sessions', []);
foreach ($tokens as $token) { /* @var $token Document */
if (!$projectDB->deleteDocument($token->getId())) {
foreach ($sessions as $session) {
/** @var Document $session */
if (!$projectDB->deleteDocument($session->getId())) {
throw new Exception('Failed to remove token from DB', 500);
}
@@ -1154,10 +1357,10 @@ App::delete('/v1/account/sessions')
;
}
$token->setAttribute('current', false);
$session->setAttribute('current', false);
if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$token->setAttribute('current', true);
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$session->setAttribute('current', true);
$response
->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
@@ -1166,9 +1369,9 @@ App::delete('/v1/account/sessions')
}
$events
->setParam('payload', $response->output(new Document([
'sum' => count($tokens),
'sessions' => $tokens
->setParam('eventData', $response->output(new Document([
'sum' => count($sessions),
'sessions' => $sessions
]), Response::MODEL_SESSION_LIST))
;
@@ -1180,7 +1383,7 @@ App::post('/v1/account/recovery')
->groups(['api', 'account'])
->label('scope', 'public')
->label('event', 'account.recovery.create')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createRecovery')
->label('sdk.description', '/docs/references/account/create-recovery.md')
@@ -1209,7 +1412,7 @@ App::post('/v1/account/recovery')
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
$isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$profile = $projectDB->getCollectionFirst([ // Get user by email address
@@ -1291,7 +1494,7 @@ App::post('/v1/account/recovery')
;
$events
->setParam('payload',
->setParam('eventData',
$response->output($recovery->setAttribute('secret', $secret),
Response::MODEL_TOKEN
))
@@ -1299,7 +1502,7 @@ App::post('/v1/account/recovery')
$recovery // Hide secret for clients, sp
->setAttribute('secret',
($isPreviliggedUser || $isAppUser) ? $secret : '');
($isPrivilegedUser || $isAppUser) ? $secret : '');
$audits
->setParam('userId', $profile->getId())
@@ -1318,7 +1521,7 @@ App::put('/v1/account/recovery')
->groups(['api', 'account'])
->label('scope', 'public')
->label('event', 'account.recovery.update')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateRecovery')
->label('sdk.description', '/docs/references/account/update-recovery.md')
@@ -1397,7 +1600,7 @@ App::post('/v1/account/verification')
->groups(['api', 'account'])
->label('scope', 'account')
->label('event', 'account.verification.create')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createVerification')
->label('sdk.description', '/docs/references/account/create-verification.md')
@@ -1427,7 +1630,7 @@ App::post('/v1/account/verification')
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $mails */
$isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$verificationSecret = Auth::tokenGenerator();
@@ -1494,7 +1697,7 @@ App::post('/v1/account/verification')
;
$events
->setParam('payload',
->setParam('eventData',
$response->output($verification->setAttribute('secret', $verificationSecret),
Response::MODEL_TOKEN
))
@@ -1502,7 +1705,7 @@ App::post('/v1/account/verification')
$verification // Hide secret for clients, sp
->setAttribute('secret',
($isPreviliggedUser || $isAppUser) ? $verificationSecret : '');
($isPrivilegedUser || $isAppUser) ? $verificationSecret : '');
$audits
->setParam('userId', $user->getId())
@@ -1521,7 +1724,7 @@ App::put('/v1/account/verification')
->groups(['api', 'account'])
->label('scope', 'public')
->label('event', 'account.verification.update')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateVerification')
->label('sdk.description', '/docs/references/account/update-verification.md')
+67 -68
View File
@@ -1,28 +1,28 @@
<?php
use Utopia\App;
use Utopia\Exception;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\Validator\Range;
use Utopia\Validator\URL;
use Utopia\Cache\Cache;
use Utopia\Cache\Adapter\Filesystem;
use Appwrite\Resize\Resize;
use Appwrite\URL\URL as URLParse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Validator\HexColor;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Utopia\Exception;
use Utopia\Image\Image;
use Utopia\Validator\Boolean;
use Utopia\Validator\HexColor;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Appwrite\Network\Validator\URL;
use Utopia\Validator\WhiteList;
$avatarCallback = function ($type, $code, $width, $height, $quality, $response) {
/** @var Appwrite\Utopia\Response $response */
$code = \strtolower($code);
$type = \strtolower($type);
$set = Config::getParam('avatar-'.$type, []);
$set = Config::getParam('avatar-' . $type, []);
if (empty($set)) {
throw new Exception('Avatar set not found', 404);
@@ -37,17 +37,17 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, $response)
}
$output = 'png';
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = \md5('/v1/avatars/:type/:code-'.$code.$width.$height.$quality.$output);
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
$key = \md5('/v1/avatars/'.$type.'/:code-' . $code . $width . $height . $quality . $output);
$path = $set[$code];
$type = 'png';
if (!\is_readable($path)) {
throw new Exception('File not readable in '.$path, 500);
throw new Exception('File not readable in ' . $path, 500);
}
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3/* 3 months */);
if ($data) {
//$output = (empty($output)) ? $type : $output;
@@ -60,38 +60,37 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, $response)
;
}
$resize = new Resize(\file_get_contents($path));
$image = new Image(\file_get_contents($path));
$resize->crop((int) $width, (int) $height);
$image->crop((int) $width, (int) $height);
$output = (empty($output)) ? $type : $output;
$data = $resize->output($output, $quality);
$data = $image->output($output, $quality);
$cache->save($key, $data);
$response
->setContentType('image/png')
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'miss')
->send($data, null);
;
unset($resize);
unset($image);
};
App::get('/v1/avatars/credit-cards/:code')
->desc('Get Credit Card Icon')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getCreditCard')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-credit-card.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: '.\implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))).'.')
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.')
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
@@ -104,7 +103,7 @@ App::get('/v1/avatars/browsers/:code')
->desc('Get Browser Icon')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getBrowser')
->label('sdk.methodType', 'location')
@@ -124,7 +123,7 @@ App::get('/v1/avatars/flags/:code')
->desc('Get Country Flag')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getFlag')
->label('sdk.methodType', 'location')
@@ -144,7 +143,7 @@ App::get('/v1/avatars/image')
->desc('Get Image from URL')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getImage')
->label('sdk.methodType', 'location')
@@ -160,11 +159,11 @@ App::get('/v1/avatars/image')
$quality = 80;
$output = 'png';
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = \md5('/v2/avatars/images-'.$url.'-'.$width.'/'.$height.'/'.$quality);
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
$key = \md5('/v2/avatars/images-' . $url . '-' . $width . '/' . $height . '/' . $quality);
$type = 'png';
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 7 /* 1 week */);
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 7/* 1 week */);
if ($data) {
return $response
@@ -186,17 +185,17 @@ App::get('/v1/avatars/image')
}
try {
$resize = new Resize($fetch);
} catch (\Exception $exception) {
$image = new Image($fetch);
} catch (\Exception$exception) {
throw new Exception('Unable to parse image', 500);
}
$resize->crop((int) $width, (int) $height);
$image->crop((int) $width, (int) $height);
$output = (empty($output)) ? $type : $output;
$data = $resize->output($output, $quality);
$data = $image->output($output, $quality);
$cache->save($key, $data);
$response
@@ -206,14 +205,14 @@ App::get('/v1/avatars/image')
->send($data);
;
unset($resize);
unset($image);
});
App::get('/v1/avatars/favicon')
->desc('Get Favicon')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getFavicon')
->label('sdk.methodType', 'location')
@@ -229,11 +228,11 @@ App::get('/v1/avatars/favicon')
$height = 56;
$quality = 80;
$output = 'png';
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = \md5('/v2/avatars/favicon-'.$url);
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
$key = \md5('/v2/avatars/favicon-' . $url);
$type = 'png';
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3/* 3 months */);
if ($data) {
return $response
@@ -316,7 +315,7 @@ App::get('/v1/avatars/favicon')
if (empty($outputHref) || empty($outputExt)) {
$default = \parse_url($url);
$outputHref = $default['scheme'].'://'.$default['host'].'/favicon.ico';
$outputHref = $default['scheme'] . '://' . $default['host'] . '/favicon.ico';
$outputExt = 'ico';
}
@@ -343,13 +342,13 @@ App::get('/v1/avatars/favicon')
throw new Exception('Icon not found', 404);
}
$resize = new Resize($fetch);
$image = new Image($fetch);
$resize->crop((int) $width, (int) $height);
$image->crop((int) $width, (int) $height);
$output = (empty($output)) ? $type : $output;
$data = $resize->output($output, $quality);
$data = $image->output($output, $quality);
$cache->save($key, $data);
@@ -359,14 +358,14 @@ App::get('/v1/avatars/favicon')
->addHeader('X-Appwrite-Cache', 'miss')
->send($data);
unset($resize);
unset($image);
});
App::get('/v1/avatars/qr')
->desc('Get QR Code')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getQR')
->label('sdk.methodType', 'location')
@@ -394,14 +393,14 @@ App::get('/v1/avatars/qr')
$response->addHeader('Content-Disposition', 'attachment; filename="qr.png"');
}
$resize = new Resize($qrcode->render($text));
$image = new Image($qrcode->render($text));
$resize->crop((int) $size, (int) $size);
$image->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
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->send($resize->output('png', 9))
->send($image->output('png', 9))
;
});
@@ -409,7 +408,7 @@ App::get('/v1/avatars/initials')
->desc('Get User Initials')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getInitials')
->label('sdk.methodType', 'location')
@@ -437,10 +436,10 @@ App::get('/v1/avatars/initials')
['color' => '#610038', 'background' => '#f5d1e6'], // PINK
['color' => '#386100', 'background' => '#dcf1bd'], // LIME
['color' => '#615800', 'background' => '#f1ecba'], // YELLOW
['color' => '#610008', 'background' => '#f6d2d5'] // RED
['color' => '#610008', 'background' => '#f6d2d5'], // RED
];
$rand = \rand(0, \count($themes)-1);
$rand = \rand(0, \count($themes) - 1);
$name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', ''));
$words = \explode(' ', \strtoupper($name));
@@ -457,23 +456,23 @@ App::get('/v1/avatars/initials')
}
$length = \count($words);
$rand = \substr($code,-1);
$background = (!empty($background)) ? '#'.$background : $themes[$rand]['background'];
$color = (!empty($color)) ? '#'.$color : $themes[$rand]['color'];
$rand = \substr($code, -1);
$background = (!empty($background)) ? '#' . $background : $themes[$rand]['background'];
$color = (!empty($color)) ? '#' . $color : $themes[$rand]['color'];
$image = new \Imagick();
$draw = new \ImagickDraw();
$fontSize = \min($width, $height) / 2;
$draw->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf");
$image->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf");
$draw->setFont(__DIR__ . "/../../../public/fonts/poppins-v9-latin-500.ttf");
$image->setFont(__DIR__ . "/../../../public/fonts/poppins-v9-latin-500.ttf");
$draw->setFillColor(new \ImagickPixel($color));
$draw->setFontSize($fontSize);
$draw->setTextAlignment(\Imagick::ALIGN_CENTER);
$draw->annotation($width / 1.97, ($height / 2) + ($fontSize / 3), $initials);
$image->newImage($width, $height, $background);
$image->setImageFormat("png");
$image->drawImage($draw);
@@ -481,7 +480,7 @@ App::get('/v1/avatars/initials')
//$image->setImageCompressionQuality(9 - round(($quality / 100) * 9));
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->send($image->getImageBlob())
;
+35 -39
View File
@@ -24,7 +24,7 @@ App::post('/v1/database/collections')
->label('event', 'database.collections.create')
->label('scope', 'collections.write')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createCollection')
->label('sdk.description', '/docs/references/database/create-collection.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
@@ -96,7 +96,7 @@ App::get('/v1/database/collections')
->groups(['api', 'database'])
->label('scope', 'collections.read')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'listCollections')
->label('sdk.description', '/docs/references/database/list-collections.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -133,7 +133,7 @@ App::get('/v1/database/collections/:collectionId')
->groups(['api', 'database'])
->label('scope', 'collections.read')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'getCollection')
->label('sdk.description', '/docs/references/database/get-collection.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -161,7 +161,7 @@ App::put('/v1/database/collections/:collectionId')
->label('scope', 'collections.write')
->label('event', 'database.collections.update')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'updateCollection')
->label('sdk.description', '/docs/references/database/update-collection.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -169,8 +169,8 @@ App::put('/v1/database/collections/:collectionId')
->label('sdk.response.model', Response::MODEL_COLLECTION)
->param('collectionId', '', new UID(), 'Collection unique ID.')
->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.')
->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions(/docs/permissions) and get a full list of available permissions.')
->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('rules', [], function ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.', true, ['projectDB'])
->inject('response')
->inject('projectDB')
@@ -187,6 +187,8 @@ App::put('/v1/database/collections/:collectionId')
}
$parsedRules = [];
$read = (is_null($read)) ? ($collection->getPermissions()['read'] ?? []) : $read; // By default inherit read permissions
$write = (is_null($write)) ? ($collection->getPermissions()['write'] ?? []) : $write; // By default inherit write permissions
foreach ($rules as &$rule) {
$parsedRules[] = \array_merge([
@@ -236,7 +238,7 @@ App::delete('/v1/database/collections/:collectionId')
->label('scope', 'collections.write')
->label('event', 'database.collections.delete')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'deleteCollection')
->label('sdk.description', '/docs/references/database/delete-collection.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
@@ -269,7 +271,7 @@ App::delete('/v1/database/collections/:collectionId')
;
$events
->setParam('payload', $response->output($collection, Response::MODEL_COLLECTION))
->setParam('eventData', $response->output($collection, Response::MODEL_COLLECTION))
;
$audits
@@ -287,25 +289,27 @@ App::post('/v1/database/collections/:collectionId/documents')
->label('event', 'database.documents.create')
->label('scope', 'documents.write')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.method', 'createDocument')
->label('sdk.description', '/docs/references/database/create-document.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ANY)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
->param('data', [], new JSON(), 'Document data as JSON object.')
->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('parentDocument', '', new UID(), 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true)
->param('parentProperty', '', new Key(), 'Parent document property name. Use when you want your new document to be a child of a parent document.', true)
->param('parentPropertyType', Document::SET_TYPE_ASSIGN, new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND], true), 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true)
->inject('response')
->inject('projectDB')
->inject('user')
->inject('audits')
->action(function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType, $response, $projectDB, $audits) {
->action(function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType, $response, $projectDB, $user, $audits) {
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Database $projectDB */
/** @var Appwrite\Database\Document $user */
/** @var Appwrite\Event\Event $audits */
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
@@ -326,8 +330,8 @@ App::post('/v1/database/collections/:collectionId/documents')
$data['$collection'] = $collectionId; // Adding this param to make API easier for developers
$data['$permissions'] = [
'read' => $read,
'write' => $write,
'read' => (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? [], // By default set read permissions for user
'write' => (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? [], // By default set write permissions for user
];
// Read parent document + validate not 404 + validate read / write permission like patch method
@@ -397,7 +401,7 @@ App::post('/v1/database/collections/:collectionId/documents')
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($data, Response::MODEL_ANY)
->dynamic($data, Response::MODEL_DOCUMENT)
;
});
@@ -406,7 +410,7 @@ App::get('/v1/database/collections/:collectionId/documents')
->groups(['api', 'database'])
->label('scope', 'documents.read')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.method', 'listDocuments')
->label('sdk.description', '/docs/references/database/list-documents.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -469,12 +473,12 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId')
->groups(['api', 'database'])
->label('scope', 'documents.read')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.method', 'getDocument')
->label('sdk.description', '/docs/references/database/get-document.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ANY)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
->param('documentId', null, new UID(), 'Document unique ID.')
->inject('response')
@@ -490,7 +494,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId')
throw new Exception('No document found', 404);
}
$response->dynamic($document, Response::MODEL_ANY);
$response->dynamic($document, Response::MODEL_DOCUMENT);
});
App::patch('/v1/database/collections/:collectionId/documents/:documentId')
@@ -499,17 +503,17 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
->label('event', 'database.documents.update')
->label('scope', 'documents.write')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.method', 'updateDocument')
->label('sdk.description', '/docs/references/database/update-document.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ANY)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
->param('documentId', null, new UID(), 'Document unique ID.')
->param('data', [], new JSON(), 'Document data as JSON object.')
->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->inject('response')
->inject('projectDB')
->inject('audits')
@@ -522,7 +526,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
$document = $projectDB->getDocument($documentId, false);
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
if (!\is_array($data)) {
throw new Exception('Data param should be a valid JSON object', 400);
}
@@ -535,20 +539,12 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
throw new Exception('No document found', 404);
}
//TODO check merge read write permissions
if (!empty($read)) { // Overwrite permissions only when passed
$data['$permissions']['read'] = $read;
}
if (!empty($write)) { // Overwrite permissions only when passed
$data['$permissions']['write'] = $write;
}
$data = \array_merge($document->getArrayCopy(), $data);
$data['$collection'] = $collection->getId(); // Make sure user don't switch collectionID
$data['$id'] = $document->getId(); // Make sure user don't switch document unique ID
$data['$permissions']['read'] = (is_null($read)) ? ($document->getPermissions()['read'] ?? []) : $read; // By default inherit read permissions
$data['$permissions']['write'] = (is_null($write)) ? ($document->getPermissions()['write'] ?? []) : $write; // By default inherit write permissions
if (empty($data)) {
throw new Exception('Missing payload', 400);
@@ -570,7 +566,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
->setParam('data', $data->getArrayCopy())
;
$response->dynamic($data, Response::MODEL_ANY);
$response->dynamic($data, Response::MODEL_DOCUMENT);
});
App::delete('/v1/database/collections/:collectionId/documents/:documentId')
@@ -579,7 +575,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
->label('scope', 'documents.write')
->label('event', 'database.documents.delete')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.method', 'deleteDocument')
->label('sdk.description', '/docs/references/database/delete-document.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
@@ -618,9 +614,9 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
}
$events
->setParam('payload', $response->output($document, Response::MODEL_ANY))
->setParam('eventData', $response->output($document, Response::MODEL_DOCUMENT))
;
$audits
->setParam('event', 'database.documents.delete')
->setParam('resource', 'database/document/'.$document->getId())
+54 -18
View File
@@ -1,5 +1,7 @@
<?php
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Database\Database;
use Appwrite\Database\Document;
use Appwrite\Database\Validator\Authorization;
@@ -27,7 +29,8 @@ App::post('/v1/functions')
->groups(['api', 'functions'])
->desc('Create Function')
->label('scope', 'functions.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('event', 'functions.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/functions/create-function.md')
@@ -80,7 +83,7 @@ App::get('/v1/functions')
->groups(['api', 'functions'])
->desc('List Functions')
->label('scope', 'functions.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/functions/list-functions.md')
@@ -117,7 +120,7 @@ App::get('/v1/functions/:functionId')
->groups(['api', 'functions'])
->desc('Get Function')
->label('scope', 'functions.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/functions/get-function.md')
@@ -144,7 +147,7 @@ App::get('/v1/functions/:functionId/usage')
->desc('Get Function Usage')
->groups(['api', 'functions'])
->label('scope', 'functions.read')
->label('sdk.platform', [APP_PLATFORM_CONSOLE])
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getUsage')
->param('functionId', '', new UID(), 'Function unique ID.')
@@ -265,7 +268,8 @@ App::put('/v1/functions/:functionId')
->groups(['api', 'functions'])
->desc('Update Function')
->label('scope', 'functions.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('event', 'functions.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'update')
->label('sdk.description', '/docs/references/functions/update-function.md')
@@ -330,7 +334,8 @@ App::patch('/v1/functions/:functionId/tag')
->groups(['api', 'functions'])
->desc('Update Function Tag')
->label('scope', 'functions.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('event', 'functions.tags.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'updateTag')
->label('sdk.description', '/docs/references/functions/update-function-tag.md')
@@ -387,7 +392,8 @@ App::delete('/v1/functions/:functionId')
->groups(['api', 'functions'])
->desc('Delete Function')
->label('scope', 'functions.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('event', 'functions.delete')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/functions/delete-function.md')
@@ -424,7 +430,8 @@ App::post('/v1/functions/:functionId/tags')
->groups(['api', 'functions'])
->desc('Create Tag')
->label('scope', 'functions.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('event', 'functions.tags.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'createTag')
->label('sdk.description', '/docs/references/functions/create-tag.md')
@@ -435,7 +442,7 @@ App::post('/v1/functions/:functionId/tags')
->label('sdk.response.model', Response::MODEL_TAG)
->param('functionId', '', new UID(), 'Function unique ID.')
->param('command', '', new Text('1028'), 'Code execution command.')
->param('code', null, new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', false)
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', false)
->inject('request')
->inject('response')
->inject('projectDB')
@@ -518,7 +525,7 @@ App::get('/v1/functions/:functionId/tags')
->groups(['api', 'functions'])
->desc('List Tags')
->label('scope', 'functions.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'listTags')
->label('sdk.description', '/docs/references/functions/list-tags.md')
@@ -563,7 +570,7 @@ App::get('/v1/functions/:functionId/tags/:tagId')
->groups(['api', 'functions'])
->desc('Get Tag')
->label('scope', 'functions.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getTag')
->label('sdk.description', '/docs/references/functions/get-tag.md')
@@ -601,7 +608,8 @@ App::delete('/v1/functions/:functionId/tags/:tagId')
->groups(['api', 'functions'])
->desc('Delete Tag')
->label('scope', 'functions.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('event', 'functions.tags.delete')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'deleteTag')
->label('sdk.description', '/docs/references/functions/delete-tag.md')
@@ -662,7 +670,8 @@ App::post('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
->desc('Create Execution')
->label('scope', 'execution.write')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('event', 'functions.executions.create')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'createExecution')
->label('sdk.description', '/docs/references/functions/create-execution.md')
@@ -672,14 +681,17 @@ App::post('/v1/functions/:functionId/executions')
->label('abuse-limit', 60)
->label('abuse-time', 60)
->param('functionId', '', new UID(), 'Function unique ID.')
->param('data', '', new Text(8192), 'String of custom data to send to function.', true)
// ->param('async', 1, new Range(0, 1), 'Execute code asynchronously. Pass 1 for true, 0 for false. Default value is 1.', true)
->inject('response')
->inject('project')
->inject('projectDB')
->action(function ($functionId, /*$async,*/ $response, $project, $projectDB) {
->inject('user')
->action(function ($functionId, $data, /*$async,*/ $response, $project, $projectDB, $user) {
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $project */
/** @var Appwrite\Database\Database $projectDB */
/** @var Appwrite\Database\Document $user */
Authorization::disable();
@@ -712,7 +724,7 @@ App::post('/v1/functions/:functionId/executions')
$execution = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_EXECUTIONS,
'$permissions' => [
'read' => $function->getPermissions()['execute'] ?? [],
'read' => (!empty($user->getId())) ? ['user:' . $user->getId()] : [],
'write' => [],
],
'dateCreated' => time(),
@@ -730,12 +742,36 @@ App::post('/v1/functions/:functionId/executions')
if (false === $execution) {
throw new Exception('Failed saving execution to DB', 500);
}
$jwt = ''; // initialize
if (!empty($user->getId())) { // If userId exists, generate a JWT for function
$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()) {
$jwtObj = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
$jwt = $jwtObj->encode([
'userId' => $user->getId(),
'sessionId' => $session->getId(),
]);
}
}
Resque::enqueue('v1-functions', 'FunctionsV1', [
'projectId' => $project->getId(),
'functionId' => $function->getId(),
'executionId' => $execution->getId(),
'trigger' => 'http',
'data' => $data,
'userId' => $user->getId(),
'jwt' => $jwt,
]);
$response
@@ -748,7 +784,7 @@ App::get('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
->desc('List Executions')
->label('scope', 'execution.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'listExecutions')
->label('sdk.description', '/docs/references/functions/list-executions.md')
@@ -793,7 +829,7 @@ App::get('/v1/functions/:functionId/executions/:executionId')
->groups(['api', 'functions'])
->desc('Get Execution')
->label('scope', 'execution.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getExecution')
->label('sdk.description', '/docs/references/functions/get-execution.md')
+17 -14
View File
@@ -11,7 +11,7 @@ App::get('/v1/health')
->desc('Get HTTP')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/health/get.md')
@@ -37,7 +37,7 @@ App::get('/v1/health/db')
->desc('Get DB')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getDB')
->label('sdk.description', '/docs/references/health/get-db.md')
@@ -56,7 +56,7 @@ App::get('/v1/health/cache')
->desc('Get Cache')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getCache')
->label('sdk.description', '/docs/references/health/get-cache.md')
@@ -74,7 +74,7 @@ App::get('/v1/health/time')
->desc('Get Time')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getTime')
->label('sdk.description', '/docs/references/health/get-time.md')
@@ -123,7 +123,7 @@ App::get('/v1/health/queue/webhooks')
->desc('Get Webhooks Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueWebhooks')
->label('sdk.description', '/docs/references/health/get-queue-webhooks.md')
@@ -138,7 +138,7 @@ App::get('/v1/health/queue/tasks')
->desc('Get Tasks Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueTasks')
->label('sdk.description', '/docs/references/health/get-queue-tasks.md')
@@ -153,7 +153,7 @@ App::get('/v1/health/queue/logs')
->desc('Get Logs Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueLogs')
->label('sdk.description', '/docs/references/health/get-queue-logs.md')
@@ -168,7 +168,7 @@ App::get('/v1/health/queue/usage')
->desc('Get Usage Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueUsage')
->label('sdk.description', '/docs/references/health/get-queue-usage.md')
@@ -183,7 +183,7 @@ App::get('/v1/health/queue/certificates')
->desc('Get Certificate Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueCertificates')
->label('sdk.description', '/docs/references/health/get-queue-certificates.md')
@@ -198,7 +198,7 @@ App::get('/v1/health/queue/functions')
->desc('Get Functions Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueFunctions')
->label('sdk.description', '/docs/references/health/get-queue-functions.md')
@@ -213,7 +213,7 @@ App::get('/v1/health/storage/local')
->desc('Get Local Storage')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getStorageLocal')
->label('sdk.description', '/docs/references/health/get-storage-local.md')
@@ -245,7 +245,7 @@ App::get('/v1/health/anti-virus')
->desc('Get Anti virus')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getAntiVirus')
->label('sdk.description', '/docs/references/health/get-storage-anti-virus.md')
@@ -254,7 +254,10 @@ App::get('/v1/health/anti-virus')
/** @var Appwrite\Utopia\Response $response */
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'disabled') { // Check if scans are enabled
throw new Exception('Anitvirus is disabled');
return $response->json([
'status' => 'disabled',
'version' => '',
]);
}
$antiVirus = new Network(App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
@@ -270,7 +273,7 @@ App::get('/v1/health/stats') // Currently only used internally
->desc('Get System Stats')
->groups(['api', 'health'])
->label('scope', 'god')
// ->label('sdk.platform', [APP_PLATFORM_SERVER])
// ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
// ->label('sdk.namespace', 'health')
// ->label('sdk.method', 'getStats')
->label('docs', false)
+7 -7
View File
@@ -9,7 +9,7 @@ App::get('/v1/locale')
->desc('Get User Locale')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/locale/get-locale.md')
@@ -74,7 +74,7 @@ App::get('/v1/locale/countries')
->desc('List Countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getCountries')
->label('sdk.description', '/docs/references/locale/get-countries.md')
@@ -106,7 +106,7 @@ App::get('/v1/locale/countries/eu')
->desc('List EU Countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getCountriesEU')
->label('sdk.description', '/docs/references/locale/get-countries-eu.md')
@@ -141,7 +141,7 @@ App::get('/v1/locale/countries/phones')
->desc('List Countries Phone Codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getCountriesPhones')
->label('sdk.description', '/docs/references/locale/get-countries-phones.md')
@@ -177,7 +177,7 @@ App::get('/v1/locale/continents')
->desc('List Continents')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getContinents')
->label('sdk.description', '/docs/references/locale/get-continents.md')
@@ -208,7 +208,7 @@ App::get('/v1/locale/currencies')
->desc('List Currencies')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getCurrencies')
->label('sdk.description', '/docs/references/locale/get-currencies.md')
@@ -233,7 +233,7 @@ App::get('/v1/locale/languages')
->desc('List Languages')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getLanguages')
->label('sdk.description', '/docs/references/locale/get-languages.md')
+108 -1
View File
@@ -6,8 +6,9 @@ use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\Validator\URL;
use Appwrite\Network\Validator\URL;
use Utopia\Validator\Range;
use Utopia\Validator\Integer;
use Utopia\Config\Config;
use Utopia\Domains\Domain;
use Appwrite\Auth\Auth;
@@ -24,6 +25,7 @@ App::post('/v1/projects')
->desc('Create Project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'create')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
@@ -96,6 +98,7 @@ App::get('/v1/projects')
->desc('List Projects')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'list')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -131,6 +134,7 @@ App::get('/v1/projects/:projectId')
->desc('Get Project')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'get')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -156,6 +160,7 @@ App::get('/v1/projects/:projectId/usage')
->desc('Get Project')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getUsage')
->param('projectId', '', new UID(), 'Project unique ID.')
@@ -356,6 +361,7 @@ App::patch('/v1/projects/:projectId')
->desc('Update Project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'update')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -408,6 +414,7 @@ App::patch('/v1/projects/:projectId/oauth2')
->desc('Update Project OAuth2')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateOAuth2')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -441,10 +448,85 @@ App::patch('/v1/projects/:projectId/oauth2')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/limit')
->desc('Update Project users limit')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateAuthLimit')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('limit', false, new Integer(true), 'Set the max number of users allowed in this project. Use 0 for unlimited.')
->inject('response')
->inject('consoleDB')
->action(function ($projectId, $limit, $response, $consoleDB) {
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Database $consoleDB */
$project = $consoleDB->getDocument($projectId);
if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
if (false === $consoleDB->updateDocument(
\array_merge($project->getArrayCopy(), [
'usersAuthLimit' => $limit,
]))
) {
throw new Exception('Failed saving project to DB', 500);
};
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/:method')
->desc('Update Project auth method status. Use this endpoint to enable or disable a given auth method for this project.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateAuthStatus')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('method', '', new WhiteList(\array_keys(Config::getParam('auth')), true), 'Auth Method. Possible values: '.implode(',', \array_keys(Config::getParam('auth'))), false)
->param('status', false, new Boolean(true), 'Set the status of this auth method.')
->inject('response')
->inject('consoleDB')
->action(function ($projectId, $method, $status, $response, $consoleDB) {
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Database $consoleDB */
$project = $consoleDB->getDocument($projectId);
$auth = Config::getParam('auth')[$method] ?? [];
$authKey = $auth['key'] ?? '';
$status = ($status === '1' || $status === 'true' || $status === 1 || $status === true);
if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
if (false === $consoleDB->updateDocument(
\array_merge($project->getArrayCopy(), [
$authKey => $status,
]))
) {
throw new Exception('Failed saving project to DB', 500);
};
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::delete('/v1/projects/:projectId')
->desc('Delete Project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'delete')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
@@ -510,6 +592,7 @@ App::post('/v1/projects/:projectId/webhooks')
->desc('Create Webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createWebhook')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
@@ -572,6 +655,7 @@ App::get('/v1/projects/:projectId/webhooks')
->desc('List Webhooks')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listWebhooks')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -602,6 +686,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Get Webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getWebhook')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -634,6 +719,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Update Webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateWebhook')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -687,6 +773,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Delete Webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteWebhook')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
@@ -724,6 +811,7 @@ App::post('/v1/projects/:projectId/keys')
->desc('Create Key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createKey')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
@@ -777,6 +865,7 @@ App::get('/v1/projects/:projectId/keys')
->desc('List Keys')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listKeys')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -807,6 +896,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
->desc('Get Key')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getKey')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -836,6 +926,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
->desc('Update Key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateKey')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -879,6 +970,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
->desc('Delete Key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteKey')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
@@ -916,6 +1008,7 @@ App::post('/v1/projects/:projectId/tasks')
->desc('Create Task')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createTask')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
@@ -996,6 +1089,7 @@ App::get('/v1/projects/:projectId/tasks')
->desc('List Tasks')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listTasks')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -1027,6 +1121,7 @@ App::get('/v1/projects/:projectId/tasks/:taskId')
->desc('Get Task')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getTask')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -1059,6 +1154,7 @@ App::put('/v1/projects/:projectId/tasks/:taskId')
->desc('Update Task')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateTask')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -1127,6 +1223,7 @@ App::delete('/v1/projects/:projectId/tasks/:taskId')
->desc('Delete Task')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteTask')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
@@ -1164,6 +1261,7 @@ App::post('/v1/projects/:projectId/platforms')
->desc('Create Platform')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createPlatform')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
@@ -1224,6 +1322,7 @@ App::get('/v1/projects/:projectId/platforms')
->desc('List Platforms')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listPlatforms')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -1254,6 +1353,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
->desc('Get Platform')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getPlatform')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -1286,6 +1386,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
->desc('Update Platform')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updatePlatform')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -1334,6 +1435,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
->desc('Delete Platform')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deletePlatform')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
@@ -1371,6 +1473,7 @@ App::post('/v1/projects/:projectId/domains')
->desc('Create Domain')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createDomain')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
@@ -1440,6 +1543,7 @@ App::get('/v1/projects/:projectId/domains')
->desc('List Domains')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listDomains')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -1470,6 +1574,7 @@ App::get('/v1/projects/:projectId/domains/:domainId')
->desc('Get Domain')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getDomain')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -1502,6 +1607,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
->desc('Update Domain Verification Status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateDomainVerification')
->label('sdk.response.code', Response::STATUS_CODE_OK)
@@ -1565,6 +1671,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
->desc('Delete Domain')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteDomain')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
+49 -25
View File
@@ -18,7 +18,7 @@ use Utopia\Storage\Validator\File;
use Utopia\Storage\Validator\FileSize;
use Utopia\Storage\Validator\Upload;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Appwrite\Resize\Resize;
use Utopia\Image\Image;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
@@ -28,7 +28,7 @@ App::post('/v1/storage/files')
->groups(['api', 'storage'])
->label('scope', 'files.write')
->label('event', 'storage.files.create')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'createFile')
->label('sdk.description', '/docs/references/storage/create-file.md')
@@ -38,17 +38,19 @@ App::post('/v1/storage/files')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->param('file', [], new File(), 'Binary file.', false)
->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->inject('request')
->inject('response')
->inject('projectDB')
->inject('user')
->inject('audits')
->inject('usage')
->action(function ($file, $read, $write, $request, $response, $projectDB, $audits, $usage) {
->action(function ($file, $read, $write, $request, $response, $projectDB, $user, $audits, $usage) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Database $projectDB */
/** @var Appwrite\Database\Document $user */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $usage */
@@ -113,7 +115,7 @@ App::post('/v1/storage/files')
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag);
if (!$device->write($path, $data)) {
if (!$device->write($path, $data, $mimeType)) {
throw new Exception('Failed to save file', 500);
}
@@ -122,8 +124,8 @@ App::post('/v1/storage/files')
$file = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_FILES,
'$permissions' => [
'read' => $read,
'write' => $write,
'read' => (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? [], // By default set read permissions for user
'write' => (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? [], // By default set write permissions for user
],
'dateCreated' => \time(),
'folderId' => '',
@@ -165,7 +167,7 @@ App::get('/v1/storage/files')
->desc('List Files')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'listFiles')
->label('sdk.description', '/docs/references/storage/list-files.md')
@@ -202,7 +204,7 @@ App::get('/v1/storage/files/:fileId')
->desc('Get File')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFile')
->label('sdk.description', '/docs/references/storage/get-file.md')
@@ -229,7 +231,7 @@ App::get('/v1/storage/files/:fileId/preview')
->desc('Get File Preview')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFilePreview')
->label('sdk.description', '/docs/references/storage/get-file-preview.md')
@@ -240,13 +242,18 @@ App::get('/v1/storage/files/:fileId/preview')
->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true)
->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true)
->param('quality', 100, new Range(0, 100), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->param('borderWidth', 0, new Range(0, 100), 'Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0.', true)
->param('borderColor', '', new HexColor(), 'Preview image border color. Use a valid HEX color, no # is needed for prefix.', true)
->param('borderRadius', 0, new Range(0, 4000), 'Preview image border radius in pixels. Pass an integer between 0 to 4000.', true)
->param('opacity', 1, new Range(0,1), 'Preview image opacity. Only works with images having an alpha channel (like png). Pass a number between 0 to 1.', true)
->param('rotation', 0, new Range(0,360), 'Preview image rotation in degrees. Pass an integer between 0 and 360.', true)
->param('background', '', new HexColor(), 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true)
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true)
->inject('request')
->inject('response')
->inject('project')
->inject('projectDB')
->action(function ($fileId, $width, $height, $quality, $background, $output, $request, $response, $project, $projectDB) {
->action(function ($fileId, $width, $height, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $projectDB) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $project */
@@ -271,7 +278,7 @@ App::get('/v1/storage/files/:fileId/preview')
$fileLogos = Config::getParam('storage-logos');
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = \md5($fileId.$width.$height.$quality.$background.$storage.$output);
$key = \md5($fileId.$width.$height.$quality.$borderWidth.$borderColor.$borderRadius.$opacity.$rotation.$background.$storage.$output);
$file = $projectDB->getDocument($fileId);
@@ -291,7 +298,7 @@ App::get('/v1/storage/files/:fileId/preview')
$cipher = null;
$background = (empty($background)) ? 'eceff1' : $background;
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
$key = \md5($path.$width.$height.$quality.$background.$storage.$output);
$key = \md5($path.$width.$height.$quality.$borderWidth.$borderColor.$borderRadius.$opacity.$rotation.$background.$storage.$output);
}
$compressor = new GZIP();
@@ -332,17 +339,34 @@ App::get('/v1/storage/files/:fileId/preview')
$source = $compressor->decompress($source);
}
$resize = new Resize($source);
$image = new Image($source);
$resize->crop((int) $width, (int) $height);
$image->crop((int) $width, (int) $height);
if (!empty($opacity) || $opacity==0) {
$image->setOpacity($opacity);
}
if (!empty($background)) {
$resize->setBackground('#'.$background);
$image->setBackground('#'.$background);
}
if (!empty($borderWidth) ) {
$image->setBorder($borderWidth, '#'.$borderColor);
}
if (!empty($borderRadius)) {
$image->setBorderRadius($borderRadius);
}
if (!empty($rotation)) {
$image->setRotation($rotation);
}
$output = (empty($output)) ? $type : $output;
$data = $resize->output($output, $quality);
$data = $image->output($output, $quality);
$cache->save($key, $data);
@@ -353,14 +377,14 @@ App::get('/v1/storage/files/:fileId/preview')
->send($data)
;
unset($resize);
unset($image);
});
App::get('/v1/storage/files/:fileId/download')
->desc('Get File for Download')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFileDownload')
->label('sdk.description', '/docs/references/storage/get-file-download.md')
@@ -418,7 +442,7 @@ App::get('/v1/storage/files/:fileId/view')
->desc('Get File for View')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFileView')
->label('sdk.description', '/docs/references/storage/get-file-view.md')
@@ -487,7 +511,7 @@ App::put('/v1/storage/files/:fileId')
->groups(['api', 'storage'])
->label('scope', 'files.write')
->label('event', 'storage.files.update')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'updateFile')
->label('sdk.description', '/docs/references/storage/update-file.md')
@@ -536,7 +560,7 @@ App::delete('/v1/storage/files/:fileId')
->groups(['api', 'storage'])
->label('scope', 'files.write')
->label('event', 'storage.files.delete')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'deleteFile')
->label('sdk.description', '/docs/references/storage/delete-file.md')
@@ -579,7 +603,7 @@ App::delete('/v1/storage/files/:fileId')
;
$events
->setParam('payload', $response->output($file, Response::MODEL_FILE))
->setParam('eventData', $response->output($file, Response::MODEL_FILE))
;
$response->noContent();
@@ -589,7 +613,7 @@ App::delete('/v1/storage/files/:fileId')
// ->desc('Scan Storage')
// ->groups(['api', 'storage'])
// ->label('scope', 'god')
// ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
// ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
// ->label('sdk.namespace', 'storage')
// ->label('sdk.method', 'getFileScan')
// ->label('sdk.hide', true)
+46 -26
View File
@@ -3,9 +3,9 @@
use Utopia\App;
use Utopia\Exception;
use Utopia\Config\Config;
use Utopia\Validator\Email;
use Appwrite\Network\Validator\Email;
use Utopia\Validator\Text;
use Utopia\Validator\Host;
use Appwrite\Network\Validator\Host;
use Utopia\Validator\Range;
use Utopia\Validator\ArrayList;
use Utopia\Validator\WhiteList;
@@ -25,7 +25,7 @@ App::post('/v1/teams')
->groups(['api', 'teams'])
->label('event', 'teams.create')
->label('scope', 'teams.write')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/teams/create-team.md')
@@ -44,7 +44,7 @@ App::post('/v1/teams')
Authorization::disable();
$isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$team = $projectDB->createDocument([
@@ -54,7 +54,7 @@ App::post('/v1/teams')
'write' => ['team:{self}/owner'],
],
'name' => $name,
'sum' => ($isPreviliggedUser || $isAppUser) ? 0 : 1,
'sum' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
'dateCreated' => \time(),
]);
@@ -64,7 +64,7 @@ App::post('/v1/teams')
throw new Exception('Failed saving team to DB', 500);
}
if (!$isPreviliggedUser && !$isAppUser) { // Don't add user on server mode
if (!$isPrivilegedUser && !$isAppUser) { // Don't add user on server mode
$membership = new Document([
'$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'$permissions' => [
@@ -100,7 +100,7 @@ App::get('/v1/teams')
->desc('List Teams')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/teams/list-teams.md')
@@ -137,7 +137,7 @@ App::get('/v1/teams/:teamId')
->desc('Get Team')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/teams/get-team.md')
@@ -165,7 +165,7 @@ App::put('/v1/teams/:teamId')
->groups(['api', 'teams'])
->label('event', 'teams.update')
->label('scope', 'teams.write')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'update')
->label('sdk.description', '/docs/references/teams/update-team.md')
@@ -202,7 +202,7 @@ App::delete('/v1/teams/:teamId')
->groups(['api', 'teams'])
->label('event', 'teams.delete')
->label('scope', 'teams.write')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/teams/delete-team.md')
@@ -243,7 +243,7 @@ App::delete('/v1/teams/:teamId')
}
$events
->setParam('payload', $response->output($team, Response::MODEL_TEAM))
->setParam('eventData', $response->output($team, Response::MODEL_TEAM))
;
$response->noContent();
@@ -251,16 +251,18 @@ App::delete('/v1/teams/:teamId')
App::post('/v1/teams/:teamId/memberships')
->desc('Create Team Membership')
->groups(['api', 'teams'])
->groups(['api', 'teams', 'auth'])
->label('event', 'teams.memberships.create')
->label('scope', 'teams.write')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('auth.type', 'invites')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'createMembership')
->label('sdk.description', '/docs/references/teams/create-team-membership.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->label('abuse-limit', 10)
->param('teamId', '', new UID(), 'Team unique ID.')
->param('email', '', new Email(), 'New team member email.')
->param('name', '', new Text(128), 'New team member name. Max length: 128 chars.', true)
@@ -281,7 +283,7 @@ App::post('/v1/teams/:teamId/memberships')
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $mails */
$isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$name = (empty($name)) ? $email : $name;
@@ -310,6 +312,22 @@ App::post('/v1/teams/:teamId/memberships')
if (empty($invitee)) { // Create new user if no user with same email found
$limit = $project->getAttribute('usersAuthLimit', 0);
if ($limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed.
$projectDB->getCollection([ // Count users
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_USERS,
],
]);
$sum = $projectDB->getSum();
if($sum >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501);
}
}
Authorization::disable();
try {
@@ -327,6 +345,7 @@ App::post('/v1/teams/:teamId/memberships')
'registration' => \time(),
'reset' => false,
'name' => $name,
'sessions' => [],
'tokens' => [],
], ['email' => $email]);
} catch (Duplicate $th) {
@@ -352,7 +371,7 @@ App::post('/v1/teams/:teamId/memberships')
}
}
if (!$isOwner && !$isPreviliggedUser && !$isAppUser) { // Not owner, not admin, not app (server)
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
throw new Exception('User is not allowed to send invitations for this team', 401);
}
@@ -368,12 +387,12 @@ App::post('/v1/teams/:teamId/memberships')
'teamId' => $team->getId(),
'roles' => $roles,
'invited' => \time(),
'joined' => ($isPreviliggedUser || $isAppUser) ? \time() : 0,
'confirm' => ($isPreviliggedUser || $isAppUser),
'joined' => ($isPrivilegedUser || $isAppUser) ? \time() : 0,
'confirm' => ($isPrivilegedUser || $isAppUser),
'secret' => Auth::hash($secret),
]);
if ($isPreviliggedUser || $isAppUser) { // Allow admin to create membership
if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership
Authorization::disable();
$membership = $projectDB->createDocument($membership->getArrayCopy());
@@ -425,7 +444,7 @@ App::post('/v1/teams/:teamId/memberships')
->setParam('{{text-cta}}', '#ffffff')
;
if (!$isPreviliggedUser && !$isAppUser) { // No need in comfirmation when in admin or app mode
if (!$isPrivilegedUser && !$isAppUser) { // No need in comfirmation when in admin or app mode
$mails
->setParam('event', 'teams.membership.create')
->setParam('from', ($project->getId() === 'console') ? '' : \sprintf($locale->getText('account.emails.team'), $project->getAttribute('name')))
@@ -456,7 +475,7 @@ App::get('/v1/teams/:teamId/memberships')
->desc('Get Team Memberships')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'getMemberships')
->label('sdk.description', '/docs/references/teams/get-team-members.md')
@@ -510,7 +529,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status')
->groups(['api', 'teams'])
->label('event', 'teams.memberships.update.status')
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'updateMembershipStatus')
->label('sdk.description', '/docs/references/teams/update-team-membership-status.md')
@@ -595,10 +614,11 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status')
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$secret = Auth::tokenGenerator();
$session = new Document(array_merge([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$collection' => Database::SYSTEM_COLLECTION_SESSIONS,
'$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]],
'userId' => $user->getId(),
'type' => Auth::TOKEN_TYPE_LOGIN,
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expiry,
'userAgent' => $request->getUserAgent('UNKNOWN'),
@@ -606,7 +626,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status')
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
$user->setAttribute('tokens', $session, Document::SET_TYPE_APPEND);
$user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND);
Authorization::setRole('user:'.$userId);
@@ -656,7 +676,7 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId')
->groups(['api', 'teams'])
->label('event', 'teams.memberships.delete')
->label('scope', 'teams.write')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'deleteMembership')
->label('sdk.description', '/docs/references/teams/delete-team-membership.md')
@@ -711,7 +731,7 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId')
;
$events
->setParam('payload', $response->output($membership, Response::MODEL_MEMBERSHIP))
->setParam('eventData', $response->output($membership, Response::MODEL_MEMBERSHIP))
;
$response->noContent();
+36 -34
View File
@@ -4,7 +4,7 @@ use Utopia\App;
use Utopia\Exception;
use Utopia\Validator\Assoc;
use Utopia\Validator\WhiteList;
use Utopia\Validator\Email;
use Appwrite\Network\Validator\Email;
use Utopia\Validator\Text;
use Utopia\Validator\Range;
use Utopia\Audit\Audit;
@@ -23,7 +23,7 @@ App::post('/v1/users')
->groups(['api', 'users'])
->label('event', 'users.create')
->label('scope', 'users.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/users/create-user.md')
@@ -81,7 +81,7 @@ App::get('/v1/users')
->desc('List Users')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/users/list-users.md')
@@ -118,7 +118,7 @@ App::get('/v1/users/:userId')
->desc('Get User')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/users/get-user.md')
@@ -145,7 +145,7 @@ App::get('/v1/users/:userId/prefs')
->desc('Get User Preferences')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'getPrefs')
->label('sdk.description', '/docs/references/users/get-user-prefs.md')
@@ -174,7 +174,7 @@ App::get('/v1/users/:userId/sessions')
->desc('Get User Sessions')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'getSessions')
->label('sdk.description', '/docs/references/users/get-user-sessions.md')
@@ -196,21 +196,18 @@ App::get('/v1/users/:userId/sessions')
throw new Exception('User not found', 404);
}
$tokens = $user->getAttribute('tokens', []);
$sessions = [];
$sessions = $user->getAttribute('sessions', []);
$countries = $locale->getText('countries');
foreach ($tokens as $token) { /* @var $token Document */
if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) {
continue;
}
foreach ($sessions as $key => $session) {
/** @var Document $session */
$token->setAttribute('countryName', (isset($countries[$token->getAttribute('contryCode')]))
? $countries[$token->getAttribute('contryCode')]
$session->setAttribute('countryName', (isset($countries[strtoupper($session->getAttribute('countryCode'))]))
? $countries[strtoupper($session->getAttribute('countryCode'))]
: $locale->getText('locale.country.unknown'));
$token->setAttribute('current', false);
$session->setAttribute('current', false);
$sessions[] = $token;
$sessions[$key] = $session;
}
$response->dynamic(new Document([
@@ -223,7 +220,7 @@ App::get('/v1/users/:userId/logs')
->desc('Get User Logs')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'getLogs')
->label('sdk.description', '/docs/references/users/get-user-logs.md')
@@ -338,7 +335,7 @@ App::patch('/v1/users/:userId/status')
->groups(['api', 'users'])
->label('event', 'users.update.status')
->label('scope', 'users.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateStatus')
->label('sdk.description', '/docs/references/users/update-user-status.md')
@@ -373,8 +370,9 @@ App::patch('/v1/users/:userId/status')
App::patch('/v1/users/:userId/prefs')
->desc('Update User Preferences')
->groups(['api', 'users'])
->label('event', 'users.update.prefs')
->label('scope', 'users.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updatePrefs')
->label('sdk.description', '/docs/references/users/update-user-prefs.md')
@@ -411,7 +409,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
->groups(['api', 'users'])
->label('event', 'users.sessions.delete')
->label('scope', 'users.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'deleteSession')
->label('sdk.description', '/docs/references/users/delete-user-session.md')
@@ -433,16 +431,18 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
throw new Exception('User not found', 404);
}
$tokens = $user->getAttribute('tokens', []);
$sessions = $user->getAttribute('sessions', []);
foreach ($tokens as $token) { /* @var $token Document */
if ($sessionId == $token->getId()) {
if (!$projectDB->deleteDocument($token->getId())) {
foreach ($sessions as $session) {
/** @var Document $session */
if ($sessionId == $session->getId()) {
if (!$projectDB->deleteDocument($session->getId())) {
throw new Exception('Failed to remove token from DB', 500);
}
$events
->setParam('payload', $response->output($user, Response::MODEL_USER))
->setParam('eventData', $response->output($user, Response::MODEL_USER))
;
}
}
@@ -456,7 +456,7 @@ App::delete('/v1/users/:userId/sessions')
->groups(['api', 'users'])
->label('event', 'users.sessions.delete')
->label('scope', 'users.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'deleteSessions')
->label('sdk.description', '/docs/references/users/delete-user-sessions.md')
@@ -477,16 +477,18 @@ App::delete('/v1/users/:userId/sessions')
throw new Exception('User not found', 404);
}
$tokens = $user->getAttribute('tokens', []);
$sessions = $user->getAttribute('sessions', []);
foreach ($tokens as $token) { /* @var $token Document */
if (!$projectDB->deleteDocument($token->getId())) {
foreach ($sessions as $session) {
/** @var Document $session */
if (!$projectDB->deleteDocument($session->getId())) {
throw new Exception('Failed to remove token from DB', 500);
}
}
$events
->setParam('payload', $response->output($user, Response::MODEL_USER))
->setParam('eventData', $response->output($user, Response::MODEL_USER))
;
// TODO : Response filter implementation
@@ -498,10 +500,10 @@ App::delete('/v1/users/:userId')
->groups(['api', 'users'])
->label('event', 'users.delete')
->label('scope', 'users.write')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'deleteUser')
->label('sdk.description', '/docs/references/users/delete-user.md')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/users/delete.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('userId', '', function () {return new UID();}, 'User unique ID.')
@@ -546,7 +548,7 @@ App::delete('/v1/users/:userId')
;
$events
->setParam('payload', $response->output($user, Response::MODEL_USER))
->setParam('eventData', $response->output($user, Response::MODEL_USER))
;
// TODO : Response filter implementation
+7 -4
View File
@@ -41,7 +41,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
$route = $utopia->match($request);
if (!empty($route->getLabel('sdk.platform', [])) && empty($project->getId()) && ($route->getLabel('scope', '') !== 'public')) {
if (!empty($route->getLabel('sdk.auth', [])) && empty($project->getId()) && ($route->getLabel('scope', '') !== 'public')) {
throw new Exception('Missing or unknown project ID', 400);
}
@@ -117,7 +117,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
->addHeader('Server', 'Appwrite')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-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-Appwrite-Response-Format, 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')
@@ -237,7 +237,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-Appwrite-JWT, 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-Appwrite-Response-Format, 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')
@@ -256,6 +256,8 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) {
$template = ($route) ? $route->getLabel('error', null) : null;
if (php_sapi_name() === 'cli') {
Console::error('[Error] Timestamp: '.date('c', time()));
if($route) {
Console::error('[Error] Method: '.$route->getMethod());
Console::error('[Error] URL: '.$route->getURL());
@@ -269,7 +271,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
switch ($error->getCode()) {
switch ($error->getCode()) { // Don't show 500 errors!
case 400: // Error allowed publicly
case 401: // Error allowed publicly
case 402: // Error allowed publicly
@@ -278,6 +280,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) {
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 429: // Error allowed publicly
case 501: // Error allowed publicly
$code = $error->getCode();
$message = $error->getMessage();
break;
+135 -54
View File
@@ -2,8 +2,9 @@
global $utopia, $request, $response;
use Appwrite\Database\Document;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Response;
use Utopia\Validator\Numeric;
use Utopia\Validator\Text;
use Utopia\Validator\ArrayList;
@@ -11,13 +12,16 @@ use Utopia\Validator\Host;
use Utopia\Storage\Validator\File;
App::get('/v1/mock/tests/foo')
->desc('Mock a get request for SDK tests')
->desc('Get Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'get')
->label('sdk.description', 'Mock a get request for SDK tests')
->label('sdk.description', 'Mock a get request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -26,13 +30,16 @@ App::get('/v1/mock/tests/foo')
});
App::post('/v1/mock/tests/foo')
->desc('Mock a post request for SDK tests')
->desc('Post Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'post')
->label('sdk.description', 'Mock a post request for SDK tests')
->label('sdk.description', 'Mock a post request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -41,13 +48,16 @@ App::post('/v1/mock/tests/foo')
});
App::patch('/v1/mock/tests/foo')
->desc('Mock a patch request for SDK tests')
->desc('Patch Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'patch')
->label('sdk.description', 'Mock a get request for SDK tests')
->label('sdk.description', 'Mock a patch request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -56,13 +66,16 @@ App::patch('/v1/mock/tests/foo')
});
App::put('/v1/mock/tests/foo')
->desc('Mock a put request for SDK tests')
->desc('Put Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'put')
->label('sdk.description', 'Mock a put request for SDK tests')
->label('sdk.description', 'Mock a put request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -71,13 +84,16 @@ App::put('/v1/mock/tests/foo')
});
App::delete('/v1/mock/tests/foo')
->desc('Mock a delete request for SDK tests')
->desc('Delete Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'delete')
->label('sdk.description', 'Mock a delete request for SDK tests')
->label('sdk.description', 'Mock a delete request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -86,13 +102,16 @@ App::delete('/v1/mock/tests/foo')
});
App::get('/v1/mock/tests/bar')
->desc('Mock a get request for SDK tests')
->desc('Get Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'get')
->label('sdk.description', 'Mock a get request for SDK tests')
->label('sdk.description', 'Mock a get request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -101,13 +120,16 @@ App::get('/v1/mock/tests/bar')
});
App::post('/v1/mock/tests/bar')
->desc('Mock a post request for SDK tests')
->desc('Post Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'post')
->label('sdk.description', 'Mock a post request for SDK tests')
->label('sdk.description', 'Mock a post request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -116,13 +138,16 @@ App::post('/v1/mock/tests/bar')
});
App::patch('/v1/mock/tests/bar')
->desc('Mock a patch request for SDK tests')
->desc('Patch Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'patch')
->label('sdk.description', 'Mock a get request for SDK tests')
->label('sdk.description', 'Mock a patch request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -131,13 +156,16 @@ App::patch('/v1/mock/tests/bar')
});
App::put('/v1/mock/tests/bar')
->desc('Mock a put request for SDK tests')
->desc('Put Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'put')
->label('sdk.description', 'Mock a put request for SDK tests')
->label('sdk.description', 'Mock a put request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -146,13 +174,16 @@ App::put('/v1/mock/tests/bar')
});
App::delete('/v1/mock/tests/bar')
->desc('Mock a delete request for SDK tests')
->desc('Delete Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'delete')
->label('sdk.description', 'Mock a delete request for SDK tests')
->label('sdk.description', 'Mock a delete request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -161,14 +192,17 @@ App::delete('/v1/mock/tests/bar')
});
App::post('/v1/mock/tests/general/upload')
->desc('Mock a post request for SDK tests')
->desc('Upload File')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'upload')
->label('sdk.description', 'Mock a delete request for SDK tests')
->label('sdk.description', 'Mock a file upload request.')
->label('sdk.request.type', 'multipart/form-data')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Numeric(), 'Sample numeric param')
@@ -203,13 +237,15 @@ App::post('/v1/mock/tests/general/upload')
});
App::get('/v1/mock/tests/general/redirect')
->desc('Mock a post request for SDK tests')
->desc('Redirect')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'redirect')
->label('sdk.description', 'Mock a redirect request for SDK tests')
->label('sdk.description', 'Mock a redirect request.')
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
->label('sdk.mock', true)
->inject('response')
->action(function ($response) {
@@ -219,25 +255,31 @@ App::get('/v1/mock/tests/general/redirect')
});
App::get('/v1/mock/tests/general/redirect/done')
->desc('Mock a post request for SDK tests')
->desc('Redirected')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'redirected')
->label('sdk.description', 'Mock a redirected request for SDK tests')
->label('sdk.description', 'Mock a redirected request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->action(function () {
});
App::get('/v1/mock/tests/general/set-cookie')
->desc('Mock a cookie request for SDK tests')
->desc('Set Cookie')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'setCookie')
->label('sdk.description', 'Mock a set cookie request for SDK tests')
->label('sdk.description', 'Mock a set cookie request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->inject('response')
->action(function ($response) {
@@ -247,13 +289,16 @@ App::get('/v1/mock/tests/general/set-cookie')
});
App::get('/v1/mock/tests/general/get-cookie')
->desc('Mock a cookie request for SDK tests')
->desc('Get Cookie')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'getCookie')
->label('sdk.description', 'Mock a get cookie request for SDK tests')
->label('sdk.description', 'Mock a cookie response.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->inject('request')
->action(function ($request) {
@@ -265,13 +310,15 @@ App::get('/v1/mock/tests/general/get-cookie')
});
App::get('/v1/mock/tests/general/empty')
->desc('Mock a post request for SDK tests')
->desc('Empty Response')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'empty')
->label('sdk.description', 'Mock a redirected request for SDK tests')
->label('sdk.description', 'Mock a an empty response.')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk.mock', true)
->inject('response')
->action(function ($response) {
@@ -280,8 +327,40 @@ App::get('/v1/mock/tests/general/empty')
$response->noContent();
});
App::get('/v1/mock/tests/general/400-error')
->desc('400 Error')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'error400')
->label('sdk.description', 'Mock a an 400 failed request.')
->label('sdk.response.code', Response::STATUS_CODE_BAD_REQUEST)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ERROR)
->label('sdk.mock', true)
->action(function () {
throw new Exception('Mock 400 error', 400);
});
App::get('/v1/mock/tests/general/500-error')
->desc('500 Error')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'error500')
->label('sdk.description', 'Mock a an 500 failed request.')
->label('sdk.response.code', Response::STATUS_CODE_INTERNAL_SERVER_ERROR)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ERROR)
->label('sdk.mock', true)
->action(function () {
throw new Exception('Mock 500 error', 500);
});
App::get('/v1/mock/tests/general/oauth2')
->desc('Mock an OAuth2 login route')
->desc('OAuth Login')
->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
@@ -298,7 +377,7 @@ App::get('/v1/mock/tests/general/oauth2')
});
App::get('/v1/mock/tests/general/oauth2/token')
->desc('Mock an OAuth2 login route')
->desc('OAuth2 Token')
->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
@@ -327,7 +406,7 @@ App::get('/v1/mock/tests/general/oauth2/token')
});
App::get('/v1/mock/tests/general/oauth2/user')
->desc('Mock an OAuth2 user route')
->desc('OAuth2 User')
->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
@@ -348,8 +427,9 @@ App::get('/v1/mock/tests/general/oauth2/user')
});
App::get('/v1/mock/tests/general/oauth2/success')
->label('scope', 'public')
->desc('OAuth2 Success')
->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
->inject('response')
->action(function ($response) {
@@ -361,6 +441,7 @@ App::get('/v1/mock/tests/general/oauth2/success')
});
App::get('/v1/mock/tests/general/oauth2/failure')
->desc('OAuth2 Failure')
->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
@@ -397,5 +478,5 @@ App::shutdown(function($utopia, $response, $request) {
throw new Exception('Failed to save resutls', 500);
}
$response->json(['result' => $route->getMethod() . ':' . $route->getURL() . ':passed']);
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getURL() . ':passed']), Response::MODEL_MOCK);
}, ['utopia', 'response', 'request'], 'mock');
+60 -5
View File
@@ -61,12 +61,12 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e
;
}
$isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles);
$isPrivilegedUser = Auth::isPrivilegedUser(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
&& (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key
{
throw new Exception('Too many requests', 429);
}
@@ -78,7 +78,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e
->setParam('projectId', $project->getId())
->setParam('userId', $user->getId())
->setParam('event', $route->getLabel('event', ''))
->setParam('payload', [])
->setParam('eventData', [])
->setParam('functionId', null)
->setParam('executionId', null)
->setParam('trigger', 'event')
@@ -110,6 +110,61 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e
}, ['utopia', 'request', 'response', 'project', 'user', 'register', 'events', 'audits', 'usage', 'deletes'], 'api');
App::init(function ($utopia, $request, $response, $project, $user) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $project */
/** @var Appwrite\Database\Document $user */
/** @var Utopia\Registry\Registry $register */
/** @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 */
$route = $utopia->match($request);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
if($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
}
switch ($route->getLabel('auth.type', '')) {
case 'emailPassword':
if($project->getAttribute('usersAuthEmailPassword', true) === false) {
throw new Exception('Email / Password authentication is disabled for this project', 501);
}
break;
case 'anonymous':
if($project->getAttribute('usersAuthAnonymous', true) === false) {
throw new Exception('Anonymous authentication is disabled for this project', 501);
}
break;
case 'invites':
if($project->getAttribute('usersAuthInvites', true) === false) {
throw new Exception('Invites authentication is disabled for this project', 501);
}
break;
case 'jwt':
if($project->getAttribute('usersAuthJWT', true) === false) {
throw new Exception('JWT authentication is disabled for this project', 501);
}
break;
default:
throw new Exception('Unsupported authentication route');
break;
}
}, ['utopia', 'request', 'response', 'project', 'user'], 'auth');
App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $mode) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
@@ -123,8 +178,8 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits
/** @var bool $mode */
if (!empty($events->getParam('event'))) {
if(empty($events->getParam('payload'))) {
$events->setParam('payload', $response->getPayload());
if(empty($events->getParam('eventData'))) {
$events->setParam('eventData', $response->getPayload());
}
$webhooks = clone $events;
+4 -1
View File
@@ -323,7 +323,10 @@ App::get('/console/users')
$page = new View(__DIR__.'/../../views/console/users/index.phtml');
$page->setParam('providers', Config::getParam('providers'));
$page
->setParam('auth', Config::getParam('auth'))
->setParam('providers', Config::getParam('providers'))
;
$layout
->setParam('title', APP_NAME.' - Users')
+81 -9
View File
@@ -267,6 +267,12 @@ App::get('/specs/:format')
'description' => 'Your project ID',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
@@ -287,6 +293,12 @@ App::get('/specs/:format')
'description' => 'Your secret API key',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
@@ -307,6 +319,12 @@ App::get('/specs/:format')
'description' => 'Your secret API key',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
@@ -322,14 +340,32 @@ App::get('/specs/:format')
],
];
$security = [
APP_PLATFORM_CLIENT => ['Project' => []],
APP_PLATFORM_SERVER => ['Project' => [], 'Key' => []],
APP_PLATFORM_CONSOLE => ['Project' => [], 'Key' => []],
];
foreach ($utopia->getRoutes() as $key => $method) {
foreach ($method as $route) { /** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
break;
}
}
if(empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('docs', true)) {
continue;
}
@@ -346,7 +382,7 @@ App::get('/specs/:format')
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $route->getLabel('sdk.platform', []))) {
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatofrms)) {
continue;
}
@@ -383,11 +419,11 @@ App::get('/specs/:format')
switch ($format) {
case 'swagger2':
$format = new Swagger2($utopia, $services, $routes, $models, $keys[$platform], $security[$platform]);
$format = new Swagger2($utopia, $services, $routes, $models, $keys[$platform]);
break;
case 'open-api3':
$format = new OpenAPI3($utopia, $services, $routes, $models, $keys[$platform], $security[$platform]);
$format = new OpenAPI3($utopia, $services, $routes, $models, $keys[$platform]);
break;
default:
@@ -416,4 +452,40 @@ App::get('/specs/:format')
$response
->json($specs->parse());
});
App::get('/versions')
->desc('Get Version')
->groups(['web', 'home'])
->label('scope', 'public')
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
$platforms = Config::getParam('platforms');
$versions = [
'server' => APP_VERSION_STABLE,
];
foreach($platforms as $platform) {
$languages = $platform['languages'] ?? [];
foreach ($languages as $key => $language) {
if(isset($language['dev']) && $language['dev']) {
continue;
}
if(isset($language['enabled']) && !$language['enabled']) {
continue;
}
$platformKey = $platform['key'] ?? '';
$languageKey = $language['key'] ?? '';
$version = $language['version'] ?? '';
$versions[$platformKey . '-' . $languageKey] = $version;
}
}
$response->json($versions);
});
+1 -1
View File
@@ -94,7 +94,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
return;
}
$app = new App('America/New_York');
$app = new App('UTC');
try {
Authorization::cleanRoles();
+10 -4
View File
@@ -39,8 +39,8 @@ const APP_USERAGENT = APP_NAME.'-Server v%s. Please report abuse at %s';
const APP_MODE_DEFAULT = 'default';
const APP_MODE_ADMIN = 'admin';
const APP_PAGING_LIMIT = 12;
const APP_CACHE_BUSTER = 144;
const APP_VERSION_STABLE = '0.7.0';
const APP_CACHE_BUSTER = 145;
const APP_VERSION_STABLE = '0.8.0';
const APP_STORAGE_UPLOADS = '/storage/uploads';
const APP_STORAGE_FUNCTIONS = '/storage/functions';
const APP_STORAGE_CACHE = '/storage/cache';
@@ -61,6 +61,11 @@ const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_AUDIT = 'audit';
const DELETE_TYPE_ABUSE = 'abuse';
const DELETE_TYPE_CERTIFICATES = 'certificates';
// Auth Types
const APP_AUTH_TYPE_SESSION = 'Session';
const APP_AUTH_TYPE_JWT = 'JWT';
const APP_AUTH_TYPE_KEY = 'Key';
const APP_AUTH_TYPE_ADMIN = 'Admin';
$register = new Registry();
@@ -70,6 +75,7 @@ App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
* ENV vars
*/
Config::load('events', __DIR__.'/config/events.php');
Config::load('auth', __DIR__.'/config/auth.php');
Config::load('providers', __DIR__.'/config/providers.php');
Config::load('platforms', __DIR__.'/config/platforms.php');
Config::load('collections', __DIR__.'/config/collections.php');
@@ -313,7 +319,7 @@ App::setResource('layout', function($locale) {
}, ['locale']);
App::setResource('locale', function() {
return new Locale('en');
return new Locale(App::getEnv('_APP_LOCALE', 'en'));
});
// Queues
@@ -419,7 +425,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response
if (empty($user->getId()) // Check a document has been found in the DB
|| Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document
|| !Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)) { // Validate user has valid login token
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)) { // Validate user has valid login token
$user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]);
}
+2 -3
View File
@@ -28,10 +28,9 @@ foreach ([
realpath(__DIR__ . '/../vendor/felixfbecker'),
realpath(__DIR__ . '/../vendor/twig/twig'),
realpath(__DIR__ . '/../vendor/guzzlehttp/guzzle'),
realpath(__DIR__ . '/../vendor/domnikl'),
realpath(__DIR__ . '/../vendor/domnikl'),
realpath(__DIR__ . '/../vendor/slickdeals'),
realpath(__DIR__ . '/../vendor/psr/log'),
realpath(__DIR__ . '/../vendor/piwik'),
realpath(__DIR__ . '/../vendor/matomo'),
realpath(__DIR__ . '/../vendor/symfony'),
] as $key => $value) {
if($value !== false) {
-1
View File
@@ -141,7 +141,6 @@ $cli
Console::success('SMTP................connected 👍');
} catch (\Throwable $th) {
Console::error('SMTP.............disconnected 👎');
var_dump($th);
}
$host = App::getEnv('_APP_STATSD_HOST', 'telegraf');
+2 -2
View File
@@ -124,7 +124,7 @@ $cli
$input[$var['name']] = Console::confirm($var['question'].' (default: \''.$var['default'].'\')');
if(empty($input[$key])) {
if(empty($input[$var['name']])) {
$input[$var['name']] = $var['default'];
}
}
@@ -181,4 +181,4 @@ $cli
$analytics->createEvent('install/server', 'install', APP_VERSION_STABLE.' - '.$message);
Console::success($message);
}
});
});
+1 -1
View File
@@ -36,7 +36,7 @@ $cli
$projects = [$console];
$count = 0;
$migration = new Version\V06($register->get('db')); //TODO: remove hardcoded version and move to dynamic migration
$migration = new Version\V07($register->get('db')); //TODO: remove hardcoded version and move to dynamic migration
while ($sum > 0) {
foreach ($projects as $project) {
+13 -1
View File
@@ -42,7 +42,7 @@ $cli
$production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false;
$message = ($git) ? Console::confirm('Please enter your commit message:') : '';
if(!in_array($version, ['0.6.2', '0.7.0'])) {
if(!in_array($version, ['0.6.x', '0.7.x', '0.8.x'])) {
throw new Exception('Unknown version given');
}
@@ -67,6 +67,8 @@ $cli
$target = \realpath(__DIR__.'/..').'/sdks/git/'.$language['key'].'/';
$readme = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/README.md');
$readme = ($readme) ? \file_get_contents($readme) : '';
$gettingStarted = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/GETTING_STARTED.md');
$gettingStarted = ($gettingStarted) ? \file_get_contents($gettingStarted) : '';
$examples = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/EXAMPLES.md');
$examples = ($examples) ? \file_get_contents($examples) : '';
$changelog = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/CHANGELOG.md');
@@ -96,6 +98,15 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
$config = new CLI();
$config->setComposerVendor('appwrite');
$config->setComposerPackage('cli');
$config->setExecutableName('appwrite');
$config->setLogo("
_ _ _ ___ __ _____
/_\ _ __ _ ____ ___ __(_) |_ ___ / __\ / / \_ \
//_\\| '_ \| '_ \ \ /\ / / '__| | __/ _ \ / / / / / /\/
/ _ \ |_) | |_) \ V V /| | | | || __/ / /___/ /___/\/ /_
\_/ \_/ .__/| .__/ \_/\_/ |_| |_|\__\___| \____/\____/\____/
|_| |_|
");
break;
case 'php':
$config = new PHP();
@@ -178,6 +189,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
->setShareVia('appwrite_io')
->setWarning($warning)
->setReadme($readme)
->setGettingStarted($gettingStarted)
->setChangelog($changelog)
->setExamples($examples)
;
+6 -5
View File
@@ -36,9 +36,6 @@ $maxCells = 10;
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/database/collection?id={{router.params.id}}&project={{router.params.project}}">
<a data-ls-if="{{project-collection.rules.length}} > 0" data-ls-attrs="href=/console/database/document?collection={{router.params.id}}&project={{router.params.project}}&buster={{project-collection.dateUpdated}}" class="button fly round text-align-center">
<i class="icon-plus"></i>
</a>
<h2>Documents</h2>
@@ -135,7 +132,7 @@ $maxCells = 10;
</div>
</div>
<div class="clear text-align-center paging">
<div class="pull-end text-align-center paging">
<form
data-service="database.listDocuments"
data-event="submit"
@@ -166,6 +163,10 @@ $maxCells = 10;
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{project-documents.sum}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<a data-ls-if="{{project-collection.rules.length}} > 0" data-ls-attrs="href=/console/database/document?collection={{router.params.id}}&project={{router.params.project}}&buster={{project-collection.dateUpdated}}" class="button">
Add Document
</a>
</div>
</li>
<li data-state="/console/database/collection/settings?id={{router.params.id}}&project={{router.params.project}}">
@@ -520,7 +521,7 @@ $maxCells = 10;
<div data-ls-loop="project-collections.collections" data-ls-as="project" data-ls-key="$index2" class="tiles cell-3 margin-bottom-negative">
<div class="margin-bottom" data-ls-if="{{project.$id}} != {{router.params.id}}">
<input type="radio" name="list" data-ls-attrs="value={{project.$id}},id={{project.$id}}" data-ls-bind="{{rule.list|firstElement}}" data-cast-to="array" required />
<input type="radio" data-ls-attrs="value={{project.$id}},id=[{{rule.$id}}].{{project.$id}},name=[{{rule.$id}}].list" data-ls-bind="{{rule.list|firstElement}}" data-cast-to="array" required />
<label data-ls-attrs="for={{project.$id}}"data-ls-bind="{{project.name}}"></label>
</div>
</div>
+36 -35
View File
@@ -12,40 +12,6 @@
<li data-state="/console/database?project={{router.params.project}}">
<h2>Collections</h2>
<div data-ui-modal class="box modal close" data-button-text="" data-button-class="fly round" data-button-icon="icon-plus">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>New Collection</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Database Collection"
data-service="database.createCollection"
data-event="submit"
data-scope="sdk"
data-success="alert,reset,redirect,trigger"
data-success-param-alert-text="Collection created successfully"
data-success-param-redirect-url="/console/database/collection/settings?id={{serviceData.$id}}&project={{router.params.project}}"
data-success-param-trigger-events="database.createCollection"
data-failure="alert"
data-failure-param-alert-text="Failed to create collection"
data-failure-param-alert-classname="error">
<label for="user-name">Name</label>
<input type="text" class="full-width" id="collection-name" name="name" required autocomplete="off" maxlength="128" />
<input type="hidden" id="collection-read" name="read" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<input type="hidden" id="collection-write" name="write" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
<div class="margin-top"
data-service="database.listCollections"
data-event="load,database.createCollection,database.updateCollection,database.deleteCollection"
@@ -76,7 +42,7 @@
</ul>
</div>
<div class="clear text-align-center paging">
<div class="pull-end text-align-center paging">
<form
data-service="database.listCollections"
data-event="submit"
@@ -105,6 +71,41 @@
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{project-collections.sum}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Collection">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>New Collection</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Database Collection"
data-service="database.createCollection"
data-event="submit"
data-scope="sdk"
data-success="alert,reset,redirect,trigger"
data-success-param-alert-text="Collection created successfully"
data-success-param-redirect-url="/console/database/collection/settings?id={{serviceData.$id}}&project={{router.params.project}}"
data-success-param-trigger-events="database.createCollection"
data-failure="alert"
data-failure-param-alert-text="Failed to create collection"
data-failure-param-alert-classname="error">
<label for="user-name">Name</label>
<input type="text" class="full-width" id="collection-name" name="name" required autocomplete="off" maxlength="128" />
<input type="hidden" id="collection-read" name="read" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<input type="hidden" id="collection-write" name="write" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</li>
<!-- <li data-state="/console/database/usage?project={{router.params.project}}">
<h2>Usage</h2>
@@ -64,6 +64,7 @@
</td>
<td data-title="Size: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileSize}}"></span>
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
</td>
<td data-title="Created: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.dateCreated|dateText}}"></span>
+29 -19
View File
@@ -50,24 +50,9 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);
<p class="text-fade margin-bottom-small" data-ls-bind="{{project-function.env|envName}} {{project-function.env|envVersion}}">
</p>
<form data-ls-if="{{project-function.tag}} !== ''" name="functions.createExecution" class="margin-top"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Function Execution"
data-service="functions.createExecution"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-success="alert,trigger"
data-success-param-alert-text="Function executed successfully"
data-success-param-trigger-events="functions.createExecution"
data-failure="alert"
data-failure-param-alert-text="Failed to execute function"
data-failure-param-alert-classname="error">
<button style="vertical-align: top;">Execute Now</button> &nbsp; <a data-ls-attrs="href=/console/functions/function/logs?id={{router.params.id}}&project={{router.params.project}}" class="button reverse" style="vertical-align: top;">View Logs</a>
</form>
<div data-ls-if="{{project-function.tag}} !== ''" class="margin-top">
<button data-ls-ui-trigger="execute-now">Execute Now</button> &nbsp; <a data-ls-attrs="href=/console/functions/function/logs?id={{router.params.id}}&project={{router.params.project}}" class="button reverse" style="vertical-align: top;">View Logs</a>
</div>
</div>
</div>
@@ -117,7 +102,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);
<b data-ls-bind="{{tag.$id}}"></b> &nbsp;
<span class="text-fade" data-ls-bind="{{tag.command}}"></span>
<div class="text-size-small margin-top-small clear">
<span class="pull-start" data-ls-bind="Created {{tag.dateCreated|timeSince}} &nbsp; | &nbsp; {{tag.size|humanFileSize}}"></span>
<span class="pull-start" data-ls-bind="Created {{tag.dateCreated|timeSince}} &nbsp; | &nbsp; {{tag.size|humanFileSize}}{{tag.size|humanFileUnit}}"></span>
<form data-ls-if="{{tag.$id}} !== {{project-function.tag}}" name="functions.deleteTag" class="pull-start"
data-analytics
@@ -575,6 +560,31 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);
</div>
</div>
<div data-ui-modal class="modal close box sticky-footer" data-button-hide="on" data-open-event="execute-now">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1 class="margin-bottom">Execute Function</h1>
<form data-ls-if="{{project-function.tag}} !== ''" name="functions.createExecution" class="margin-top"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Function Execution"
data-service="functions.createExecution"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-success="alert,trigger"
data-success-param-alert-text="Function executed successfully"
data-success-param-trigger-events="functions.createExecution"
data-failure="alert"
data-failure-param-alert-text="Failed to execute function"
data-failure-param-alert-classname="error">
<label for="execution-data">Custom Data</label>
<textarea id="execution-data" name="data" autocomplete="off" class="margin-bottom" placeholder="Data string (optional)"></textarea>
<button type="submit" style="vertical-align: top;">Execute Now</button>
</form>
</div>
<div data-ui-modal class="modal close box sticky-footer" data-button-hide="on" data-open-event="deploy-tag">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
+8 -2
View File
@@ -97,7 +97,10 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);
</div>
</div>
<div class="col span-3">
<div class="value margin-bottom-small"><span class="sum" data-ls-bind="{{usage.network.total|humanFileSize}}" data-default="0">0</span></div>
<div class="value margin-bottom-small">
<span class="sum" data-ls-bind="{{usage.network.total|humanFileSize}}" data-default="0">0</span>
<span data-ls-bind="{{usage.network.total|humanFileUnit}}" class="text-size-small unit"></span>
</div>
<div class="metric margin-bottom-small">Bandwidth</div>
<div class="margin-top-large value small">
@@ -117,7 +120,10 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);
<div class="margin-top-small"><b class="text-size-small unit">Documents</b></div>
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.storage.total|humanFileSize}}" data-default="0">0</span></div>
<div class="value">
<span class="sum" data-ls-bind="{{usage.storage.total|humanFileSize}}" data-default="0">0</span>
<span data-ls-bind="{{usage.storage.total|humanFileUnit}}" class="text-size-small unit"></span>
</div>
<div class="margin-top-small"><b class="text-size-small unit">Storage</b></div>
</div>
<div class="col span-3">
+44 -43
View File
@@ -17,48 +17,6 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
<li data-state="/console/database?project={{router.params.project}}">
<h2 class="margin-bottom">Files</h2>
<div data-ui-modal class="box modal sticky-footer close" data-button-text="" data-button-class="fly round" data-button-icon="icon-plus">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Upload File</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Storage File"
data-service="storage.createFile"
data-event="submit"
data-scope="sdk"
data-loading="Uploading File..."
data-success="alert,trigger,reset"
data-success-param-alert-text="File uploaded successfully"
data-success-param-trigger-events="storage.createFile"
data-failure="alert"
data-failure-param-alert-text="Failed to upload file"
data-failure-param-alert-classname="error">
<input type="hidden" name="folderId" id="files-folderId" data-cast-to="integer" value="1">
<label for="file-read">File</label>
<input type="file" name="file" id="file-file" size="1" required>
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">(Max file size allowed: <?php echo $fileLimitHuman; ?>)</div>
<label for="file-read">Read Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" id="file-read" name="read" data-forms-tags data-cast-to="json" value="<?php echo htmlentities(json_encode(['*'])); ?>" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add * for wildcard access</div>
<label for="file-write">Write Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" id="file-write" name="write" data-forms-tags data-cast-to="json" value="" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add * for wildcard access</div>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<form class="box padding-small margin-bottom search"
data-service="storage.listFiles"
data-event="submit"
@@ -204,6 +162,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
</td>
<td data-title="Size: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileSize}}"></span>
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
</td>
<td data-title="Created: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.dateCreated|dateText}}"></span>
@@ -214,7 +173,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
</div>
</div>
<div class="clear text-align-center paging">
<div class="pull-end text-align-center paging">
<form
data-service="storage.listFiles"
data-event="submit"
@@ -243,6 +202,48 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{project-files.sum}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="box modal sticky-footer close" data-button-text="Add File">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Upload File</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Storage File"
data-service="storage.createFile"
data-event="submit"
data-scope="sdk"
data-loading="Uploading File..."
data-success="alert,trigger,reset"
data-success-param-alert-text="File uploaded successfully"
data-success-param-trigger-events="storage.createFile"
data-failure="alert"
data-failure-param-alert-text="Failed to upload file"
data-failure-param-alert-classname="error">
<input type="hidden" name="folderId" id="files-folderId" data-cast-to="integer" value="1">
<label for="file-read">File</label>
<input type="file" name="file" id="file-file" size="1" required>
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">(Max file size allowed: <?php echo $fileLimitHuman; ?>)</div>
<label for="file-read">Read Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" id="file-read" name="read" data-forms-tags data-cast-to="json" value="<?php echo htmlentities(json_encode(['*'])); ?>" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add * for wildcard access</div>
<label for="file-write">Write Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" id="file-write" name="write" data-forms-tags data-cast-to="json" value="" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add * for wildcard access</div>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
</div>
</li>
</ul>
+163 -72
View File
@@ -1,5 +1,6 @@
<?php
$providers = $this->getParam('providers', []);
$auth = $this->getParam('auth', []);
?>
<div class="cover">
@@ -17,41 +18,6 @@ $providers = $this->getParam('providers', []);
<h2>Users</h2>
<div data-ui-modal class="box modal close" data-button-text="" data-button-class="fly round" data-button-icon="icon-plus">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Create User</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create User"
data-service="users.create"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created user successfully"
data-success-param-trigger-events="users.create"
data-failure="alert"
data-failure-param-alert-text="Failed to create user"
data-failure-param-alert-classname="error">
<label for="user-name">Name</label>
<input type="text" class="full-width" id="user-name" name="name" required autocomplete="off" maxlength="128" />
<label for="user-email">Email</label>
<input type="email" class="full-width" id="user-email" name="email" required autocomplete="off" />
<label for="user-password">Password</label>
<input type="password" class="full-width" id="user-password" name="password" required pattern=".{6,}" title="Six or more characters" autocomplete="off" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
<form class="box padding-small margin-bottom search"
data-service="users.list"
data-event="submit"
@@ -137,7 +103,7 @@ $providers = $this->getParam('providers', []);
</div>
</div>
<div class="clear text-align-center paging">
<div class="pull-end text-align-center paging">
<form
data-service="users.list"
data-event="submit"
@@ -166,41 +132,47 @@ $providers = $this->getParam('providers', []);
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{project-users.sum}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="box modal close" data-button-text="Add User">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Create User</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create User"
data-service="users.create"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created user successfully"
data-success-param-trigger-events="users.create"
data-failure="alert"
data-failure-param-alert-text="Failed to create user"
data-failure-param-alert-classname="error">
<label for="user-name">Name</label>
<input type="text" class="full-width" id="user-name" name="name" required autocomplete="off" maxlength="128" />
<label for="user-email">Email</label>
<input type="email" class="full-width" id="user-email" name="email" required autocomplete="off" />
<label for="user-password">Password</label>
<input type="password" class="full-width" id="user-password" name="password" required pattern=".{6,}" title="Six or more characters" autocomplete="off" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</div>
</li>
<li data-state="/console/users/teams?project={{router.params.project}}">
<h2>Teams</h2>
<div data-ui-modal class="box modal close" data-button-text="" data-button-class="fly round" data-button-icon="icon-plus">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Create Team</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Team"
data-service="teams.create"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created team successfully"
data-success-param-trigger-events="filter-teams-changed,teams.create"
data-failure="alert"
data-failure-param-alert-text="Failed to create team"
data-failure-param-alert-classname="error">
<label for="team-name">Name</label>
<input type="text" class="full-width" id="team-name" name="name" required autocomplete="off" maxlength="128" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
<form class="box padding-small margin-bottom search"
data-service="teams.list"
data-event="submit"
@@ -239,7 +211,7 @@ $providers = $this->getParam('providers', []);
</div>
<div data-ls-if="0 != {{project-teams.sum}}">
<div class="margin-bottom-small margin-end-small text-align-end text-size-small"><span data-ls-bind="{{project-teams.sum}}"></span> results found</div>
<div class="margin-bottom-small margin-end-small text-align-end text-size-small"><span data-ls-bind="{{project-teams.sum}}"></span> results found</div>
<div class="box margin-bottom">
<table class="vertical">
@@ -267,7 +239,7 @@ $providers = $this->getParam('providers', []);
</div>
</div>
<div class="clear text-align-center paging">
<div class="pull-end text-align-center paging">
<form
data-service="teams.list"
data-event="submit"
@@ -296,13 +268,131 @@ $providers = $this->getParam('providers', []);
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{project-teams.sum}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="box modal close" data-button-text="Add Team">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Create Team</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Team"
data-service="teams.create"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created team successfully"
data-success-param-trigger-events="filter-teams-changed,teams.create"
data-failure="alert"
data-failure-param-alert-text="Failed to create team"
data-failure-param-alert-classname="error">
<label for="team-name">Name</label>
<input type="text" class="full-width" id="team-name" name="name" required autocomplete="off" maxlength="128" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</div>
</li>
<li data-state="/console/users/providers?project={{router.params.project}}">
<h2>OAuth2 Providers</h2>
<p data-ls-if="{{console-project.usersAuthLimit}} == 0" class="text-fade text-size-small margin-bottom pull-end">Unlimited Users <span class="link" data-ls-ui-trigger="project-update-auth-users-limit">Set Limit</a></p>
<p data-ls-if="{{console-project.usersAuthLimit}} != 0" class="text-fade text-size-small margin-bottom pull-end"><span data-ls-bind="{{console-project.usersAuthLimit|statsTotal}}"></span> Users allowed <span class="link" data-ls-ui-trigger="project-update-auth-users-limit">Change Limit</a></p>
<h2>Settings</h2>
<div class="margin-bottom margin-top-large"
<div data-ui-modal class="modal close" data-button-alias="none" data-open-event="project-update-auth-users-limit">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Max Allowed Users</h1>
<form data-debug="1"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Users Limit"
data-service="projects.updateAuthLimit"
data-scope="console"
data-event="submit"
data-param-project-id="{{router.params.project}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated project users limit successfully"
data-success-param-trigger-events="projects.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update project users limit"
data-failure-param-alert-classname="error">
<input name="limit" id="users-limit" type="number" data-ls-bind="{{console-project.usersAuthLimit}}" data-cast-to="numeric" min="0" />
<div class="info row thin margin-bottom margin-top">
<div class="col span-1">
<i class="icon-info-circled text-sign"></i>
</div>
<div class="col span-11">This limit will prevent new users from signing up for your project, no matter what auth method has been used. You will still be able to create users and team memberships from your Appwrite console. For an unlimited amount of users, set the limit to 0.</div>
</div>
<button>Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
<p class="text-fade margin-bottom">Choose auth methods you wish to use.</p>
<ul class="tiles cell-3 margin-bottom-small">
<?php foreach($auth as $index => $method):
$key = $method['key'] ?? '';
$name = $method['name'] ?? '';
$icon = $method['icon'] ?? '';
$docs = $method['docs'] ?? '';
$enabled = $method['enabled'] ?? false;
?>
<li class="">
<div class="box padding-small margin-bottom clear">
<?php if($enabled): ?>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Auth Status (<?php echo $this->escape($name); ?>)"
data-service="projects.updateAuthStatus"
data-scope="console"
data-event="change"
data-param-project-id="{{router.params.project}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated project auth status successfully"
data-success-param-trigger-events="projects.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update project auth status settings"
data-failure-param-alert-classname="error">
<input name="method" id="<?php echo $this->escape($key); ?>" type="hidden" autocomplete="off" value="<?php echo $this->escape($index); ?>">
<input name="status" type="hidden" data-forms-switch data-ls-bind="{{console-project.<?php echo $this->escape($key); ?>}}" data-cast-to="boolean" class="pull-end" />
</form>
<?php endif; ?>
<img src="<?php echo $this->escape($icon); ?>?buster=<?php echo APP_CACHE_BUSTER; ?>" alt="Email/Password Logo" class="pull-start provider margin-end" />
<span class="text-size-small"><?php echo $this->escape($name); ?><?php if(!$enabled): ?> <spann class="text-fade text-size-xs">soon</span><?php endif; ?></span>
<?php if($docs): ?>
<p class="margin-bottom-no text-one-liner text-size-small">
<a href="<?php echo $this->escape($docs); ?>" target="_blank" rel="noopener">Docs<i class="icon-link-ext"></i></a>
</p>
<?php endif; ?>
</div>
</li>
<?php endforeach; ?>
</ul>
<h3>OAuth2 Providers</h3>
<div class="margin-bottom margin-top"
data-service="projects.get"
data-event="load,projects.create,projects.update,projects.deleteProject"
data-name="console-project"
@@ -312,6 +402,7 @@ $providers = $this->getParam('providers', []);
<?php foreach ($providers as $provider => $data):
if (isset($data['enabled']) && !$data['enabled']) { continue; }
if (isset($data['mock']) && $data['mock']) { continue; }
$sandbox = $data['sandbox'] ?? false;
$form = $data['form'] ?? false;
$name = $data['name'] ?? 'Unknown';
$beta = $data['beta'] ?? false;
@@ -320,7 +411,7 @@ $providers = $this->getParam('providers', []);
<div data-ui-modal class="modal close" data-button-alias="none" data-open-event="provider-update-<?php echo $provider; ?>">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1><?php echo $this->escape($name); ?> OAuth2 Settings</h1>
<h1><?php echo $this->escape($name); ?> <?php if($sandbox): ?>Sandbox<?php endif; ?> OAuth2 Settings</h1>
<form
data-analytics
@@ -383,10 +474,10 @@ $providers = $this->getParam('providers', []);
<button class="switch pull-end" data-ls-ui-trigger="provider-update-<?php echo $this->escape($provider); ?>"></button>
</span>
<img src="/images/oauth2/<?php echo $this->escape(strtolower($provider)); ?>.png?buster=<?php echo APP_CACHE_BUSTER; ?>" alt="<?php echo $this->escape(ucfirst($provider)); ?> Logo" class="pull-start provider margin-end" />
<img src="/images/users/oauth2/<?php echo $this->escape(strtolower($provider)); ?>.png?buster=<?php echo APP_CACHE_BUSTER; ?>" alt="<?php echo $this->escape(ucfirst($provider)); ?> Logo" class="pull-start provider margin-end" />
<span class="text-size-small">
<?php echo $this->escape($name); ?> <?php if($beta): ?>(beta)<?php endif; ?>
<?php echo $this->escape($name); ?> <?php if($sandbox): ?><span class="text-size-xs text-fade">sandbox</span><?php endif; ?> <?php if($beta): ?><span class="text-size-xs text-fade">beta</span><?php endif; ?>
</span>
<p class="margin-bottom-no text-one-liner text-size-small">
+10 -9
View File
@@ -52,10 +52,11 @@ services:
depends_on:
- mariadb
- redis
- clamav
# - clamav
- influxdb
environment:
- _APP_ENV
- _APP_LOCALE
- _APP_CONSOLE_WHITELIST_GOD
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_WHITELIST_IPS
@@ -352,14 +353,14 @@ services:
volumes:
- appwrite-redis:/data:rw
clamav:
image: appwrite/clamav:1.2.0
container_name: appwrite-clamav
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-uploads:/storage/uploads
# clamav:
# image: appwrite/clamav:1.2.0
# container_name: appwrite-clamav
# restart: unless-stopped
# networks:
# - appwrite
# volumes:
# - appwrite-uploads:/storage/uploads
influxdb:
image: influxdb:1.8-alpine
+1 -1
View File
@@ -124,7 +124,7 @@ class CertificatesV1
." -w ".APP_STORAGE_CERTIFICATES
." -d {$domain->get()}", '', $stdout, $stderr);
if($stderr || $exit !== 0) {
if($exit !== 0) {
throw new Exception('Failed to issue a certificate with message: '.$stderr);
}
+9 -1
View File
@@ -112,13 +112,21 @@ class DeletesV1
protected function deleteUser(Document $document, $projectId)
{
$tokens = $document->getAttribute('tokens', []);
foreach ($tokens as $token) {
if (!$this->getProjectDB($projectId)->deleteDocument($token->getId())) {
throw new Exception('Failed to remove token from DB');
}
}
$sessions = $document->getAttribute('sessions', []);
foreach ($sessions as $session) {
if (!$this->getProjectDB($projectId)->deleteDocument($session->getId())) {
throw new Exception('Failed to remove session from DB');
}
}
// Delete Memberships
$this->deleteByGroup([
'$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
+35 -8
View File
@@ -147,7 +147,10 @@ class FunctionsV1
$trigger = $this->args['trigger'] ?? '';
$event = $this->args['event'] ?? '';
$scheduleOriginal = $this->args['scheduleOriginal'] ?? '';
$payload = (!empty($this->args['payload'])) ? json_encode($this->args['payload']) : '';
$eventData = (!empty($this->args['eventData'])) ? json_encode($this->args['eventData']) : '';
$data = $this->args['data'] ?? '';
$userId = $this->args['userId'] ?? '';
$jwt = $this->args['jwt'] ?? '';
$database = new Database();
$database->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register));
@@ -195,7 +198,7 @@ class FunctionsV1
Console::success('Triggered function: '.$event);
$this->execute('event', $projectId, '', $database, $function, $event, $payload);
$this->execute('event', $projectId, '', $database, $function, $event, $eventData, $data, $userId, $jwt);
}
}
break;
@@ -251,8 +254,7 @@ class FunctionsV1
'scheduleOriginal' => $function->getAttribute('schedule', ''),
]); // Async task rescheduale
$this->execute($trigger, $projectId, $executionId, $database, $function);
$this->execute($trigger, $projectId, $executionId, $database, $function, /*$event*/'', /*$eventData*/'', $data, $userId, $jwt);
break;
case 'http':
@@ -264,7 +266,7 @@ class FunctionsV1
throw new Exception('Function not found ('.$functionId.')');
}
$this->execute($trigger, $projectId, $executionId, $database, $function);
$this->execute($trigger, $projectId, $executionId, $database, $function, /*$event*/'', /*$eventData*/'', $data, $userId, $jwt);
break;
default:
@@ -282,11 +284,12 @@ class FunctionsV1
* @param Database $database
* @param Database $function
* @param string $event
* @param string $payload
* @param string $eventData
* @param string $data
*
* @return void
*/
public function execute(string $trigger, string $projectId, string $executionId, Database $database, Document $function, string $event = '', string $payload = ''): void
public function execute(string $trigger, string $projectId, string $executionId, Database $database, Document $function, string $event = '', string $eventData = '', string $data = '', string $userId = '', string $jwt = ''): void
{
global $list;
@@ -340,7 +343,11 @@ class FunctionsV1
'APPWRITE_FUNCTION_ENV_NAME' => $environment['name'],
'APPWRITE_FUNCTION_ENV_VERSION' => $environment['version'],
'APPWRITE_FUNCTION_EVENT' => $event,
'APPWRITE_FUNCTION_EVENT_PAYLOAD' => $payload,
'APPWRITE_FUNCTION_EVENT_DATA' => $eventData,
'APPWRITE_FUNCTION_DATA' => $data,
'APPWRITE_FUNCTION_USER_ID' => $userId,
'APPWRITE_FUNCTION_JWT' => $jwt,
'APPWRITE_FUNCTION_PROJECT_ID' => $projectId,
]);
\array_walk($vars, function (&$value, $key) {
@@ -469,6 +476,26 @@ class FunctionsV1
throw new Exception('Failed saving execution to DB', 500);
}
$executionUpdate = new Event('v1-webhooks', 'WebhooksV1');
$executionUpdate
->setParam('projectId', $projectId)
->setParam('userId', $userId)
->setParam('event', 'functions.executions.update')
->setParam('eventData', [
'$id' => $execution['$id'],
'functionId' => $execution['functionId'],
'dateCreated' => $execution['dateCreated'],
'trigger' => $execution['trigger'],
'status' => $execution['status'],
'exitCode' => $execution['exitCode'],
'stdout' => $execution['stdout'],
'stderr' => $execution['stderr'],
'time' => $execution['time']
]);
$executionUpdate->trigger();
$usage = new Event('v1-usage', 'UsageV1');
$usage
+3 -3
View File
@@ -37,7 +37,7 @@ class WebhooksV1
$projectId = $this->args['projectId'] ?? '';
$userId = $this->args['userId'] ?? '';
$event = $this->args['event'] ?? '';
$payload = \json_encode($this->args['payload']);
$eventData = \json_encode($this->args['eventData']);
// Webhook
@@ -67,7 +67,7 @@ class WebhooksV1
$ch = \curl_init($url);
\curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
\curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
\curl_setopt($ch, CURLOPT_POSTFIELDS, $eventData);
\curl_setopt($ch, CURLOPT_HEADER, 0);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
\curl_setopt($ch, CURLOPT_USERAGENT, \sprintf(APP_USERAGENT,
@@ -79,7 +79,7 @@ class WebhooksV1
CURLOPT_HTTPHEADER,
[
'Content-Type: application/json',
'Content-Length: '.\strlen($payload),
'Content-Length: '.\strlen($eventData),
'X-'.APP_NAME.'-Webhook-Id: '.$id,
'X-'.APP_NAME.'-Webhook-Event: '.$event,
'X-'.APP_NAME.'-Webhook-Name: '.$name,
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
/usr/src/code/vendor/bin/phpunit --verbose --configuration /usr/src/code/phpunit.xml $@
/usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
+12 -9
View File
@@ -15,7 +15,10 @@
}
},
"autoload-dev": {
"psr-4": {"Tests\\E2E\\": "tests/e2e"}
"psr-4": {
"Tests\\E2E\\": "tests/e2e",
"Appwrite\\Tests\\": "tests/extensions"
}
},
"require": {
"php": ">=7.4.0",
@@ -34,9 +37,9 @@
"appwrite/php-clamav": "1.0.*",
"utopia-php/framework": "0.10.0",
"utopia-php/abuse": "0.3.*",
"utopia-php/analytics": "0.1.*",
"utopia-php/framework": "0.12.*",
"utopia-php/abuse": "0.4.*",
"utopia-php/analytics": "0.2.*",
"utopia-php/audit": "0.5.*",
"utopia-php/cache": "0.2.*",
"utopia-php/cli": "0.10.0",
@@ -47,19 +50,19 @@
"utopia-php/domains": "0.2.*",
"utopia-php/swoole": "0.2.*",
"utopia-php/system": "0.4.*",
"utopia-php/storage": "0.2.*",
"utopia-php/storage": "0.4.3",
"utopia-php/image": "0.2.*",
"resque/php-resque": "1.3.6",
"matomo/device-detector": "4.1.0",
"dragonmantank/cron-expression": "3.1.0",
"domnikl/statsd": "3.0.2",
"influxdb/influxdb-php": "1.15.2",
"phpmailer/phpmailer": "6.3.0",
"chillerlan/php-qrcode": "4.3.0",
"adhocore/jwt": "1.1.2"
"adhocore/jwt": "1.1.2",
"slickdeals/statsd": "~3.0"
},
"require-dev": {
"appwrite/sdk-generator": "0.5.5",
"appwrite/sdk-generator": "dev-feat-preps-for-0.8",
"phpunit/phpunit": "9.4.2",
"swoole/ide-helper": "4.5.5",
"vimeo/psalm": "4.1.1"
Generated
+480 -305
View File
File diff suppressed because it is too large Load Diff
+16 -15
View File
@@ -71,10 +71,11 @@ services:
depends_on:
- mariadb
- redis
- clamav
# - clamav
- influxdb
environment:
- _APP_ENV
- _APP_LOCALE
- _APP_CONSOLE_WHITELIST_GOD
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_WHITELIST_IPS
@@ -416,16 +417,16 @@ services:
volumes:
- appwrite-redis:/data:rw
clamav:
image: appwrite/clamav:1.2.0
container_name: appwrite-clamav
networks:
- appwrite
volumes:
- appwrite-uploads:/storage/uploads
# clamav:
# image: appwrite/clamav:1.2.0
# container_name: appwrite-clamav
# networks:
# - appwrite
# volumes:
# - appwrite-uploads:/storage/uploads
influxdb:
image: influxdb:1.8-alpine
image: appwrite/influxdb:1.0.0
container_name: appwrite-influxdb
networks:
- appwrite
@@ -433,7 +434,7 @@ services:
- appwrite-influxdb:/var/lib/influxdb:rw
telegraf:
image: appwrite/telegraf:1.0.0
image: appwrite/telegraf:1.1.0
container_name: appwrite-telegraf
networks:
- appwrite
@@ -452,16 +453,16 @@ services:
# Webgrind - A nice UI for exploring and debugging code-level stuff
maildev: # used mainly for dev tests
image: djfarrelly/maildev
container_name: appwrite-maildev
image: appwrite/mailcatcher:1.0.0
container_name: appwrite-mailcatcher
ports:
- '9503:80'
- '9503:1080'
networks:
- appwrite
request-catcher: # used mainly for dev tests
image: smarterdm/http-request-catcher
container_name: appwrite-request-catcher
image: appwrite/requestcatcher:1.0.0
container_name: appwrite-requestcatcher
ports:
- '9504:5000'
networks:
+10 -1
View File
@@ -9,6 +9,9 @@ docker buildx build --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
echo 'Deno 1.6...'
docker buildx build --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/ppc64le -t appwrite/env-deno-1.6:1.0.0 ./docker/environments/deno-1.6/ --push
echo 'Deno 1.8...'
docker buildx build --platform linux/amd64,linux/386 -t appwrite/env-deno-1.8:1.0.0 ./docker/environments/deno-1.8/ --push
echo 'Node 14.5...'
docker buildx build --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le -t appwrite/env-node-14.5:1.0.0 ./docker/environments/node-14.5/ --push
@@ -24,6 +27,9 @@ docker buildx build --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
echo 'Python 3.8...'
docker buildx build --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/ppc64le -t appwrite/env-python-3.8:1.0.0 ./docker/environments/python-3.8/ --push
echo 'Python 3.9...'
docker buildx build --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/ppc64le -t appwrite/env-python-3.9:1.0.0 ./docker/environments/python-3.9/ --push
echo 'Ruby 2.7...'
docker buildx build --platform linux/amd64,linux/arm64,linux/386,linux/ppc64le -t appwrite/env-ruby-2.7:1.0.2 ./docker/environments/ruby-2.7/ --push
@@ -33,8 +39,11 @@ docker buildx build --platform linux/amd64,linux/arm64,linux/386,linux/ppc64le -
echo 'Dart 2.10...'
docker buildx build --platform linux/amd64 -t appwrite/env-dart-2.10:1.0.0 ./docker/environments/dart-2.10/ --push
echo 'Dart 2.12...'
docker buildx build --platform linux/amd64 -t appwrite/env-dart-2.12:1.0.0 ./docker/environments/dart-2.12/ --push
echo '.NET 3.1...'
docker buildx build --platform linux/amd64,linux/arm64 -t appwrite/env-dotnet-3.1:1.0.0 ./docker/environments/dotnet-3.1/ --push
docker buildx build --platform linux/amd64 -t appwrite/env-dotnet-3.1:1.0.0 ./docker/environments/dotnet-3.1/ --push
echo '.NET 5.0...'
docker buildx build --platform linux/amd64,linux/arm64 -t appwrite/env-dotnet-5.0:1.0.0 ./docker/environments/dotnet-5.0/ --push
+9
View File
@@ -0,0 +1,9 @@
FROM google/dart:2.12
LABEL maintainer="team@appwrite.io"
RUN apt-get update -y && apt-get install -y tar
WORKDIR /usr/local/src/
ENV PUB_CACHE=/usr/local/src/.appwrite
+11
View File
@@ -0,0 +1,11 @@
FROM hayd/deno:alpine-1.8.3
LABEL maintainer="team@appwrite.io"
RUN apk add tar
RUN mkdir /usr/local/src
WORKDIR /usr/local/src/
ENV DENO_DIR=/usr/local/src/.appwrite
+11
View File
@@ -0,0 +1,11 @@
FROM python:3.9-alpine
LABEL maintainer="team@appwrite.io"
RUN apk add tar
RUN mkdir /usr/local/src
WORKDIR /usr/local/src/
ENV PYTHONPATH "${PYTHONPATH}:/usr/local/src/.appwrite"

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