diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 0b8534b7c9..28656d3229 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,4 +1,9 @@ reviews: + path_filters: + - "!app/config/specs/**" + - "!docs/examples/**" + - "!docs/references/**" + - "!docs/sdks/**" auto_review: base_branches: - main diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 645d5cb560..9ea72e9213 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -197,7 +197,7 @@ jobs: docker compose exec -T \ -e _APP_DATABASE_SHARED_TABLES \ -e _APP_DATABASE_SHARED_TABLES_V1 \ - appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug + appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude=devKeys e2e_shared_mode_test: name: E2E Shared Mode Service Test @@ -275,7 +275,89 @@ jobs: docker compose exec -T \ -e _APP_DATABASE_SHARED_TABLES \ -e _APP_DATABASE_SHARED_TABLES_V1 \ - appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug + appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude=devKeys + + e2e_dev_keys: + name: E2E Service Test (Dev Keys) + runs-on: ubuntu-latest + needs: setup + strategy: + fail-fast: false + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Load Cache + uses: actions/cache@v4 + with: + key: ${{ env.CACHE_KEY }} + path: /tmp/${{ env.IMAGE }}.tar + fail-on-cache-miss: true + + - name: Load and Start Appwrite + run: | + docker load --input /tmp/${{ env.IMAGE }}.tar + sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env + docker compose up -d + sleep 30 + + - name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode + run: | + echo "Using project tables" + export _APP_DATABASE_SHARED_TABLES= + export _APP_DATABASE_SHARED_TABLES_V1= + + docker compose exec -T \ + -e _APP_DATABASE_SHARED_TABLES \ + -e _APP_DATABASE_SHARED_TABLES_V1 \ + appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys + + e2e_dev_keys_shared_mode: + name: E2E Shared Mode Service Test (Dev Keys) + runs-on: ubuntu-latest + needs: [ setup, check_database_changes ] + if: needs.check_database_changes.outputs.database_changed == 'true' + strategy: + fail-fast: false + matrix: + tables-mode: [ + 'Shared V1', + 'Shared V2', + ] + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Load Cache + uses: actions/cache@v4 + with: + key: ${{ env.CACHE_KEY }} + path: /tmp/${{ env.IMAGE }}.tar + fail-on-cache-miss: true + + - name: Load and Start Appwrite + run: | + docker load --input /tmp/${{ env.IMAGE }}.tar + sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env + docker compose up -d + sleep 30 + + - name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode + run: | + if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then + echo "Using shared tables V1" + export _APP_DATABASE_SHARED_TABLES=database_db_main + export _APP_DATABASE_SHARED_TABLES_V1=database_db_main + elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then + echo "Using shared tables V2" + export _APP_DATABASE_SHARED_TABLES=database_db_main + export _APP_DATABASE_SHARED_TABLES_V1= + fi + + docker compose exec -T \ + -e _APP_DATABASE_SHARED_TABLES \ + -e _APP_DATABASE_SHARED_TABLES_V1 \ + appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys benchmarking: name: Benchmark diff --git a/app/config/collections/platform.php b/app/config/collections/platform.php index 6a26054636..ce8be6a874 100644 --- a/app/config/collections/platform.php +++ b/app/config/collections/platform.php @@ -287,6 +287,17 @@ return [ 'array' => false, 'filters' => ['subQueryKeys'], ], + [ + '$id' => ID::custom('devKeys'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['subQueryDevKeys'], + ], [ '$id' => ID::custom('search'), 'type' => Database::VAR_STRING, @@ -689,6 +700,125 @@ return [ ], ], + 'devKeys' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('devKeys'), + 'name' => 'Dev keys', + 'attributes' => [ + [ + '$id' => ID::custom('projectInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('projectId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => 0, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('name'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('secret'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 512, // var_dump of \bin2hex(\random_bytes(128)) => string(256) doubling for encryption + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => ['encrypt'], + ], + [ + '$id' => ID::custom('expire'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('accessedAt'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('sdks'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => ID::custom('search'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_project'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['projectInternalId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => '_key_accessedAt', + 'type' => Database::INDEX_KEY, + 'attributes' => ['accessedAt'], + 'lengths' => [], + 'orders' => [], + ], + [ + '$id' => ID::custom('_key_search'), + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['search'], + 'lengths' => [], + 'orders' => [], + ], + ], + ], + 'webhooks' => [ '$collection' => ID::custom(Database::METADATA), '$id' => ID::custom('webhooks'), diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index 6d50b1e06e..51fa03872f 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -4763,7 +4763,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 384, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -4837,7 +4837,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 382, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -4951,7 +4951,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 383, + "weight": 388, "cookies": false, "type": "", "deprecated": false, diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index a1f9e94f6c..bb75849441 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -4354,7 +4354,7 @@ }, "x-appwrite": { "method": "getResource", - "weight": 424, + "weight": 429, "cookies": false, "type": "", "deprecated": false, @@ -9051,7 +9051,7 @@ }, "x-appwrite": { "method": "list", - "weight": 368, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -9123,7 +9123,7 @@ }, "x-appwrite": { "method": "create", - "weight": 365, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -9353,7 +9353,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 370, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -9401,7 +9401,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 371, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -9450,7 +9450,7 @@ }, "x-appwrite": { "method": "listTemplates", - "weight": 394, + "weight": 399, "cookies": false, "type": "", "deprecated": false, @@ -9549,7 +9549,7 @@ }, "x-appwrite": { "method": "getTemplate", - "weight": 393, + "weight": 398, "cookies": false, "type": "", "deprecated": false, @@ -9608,7 +9608,7 @@ }, "x-appwrite": { "method": "listUsage", - "weight": 387, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -9679,7 +9679,7 @@ }, "x-appwrite": { "method": "get", - "weight": 366, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -9737,7 +9737,7 @@ }, "x-appwrite": { "method": "update", - "weight": 367, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -9964,7 +9964,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 369, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -10024,7 +10024,7 @@ }, "x-appwrite": { "method": "updateFunctionDeployment", - "weight": 374, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -10103,7 +10103,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 375, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -10185,7 +10185,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 372, + "weight": 377, "cookies": false, "type": "upload", "deprecated": false, @@ -10280,7 +10280,7 @@ }, "x-appwrite": { "method": "createDuplicateDeployment", - "weight": 380, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -10364,7 +10364,7 @@ }, "x-appwrite": { "method": "createTemplateDeployment", - "weight": 377, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -10466,7 +10466,7 @@ }, "x-appwrite": { "method": "createVcsDeployment", - "weight": 378, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -10562,7 +10562,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 373, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -10623,7 +10623,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 376, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -10686,7 +10686,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 379, + "weight": 384, "cookies": false, "type": "location", "deprecated": false, @@ -10775,7 +10775,7 @@ }, "x-appwrite": { "method": "updateDeploymentStatus", - "weight": 381, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -10845,7 +10845,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 384, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -10919,7 +10919,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 382, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -11033,7 +11033,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 383, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -11097,7 +11097,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 385, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -11167,7 +11167,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 386, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -11248,7 +11248,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 390, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -11306,7 +11306,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 388, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -11396,7 +11396,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 389, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -11464,7 +11464,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 391, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -11554,7 +11554,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 392, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -20708,6 +20708,389 @@ } } }, + "\/projects\/{projectId}\/dev-keys": { + "get": { + "summary": "List dev keys", + "operationId": "projectsListDevKeys", + "tags": [ + "projects" + ], + "description": "List all the project\\'s dev keys. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.'", + "responses": { + "200": { + "description": "Dev Keys List", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/devKeyList" + } + } + } + } + }, + "x-appwrite": { + "method": "listDevKeys", + "weight": 368, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/list-dev-keys.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterList all the project\\'s dev keys. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.'", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.read", + "platforms": [ + "console" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + }, + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: accessedAt, expire", + "required": false, + "schema": { + "type": "string", + "default": [] + }, + "in": "query" + }, + { + "name": "search", + "description": "Search term to filter your list results. Max length: 256 chars.", + "required": false, + "schema": { + "type": "string", + "x-example": "", + "default": "" + }, + "in": "query" + } + ] + }, + "post": { + "summary": "Create dev key", + "operationId": "projectsCreateDevKey", + "tags": [ + "projects" + ], + "description": "Create a new project dev key. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development. Strictly meant for development purposes only.", + "responses": { + "201": { + "description": "DevKey", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/devKey" + } + } + } + } + }, + "x-appwrite": { + "method": "createDevKey", + "weight": 365, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/create-dev-key.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new project dev key. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development. Strictly meant for development purposes only.", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.write", + "platforms": [ + "console" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Key name. Max length: 128 chars.", + "x-example": "" + }, + "expire": { + "type": "string", + "description": "Expiration time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format.", + "x-example": null + } + }, + "required": [ + "name", + "expire" + ] + } + } + } + } + } + }, + "\/projects\/{projectId}\/dev-keys\/{keyId}": { + "get": { + "summary": "Get dev key", + "operationId": "projectsGetDevKey", + "tags": [ + "projects" + ], + "description": "Get a project\\'s dev key by its unique ID. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.", + "responses": { + "200": { + "description": "DevKey", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/devKey" + } + } + } + } + }, + "x-appwrite": { + "method": "getDevKey", + "weight": 367, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/get-dev-key.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a project\\'s dev key by its unique ID. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.read", + "platforms": [ + "console" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + }, + { + "name": "keyId", + "description": "Key unique ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + }, + "put": { + "summary": "Update dev key", + "operationId": "projectsUpdateDevKey", + "tags": [ + "projects" + ], + "description": "Update a project\\'s dev key by its unique ID. Use this endpoint to update a project\\'s dev key name or expiration time.'", + "responses": { + "200": { + "description": "DevKey", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/devKey" + } + } + } + } + }, + "x-appwrite": { + "method": "updateDevKey", + "weight": 366, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/update-dev-key.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterUpdate a project\\'s dev key by its unique ID. Use this endpoint to update a project\\'s dev key name or expiration time.'", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.write", + "platforms": [ + "console" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + }, + { + "name": "keyId", + "description": "Key unique ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Key name. Max length: 128 chars.", + "x-example": "" + }, + "expire": { + "type": "string", + "description": "Expiration time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format.", + "x-example": null + } + }, + "required": [ + "name", + "expire" + ] + } + } + } + } + }, + "delete": { + "summary": "Delete dev key", + "operationId": "projectsDeleteDevKey", + "tags": [ + "projects" + ], + "description": "Delete a project\\'s dev key by its unique ID. Once deleted, the key will no longer allow bypassing of rate limits and better logging of errors.", + "responses": { + "204": { + "description": "No content" + } + }, + "x-appwrite": { + "method": "deleteDevKey", + "weight": 369, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/delete-dev-key.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterDelete a project\\'s dev key by its unique ID. Once deleted, the key will no longer allow bypassing of rate limits and better logging of errors.", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.write", + "platforms": [ + "console" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + }, + { + "name": "keyId", + "description": "Key unique ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ] + } + }, "\/projects\/{projectId}\/jwts": { "post": { "summary": "Create JWT", @@ -24194,7 +24577,7 @@ }, "x-appwrite": { "method": "createAPIRule", - "weight": 425, + "weight": 430, "cookies": false, "type": "", "deprecated": false, @@ -24260,7 +24643,7 @@ }, "x-appwrite": { "method": "createFunctionRule", - "weight": 427, + "weight": 432, "cookies": false, "type": "", "deprecated": false, @@ -24337,7 +24720,7 @@ }, "x-appwrite": { "method": "createRedirectRule", - "weight": 428, + "weight": 433, "cookies": false, "type": "", "deprecated": false, @@ -24428,7 +24811,7 @@ }, "x-appwrite": { "method": "createSiteRule", - "weight": 426, + "weight": 431, "cookies": false, "type": "", "deprecated": false, @@ -24673,7 +25056,7 @@ }, "x-appwrite": { "method": "list", - "weight": 397, + "weight": 402, "cookies": false, "type": "", "deprecated": false, @@ -24742,7 +25125,7 @@ }, "x-appwrite": { "method": "create", - "weight": 395, + "weight": 400, "cookies": false, "type": "", "deprecated": false, @@ -24987,7 +25370,7 @@ }, "x-appwrite": { "method": "listFrameworks", - "weight": 400, + "weight": 405, "cookies": false, "type": "", "deprecated": false, @@ -25035,7 +25418,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 423, + "weight": 428, "cookies": false, "type": "", "deprecated": false, @@ -25084,7 +25467,7 @@ }, "x-appwrite": { "method": "listTemplates", - "weight": 419, + "weight": 424, "cookies": false, "type": "", "deprecated": false, @@ -25183,7 +25566,7 @@ }, "x-appwrite": { "method": "getTemplate", - "weight": 420, + "weight": 425, "cookies": false, "type": "", "deprecated": false, @@ -25242,7 +25625,7 @@ }, "x-appwrite": { "method": "listUsage", - "weight": 421, + "weight": 426, "cookies": false, "type": "", "deprecated": false, @@ -25313,7 +25696,7 @@ }, "x-appwrite": { "method": "get", - "weight": 396, + "weight": 401, "cookies": false, "type": "", "deprecated": false, @@ -25371,7 +25754,7 @@ }, "x-appwrite": { "method": "update", - "weight": 398, + "weight": 403, "cookies": false, "type": "", "deprecated": false, @@ -25612,7 +25995,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 399, + "weight": 404, "cookies": false, "type": "", "deprecated": false, @@ -25672,7 +26055,7 @@ }, "x-appwrite": { "method": "updateSiteDeployment", - "weight": 406, + "weight": 411, "cookies": false, "type": "", "deprecated": false, @@ -25751,7 +26134,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 405, + "weight": 410, "cookies": false, "type": "", "deprecated": false, @@ -25833,7 +26216,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 401, + "weight": 406, "cookies": false, "type": "upload", "deprecated": false, @@ -25933,7 +26316,7 @@ }, "x-appwrite": { "method": "createDuplicateDeployment", - "weight": 409, + "weight": 414, "cookies": false, "type": "", "deprecated": false, @@ -26012,7 +26395,7 @@ }, "x-appwrite": { "method": "createTemplateDeployment", - "weight": 402, + "weight": 407, "cookies": false, "type": "", "deprecated": false, @@ -26114,7 +26497,7 @@ }, "x-appwrite": { "method": "createVcsDeployment", - "weight": 403, + "weight": 408, "cookies": false, "type": "", "deprecated": false, @@ -26211,7 +26594,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 404, + "weight": 409, "cookies": false, "type": "", "deprecated": false, @@ -26272,7 +26655,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 407, + "weight": 412, "cookies": false, "type": "", "deprecated": false, @@ -26335,7 +26718,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 408, + "weight": 413, "cookies": false, "type": "location", "deprecated": false, @@ -26424,7 +26807,7 @@ }, "x-appwrite": { "method": "updateDeploymentStatus", - "weight": 410, + "weight": 415, "cookies": false, "type": "", "deprecated": false, @@ -26494,7 +26877,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 412, + "weight": 417, "cookies": false, "type": "", "deprecated": false, @@ -26564,7 +26947,7 @@ }, "x-appwrite": { "method": "getLog", - "weight": 411, + "weight": 416, "cookies": false, "type": "", "deprecated": false, @@ -26625,7 +27008,7 @@ }, "x-appwrite": { "method": "deleteLog", - "weight": 413, + "weight": 418, "cookies": false, "type": "", "deprecated": false, @@ -26695,7 +27078,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 422, + "weight": 427, "cookies": false, "type": "", "deprecated": false, @@ -26776,7 +27159,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 416, + "weight": 421, "cookies": false, "type": "", "deprecated": false, @@ -26834,7 +27217,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 414, + "weight": 419, "cookies": false, "type": "", "deprecated": false, @@ -26924,7 +27307,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 415, + "weight": 420, "cookies": false, "type": "", "deprecated": false, @@ -26992,7 +27375,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 417, + "weight": 422, "cookies": false, "type": "", "deprecated": false, @@ -27082,7 +27465,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 418, + "weight": 423, "cookies": false, "type": "", "deprecated": false, @@ -34331,6 +34714,30 @@ "keys" ] }, + "devKeyList": { + "description": "Dev Keys List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of devKeys documents that matched your query.", + "x-example": 5, + "format": "int32" + }, + "devKeys": { + "type": "array", + "description": "List of devKeys.", + "items": { + "$ref": "#\/components\/schemas\/devKey" + }, + "x-example": "" + } + }, + "required": [ + "total", + "devKeys" + ] + }, "platformList": { "description": "Platforms List", "type": "object", @@ -38603,6 +39010,14 @@ }, "x-example": {} }, + "devKeys": { + "type": "array", + "description": "List of dev keys.", + "items": { + "$ref": "#\/components\/schemas\/devKey" + }, + "x-example": {} + }, "smtpEnabled": { "type": "boolean", "description": "Status for custom SMTP", @@ -38786,6 +39201,7 @@ "platforms", "webhooks", "keys", + "devKeys", "smtpEnabled", "smtpSenderName", "smtpSenderEmail", @@ -38976,6 +39392,65 @@ "sdks" ] }, + "devKey": { + "description": "DevKey", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Key ID.", + "x-example": "5e5ea5c16897e" + }, + "$createdAt": { + "type": "string", + "description": "Key creation date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Key update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "name": { + "type": "string", + "description": "Key name.", + "x-example": "Dev API Key" + }, + "expire": { + "type": "string", + "description": "Key expiration date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "secret": { + "type": "string", + "description": "Secret key.", + "x-example": "919c2d18fb5d4...a2ae413da83346ad2" + }, + "accessedAt": { + "type": "string", + "description": "Most recent access date in ISO 8601 format. This attribute is only updated again after 24 hours.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "sdks": { + "type": "array", + "description": "List of SDK user agents that used this key.", + "items": { + "type": "string" + }, + "x-example": "appwrite:flutter" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "name", + "expire", + "secret", + "accessedAt", + "sdks" + ] + }, "mockNumber": { "description": "Mock Number", "type": "object", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index df5948090b..ef67d12e0d 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -8136,7 +8136,7 @@ }, "x-appwrite": { "method": "list", - "weight": 368, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -8209,7 +8209,7 @@ }, "x-appwrite": { "method": "create", - "weight": 365, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -8440,7 +8440,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 370, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -8489,7 +8489,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 371, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -8539,7 +8539,7 @@ }, "x-appwrite": { "method": "get", - "weight": 366, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -8598,7 +8598,7 @@ }, "x-appwrite": { "method": "update", - "weight": 367, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -8826,7 +8826,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 369, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -8887,7 +8887,7 @@ }, "x-appwrite": { "method": "updateFunctionDeployment", - "weight": 374, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -8967,7 +8967,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 375, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -9050,7 +9050,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 372, + "weight": 377, "cookies": false, "type": "upload", "deprecated": false, @@ -9146,7 +9146,7 @@ }, "x-appwrite": { "method": "createDuplicateDeployment", - "weight": 380, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -9231,7 +9231,7 @@ }, "x-appwrite": { "method": "createTemplateDeployment", - "weight": 377, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -9334,7 +9334,7 @@ }, "x-appwrite": { "method": "createVcsDeployment", - "weight": 378, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -9431,7 +9431,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 373, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -9493,7 +9493,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 376, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -9557,7 +9557,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 379, + "weight": 384, "cookies": false, "type": "location", "deprecated": false, @@ -9647,7 +9647,7 @@ }, "x-appwrite": { "method": "updateDeploymentStatus", - "weight": 381, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -9718,7 +9718,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 384, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -9794,7 +9794,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 382, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -9910,7 +9910,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 383, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -9976,7 +9976,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 385, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -10047,7 +10047,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 390, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -10106,7 +10106,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 388, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -10197,7 +10197,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 389, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -10266,7 +10266,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 391, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -10357,7 +10357,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 392, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -16691,7 +16691,7 @@ }, "x-appwrite": { "method": "list", - "weight": 397, + "weight": 402, "cookies": false, "type": "", "deprecated": false, @@ -16761,7 +16761,7 @@ }, "x-appwrite": { "method": "create", - "weight": 395, + "weight": 400, "cookies": false, "type": "", "deprecated": false, @@ -17007,7 +17007,7 @@ }, "x-appwrite": { "method": "listFrameworks", - "weight": 400, + "weight": 405, "cookies": false, "type": "", "deprecated": false, @@ -17056,7 +17056,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 423, + "weight": 428, "cookies": false, "type": "", "deprecated": false, @@ -17106,7 +17106,7 @@ }, "x-appwrite": { "method": "get", - "weight": 396, + "weight": 401, "cookies": false, "type": "", "deprecated": false, @@ -17165,7 +17165,7 @@ }, "x-appwrite": { "method": "update", - "weight": 398, + "weight": 403, "cookies": false, "type": "", "deprecated": false, @@ -17407,7 +17407,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 399, + "weight": 404, "cookies": false, "type": "", "deprecated": false, @@ -17468,7 +17468,7 @@ }, "x-appwrite": { "method": "updateSiteDeployment", - "weight": 406, + "weight": 411, "cookies": false, "type": "", "deprecated": false, @@ -17548,7 +17548,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 405, + "weight": 410, "cookies": false, "type": "", "deprecated": false, @@ -17631,7 +17631,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 401, + "weight": 406, "cookies": false, "type": "upload", "deprecated": false, @@ -17732,7 +17732,7 @@ }, "x-appwrite": { "method": "createDuplicateDeployment", - "weight": 409, + "weight": 414, "cookies": false, "type": "", "deprecated": false, @@ -17812,7 +17812,7 @@ }, "x-appwrite": { "method": "createTemplateDeployment", - "weight": 402, + "weight": 407, "cookies": false, "type": "", "deprecated": false, @@ -17915,7 +17915,7 @@ }, "x-appwrite": { "method": "createVcsDeployment", - "weight": 403, + "weight": 408, "cookies": false, "type": "", "deprecated": false, @@ -18013,7 +18013,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 404, + "weight": 409, "cookies": false, "type": "", "deprecated": false, @@ -18075,7 +18075,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 407, + "weight": 412, "cookies": false, "type": "", "deprecated": false, @@ -18139,7 +18139,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 408, + "weight": 413, "cookies": false, "type": "location", "deprecated": false, @@ -18229,7 +18229,7 @@ }, "x-appwrite": { "method": "updateDeploymentStatus", - "weight": 410, + "weight": 415, "cookies": false, "type": "", "deprecated": false, @@ -18300,7 +18300,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 412, + "weight": 417, "cookies": false, "type": "", "deprecated": false, @@ -18371,7 +18371,7 @@ }, "x-appwrite": { "method": "getLog", - "weight": 411, + "weight": 416, "cookies": false, "type": "", "deprecated": false, @@ -18433,7 +18433,7 @@ }, "x-appwrite": { "method": "deleteLog", - "weight": 413, + "weight": 418, "cookies": false, "type": "", "deprecated": false, @@ -18504,7 +18504,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 416, + "weight": 421, "cookies": false, "type": "", "deprecated": false, @@ -18563,7 +18563,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 414, + "weight": 419, "cookies": false, "type": "", "deprecated": false, @@ -18654,7 +18654,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 415, + "weight": 420, "cookies": false, "type": "", "deprecated": false, @@ -18723,7 +18723,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 417, + "weight": 422, "cookies": false, "type": "", "deprecated": false, @@ -18814,7 +18814,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 418, + "weight": 423, "cookies": false, "type": "", "deprecated": false, diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index dc5600aa86..c156923114 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -4929,7 +4929,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 384, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -5002,7 +5002,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 382, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -5120,7 +5120,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 383, + "weight": 388, "cookies": false, "type": "", "deprecated": false, diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 76f1152287..6456cc6451 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -4563,7 +4563,7 @@ }, "x-appwrite": { "method": "getResource", - "weight": 424, + "weight": 429, "cookies": false, "type": "", "deprecated": false, @@ -9200,7 +9200,7 @@ }, "x-appwrite": { "method": "list", - "weight": 368, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -9271,7 +9271,7 @@ }, "x-appwrite": { "method": "create", - "weight": 365, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -9521,7 +9521,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 370, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -9571,7 +9571,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 371, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -9622,7 +9622,7 @@ }, "x-appwrite": { "method": "listTemplates", - "weight": 394, + "weight": 399, "cookies": false, "type": "", "deprecated": false, @@ -9717,7 +9717,7 @@ }, "x-appwrite": { "method": "getTemplate", - "weight": 393, + "weight": 398, "cookies": false, "type": "", "deprecated": false, @@ -9776,7 +9776,7 @@ }, "x-appwrite": { "method": "listUsage", - "weight": 387, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -9847,7 +9847,7 @@ }, "x-appwrite": { "method": "get", - "weight": 366, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -9905,7 +9905,7 @@ }, "x-appwrite": { "method": "update", - "weight": 367, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -10149,7 +10149,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 369, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -10209,7 +10209,7 @@ }, "x-appwrite": { "method": "updateFunctionDeployment", - "weight": 374, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -10287,7 +10287,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 375, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -10366,7 +10366,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 372, + "weight": 377, "cookies": false, "type": "upload", "deprecated": false, @@ -10457,7 +10457,7 @@ }, "x-appwrite": { "method": "createDuplicateDeployment", - "weight": 380, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -10541,7 +10541,7 @@ }, "x-appwrite": { "method": "createTemplateDeployment", - "weight": 377, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -10646,7 +10646,7 @@ }, "x-appwrite": { "method": "createVcsDeployment", - "weight": 378, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -10743,7 +10743,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 373, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -10804,7 +10804,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 376, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -10872,7 +10872,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 379, + "weight": 384, "cookies": false, "type": "location", "deprecated": false, @@ -10957,7 +10957,7 @@ }, "x-appwrite": { "method": "updateDeploymentStatus", - "weight": 381, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -11025,7 +11025,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 384, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -11098,7 +11098,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 382, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -11216,7 +11216,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 383, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -11280,7 +11280,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 385, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -11348,7 +11348,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 386, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -11427,7 +11427,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 390, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -11485,7 +11485,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 388, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -11576,7 +11576,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 389, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -11642,7 +11642,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 391, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -11733,7 +11733,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 392, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -21190,6 +21190,385 @@ ] } }, + "\/projects\/{projectId}\/dev-keys": { + "get": { + "summary": "List dev keys", + "operationId": "projectsListDevKeys", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "projects" + ], + "description": "List all the project\\'s dev keys. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.'", + "responses": { + "200": { + "description": "Dev Keys List", + "schema": { + "$ref": "#\/definitions\/devKeyList" + } + } + }, + "x-appwrite": { + "method": "listDevKeys", + "weight": 368, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/list-dev-keys.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterList all the project\\'s dev keys. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.'", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.read", + "platforms": [ + "console" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: accessedAt, expire", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + }, + { + "name": "search", + "description": "Search term to filter your list results. Max length: 256 chars.", + "required": false, + "type": "string", + "x-example": "", + "default": "", + "in": "query" + } + ] + }, + "post": { + "summary": "Create dev key", + "operationId": "projectsCreateDevKey", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "projects" + ], + "description": "Create a new project dev key. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development. Strictly meant for development purposes only.", + "responses": { + "201": { + "description": "DevKey", + "schema": { + "$ref": "#\/definitions\/devKey" + } + } + }, + "x-appwrite": { + "method": "createDevKey", + "weight": 365, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/create-dev-key.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new project dev key. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development. Strictly meant for development purposes only.", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.write", + "platforms": [ + "console" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Key name. Max length: 128 chars.", + "default": null, + "x-example": "" + }, + "expire": { + "type": "string", + "description": "Expiration time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format.", + "default": null, + "x-example": null + } + }, + "required": [ + "name", + "expire" + ] + } + } + ] + } + }, + "\/projects\/{projectId}\/dev-keys\/{keyId}": { + "get": { + "summary": "Get dev key", + "operationId": "projectsGetDevKey", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "projects" + ], + "description": "Get a project\\'s dev key by its unique ID. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.", + "responses": { + "200": { + "description": "DevKey", + "schema": { + "$ref": "#\/definitions\/devKey" + } + } + }, + "x-appwrite": { + "method": "getDevKey", + "weight": 367, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/get-dev-key.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a project\\'s dev key by its unique ID. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.read", + "platforms": [ + "console" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "keyId", + "description": "Key unique ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + }, + "put": { + "summary": "Update dev key", + "operationId": "projectsUpdateDevKey", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "projects" + ], + "description": "Update a project\\'s dev key by its unique ID. Use this endpoint to update a project\\'s dev key name or expiration time.'", + "responses": { + "200": { + "description": "DevKey", + "schema": { + "$ref": "#\/definitions\/devKey" + } + } + }, + "x-appwrite": { + "method": "updateDevKey", + "weight": 366, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/update-dev-key.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterUpdate a project\\'s dev key by its unique ID. Use this endpoint to update a project\\'s dev key name or expiration time.'", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.write", + "platforms": [ + "console" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "keyId", + "description": "Key unique ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Key name. Max length: 128 chars.", + "default": null, + "x-example": "" + }, + "expire": { + "type": "string", + "description": "Expiration time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format.", + "default": null, + "x-example": null + } + }, + "required": [ + "name", + "expire" + ] + } + } + ] + }, + "delete": { + "summary": "Delete dev key", + "operationId": "projectsDeleteDevKey", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "projects" + ], + "description": "Delete a project\\'s dev key by its unique ID. Once deleted, the key will no longer allow bypassing of rate limits and better logging of errors.", + "responses": { + "204": { + "description": "No content" + } + }, + "x-appwrite": { + "method": "deleteDevKey", + "weight": 369, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/delete-dev-key.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterDelete a project\\'s dev key by its unique ID. Once deleted, the key will no longer allow bypassing of rate limits and better logging of errors.", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.write", + "platforms": [ + "console" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "keyId", + "description": "Key unique ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + } + ] + } + }, "\/projects\/{projectId}\/jwts": { "post": { "summary": "Create JWT", @@ -24676,7 +25055,7 @@ }, "x-appwrite": { "method": "createAPIRule", - "weight": 425, + "weight": 430, "cookies": false, "type": "", "deprecated": false, @@ -24745,7 +25124,7 @@ }, "x-appwrite": { "method": "createFunctionRule", - "weight": 427, + "weight": 432, "cookies": false, "type": "", "deprecated": false, @@ -24827,7 +25206,7 @@ }, "x-appwrite": { "method": "createRedirectRule", - "weight": 428, + "weight": 433, "cookies": false, "type": "", "deprecated": false, @@ -24923,7 +25302,7 @@ }, "x-appwrite": { "method": "createSiteRule", - "weight": 426, + "weight": 431, "cookies": false, "type": "", "deprecated": false, @@ -25175,7 +25554,7 @@ }, "x-appwrite": { "method": "list", - "weight": 397, + "weight": 402, "cookies": false, "type": "", "deprecated": false, @@ -25246,7 +25625,7 @@ }, "x-appwrite": { "method": "create", - "weight": 395, + "weight": 400, "cookies": false, "type": "", "deprecated": false, @@ -25511,7 +25890,7 @@ }, "x-appwrite": { "method": "listFrameworks", - "weight": 400, + "weight": 405, "cookies": false, "type": "", "deprecated": false, @@ -25561,7 +25940,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 423, + "weight": 428, "cookies": false, "type": "", "deprecated": false, @@ -25612,7 +25991,7 @@ }, "x-appwrite": { "method": "listTemplates", - "weight": 419, + "weight": 424, "cookies": false, "type": "", "deprecated": false, @@ -25707,7 +26086,7 @@ }, "x-appwrite": { "method": "getTemplate", - "weight": 420, + "weight": 425, "cookies": false, "type": "", "deprecated": false, @@ -25766,7 +26145,7 @@ }, "x-appwrite": { "method": "listUsage", - "weight": 421, + "weight": 426, "cookies": false, "type": "", "deprecated": false, @@ -25837,7 +26216,7 @@ }, "x-appwrite": { "method": "get", - "weight": 396, + "weight": 401, "cookies": false, "type": "", "deprecated": false, @@ -25895,7 +26274,7 @@ }, "x-appwrite": { "method": "update", - "weight": 398, + "weight": 403, "cookies": false, "type": "", "deprecated": false, @@ -26153,7 +26532,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 399, + "weight": 404, "cookies": false, "type": "", "deprecated": false, @@ -26213,7 +26592,7 @@ }, "x-appwrite": { "method": "updateSiteDeployment", - "weight": 406, + "weight": 411, "cookies": false, "type": "", "deprecated": false, @@ -26291,7 +26670,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 405, + "weight": 410, "cookies": false, "type": "", "deprecated": false, @@ -26370,7 +26749,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 401, + "weight": 406, "cookies": false, "type": "upload", "deprecated": false, @@ -26469,7 +26848,7 @@ }, "x-appwrite": { "method": "createDuplicateDeployment", - "weight": 409, + "weight": 414, "cookies": false, "type": "", "deprecated": false, @@ -26547,7 +26926,7 @@ }, "x-appwrite": { "method": "createTemplateDeployment", - "weight": 402, + "weight": 407, "cookies": false, "type": "", "deprecated": false, @@ -26652,7 +27031,7 @@ }, "x-appwrite": { "method": "createVcsDeployment", - "weight": 403, + "weight": 408, "cookies": false, "type": "", "deprecated": false, @@ -26750,7 +27129,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 404, + "weight": 409, "cookies": false, "type": "", "deprecated": false, @@ -26811,7 +27190,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 407, + "weight": 412, "cookies": false, "type": "", "deprecated": false, @@ -26879,7 +27258,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 408, + "weight": 413, "cookies": false, "type": "location", "deprecated": false, @@ -26964,7 +27343,7 @@ }, "x-appwrite": { "method": "updateDeploymentStatus", - "weight": 410, + "weight": 415, "cookies": false, "type": "", "deprecated": false, @@ -27032,7 +27411,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 412, + "weight": 417, "cookies": false, "type": "", "deprecated": false, @@ -27104,7 +27483,7 @@ }, "x-appwrite": { "method": "getLog", - "weight": 411, + "weight": 416, "cookies": false, "type": "", "deprecated": false, @@ -27167,7 +27546,7 @@ }, "x-appwrite": { "method": "deleteLog", - "weight": 413, + "weight": 418, "cookies": false, "type": "", "deprecated": false, @@ -27235,7 +27614,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 422, + "weight": 427, "cookies": false, "type": "", "deprecated": false, @@ -27314,7 +27693,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 416, + "weight": 421, "cookies": false, "type": "", "deprecated": false, @@ -27372,7 +27751,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 414, + "weight": 419, "cookies": false, "type": "", "deprecated": false, @@ -27463,7 +27842,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 415, + "weight": 420, "cookies": false, "type": "", "deprecated": false, @@ -27529,7 +27908,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 417, + "weight": 422, "cookies": false, "type": "", "deprecated": false, @@ -27620,7 +27999,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 418, + "weight": 423, "cookies": false, "type": "", "deprecated": false, @@ -34894,6 +35273,31 @@ "keys" ] }, + "devKeyList": { + "description": "Dev Keys List", + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Total number of devKeys documents that matched your query.", + "x-example": 5, + "format": "int32" + }, + "devKeys": { + "type": "array", + "description": "List of devKeys.", + "items": { + "type": "object", + "$ref": "#\/definitions\/devKey" + }, + "x-example": "" + } + }, + "required": [ + "total", + "devKeys" + ] + }, "platformList": { "description": "Platforms List", "type": "object", @@ -39201,6 +39605,15 @@ }, "x-example": {} }, + "devKeys": { + "type": "array", + "description": "List of dev keys.", + "items": { + "type": "object", + "$ref": "#\/definitions\/devKey" + }, + "x-example": {} + }, "smtpEnabled": { "type": "boolean", "description": "Status for custom SMTP", @@ -39384,6 +39797,7 @@ "platforms", "webhooks", "keys", + "devKeys", "smtpEnabled", "smtpSenderName", "smtpSenderEmail", @@ -39574,6 +39988,65 @@ "sdks" ] }, + "devKey": { + "description": "DevKey", + "type": "object", + "properties": { + "$id": { + "type": "string", + "description": "Key ID.", + "x-example": "5e5ea5c16897e" + }, + "$createdAt": { + "type": "string", + "description": "Key creation date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "$updatedAt": { + "type": "string", + "description": "Key update date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "name": { + "type": "string", + "description": "Key name.", + "x-example": "Dev API Key" + }, + "expire": { + "type": "string", + "description": "Key expiration date in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "secret": { + "type": "string", + "description": "Secret key.", + "x-example": "919c2d18fb5d4...a2ae413da83346ad2" + }, + "accessedAt": { + "type": "string", + "description": "Most recent access date in ISO 8601 format. This attribute is only updated again after 24 hours.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, + "sdks": { + "type": "array", + "description": "List of SDK user agents that used this key.", + "items": { + "type": "string" + }, + "x-example": "appwrite:flutter" + } + }, + "required": [ + "$id", + "$createdAt", + "$updatedAt", + "name", + "expire", + "secret", + "accessedAt", + "sdks" + ] + }, "mockNumber": { "description": "Mock Number", "type": "object", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 41661b20ac..d1940fbcb0 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -8282,7 +8282,7 @@ }, "x-appwrite": { "method": "list", - "weight": 368, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -8354,7 +8354,7 @@ }, "x-appwrite": { "method": "create", - "weight": 365, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -8605,7 +8605,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 370, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -8656,7 +8656,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 371, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -8708,7 +8708,7 @@ }, "x-appwrite": { "method": "get", - "weight": 366, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -8767,7 +8767,7 @@ }, "x-appwrite": { "method": "update", - "weight": 367, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -9012,7 +9012,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 369, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -9073,7 +9073,7 @@ }, "x-appwrite": { "method": "updateFunctionDeployment", - "weight": 374, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -9152,7 +9152,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 375, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -9232,7 +9232,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 372, + "weight": 377, "cookies": false, "type": "upload", "deprecated": false, @@ -9324,7 +9324,7 @@ }, "x-appwrite": { "method": "createDuplicateDeployment", - "weight": 380, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -9409,7 +9409,7 @@ }, "x-appwrite": { "method": "createTemplateDeployment", - "weight": 377, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -9515,7 +9515,7 @@ }, "x-appwrite": { "method": "createVcsDeployment", - "weight": 378, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -9613,7 +9613,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 373, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -9675,7 +9675,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 376, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -9744,7 +9744,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 379, + "weight": 384, "cookies": false, "type": "location", "deprecated": false, @@ -9830,7 +9830,7 @@ }, "x-appwrite": { "method": "updateDeploymentStatus", - "weight": 381, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -9899,7 +9899,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 384, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -9974,7 +9974,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 382, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -10094,7 +10094,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 383, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -10160,7 +10160,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 385, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -10229,7 +10229,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 390, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -10288,7 +10288,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 388, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -10380,7 +10380,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 389, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -10447,7 +10447,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 391, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -10539,7 +10539,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 392, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -17154,7 +17154,7 @@ }, "x-appwrite": { "method": "list", - "weight": 397, + "weight": 402, "cookies": false, "type": "", "deprecated": false, @@ -17226,7 +17226,7 @@ }, "x-appwrite": { "method": "create", - "weight": 395, + "weight": 400, "cookies": false, "type": "", "deprecated": false, @@ -17492,7 +17492,7 @@ }, "x-appwrite": { "method": "listFrameworks", - "weight": 400, + "weight": 405, "cookies": false, "type": "", "deprecated": false, @@ -17543,7 +17543,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 423, + "weight": 428, "cookies": false, "type": "", "deprecated": false, @@ -17595,7 +17595,7 @@ }, "x-appwrite": { "method": "get", - "weight": 396, + "weight": 401, "cookies": false, "type": "", "deprecated": false, @@ -17654,7 +17654,7 @@ }, "x-appwrite": { "method": "update", - "weight": 398, + "weight": 403, "cookies": false, "type": "", "deprecated": false, @@ -17913,7 +17913,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 399, + "weight": 404, "cookies": false, "type": "", "deprecated": false, @@ -17974,7 +17974,7 @@ }, "x-appwrite": { "method": "updateSiteDeployment", - "weight": 406, + "weight": 411, "cookies": false, "type": "", "deprecated": false, @@ -18053,7 +18053,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 405, + "weight": 410, "cookies": false, "type": "", "deprecated": false, @@ -18133,7 +18133,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 401, + "weight": 406, "cookies": false, "type": "upload", "deprecated": false, @@ -18233,7 +18233,7 @@ }, "x-appwrite": { "method": "createDuplicateDeployment", - "weight": 409, + "weight": 414, "cookies": false, "type": "", "deprecated": false, @@ -18312,7 +18312,7 @@ }, "x-appwrite": { "method": "createTemplateDeployment", - "weight": 402, + "weight": 407, "cookies": false, "type": "", "deprecated": false, @@ -18418,7 +18418,7 @@ }, "x-appwrite": { "method": "createVcsDeployment", - "weight": 403, + "weight": 408, "cookies": false, "type": "", "deprecated": false, @@ -18517,7 +18517,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 404, + "weight": 409, "cookies": false, "type": "", "deprecated": false, @@ -18579,7 +18579,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 407, + "weight": 412, "cookies": false, "type": "", "deprecated": false, @@ -18648,7 +18648,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 408, + "weight": 413, "cookies": false, "type": "location", "deprecated": false, @@ -18734,7 +18734,7 @@ }, "x-appwrite": { "method": "updateDeploymentStatus", - "weight": 410, + "weight": 415, "cookies": false, "type": "", "deprecated": false, @@ -18803,7 +18803,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 412, + "weight": 417, "cookies": false, "type": "", "deprecated": false, @@ -18876,7 +18876,7 @@ }, "x-appwrite": { "method": "getLog", - "weight": 411, + "weight": 416, "cookies": false, "type": "", "deprecated": false, @@ -18940,7 +18940,7 @@ }, "x-appwrite": { "method": "deleteLog", - "weight": 413, + "weight": 418, "cookies": false, "type": "", "deprecated": false, @@ -19009,7 +19009,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 416, + "weight": 421, "cookies": false, "type": "", "deprecated": false, @@ -19068,7 +19068,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 414, + "weight": 419, "cookies": false, "type": "", "deprecated": false, @@ -19160,7 +19160,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 415, + "weight": 420, "cookies": false, "type": "", "deprecated": false, @@ -19227,7 +19227,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 417, + "weight": 422, "cookies": false, "type": "", "deprecated": false, @@ -19319,7 +19319,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 418, + "weight": 423, "cookies": false, "type": "", "deprecated": false, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 20f64496ac..1deef8afdd 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1188,8 +1188,8 @@ App::get('/v1/account/sessions/oauth2/:provider') ->label('abuse-limit', 50) ->label('abuse-key', 'ip:{ip}') ->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.') - ->param('success', '', fn ($clients) => new Host($clients), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) - ->param('failure', '', fn ($clients) => new Host($clients), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) + ->param('success', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey']) + ->param('failure', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey']) ->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) ->inject('request') ->inject('response') @@ -1784,8 +1784,8 @@ App::get('/v1/account/tokens/oauth2/:provider') ->label('abuse-limit', 50) ->label('abuse-key', 'ip:{ip}') ->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.') - ->param('success', '', fn ($clients) => new Host($clients), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) - ->param('failure', '', fn ($clients) => new Host($clients), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) + ->param('success', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey']) + ->param('failure', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey']) ->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) ->inject('request') ->inject('response') @@ -1864,7 +1864,7 @@ App::post('/v1/account/tokens/magic-url') ->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}']) ->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('email', '', new Email(), 'User email.') - ->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) + ->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey']) ->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true) ->inject('request') ->inject('response') @@ -3159,7 +3159,7 @@ App::post('/v1/account/recovery') ->label('abuse-limit', 10) ->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}']) ->param('email', '', new Email(), 'User email.') - ->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) + ->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients', 'devKey']) ->inject('request') ->inject('response') ->inject('user') @@ -3434,7 +3434,7 @@ App::post('/v1/account/verification') )) ->label('abuse-limit', 10) ->label('abuse-key', 'url:{url},userId:{userId}') - ->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add built-in confirm page + ->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients', 'devKey']) // TODO add built-in confirm page ->inject('request') ->inject('response') ->inject('project') diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 3e0e366b6b..f58be39857 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -51,6 +51,7 @@ use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; use Utopia\Validator\Host; use Utopia\Validator\Text; +use Utopia\Validator\URL; use Utopia\Validator\WhiteList; App::post('/v1/teams') @@ -455,7 +456,7 @@ App::post('/v1/teams/:teamId/memberships') } return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE); }, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project']) - ->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page + ->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey']) // TODO add our own built-in confirm page ->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true) ->inject('response') ->inject('project') diff --git a/app/controllers/general.php b/app/controllers/general.php index 8985c82db6..88fa94c097 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -106,7 +106,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw ); if (!$project->isEmpty() && $project->getId() !== 'console') { - $accessedAt = $project->getAttribute('accessedAt', ''); + $accessedAt = $project->getAttribute('accessedAt', 0); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) { $project->setAttribute('accessedAt', DateTime::now()); Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project)); @@ -760,8 +760,9 @@ App::init() ->inject('executor') ->inject('isResourceBlocked') ->inject('previewHostname') + ->inject('devKey') ->inject('apiKey') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey) { /* * Appwrite Router */ @@ -994,6 +995,10 @@ App::init() ->addHeader('Access-Control-Allow-Origin', $refDomain) ->addHeader('Access-Control-Allow-Credentials', 'true'); + if (!$devKey->isEmpty()) { + $response->addHeader('Access-Control-Allow-Origin', '*'); + } + /* * Validate Client Domain - Check to avoid CSRF attack * Adding Appwrite API domains to allow XDOMAIN communication @@ -1004,6 +1009,7 @@ App::init() if ( !$originValidator->isValid($origin) + && $devKey->isEmpty() && \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE]) && $route->getLabel('origin', false) !== '*' && empty($request->getHeader('x-appwrite-key', '')) @@ -1027,8 +1033,9 @@ App::options() ->inject('isResourceBlocked') ->inject('previewHostname') ->inject('project') + ->inject('devKey') ->inject('apiKey') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project, ?Key $apiKey) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey) { /* * Appwrite Router */ @@ -1052,6 +1059,10 @@ App::options() ->addHeader('Access-Control-Allow-Credentials', 'true') ->noContent(); + if (!$devKey->isEmpty()) { + $response->addHeader('Access-Control-Allow-Origin', '*'); + } + /** OPTIONS requests in utopia do not execute shutdown handlers, as a result we need to track the OPTIONS requests explicitly * @see https://github.com/utopia-php/http/blob/0.33.16/src/App.php#L825-L855 */ @@ -1072,6 +1083,7 @@ App::error() ->inject('logger') ->inject('log') ->inject('queueForStatsUsage') + ->inject('devKey') ->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage) { $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); $route = $utopia->getRoute(); @@ -1286,7 +1298,7 @@ App::error() $type = $error->getType(); - $output = ((App::isDevelopment())) ? [ + $output = App::isDevelopment() ? [ 'message' => $message, 'code' => $code, 'file' => $file, diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 957a7a7d23..b24d452b9b 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -250,7 +250,7 @@ App::init() ); if ($dbKey) { - $accessedAt = $dbKey->getAttribute('accessedAt', ''); + $accessedAt = $dbKey->getAttribute('accessedAt', 0); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) { $dbKey->setAttribute('accessedAt', DateTime::now()); @@ -261,7 +261,7 @@ App::init() $sdkValidator = new WhiteList($servers, true); $sdk = $request->getHeader('x-sdk-name', 'UNKNOWN'); - if ($sdkValidator->isValid($sdk)) { + if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) { $sdks = $dbKey->getAttribute('sdks', []); if (!in_array($sdk, $sdks)) { @@ -311,7 +311,7 @@ App::init() // Update project last activity if (!$project->isEmpty() && $project->getId() !== 'console') { - $accessedAt = $project->getAttribute('accessedAt', ''); + $accessedAt = $project->getAttribute('accessedAt', 0); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) { $project->setAttribute('accessedAt', DateTime::now()); Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project)); @@ -320,7 +320,7 @@ App::init() // Update user last activity if (!empty($user->getId())) { - $accessedAt = $user->getAttribute('accessedAt', ''); + $accessedAt = $user->getAttribute('accessedAt', 0); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) { $user->setAttribute('accessedAt', DateTime::now()); @@ -402,7 +402,8 @@ App::init() ->inject('mode') ->inject('apiKey') ->inject('plan') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, string $mode, ?Key $apiKey, array $plan) use ($usageDatabaseListener, $eventDatabaseListener) { + ->inject('devKey') + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, string $mode, ?Key $apiKey, array $plan, Document $devKey) use ($usageDatabaseListener, $eventDatabaseListener) { $route = $utopia->getRoute(); @@ -469,6 +470,7 @@ App::init() $enabled // Abuse is enabled && !$isAppUser // User is not API key && !$isPrivilegedUser // User is not an admin + && $devKey->isEmpty() // request doesn't not contain development key && $abuse->check() // Route is rate-limited ) { throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED); @@ -798,7 +800,7 @@ App::shutdown() $key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); $signature = md5($data['payload']); $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); - $accessedAt = $cacheLog->getAttribute('accessedAt', ''); + $accessedAt = $cacheLog->getAttribute('accessedAt', 0); $now = DateTime::now(); if ($cacheLog->isEmpty()) { Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([ diff --git a/app/init/database/filters.php b/app/init/database/filters.php index 933f291c0d..f110fe1554 100644 --- a/app/init/database/filters.php +++ b/app/init/database/filters.php @@ -133,6 +133,20 @@ Database::addFilter( } ); +Database::addFilter( + 'subQueryDevKeys', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('devKeys', [ + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + Database::addFilter( 'subQueryWebhooks', function (mixed $value) { diff --git a/app/init/resources.php b/app/init/resources.php index 1e6154df10..da013cd2ce 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -29,6 +29,7 @@ use Utopia\Cache\Cache; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; +use Utopia\Database\DateTime as DatabaseDateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; @@ -49,6 +50,7 @@ use Utopia\Storage\Device\Wasabi; use Utopia\Storage\Storage; use Utopia\System\System; use Utopia\Validator\Hostname; +use Utopia\Validator\WhiteList; use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub; // Runtime Execution @@ -789,6 +791,49 @@ App::setResource('smsRates', function () { return []; }); +App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform) { + $devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', '')); + + // Check if given key match project's development keys + $key = $project->find('secret', $devKey, 'devKeys'); + if (!$key) { + return new Document([]); + } + + // check expiration + $expire = $key->getAttribute('expire'); + if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) { + return new Document([]); + } + + // update access time + $accessedAt = $key->getAttribute('accessedAt', 0); + if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) { + $key->setAttribute('accessedAt', DatabaseDateTime::now()); + Authorization::skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key)); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); + } + + // add sdk to key + $sdkValidator = new WhiteList($servers, true); + $sdk = $request->getHeader('x-sdk-name', 'UNKNOWN'); + + if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) { + $sdks = $key->getAttribute('sdks', []); + + if (!in_array($sdk, $sdks)) { + $sdks[] = $sdk; + $key->setAttribute('sdks', $sdks); + + /** Update access time as well */ + $key->setAttribute('accessedAt', DatabaseDateTime::now()); + $key = Authorization::skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key)); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); + } + } + return $key; +}, ['request', 'project', 'servers', 'dbForPlatform']); + App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) { $teamInternalId = ''; if ($project->getId() !== 'console') { diff --git a/src/Appwrite/Platform/Appwrite.php b/src/Appwrite/Platform/Appwrite.php index be6ddbae8a..9968dd22b5 100644 --- a/src/Appwrite/Platform/Appwrite.php +++ b/src/Appwrite/Platform/Appwrite.php @@ -5,6 +5,7 @@ namespace Appwrite\Platform; use Appwrite\Platform\Modules\Console; use Appwrite\Platform\Modules\Core; use Appwrite\Platform\Modules\Functions; +use Appwrite\Platform\Modules\Projects; use Appwrite\Platform\Modules\Proxy; use Appwrite\Platform\Modules\Sites; use Utopia\Platform\Platform; @@ -14,6 +15,7 @@ class Appwrite extends Platform public function __construct() { parent::__construct(new Core()); + $this->addModule(new Projects\Module()); $this->addModule(new Functions\Module()); $this->addModule(new Sites\Module()); $this->addModule(new Console\Module()); diff --git a/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Create.php b/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Create.php new file mode 100644 index 0000000000..9636a166c1 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Create.php @@ -0,0 +1,96 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/projects/:projectId/dev-keys') + ->desc('Create dev key') + ->groups(['api', 'projects']) + ->label('scope', 'projects.write') + ->label('sdk', new Method( + namespace: 'projects', + name: 'createDevKey', + description: <<param('projectId', '', new UID(), 'Project unique ID.') + ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') + ->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format.', false) + ->inject('user') + ->inject('response') + ->inject('dbForPlatform') + ->callback([$this, 'action']); + } + + public function action(string $projectId, string $name, ?string $expire, Document $user, Response $response, Database $dbForPlatform) + { + $project = $dbForPlatform->getDocument('projects', $projectId); + + if ($project->isEmpty()) { + throw new Exception(Exception::PROJECT_NOT_FOUND); + } + + $devKeyId = ID::unique(); + $key = new Document([ + '$id' => $devKeyId, + '$permissions' => [ + Permission::read(Role::user($user->getId())), + Permission::update(Role::user($user->getId())), + Permission::delete(Role::user($user->getId())), + ], + 'projectInternalId' => $project->getInternalId(), + 'projectId' => $project->getId(), + 'name' => $name, + 'expire' => $expire, + 'sdks' => [], + 'search' => implode('', [$name, $project->getId(), $devKeyId]), + 'accessedAt' => null, + 'secret' => \bin2hex(\random_bytes(128)), + ]); + + $key = $dbForPlatform->createDocument('devKeys', $key); + + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($key, Response::MODEL_DEV_KEY); + } +} diff --git a/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Delete.php b/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Delete.php new file mode 100644 index 0000000000..3adf26f816 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Delete.php @@ -0,0 +1,75 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE) + ->setHttpPath('/v1/projects/:projectId/dev-keys/:keyId') + ->desc('Delete dev key') + ->groups(['api', 'projects']) + ->label('scope', 'projects.write') + ->label('sdk', new Method( + namespace: 'projects', + name: 'deleteDevKey', + description: <<param('projectId', '', new UID(), 'Project unique ID.') + ->param('keyId', '', new UID(), 'Key unique ID.') + ->inject('response') + ->inject('dbForPlatform') + ->callback([$this, 'action']); + } + + public function action(string $projectId, string $keyId, Response $response, Database $dbForPlatform) + { + + $project = $dbForPlatform->getDocument('projects', $projectId); + + if ($project->isEmpty()) { + throw new Exception(Exception::PROJECT_NOT_FOUND); + } + + $key = $dbForPlatform->getDocument('devKeys', $keyId); + + if ($key === false || $key->isEmpty() || $key->getAttribute('projectInternalId') !== $project->getInternalId()) { + throw new Exception(Exception::KEY_NOT_FOUND); + } + + $dbForPlatform->deleteDocument('devKeys', $key->getId()); + + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); + + $response->noContent(); + } +} diff --git a/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Get.php b/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Get.php new file mode 100644 index 0000000000..c933c5be93 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Get.php @@ -0,0 +1,71 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/projects/:projectId/dev-keys/:keyId') + ->desc('Get dev key') + ->groups(['api', 'projects']) + ->label('scope', 'projects.read') + ->label('sdk', new Method( + namespace: 'projects', + name: 'getDevKey', + description: <<param('projectId', '', new UID(), 'Project unique ID.') + ->param('keyId', '', new UID(), 'Key unique ID.') + ->inject('response') + ->inject('dbForPlatform') + ->callback([$this, 'action']); + } + + public function action(string $projectId, string $keyId, Response $response, Database $dbForPlatform) + { + + $project = $dbForPlatform->getDocument('projects', $projectId); + + if ($project->isEmpty()) { + throw new Exception(Exception::PROJECT_NOT_FOUND); + } + + $key = $dbForPlatform->getDocument('devKeys', $keyId); + + if ($key === false || $key->isEmpty() || $key->getAttribute('projectInternalId') !== $project->getInternalId()) { + throw new Exception(Exception::KEY_NOT_FOUND); + } + + $response->dynamic($key, Response::MODEL_DEV_KEY); + } +} diff --git a/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php b/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php new file mode 100644 index 0000000000..c556578bc2 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php @@ -0,0 +1,82 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT) + ->setHttpPath('/v1/projects/:projectId/dev-keys/:keyId') + ->desc('Update dev key') + ->groups(['api', 'projects']) + ->label('scope', 'projects.write') + ->label('sdk', new Method( + namespace: 'projects', + name: 'updateDevKey', + description: <<param('projectId', '', new UID(), 'Project unique ID.') + ->param('keyId', '', new UID(), 'Key unique ID.') + ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') + ->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format.') + ->inject('response') + ->inject('dbForPlatform') + ->callback([$this, 'action']); + } + public function action(string $projectId, string $keyId, string $name, ?string $expire, Response $response, Database $dbForPlatform) + { + + $project = $dbForPlatform->getDocument('projects', $projectId); + + if ($project->isEmpty()) { + throw new Exception(Exception::PROJECT_NOT_FOUND); + } + + $key = $dbForPlatform->getDocument('devKeys', $keyId); + + if ($key === false || $key->isEmpty() || $key->getAttribute('projectInternalId') !== $project->getInternalId()) { + throw new Exception(Exception::KEY_NOT_FOUND); + } + + $key + ->setAttribute('name', $name) + ->setAttribute('expire', $expire) + ->setAttribute('search', implode('', [$name, $project->getId(), $key->getId()])); + + $dbForPlatform->updateDocument('devKeys', $key->getId(), $key); + + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); + + $response->dynamic($key, Response::MODEL_DEV_KEY); + } +} diff --git a/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/XList.php b/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/XList.php new file mode 100644 index 0000000000..0d3516558e --- /dev/null +++ b/src/Appwrite/Platform/Modules/Projects/Http/DevKeys/XList.php @@ -0,0 +1,88 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/projects/:projectId/dev-keys') + ->desc('List dev keys') + ->groups(['api', 'projects']) + ->label('scope', 'projects.read') + ->label('sdk', new Method( + namespace: 'projects', + name: 'listDevKeys', + description: <<param('projectId', '', new UID(), 'Project unique ID.') + ->param('queries', [], new DevKeys(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', DevKeys::ALLOWED_ATTRIBUTES), true) + ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) + ->inject('response') + ->inject('dbForPlatform') + ->callback([$this, 'action']); + } + + public function action(string $projectId, ?array $queries, ?string $search, Response $response, Database $dbForPlatform) + { + + $project = $dbForPlatform->getDocument('projects', $projectId); + + if ($project->isEmpty()) { + throw new Exception(Exception::PROJECT_NOT_FOUND); + } + + try { + $queries = Query::parseQueries($queries); + } catch (QueryException $e) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); + } + + if (!empty($search)) { + $queries[] = Query::search('search', $search); + } + + $queries[] = Query::equal('projectInternalId', [$project->getInternalId()]); + + $keys = $dbForPlatform->find('devKeys', $queries); + + $response->dynamic(new Document([ + 'devKeys' => $keys, + 'total' => count($keys), + ]), Response::MODEL_DEV_KEY_LIST); + } +} diff --git a/src/Appwrite/Platform/Modules/Projects/Module.php b/src/Appwrite/Platform/Modules/Projects/Module.php new file mode 100644 index 0000000000..2a550acf54 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Projects/Module.php @@ -0,0 +1,14 @@ +addService('http', new Http()); + } +} diff --git a/src/Appwrite/Platform/Modules/Projects/Services/Http.php b/src/Appwrite/Platform/Modules/Projects/Services/Http.php new file mode 100644 index 0000000000..cec8ed6d16 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Projects/Services/Http.php @@ -0,0 +1,23 @@ +type = Service::TYPE_HTTP; + $this->addAction(CreateDevKey::getName(), new CreateDevKey()); + $this->addAction(UpdateDevKey::getName(), new UpdateDevKey()); + $this->addAction(GetDevKey::getName(), new GetDevKey()); + $this->addAction(ListDevKeys::getName(), new ListDevKeys()); + $this->addAction(DeleteDevKey::getName(), new DeleteDevKey()); + } +} diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index 303e03cb8c..88f98fa76a 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -43,7 +43,7 @@ abstract class ScheduleBase extends Action protected function updateProjectAccess(Document $project, Database $dbForPlatform): void { if (!$project->isEmpty() && $project->getId() !== 'console') { - $accessedAt = $project->getAttribute('accessedAt', ''); + $accessedAt = $project->getAttribute('accessedAt', 0); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) { $project->setAttribute('accessedAt', DateTime::now()); Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project)); diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/DevKeys.php b/src/Appwrite/Utopia/Database/Validator/Queries/DevKeys.php new file mode 100644 index 0000000000..d9dbbeadc4 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/DevKeys.php @@ -0,0 +1,20 @@ +setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false)) ->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false)) ->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false)) + ->setModel(new BaseList('Dev Keys List', self::MODEL_DEV_KEY_LIST, 'devKeys', self::MODEL_DEV_KEY, true, false)) ->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false)) ->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false)) ->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY)) @@ -472,6 +476,7 @@ class Response extends SwooleResponse ->setModel(new Project()) ->setModel(new Webhook()) ->setModel(new Key()) + ->setModel(new DevKey()) ->setModel(new MockNumber()) ->setModel(new AuthProvider()) ->setModel(new Platform()) diff --git a/src/Appwrite/Utopia/Response/Model/DevKey.php b/src/Appwrite/Utopia/Response/Model/DevKey.php new file mode 100644 index 0000000000..b8da6c0cfc --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/DevKey.php @@ -0,0 +1,89 @@ +addRule('$id', [ + 'type' => self::TYPE_STRING, + 'description' => 'Key ID.', + 'default' => '', + 'example' => '5e5ea5c16897e', + ]) + ->addRule('$createdAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Key creation date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('$updatedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Key update date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('name', [ + 'type' => self::TYPE_STRING, + 'description' => 'Key name.', + 'default' => '', + 'example' => 'Dev API Key', + ]) + ->addRule('expire', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Key expiration date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('secret', [ + 'type' => self::TYPE_STRING, + 'description' => 'Secret key.', + 'default' => '', + 'example' => '919c2d18fb5d4...a2ae413da83346ad2', + ]) + ->addRule('accessedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_KEY_ACCESS / 60 / 60 . ' hours.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE + ]) + ->addRule('sdks', [ + 'type' => self::TYPE_STRING, + 'description' => 'List of SDK user agents that used this key.', + 'default' => null, + 'example' => 'appwrite:flutter', + 'array' => true + ]) + ; + } + + /** + * Get Name + * + * @return string + */ + public function getName(): string + { + return 'DevKey'; + } + + /** + * Get Type + * + * @return string + */ + public function getType(): string + { + return Response::MODEL_DEV_KEY; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index fbbe062531..efd002654e 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -197,6 +197,13 @@ class Project extends Model 'example' => new \stdClass(), 'array' => true, ]) + ->addRule('devKeys', [ + 'type' => Response::MODEL_DEV_KEY, + 'description' => 'List of dev keys.', + 'default' => [], + 'example' => new \stdClass(), + 'array' => true, + ]) ->addRule('smtpEnabled', [ 'type' => self::TYPE_BOOLEAN, 'description' => 'Status for custom SMTP', diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index a1616f2f74..8a3877d317 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Scopes; use Tests\E2E\Client; +use Utopia\Database\DateTime; use Utopia\Database\Helpers\ID; use Utopia\System\System; @@ -110,6 +111,19 @@ trait ProjectCustom $this->assertNotEmpty($key['body']); $this->assertNotEmpty($key['body']['secret']); + $devKey = $this->client->call(Client::METHOD_POST, '/projects/' . $project['body']['$id'] . '/dev-keys', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + 'x-appwrite-project' => 'console', + ], [ + 'name' => 'Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 3600), + ]); + $this->assertEquals(201, $devKey['headers']['status-code']); + $this->assertNotEmpty($devKey['body']); + $this->assertNotEmpty($devKey['body']['secret']); + $webhook = $this->client->call(Client::METHOD_POST, '/projects/' . $project['body']['$id'] . '/webhooks', [ 'origin' => 'http://localhost', 'content-type' => 'application/json', @@ -150,9 +164,11 @@ trait ProjectCustom '$id' => $project['body']['$id'], 'name' => $project['body']['name'], 'apiKey' => $key['body']['secret'], + 'devKey' => $devKey['body']['secret'], 'webhookId' => $webhook['body']['$id'], 'signatureKey' => $webhook['body']['signatureKey'], ]; + if ($fresh) { return $project; } diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php index 3213ff4c5d..6deaa62c05 100644 --- a/tests/e2e/Scopes/Scope.php +++ b/tests/e2e/Scopes/Scope.php @@ -56,7 +56,7 @@ abstract class Scope extends TestCase /** * @return array */ - abstract public function getHeaders(): array; + abstract public function getHeaders(bool $devKey = true): array; /** * @return array diff --git a/tests/e2e/Scopes/SideClient.php b/tests/e2e/Scopes/SideClient.php index 54f77a9747..17ee7a3002 100644 --- a/tests/e2e/Scopes/SideClient.php +++ b/tests/e2e/Scopes/SideClient.php @@ -4,12 +4,17 @@ namespace Tests\E2E\Scopes; trait SideClient { - public function getHeaders(): array + public function getHeaders(bool $devKey = true): array { - return [ + $headers = [ 'origin' => 'http://localhost', 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $this->getUser()['session'], + ]; + if ($devKey && isset($this->getProject()['devKey'])) { + $headers['x-appwrite-dev-key'] = $this->getProject()['devKey']; + } + return $headers; } /** diff --git a/tests/e2e/Scopes/SideConsole.php b/tests/e2e/Scopes/SideConsole.php index 74a0dd0c60..9ad3e93d6a 100644 --- a/tests/e2e/Scopes/SideConsole.php +++ b/tests/e2e/Scopes/SideConsole.php @@ -4,7 +4,7 @@ namespace Tests\E2E\Scopes; trait SideConsole { - public function getHeaders(): array + public function getHeaders(bool $devKey = true): array { return [ 'origin' => 'http://localhost', diff --git a/tests/e2e/Scopes/SideNone.php b/tests/e2e/Scopes/SideNone.php index 79c52afe00..1660beb777 100644 --- a/tests/e2e/Scopes/SideNone.php +++ b/tests/e2e/Scopes/SideNone.php @@ -4,7 +4,7 @@ namespace Tests\E2E\Scopes; trait SideNone { - public function getHeaders(): array + public function getHeaders(bool $devKey = true): array { return []; } diff --git a/tests/e2e/Scopes/SideServer.php b/tests/e2e/Scopes/SideServer.php index b5e15150e9..d27b2092b0 100644 --- a/tests/e2e/Scopes/SideServer.php +++ b/tests/e2e/Scopes/SideServer.php @@ -9,7 +9,7 @@ trait SideServer */ protected $key = []; - public function getHeaders(): array + public function getHeaders(bool $devKey = false): array { return [ 'x-appwrite-key' => $this->getProject()['apiKey'] diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 2d72625121..1a77cccb18 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -99,6 +99,7 @@ trait AccountBase 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? '', ]), [ 'userId' => ID::unique(), 'email' => '', @@ -112,13 +113,14 @@ trait AccountBase 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? '' ]), [ 'userId' => ID::unique(), 'email' => 'shortpass@appwrite.io', 'password' => $shortPassword ]); - $this->assertEquals($response['headers']['status-code'], 400); + $this->assertEquals(400, $response['headers']['status-code']); $longPassword = ''; for ($i = 0; $i < 257; $i++) { // 256 is the limit @@ -129,13 +131,14 @@ trait AccountBase 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? '' ]), [ 'userId' => ID::unique(), 'email' => 'longpass@appwrite.io', 'password' => $longPassword, ]); - $this->assertEquals($response['headers']['status-code'], 400); + $this->assertEquals(400, $response['headers']['status-code']); return [ 'id' => $id, @@ -156,7 +159,7 @@ trait AccountBase 'email' => 'otpuser@appwrite.io' ]); - $this->assertEquals($response['headers']['status-code'], 201); + $this->assertEquals(201, $response['headers']['status-code'], ); $this->assertNotEmpty($response['body']['$id']); $this->assertNotEmpty($response['body']['$createdAt']); $this->assertNotEmpty($response['body']['userId']); @@ -286,6 +289,7 @@ trait AccountBase 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? '' ]), [ 'userId' => ID::unique(), 'email' => $email, diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index daa5bcbff8..452b725c11 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -619,6 +619,7 @@ class AccountCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? '' ]), [ 'userId' => ID::unique(), 'email' => $data['email'], @@ -1217,6 +1218,7 @@ class AccountCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? '' ]), [ 'userId' => ID::unique(), 'email' => $email, diff --git a/tests/e2e/Services/Databases/DatabasesPermissionsScope.php b/tests/e2e/Services/Databases/DatabasesPermissionsScope.php index 336e47db08..0042d253ac 100644 --- a/tests/e2e/Services/Databases/DatabasesPermissionsScope.php +++ b/tests/e2e/Services/Databases/DatabasesPermissionsScope.php @@ -15,6 +15,7 @@ trait DatabasesPermissionsScope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? '', ], [ 'userId' => $id, 'email' => $email, diff --git a/tests/e2e/Services/GraphQL/AbuseTest.php b/tests/e2e/Services/GraphQL/AbuseTest.php index d4e87cf029..ea97492c2b 100644 --- a/tests/e2e/Services/GraphQL/AbuseTest.php +++ b/tests/e2e/Services/GraphQL/AbuseTest.php @@ -88,7 +88,7 @@ class AbuseTest extends Scope $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, - ], $this->getHeaders()), $graphQLPayload); + ], $this->getHeaders(false)), $graphQLPayload); $max = System::getEnv('_APP_GRAPHQL_MAX_QUERY_COMPLEXITY', 250); diff --git a/tests/e2e/Services/Projects/ProjectsBase.php b/tests/e2e/Services/Projects/ProjectsBase.php index 53d9626252..0d1d6a5a44 100644 --- a/tests/e2e/Services/Projects/ProjectsBase.php +++ b/tests/e2e/Services/Projects/ProjectsBase.php @@ -2,6 +2,48 @@ namespace Tests\E2E\Services\Projects; +use Tests\E2E\Client; +use Utopia\Database\Helpers\ID; + trait ProjectsBase { + protected function setupProject(mixed $params): string + { + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Project Test', + ]); + + $this->assertEquals(201, $team['headers']['status-code'], 'Setup team failed with status code: ' . $team['headers']['status-code'] . ' and response: ' . json_encode($team['body'], JSON_PRETTY_PRINT)); + + $project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + ...$params, + 'teamId' => $team['body']['$id'], + ]); + + $this->assertEquals(201, $project['headers']['status-code'], 'Setup project failed with status code: ' . $project['headers']['status-code'] . ' and response: ' . json_encode($project['body'], JSON_PRETTY_PRINT)); + + return $project['body']['$id']; + } + + protected function setupDevKey(mixed $params): array + { + $devKey = $this->client->call(Client::METHOD_POST, '/projects/' . $params['projectId'] . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + $this->assertEquals(201, $devKey['headers']['status-code'], 'Setup devKey failed with status code: ' . $devKey['headers']['status-code'] . ' and response: ' . json_encode($devKey['body'], JSON_PRETTY_PRINT)); + + return [ + '$id' => $devKey['body']['$id'], + 'secret' => $devKey['body']['secret'], + ]; + } } diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index afd1189496..c4a0975f06 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -24,6 +24,7 @@ class ProjectsConsoleClientTest extends Scope use Async; /** + * @group devKeys * @group smtpAndTemplates * @group projectsCRUD */ public function testCreateProject(): array @@ -4255,4 +4256,611 @@ class ProjectsConsoleClientTest extends Scope return $data; } + + /** + * Devkeys Tests starts here ------------------------------------------------ + */ + + /** + * @group devKeys + */ + public function testCreateProjectDevKey(): void + { + /** + * Test for SUCCESS + */ + $id = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testCreateProjectDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('Key Test', $response['body']['name']); + $this->assertNotEmpty($response['body']['secret']); + $this->assertArrayHasKey('accessedAt', $response['body']); + $this->assertEmpty($response['body']['accessedAt']); + + /** Create a second dev key */ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('Dev Key Test', $response['body']['name']); + $this->assertNotEmpty($response['body']['secret']); + $this->assertArrayHasKey('accessedAt', $response['body']); + $this->assertEmpty($response['body']['accessedAt']); + + /** + * Test for FAILURE + */ + + /** TEST expiry date is required */ + $res = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test' + ]); + + $this->assertEquals(400, $res['headers']['status-code']); + } + + + /** + * @group devKeys + */ + public function testListProjectDevKey(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testListProjectDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + /** Create devKey 1 */ + $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + /** Create devKey 2 */ + $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + /** List all dev keys */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(2, $response['body']['total']); + + /** List dev keys with limit */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::limit(1)->toString() + ] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, $response['body']['total']); + + /** List dev keys with search */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'search' => 'Dev' + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, $response['body']['total']); + $this->assertEquals('Dev Key Test', $response['body']['devKeys'][0]['name']); + + /** List dev keys with querying `expire` */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::lessThan('expire', (new \DateTime())->format('Y-m-d H:i:s'))->toString()] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(0, $response['body']['total']); // No dev keys expired + + /** + * Test for FAILURE + */ + + /** Test for search with invalid query */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::search('name', 'Invalid')->toString() + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('Invalid `queries` param: Invalid query: Attribute not found in schema: name', $response['body']['message']); + } + + + /** + * @group devKeys + */ + public function testGetProjectDevKey(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testGetProjectDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($devKey['$id'], $response['body']['$id']); + $this->assertEquals('Dev Key Test', $response['body']['name']); + $this->assertNotEmpty($response['body']['secret']); + $this->assertArrayHasKey('accessedAt', $response['body']); + $this->assertEmpty($response['body']['accessedAt']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys/error', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(404, $response['headers']['status-code']); + } + + /** + * @group devKeys + */ + public function testGetDevKeyWithSdks(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testGetDevKeyWithSdks', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + /** Use dev key with python sdk */ + $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'], + 'x-sdk-name' => 'python' + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(401, $res['headers']['status-code']); + + /** Use dev key with php sdk */ + $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'], + 'x-sdk-name' => 'php' + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(401, $res['headers']['status-code']); + + /** Get the dev key */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayHasKey('sdks', $response['body']); + $this->assertCount(2, $response['body']['sdks']); + $this->assertContains('python', $response['body']['sdks']); + $this->assertContains('php', $response['body']['sdks']); + } + + /** + * @group devKeys + */ + public function testNoHostValidationWithDevKey(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testNoHostValidationWithDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $provider = 'mock'; + $appId = '1'; + $secret = '123456'; + + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/oauth2', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'provider' => $provider, + 'appId' => $appId, + 'secret' => $secret, + 'enabled' => true, + ]); + $this->assertEquals(200, $response['headers']['status-code']); + + /** Test oauth2 and get invalid `success` URL */ + $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'success' => 'https://example.com', + 'failure' => 'https://example.com' + ]); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString('Invalid `success` param: URL host must be one of: localhost, appwrite.io, *.appwrite.io', $response['body']); + + /** Test oauth2 with devKey and now get oauth2 is disabled */ + $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'success' => 'https://example.com', + 'failure' => 'https://example.com' + ]); + $this->assertEquals(200, $response['headers']['status-code']); + + /** Test hostname in Magic URL */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'userId' => ID::unique(), + 'email' => 'user@appwrite.io', + 'url' => 'https://example.com', + ]); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('Invalid `url` param: URL host must be one of: localhost, appwrite.io, *.appwrite.io', $response['body']['message']); + + /** Test hostname in Magic URL with devKey */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'userId' => ID::unique(), + 'email' => 'user@appwrite.io', + 'url' => 'https://example.com', + ]); + $this->assertEquals(201, $response['headers']['status-code']); + } + + /** + * @group devKeys + */ + public function testCorsWithDevKey(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testCorsWithDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $origin = 'http://example.com'; + + /** + * Test CORS without Dev Key (should fail due to origin) + */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'origin' => $origin, + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + + $this->assertEquals(403, $response['headers']['status-code']); + $this->assertNotEquals($origin, $response['headers']['access-control-allow-origin'] ?? null); + $this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin'] ?? null); + + + /** + * Test CORS with Dev Key (should bypass origin check) + */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'origin' => $origin, + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + $this->assertEquals('*', $response['headers']['access-control-allow-origin'] ?? null); + } + + /** + * @group devKeys + */ + public function testNoRateLimitWithDevKey(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testNoRateLimitWithDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + /** + * Test for SUCCESS + */ + for ($i = 0; $i < 10; $i++) { + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(401, $response['headers']['status-code']); + } + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(429, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(401, $response['headers']['status-code']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), -3600), + ]); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $response['body']['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(429, $response['headers']['status-code']); + + /** + * Test for FAILURE after expire + */ + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test Expire 5 seconds', + 'expire' => DateTime::addSeconds(new \DateTime(), 5) + ]); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(401, $response['headers']['status-code']); + + sleep(5); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(429, $response['headers']['status-code']); + } + + /** + * @group devKeys + */ + public function testUpdateProjectDevKey(): void + { + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testUpdateProjectDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test Update', + 'expire' => DateTime::addSeconds(new \DateTime(), 360), + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($devKey['$id'], $response['body']['$id']); + $this->assertEquals('Key Test Update', $response['body']['name']); + $this->assertArrayHasKey('accessedAt', $response['body']); + $this->assertEmpty($response['body']['accessedAt']); + + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($devKey['$id'], $response['body']['$id']); + $this->assertEquals('Key Test Update', $response['body']['name']); + $this->assertArrayHasKey('accessedAt', $response['body']); + $this->assertEmpty($response['body']['accessedAt']); + } + + /** + * @group devKeys + */ + public function testDeleteProjectDevKey(): void + { + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testDeleteProjectDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(204, $response['headers']['status-code']); + $this->assertEmpty($response['body']); + + /** + * Get rate limit trying to use the deleted key + */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(429, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(404, $response['headers']['status-code']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId . '/dev-keys/error', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(404, $response['headers']['status-code']); + } + + /** + * Devkeys Tests ends here ------------------------------------------------ + */ } diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 3fcd9c043d..1858fd50ad 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -337,18 +337,6 @@ trait TeamsBaseClient $this->assertEquals(400, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'email' => $email, - 'name' => $name, - 'roles' => ['developer'], - 'url' => 'http://example.com/join-us#title' // bad url - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - return [ 'teamUid' => $teamUid, 'teamName' => $teamName, diff --git a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php index 4b5ade7cbf..dec61d5258 100644 --- a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php +++ b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php @@ -14,6 +14,30 @@ class TeamsConsoleClientTest extends Scope use ProjectConsole; use SideClient; + /** + * @depends testCreateTeam + */ + public function testTeamCreateMembershipConsole($data): array + { + $teamUid = $data['teamUid'] ?? ''; + $email = uniqid() . 'friend@localhost.test'; + $name = 'Friend User'; + + $response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => $email, + 'name' => $name, + 'roles' => ['developer'], + 'url' => 'http://example.com/join-us#title' // bad url + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + return $data; + } + /** * @depends testCreateTeam */