Compare commits

...
Author SHA1 Message Date
Chirag Aggarwal e3a093c960 chore: update version to 66 2025-04-24 13:15:16 +00:00
Chirag Aggarwal 20c3663736 chore: update database library to 0.65.x 2025-04-24 09:05:16 +00:00
Matej BačoandGitHub 77739390dd Merge pull request #9664 from appwrite/chore-separate-benchmark-in-cicd
Chore separate benchmark in ci/cd
2025-04-22 15:43:44 +02:00
Matej Bačo 21f3221a21 Fix consistency with previous implementation 2025-04-22 12:21:10 +00:00
Matej Bačo 00f1723878 Fix merge conflict 2025-04-22 12:18:30 +00:00
Matej Bačo 3d16901ea1 Merge remote-tracking branch 'origin/1.7.x' into chore-separate-benchmark-in-cicd 2025-04-22 12:17:33 +00:00
Matej BačoandGitHub bd204ddca5 Merge pull request #9672 from appwrite/feat-add-favicon-to-error-pages
Feat add favicon to error pages
2025-04-22 13:57:47 +02:00
Khushboo VermaandGitHub 51af7e35bf Merge branch '1.7.x' into feat-add-favicon-to-error-pages 2025-04-22 16:53:03 +05:30
Khushboo Verma 9c2064a5ce dynamic urls 2025-04-22 16:52:29 +05:30
Matej BačoandGitHub 304f33991f Merge pull request #8997 from appwrite/feat-tokens-module
Feat: tokens
2025-04-22 13:16:07 +02:00
Khushboo Verma 8bd89441a4 Add png logo 2025-04-22 16:41:22 +05:30
Khushboo Verma 1401e20247 Merge branch '1.7.x' into feat-add-favicon-to-error-pages 2025-04-22 16:32:19 +05:30
Khushboo Verma bc2e17827c Merge branch '1.7.x' of github.com:appwrite/appwrite into 1.7.x 2025-04-22 16:32:08 +05:30
Matej BačoandGitHub d5cb9d0f1f Merge branch '1.7.x' into feat-tokens-module 2025-04-22 12:25:51 +02:00
Matej BačoandGitHub fd0d891d45 Merge pull request #8378 from appwrite/feat-development-keys
Development Keys
2025-04-22 12:22:12 +02:00
Matej Bačo cb7aad9885 Improve code rabbit 2025-04-22 11:08:19 +02:00
Matej Bačo 1a33b75d42 Fix html error page test 2025-04-22 10:48:25 +02:00
Khushboo Verma 1f51381e4b Merge branch '1.7.x' of github.com:appwrite/appwrite into 1.7.x 2025-04-22 12:46:33 +05:30
Chirag AggarwalandGitHub 1065f27a44 Merge branch '1.7.x' into feat-tokens-module 2025-04-21 19:28:27 +05:30
Chirag Aggarwal b0d03c5109 chore: update file security logic 2025-04-21 13:44:30 +00:00
Eldad A. FuxandGitHub 0bf2318cdd Merge pull request #9673 from appwrite/allow-compressed-csv
Allow compressed CSV
2025-04-21 12:03:09 +02:00
Darshan 53a0424326 update: tests. 2025-04-21 12:12:01 +05:30
Darshan 927489bc5b update: handle decryption as well. 2025-04-21 12:04:36 +05:30
Darshan 0accc494f0 update: tests. 2025-04-21 10:25:43 +05:30
Darshan 21be819025 fix: test message! 2025-04-21 10:12:09 +05:30
Darshan 873b384f70 update: allow compressed files. 2025-04-21 10:01:13 +05:30
Khushboo Verma cda521a5d9 Add favicon 2025-04-20 15:52:29 +05:30
Chirag Aggarwal 421bfed8b7 chore: regenerate specs 2025-04-18 16:06:02 +00:00
Chirag Aggarwal 6d0d15f469 Merge branch '1.7.x' into feat-tokens-module 2025-04-18 16:01:06 +00:00
Chirag Aggarwal d130e7d3bd chore: add stricter checks according to review 2025-04-18 15:32:25 +00:00
Khushboo Verma fe287044a7 Format benchmark.yml 2025-04-17 20:45:53 +05:30
Khushboo Verma 816c3a1401 Fix version 2025-04-17 20:31:08 +05:30
Khushboo Verma deac241e68 Create new file for benchmarking workflow 2025-04-17 20:25:11 +05:30
Matej Bačo f8343e01cd PR review suggestions 2025-04-17 16:13:09 +02:00
Matej Bačo b8ce3cb9fe Post-merge fixes 2025-04-17 15:57:47 +02:00
Matej Bačo 9993cca484 Merge branch '1.7.x' into feat-development-keys 2025-04-17 15:51:31 +02:00
Chirag Aggarwal ba22e5f457 chore: simplify action constructor 2025-04-17 07:38:29 +00:00
Chirag Aggarwal 04b6f62ac2 chore: review changes 2025-04-17 07:21:33 +00:00
Chirag Aggarwal 7172ccfc5b chore: specs + formatting 2025-04-17 06:25:42 +00:00
Chirag Aggarwal b2dffe4057 Merge branch '1.7.x' into feat-tokens-module 2025-04-17 06:19:18 +00:00
Chirag Aggarwal 76c7eb14eb chore: fix sdk fallback back to unknown 2025-04-15 12:08:18 +00:00
Chirag Aggarwal c365480aec chore: small changes from review devkeys 2025-04-15 12:02:55 +00:00
Chirag Aggarwal 40c15077e2 chore: update devkey init and make tests use mock oauth 2025-04-15 10:47:04 +00:00
Chirag Aggarwal 9bec94c976 fix: env 2025-04-14 18:02:30 +00:00
Chirag Aggarwal 6596569259 chore: make devkeys test dependence free 2025-04-14 18:01:40 +00:00
Chirag Aggarwal 74abf4897b chore: fix teamCreateMembership 2025-04-14 08:47:57 +00:00
Chirag Aggarwal 6372180537 chore: update specs 2025-04-14 08:22:42 +00:00
Chirag Aggarwal a1aabd351c fix: env 2025-04-14 07:34:52 +00:00
Chirag Aggarwal f31cb0cdfc chore: added tests for checking if sdks are updated in devkeys 2025-04-14 07:34:05 +00:00
Chirag Aggarwal 3b8354d2ea chore: add sdk to key 2025-04-14 03:55:04 +00:00
Chirag Aggarwal 0479f4cca7 chore: change abuse back to disabled 2025-04-10 07:29:24 +00:00
Chirag Aggarwal 45df0ef854 chore: add cors test 2025-04-09 16:24:24 +00:00
Chirag Aggarwal 013555cc8e chore: add tests for hostname check with devkeys 2025-04-09 14:21:56 +00:00
Chirag Aggarwal b9f50d9f01 chore: fix test 2025-04-09 13:21:06 +00:00
Chirag Aggarwal 3c78e7efff fix: query description 2025-04-09 13:15:59 +00:00
Chirag Aggarwal c6399a2a01 chore: update specs and tests 2025-04-09 13:11:48 +00:00
Chirag Aggarwal e8a0425cc3 chore: add allowed attributes to query 2025-04-09 13:03:56 +00:00
Chirag Aggarwal b4b356e61f chore: add queries and search to listing devkeys 2025-04-09 11:14:02 +00:00
Chirag Aggarwal 4a1bf2eb74 fix: permissions on create devKey 2025-04-09 09:44:30 +00:00
Chirag Aggarwal 84595e625d chore: use getDocument instead to fetch devKey 2025-04-09 09:31:05 +00:00
Chirag Aggarwal 7aecd3814a fix: minor mistakes in devkeys naming and initialization 2025-04-09 09:15:36 +00:00
Chirag Aggarwal 5a5d1752f9 Merge branch '1.7.x' into feat-development-keys 2025-04-09 09:02:20 +00:00
Chirag Aggarwal 7b7fd218f5 chore: formatting + updating specs 2025-03-24 05:06:18 +00:00
Darshan 26142f4f21 revert: local dockerfile changes! 🤦‍♂️ 2025-03-22 18:46:13 +05:30
Darshan a62c489a72 fix: missing import. 2025-03-22 18:38:12 +05:30
Darshan b358dad817 update: merge '1.7.x' on feat-development-keys. 2025-03-22 18:35:10 +05:30
Chirag AggarwalandGitHub 026dd56f55 Merge branch '1.7.x' into feat-development-keys 2025-03-15 19:18:44 +05:30
Chirag AggarwalandGitHub 9df221d333 Merge branch '1.6.x' into feat-development-keys 2025-03-14 19:42:31 +05:30
Chirag AggarwalandGitHub afabd5b87a Merge branch '1.6.x' into feat-development-keys 2025-03-14 17:20:47 +05:30
Chirag Aggarwal 3f133cab74 Merge branch '1.7.x' into feat-tokens-module 2025-03-06 15:25:42 +00:00
Chirag AggarwalandGitHub 3e9e0652b0 Merge branch '1.6.x' into feat-development-keys 2025-03-05 15:10:13 +05:30
Chirag AggarwalandGitHub 6bc72cf43f Merge branch '1.6.x' into feat-development-keys 2025-03-04 21:28:14 +05:30
Chirag Aggarwal c05ef9121f Merge branch '1.6.x' into feat-development-keys 2025-02-28 12:56:26 +00:00
Chirag Aggarwal f5172f8462 chore: regenerate specs 2025-02-25 12:30:21 +00:00
Chirag AggarwalandGitHub b72b118c07 Merge branch '1.6.x' into feat-development-keys 2025-02-24 13:53:24 +05:30
Chirag Aggarwal 431eea2787 Merge branch '1.6.x' into feat-development-keys 2025-02-21 09:24:04 +00:00
Chirag Aggarwal 46c4c7b28b chore: fix name 2025-02-13 19:36:01 +00:00
Chirag Aggarwal f67d32fd35 chore: updated devkeys test for shared mode 2025-02-13 19:32:33 +00:00
Chirag Aggarwal 392803b5e8 chore: fix merge conflicts 2025-02-13 19:24:00 +00:00
Chirag Aggarwal a0138455bc fix: queueForStats naming 2025-02-13 19:21:15 +00:00
Chirag Aggarwal e59d95a108 Merge branch '1.6.x' into feat-development-keys 2025-02-13 18:25:39 +00:00
Chirag Aggarwal 351afffdfe Merge branch '1.6.x' into feat-development-keys 2025-02-13 17:55:37 +00:00
Chirag Aggarwal d1502bc7d6 chore: update descriptions for token endpoints 2025-02-06 11:08:58 +00:00
Chirag Aggarwal 37dbbf226e chore: fix: descContents handling 2025-02-06 10:42:41 +00:00
Chirag AggarwalandGitHub 142c5731ca Merge branch '1.6.x' into feat-development-keys 2025-02-06 16:01:33 +05:30
Chirag Aggarwal 1d571e470b chore: update module endpoint descriptions 2025-02-06 10:30:00 +00:00
Chirag Aggarwal c269fc22ed chore: remove unnecessary injections 2025-02-06 10:04:03 +00:00
Chirag Aggarwal fd3da47e8a chore: added required perms in migrations 2025-02-06 09:55:32 +00:00
Chirag Aggarwal ba0a6f0097 chore: added requires scopes, fix call method 2025-02-05 14:14:03 +00:00
Chirag Aggarwal 97ed0dc89c chore: fix directory naming 2025-02-05 13:42:19 +00:00
Chirag Aggarwal 4f63c520ec chore: remove extra space 2025-02-05 13:36:34 +00:00
Chirag Aggarwal c7611ed382 chore: refactor file tokens with new project structuring 2025-02-05 13:28:22 +00:00
Chirag Aggarwal 24f11dc8d6 Merge branch '1.6.x' into feat-development-keys 2025-02-05 09:58:45 +00:00
Chirag Aggarwal 4aa3a51ef0 chore: refactor module structure 2025-02-05 09:52:43 +00:00
Chirag Aggarwal ca7cf765b2 chore: refactor tokens 2025-02-05 09:22:35 +00:00
Chirag Aggarwal 92b7b1956f Merge remote-tracking branch 'origin/1.7.x' into feat-tokens-module 2025-02-04 17:00:45 +00:00
Chirag Aggarwal a98f32b5e1 merge with 1.7.x 2025-02-04 16:56:14 +00:00
Chirag Aggarwal cea308ef71 chore: remove confusing comment 2025-02-04 11:55:05 +00:00
Chirag Aggarwal fccc6d5605 chore: added docs for devkeys 2025-02-04 11:45:41 +00:00
Chirag Aggarwal ebd6b1b013 chore: added missing import 2025-02-04 11:32:02 +00:00
Chirag Aggarwal c8afd2c613 Merge branch '1.6.x' into feat-development-keys 2025-02-04 11:21:07 +00:00
Chirag Aggarwal 9420340871 chore: changed directory structure for devkeys 2025-02-04 10:53:15 +00:00
Chirag Aggarwal b4849cd6ca chore: remove id 2025-01-30 14:18:28 +00:00
Chirag Aggarwal 1b91b601bc chore: remove cache rebuild 2025-01-30 14:18:08 +00:00
Chirag Aggarwal 2fa22b4b85 chore: workflow added rebuild on cache miss 2025-01-30 14:05:53 +00:00
Chirag Aggarwal 3522e63404 chore: fix tests 2025-01-30 12:36:28 +00:00
Chirag Aggarwal 67ba9a880d chore: fix tests 2025-01-30 12:25:56 +00:00
Chirag Aggarwal 751f7e3fad chore: shifted abuse project test to different job in ci 2025-01-30 12:11:09 +00:00
Chirag Aggarwal bd801712b9 chore: disabled options abuse env 2025-01-30 11:51:43 +00:00
Chirag Aggarwal 5a1ad4d5ae chore: added grouping of devkeys in ci 2025-01-30 11:48:43 +00:00
Chirag Aggarwal 23111d26c4 chore: filtered out customclienttests 2025-01-30 09:28:23 +00:00
Chirag Aggarwal f132c2b266 chore: changed ordering of tests 2025-01-29 05:52:03 +00:00
Chirag Aggarwal 1e71eb84c4 chore: added devkeys to account and database tests 2025-01-29 04:25:52 +00:00
Chirag Aggarwal 28e1ce9dc2 chore: fix recursive tests 2025-01-28 17:49:05 +00:00
Chirag Aggarwal c73f2afbe1 chore: fix run tests loop 2025-01-28 17:41:48 +00:00
Chirag Aggarwal c2aff2c2cb chore: fix fetching of test files 2025-01-28 17:16:35 +00:00
Chirag Aggarwal c01f67f44f chore: fix parsing of test files 2025-01-28 16:58:27 +00:00
Chirag Aggarwal af7f599525 chore: fix remove base 2025-01-28 16:22:32 +00:00
Chirag Aggarwal acadaf2307 chore: fix naming for tests 2025-01-28 16:13:07 +00:00
Chirag Aggarwal 7bd29501c2 chore: added test_suffixes to outputs 2025-01-28 16:07:41 +00:00
Chirag AggarwalandGitHub eecf3aae68 Merge branch '1.6.x' into feat-development-keys 2025-01-28 20:12:15 +05:30
Chirag Aggarwal 222516dbd4 chore: added base tests in e2e service 2025-01-28 14:41:58 +00:00
Chirag Aggarwal cad18dc7c7 chore: fix abstract implementations for scope tests 2025-01-28 14:29:45 +00:00
Chirag Aggarwal 9561ed99d2 chore: fix getHeaders implementation in scope tests 2025-01-28 14:18:00 +00:00
Chirag Aggarwal b399ff00a6 chore: update e2e tests to use abuse env 2025-01-28 14:06:56 +00:00
Chirag Aggarwal d5e775416d chore: added devkeys collections to new file, and formatting 2025-01-27 10:36:40 +00:00
Chirag Aggarwal f867fddaae Merge branch '1.6.x' into feat-development-keys 2025-01-27 10:33:58 +00:00
Damodar Lohani 195ae3fdcb update project desc 2024-12-15 11:06:46 +00:00
Damodar Lohani cd54ba1b83 fix account test 2024-12-15 10:49:35 +00:00
Damodar Lohani b8388a4092 fix expected in test 2024-12-15 10:41:01 +00:00
Damodar Lohani 05baa6090d fix cycle 2024-12-15 10:31:55 +00:00
Damodar Lohani 6f3c60d462 fix tests 2024-12-15 10:25:22 +00:00
Damodar Lohani 85656f81a5 update db name 2024-12-15 10:03:52 +00:00
Damodar Lohani ba83dd9252 fix dev key creation 2024-12-15 09:33:17 +00:00
Damodar Lohani 3ad865319c refactor: rename endpoint 2024-12-15 09:22:16 +00:00
Damodar Lohani 4ffdc9c103 fix existing test by adding dev key 2024-12-15 09:22:05 +00:00
Damodar Lohani b25772483b key that expires after 5 second 2024-12-15 08:57:44 +00:00
Damodar Lohani ae0e1eb302 fix lint 2024-12-15 08:54:13 +00:00
Damodar Lohani 172ef61663 remove devkey check on error 2024-12-15 08:54:10 +00:00
Damodar Lohani 44f70dad56 Merge remote-tracking branch 'origin/1.6.x' into feat-development-keys 2024-12-15 08:51:11 +00:00
Damodar Lohani dcacb5ee2c test fix 2024-12-15 08:45:09 +00:00
Damodar Lohani 5627082165 update 2024-12-08 09:45:40 +00:00
Damodar Lohani 30a079a6b9 fix missing key 2024-12-08 09:07:55 +00:00
Damodar LohaniandGitHub a7122ed535 Merge branch '1.6.x' into feat-development-keys 2024-12-08 14:32:08 +05:45
Damodar Lohani 82d64c8daf fix test 2024-12-08 08:45:03 +00:00
Damodar Lohani 98cf3d4b80 format 2024-12-05 06:19:25 +00:00
Damodar Lohani 7c82ff6491 fix naming 2024-12-05 06:17:17 +00:00
Damodar Lohani e21467d9f0 format 2024-12-05 05:40:52 +00:00
Damodar Lohani 0770748c0f validate file and bucket permission
- validate file update permission when creating file token
2024-12-05 05:40:45 +00:00
Damodar Lohani 285544d2d6 delete token test 2024-12-04 09:39:30 +00:00
Damodar Lohani 11bf99c871 restructure endpoints 2024-12-04 08:25:55 +00:00
Damodar Lohani f21281ebf5 format 2024-12-04 06:02:21 +00:00
Damodar Lohani cb1aff42c5 make 10 requests 2024-12-04 05:59:03 +00:00
Damodar Lohani e7943e8490 update test 2024-12-04 05:57:08 +00:00
Damodar Lohani 136cc5fe46 update put request 2024-12-04 05:45:43 +00:00
Damodar Lohani 01f579151d updated project model 2024-12-04 05:43:15 +00:00
Damodar Lohani ec14cc170f fixing response model 2024-12-04 05:40:09 +00:00
Damodar Lohani e536e6ea74 support dev key from param as well 2024-12-04 05:29:13 +00:00
Damodar Lohani fb0da77d2f add todo 2024-12-04 05:26:52 +00:00
Damodar Lohani 8a59a22c2e update suggested changes 2024-12-04 05:25:16 +00:00
b8917bc811 Update tests/e2e/Services/Projects/ProjectsDevKeys.php
Co-authored-by: Matej Bačo <matejbacocom@gmail.com>
2024-12-04 10:58:05 +05:45
8206cee449 Update tests/e2e/Services/Projects/ProjectsDevKeys.php
Co-authored-by: Matej Bačo <matejbacocom@gmail.com>
2024-12-04 10:57:49 +05:45
Damodar LohaniandGitHub 5cf4a46555 Merge branch '1.6.x' into feat-development-keys 2024-12-02 16:17:22 +05:45
Damodar Lohani 2c6e5d0bea remove old file 2024-11-28 01:43:21 +00:00
Damodar Lohani fe7851b97f Merge remote-tracking branch 'origin/1.6.x' into feat-development-keys 2024-11-28 01:37:25 +00:00
Damodar LohaniandGitHub 27905c2a90 Merge branch '1.6.x' into feat-development-keys 2024-11-25 12:36:10 +05:45
Damodar LohaniandGitHub d07f39c0d2 Merge branch '1.6.x' into feat-development-keys 2024-11-22 15:45:17 +05:45
Damodar Lohani 010ace3b57 update redirect validator 2024-11-22 06:56:36 +00:00
Damodar Lohani b3c2f8a024 disable CORS 2024-11-22 05:55:39 +00:00
Damodar Lohani cc3edd7b2b Refactor naming to Dev keys 2024-11-22 04:21:03 +00:00
Damodar Lohani d32dc0a4d9 tokens test and scopes 2024-11-18 08:52:16 +00:00
Damodar Lohani 0d1fec47c6 tokens test 2024-11-18 08:00:14 +00:00
Damodar Lohani a9da4b7b3b refactor token apis 2024-11-18 07:38:48 +00:00
Damodar Lohani 10dab10cc5 refactor module namings 2024-11-17 07:01:20 +00:00
Damodar Lohani 33aca37c1c Tokens module setup 2024-11-17 06:17:55 +00:00
Damodar Lohani c555a707e6 Merge remote-tracking branch 'origin/1.7.x' into feat-file-tokens 2024-11-17 06:00:03 +00:00
Damodar Lohani 179684e8b2 improve dependency 2024-11-14 04:48:46 +00:00
Damodar Lohani 24353a1077 Merge remote-tracking branch 'origin/1.6.x' into feat-development-keys 2024-11-14 04:29:36 +00:00
Damodar Lohani 8a244708fa Merge remote-tracking branch 'origin/1.6.x' into feat-development-keys 2024-10-29 01:23:32 +00:00
Damodar Lohani f2377ce5d2 remove submodule 2024-08-12 08:55:14 +00:00
Damodar Lohani 1771b3fed3 Merge remote-tracking branch 'origin/1.6.x' into feat-development-keys 2024-08-12 08:48:43 +00:00
Damodar Lohani 383cff5eb8 enable abuse to test development keys 2024-08-12 08:39:03 +00:00
Damodar Lohani 0a2eee0118 detailed error logs if development key is provided 2024-08-12 05:44:52 +00:00
Damodar Lohani 61c5848be4 refactor development keys 2024-08-12 05:34:25 +00:00
Damodar LohaniandGitHub d7b19a8b52 Merge branch '1.6.x' into feat-development-keys 2024-08-06 08:23:51 +05:45
Damodar Lohani d9556ba603 make expiry date required 2024-07-29 10:18:06 +00:00
Damodar Lohani 55fc9d7fbd tests and fixes 2024-07-29 10:12:57 +00:00
Damodar Lohani ca0c38b3c0 subquery for development keys 2024-07-29 10:12:50 +00:00
Damodar Lohani fe26c82dc1 Merge remote-tracking branch 'origin/1.6.x' into feat-development-keys 2024-07-29 09:32:58 +00:00
Damodar Lohani 681e382459 Merge remote-tracking branch 'origin/1.6.x' into feat-development-keys 2024-07-16 08:34:38 +00:00
Damodar Lohani ae5a60db89 composer update 2024-07-16 08:33:38 +00:00
Damodar Lohani daf208fe35 Merge remote-tracking branch 'origin/main' into feat-development-keys 2024-07-16 08:32:53 +00:00
Damodar Lohani 760c552ffb support for api test keys 2024-07-16 08:28:22 +00:00
Damodar Lohani 03000c3ad0 add collection for development keys 2024-07-16 07:50:42 +00:00
Damodar Lohani af3dc25e2c add new endpoints to module 2024-07-16 07:48:21 +00:00
Damodar Lohani c058d30d0a all endpoints for development keys 2024-07-16 05:20:56 +00:00
Damodar Lohani bcde2ea223 remove duplicates 2024-07-10 07:32:57 +00:00
Damodar Lohani c21402733f development keys module setup 2024-07-10 07:16:45 +00:00
Damodar Lohani 0dbc37dacf renames and fixes 2024-06-30 08:53:21 +00:00
Damodar Lohani 8f667671ad Merge remote-tracking branch 'origin/main' into feat-file-tokens 2024-06-30 08:45:24 +00:00
Damodar Lohani 61a8457232 format 2024-06-30 08:42:05 +00:00
Damodar Lohani c20504fc7d missing import 2024-06-30 08:40:35 +00:00
Damodar LohaniandGitHub 74a293d425 Merge branch 'main' into feat-file-tokens 2024-06-20 15:43:38 +05:45
Damodar Lohani 52d27179a9 fix jwt response 2024-03-17 03:01:55 +00:00
Damodar Lohani c6d524c25d fix formatting 2024-03-17 02:08:22 +00:00
Damodar Lohani 8d67498e52 Merge remote-tracking branch 'origin/main' into feat-file-tokens 2024-03-17 01:59:07 +00:00
Damodar Lohani 852137b84d update token test 2024-03-17 01:53:08 +00:00
Damodar Lohani b752d29854 basic test and fix response model 2024-01-04 07:27:06 +00:00
Damodar Lohani fa40a33631 fixes and refactor 2024-01-04 06:53:22 +00:00
Damodar Lohani 1065abb0a7 update preview to use token 2024-01-04 04:57:10 +00:00
Damodar Lohani 56087b691b verify and inject resource token 2024-01-04 03:03:07 +00:00
Damodar Lohani c1c98e4ac8 improve API to new data structure and supprot JWT 2024-01-04 02:46:52 +00:00
Damodar Lohani fa09a69a7f Merge remote-tracking branch 'origin/main' into feat-file-tokens 2024-01-04 02:23:50 +00:00
Damodar Lohani a28312be46 refactor collections 2024-01-04 02:23:08 +00:00
Damodar Lohani eb1562f56b rename param to expiry for consistency 2023-12-28 10:48:42 +00:00
Damodar Lohani 324f65c838 token response model 2023-12-27 02:21:53 +00:00
Damodar Lohani b23369c548 improve token endpoints 2023-12-27 02:15:59 +00:00
Damodar Lohani 23d0aae23b improve get file token 2023-12-27 00:03:16 +00:00
Damodar Lohani 92c2e26f3a improve list file tokens 2023-12-26 23:59:41 +00:00
Damodar Lohani 9ccdabb8e9 improve collection config 2023-12-26 23:59:31 +00:00
Damodar Lohani 2e39fbe70f improve file token create 2023-12-26 23:52:20 +00:00
Damodar Lohani bfa941d69c WIP: file tokens endpoint 2023-12-25 07:09:35 +00:00
Damodar Lohani 07ca18caba file tokens collection 2023-12-25 05:28:32 +00:00
79 changed files with 12115 additions and 1397 deletions
+6 -2
View File
@@ -1,7 +1,11 @@
reviews:
path_filters:
- "!app/config/specs/**"
- "!docs/examples/**"
- "!docs/references/**"
- "!docs/sdks/**"
auto_review:
base_branches:
- main
- 1.6.x
- 1.7.x
- feat-sites # temporary until merged to 1.7.x
- 1.7.x
+120
View File
@@ -0,0 +1,120 @@
name: Benchmark
concurrency:
group: '${{ github.workflow }}-${{ github.ref }}'
cancel-in-progress: true
env:
IMAGE: appwrite-dev
CACHE_KEY: 'appwrite-dev-${{ github.event.pull_request.head.sha }}'
'on':
- pull_request
jobs:
setup:
name: Setup & Build Appwrite Image
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Appwrite
uses: docker/build-push-action@v6
with:
context: .
push: false
tags: '${{ env.IMAGE }}'
load: true
cache-from: type=gha
cache-to: 'type=gha,mode=max'
outputs: 'type=docker,dest=/tmp/${{ env.IMAGE }}.tar'
build-args: |
DEBUG=false
TESTING=true
VERSION=dev
- name: Cache Docker Image
uses: actions/cache@v4
with:
key: '${{ env.CACHE_KEY }}'
path: '/tmp/${{ env.IMAGE }}.tar'
benchmarking:
name: Benchmark
runs-on: ubuntu-latest
needs: setup
permissions:
pull-requests: write
steps:
- name: Checkout repository
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: |
sed -i 's/traefik/localhost/g' .env
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 10
- name: Install Oha
run: |
echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list
sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg
sudo apt update
sudo apt install oha
- name: Benchmark PR
run: 'oha -z 180s http://localhost/v1/health/version -j > benchmark.json'
- name: Cleaning
run: docker compose down -v
- name: Installing latest version
run: |
rm docker-compose.yml
rm .env
curl https://appwrite.io/install/compose -o docker-compose.yml
curl https://appwrite.io/install/env -o .env
sed -i 's/_APP_OPTIONS_ABUSE=enabled/_APP_OPTIONS_ABUSE=disabled/g' .env
docker compose up -d
sleep 10
- name: Benchmark Latest
run: oha -z 180s http://localhost/v1/health/version -j > benchmark-latest.json
- name: Prepare comment
run: |
echo '## :sparkles: Benchmark results' > benchmark.txt
echo ' ' >> benchmark.txt
echo "- Requests per second: $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
echo "- Requests with 200 status code: $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
echo "- P99 latency: $(jq -r '.latencyPercentiles.p99' benchmark.json )" >> benchmark.txt
echo " " >> benchmark.txt
echo " " >> benchmark.txt
echo "## :zap: Benchmark Comparison" >> benchmark.txt
echo " " >> benchmark.txt
echo "| Metric | This PR | Latest version | " >> benchmark.txt
echo "| --- | --- | --- | " >> benchmark.txt
echo "| RPS | $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.summary.requestsPerSec|tonumber|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt
- name: Save results
uses: actions/upload-artifact@v4
if: '${{ !cancelled() }}'
with:
name: benchmark.json
path: benchmark.json
retention-days: 7
- name: Find Comment
if: github.event.pull_request.head.repo.full_name == github.repository
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: '${{ github.event.pull_request.number }}'
comment-author: 'github-actions[bot]'
body-includes: Benchmark results
- name: Comment on PR
if: github.event.pull_request.head.repo.full_name == github.repository
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: '${{ steps.fc.outputs.comment-id }}'
issue-number: '${{ github.event.pull_request.number }}'
body-path: benchmark.txt
edit-mode: replace
+68 -67
View File
@@ -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
@@ -228,7 +228,8 @@ jobs:
Webhooks,
VCS,
Messaging,
Migrations
Migrations,
Tokens
]
tables-mode: [
'Shared V1',
@@ -275,86 +276,86 @@ 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
benchmarking:
name: Benchmark
e2e_dev_keys:
name: E2E Service Test (Dev Keys)
runs-on: ubuntu-latest
needs: setup
permissions:
pull-requests: write
strategy:
fail-fast: false
steps:
- name: Checkout repository
- 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: |
sed -i 's/traefik/localhost/g' .env
docker load --input /tmp/${{ env.IMAGE }}.tar
docker load --input /tmp/${{ env.IMAGE }}.tar
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
docker compose up -d
sleep 10
- name: Install Oha
sleep 30
- name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode
run: |
echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list
sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg
sudo apt update
sudo apt install oha
- name: Benchmark PR
run: oha -z 180s http://localhost/v1/health/version -j > benchmark.json
- name: Cleaning
run: docker compose down -v
- name: Installing latest version
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: |
rm docker-compose.yml
rm .env
curl https://appwrite.io/install/compose -o docker-compose.yml
curl https://appwrite.io/install/env -o .env
sed -i 's/_APP_OPTIONS_ABUSE=enabled/_APP_OPTIONS_ABUSE=disabled/g' .env
docker load --input /tmp/${{ env.IMAGE }}.tar
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
docker compose up -d
sleep 10
- name: Benchmark Latest
run: oha -z 180s http://localhost/v1/health/version -j > benchmark-latest.json
- name: Prepare comment
sleep 30
- name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode
run: |
echo '## :sparkles: Benchmark results' > benchmark.txt
echo ' ' >> benchmark.txt
echo "- Requests per second: $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
echo "- Requests with 200 status code: $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
echo "- P99 latency: $(jq -r '.latencyPercentiles.p99' benchmark.json )" >> benchmark.txt
echo " " >> benchmark.txt
echo " " >> benchmark.txt
echo "## :zap: Benchmark Comparison" >> benchmark.txt
echo " " >> benchmark.txt
echo "| Metric | This PR | Latest version | " >> benchmark.txt
echo "| --- | --- | --- | " >> benchmark.txt
echo "| RPS | $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.summary.requestsPerSec|tonumber|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt
- name: Save results
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: benchmark.json
path: benchmark.json
retention-days: 7
- name: Find Comment
if: github.event.pull_request.head.repo.full_name == github.repository
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Benchmark results
- name: Comment on PR
if: github.event.pull_request.head.repo.full_name == github.repository
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body-path: benchmark.txt
edit-mode: replace
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
+130
View File
@@ -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'),
+73
View File
@@ -2402,4 +2402,77 @@ return [
]
],
],
'resourceTokens' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('resourceTokens'),
'name' => 'Resource Tokens',
'attributes' => [
[
'$id' => ID::custom('resourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 100,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('secret'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 512,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => ['encrypt'],
],
[
'$id' => ID::custom('expire'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 255,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
]
],
'indexes' => [
[
'$id' => '_key_expiry_date',
'type' => Database::INDEX_KEY,
'attributes' => ['expire'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
],
];
+12
View File
@@ -488,6 +488,18 @@ return [
'code' => 403,
],
/** Tokens */
Exception::TOKEN_NOT_FOUND => [
'name' => Exception::TOKEN_NOT_FOUND,
'description' => 'The requested file token could not be found.',
'code' => 404,
],
Exception::TOKEN_EXPIRED => [
'name' => Exception::TOKEN_EXPIRED,
'description' => 'The requested file token has expired.',
'code' => 401,
],
/** VCS */
Exception::INSTALLATION_NOT_FOUND => [
'name' => Exception::INSTALLATION_NOT_FOUND,
+2
View File
@@ -81,6 +81,8 @@ $admins = [
'topics.read',
'subscribers.write',
'subscribers.read',
'tokens.read',
'tokens.write',
];
return [
+6
View File
@@ -142,4 +142,10 @@ return [ // List of publicly visible scopes
'assistant.read' => [
'description' => 'Access to read the Assistant service',
],
'tokens.read' => [
'description' => 'Access to read your project\'s tokens',
],
'tokens.write' => [
'description' => 'Access to create, update, and delete your project\'s tokens',
],
];
+521 -8
View File
@@ -4748,7 +4748,7 @@
"tags": [
"functions"
],
"description": "",
"description": "Get a list of all the current user function execution logs. You can use the query params to filter your results.",
"responses": {
"200": {
"description": "Executions List",
@@ -4763,7 +4763,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 383,
"weight": 384,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4822,7 +4822,7 @@
"tags": [
"functions"
],
"description": "",
"description": "Trigger a function execution. The returned object will return you the current execution status. You can ping the `Get Execution` endpoint to get updates on the current execution status. Once this endpoint is called, your function execution process will start asynchronously.",
"responses": {
"201": {
"description": "Execution",
@@ -4837,7 +4837,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 381,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4936,7 +4936,7 @@
"tags": [
"functions"
],
"description": "",
"description": "Get a function execution log by its unique ID.",
"responses": {
"200": {
"description": "Execution",
@@ -4951,7 +4951,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 382,
"weight": 383,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5534,7 +5534,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 348,
"weight": 349,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5616,7 +5616,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 352,
"weight": 353,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7448,6 +7448,451 @@
}
}
}
},
"\/tokens\/buckets\/{bucketId}\/files\/{fileId}": {
"get": {
"summary": "List tokens",
"operationId": "tokensList",
"tags": [
"tokens"
],
"description": "List all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"responses": {
"200": {
"description": "Resource Tokens List",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceTokenList"
}
}
}
}
},
"x-appwrite": {
"method": "list",
"weight": 432,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/list.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterList all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"schema": {
"type": "string",
"x-example": "<BUCKET_ID>"
},
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<FILE_ID>"
},
"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: expire",
"required": false,
"schema": {
"type": "string",
"default": []
},
"in": "query"
}
]
},
"post": {
"summary": "Create file token",
"operationId": "tokensCreateFileToken",
"tags": [
"tokens"
],
"description": "Create a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"responses": {
"201": {
"description": "ResourceToken",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceToken"
}
}
}
}
},
"x-appwrite": {
"method": "createFileToken",
"weight": 429,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/create-file-token.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"schema": {
"type": "string",
"x-example": "<BUCKET_ID>"
},
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<FILE_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "Token expiry date",
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
},
"\/tokens\/{tokenId}": {
"get": {
"summary": "Get token",
"operationId": "tokensGet",
"tags": [
"tokens"
],
"description": "Get a token by its unique ID.",
"responses": {
"200": {
"description": "ResourceToken",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceToken"
}
}
}
}
},
"x-appwrite": {
"method": "get",
"weight": 430,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a token by its unique ID.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
]
},
"patch": {
"summary": "Update token",
"operationId": "tokensUpdate",
"tags": [
"tokens"
],
"description": "Update a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"responses": {
"200": {
"description": "ResourceToken",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceToken"
}
}
}
}
},
"x-appwrite": {
"method": "update",
"weight": 433,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/update.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterUpdate a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token unique ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "File token expiry date",
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"delete": {
"summary": "Delete token",
"operationId": "tokensDelete",
"tags": [
"tokens"
],
"description": "Delete a token by its unique ID.",
"responses": {
"204": {
"description": "No content"
}
},
"x-appwrite": {
"method": "delete",
"weight": 434,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/delete.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterDelete a token by its unique ID.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
]
}
},
"\/tokens\/{tokenId}\/jwt": {
"get": {
"summary": "Get token as JWT",
"operationId": "tokensGetJWT",
"tags": [
"tokens"
],
"description": "Get a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"responses": {
"200": {
"description": "JWT",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/jwt"
}
}
}
}
},
"x-appwrite": {
"method": "getJWT",
"weight": 431,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get-j-w-t.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "File token ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
]
}
}
},
"tags": [
@@ -7698,6 +8143,30 @@
"files"
]
},
"resourceTokenList": {
"description": "Resource Tokens List",
"type": "object",
"properties": {
"total": {
"type": "integer",
"description": "Total number of tokens documents that matched your query.",
"x-example": 5,
"format": "int32"
},
"tokens": {
"type": "array",
"description": "List of tokens.",
"items": {
"$ref": "#\/components\/schemas\/resourceToken"
},
"x-example": ""
}
},
"required": [
"total",
"tokens"
]
},
"teamList": {
"description": "Teams List",
"type": "object",
@@ -8894,6 +9363,50 @@
"chunksUploaded"
]
},
"resourceToken": {
"description": "ResourceToken",
"type": "object",
"properties": {
"$id": {
"type": "string",
"description": "Token ID.",
"x-example": "bb8ea5c16897e"
},
"$createdAt": {
"type": "string",
"description": "Token creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"resourceId": {
"type": "string",
"description": "Resource ID.",
"x-example": "5e5ea5c168bb8:5e5ea5c168bb8"
},
"resourceInternalId": {
"type": "string",
"description": "File ID.",
"x-example": "1:1"
},
"resourceType": {
"type": "string",
"description": "Resource type.",
"x-example": "file"
},
"expire": {
"type": "string",
"description": "Token expiration date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
"$id",
"$createdAt",
"resourceId",
"resourceInternalId",
"resourceType",
"expire"
]
},
"team": {
"description": "Team",
"type": "object",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+516 -3
View File
@@ -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,
@@ -7448,6 +7448,451 @@
}
}
}
},
"\/tokens\/buckets\/{bucketId}\/files\/{fileId}": {
"get": {
"summary": "List tokens",
"operationId": "tokensList",
"tags": [
"tokens"
],
"description": "List all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"responses": {
"200": {
"description": "Resource Tokens List",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceTokenList"
}
}
}
}
},
"x-appwrite": {
"method": "list",
"weight": 432,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/list.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterList all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"schema": {
"type": "string",
"x-example": "<BUCKET_ID>"
},
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<FILE_ID>"
},
"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: expire",
"required": false,
"schema": {
"type": "string",
"default": []
},
"in": "query"
}
]
},
"post": {
"summary": "Create file token",
"operationId": "tokensCreateFileToken",
"tags": [
"tokens"
],
"description": "Create a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"responses": {
"201": {
"description": "ResourceToken",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceToken"
}
}
}
}
},
"x-appwrite": {
"method": "createFileToken",
"weight": 429,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/create-file-token.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"schema": {
"type": "string",
"x-example": "<BUCKET_ID>"
},
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<FILE_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "Token expiry date",
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
},
"\/tokens\/{tokenId}": {
"get": {
"summary": "Get token",
"operationId": "tokensGet",
"tags": [
"tokens"
],
"description": "Get a token by its unique ID.",
"responses": {
"200": {
"description": "ResourceToken",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceToken"
}
}
}
}
},
"x-appwrite": {
"method": "get",
"weight": 430,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a token by its unique ID.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
]
},
"patch": {
"summary": "Update token",
"operationId": "tokensUpdate",
"tags": [
"tokens"
],
"description": "Update a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"responses": {
"200": {
"description": "ResourceToken",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceToken"
}
}
}
}
},
"x-appwrite": {
"method": "update",
"weight": 433,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/update.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterUpdate a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token unique ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "File token expiry date",
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"delete": {
"summary": "Delete token",
"operationId": "tokensDelete",
"tags": [
"tokens"
],
"description": "Delete a token by its unique ID.",
"responses": {
"204": {
"description": "No content"
}
},
"x-appwrite": {
"method": "delete",
"weight": 434,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/delete.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterDelete a token by its unique ID.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
]
}
},
"\/tokens\/{tokenId}\/jwt": {
"get": {
"summary": "Get token as JWT",
"operationId": "tokensGetJWT",
"tags": [
"tokens"
],
"description": "Get a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"responses": {
"200": {
"description": "JWT",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/jwt"
}
}
}
}
},
"x-appwrite": {
"method": "getJWT",
"weight": 431,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get-j-w-t.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "File token ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
]
}
}
},
"tags": [
@@ -7698,6 +8143,30 @@
"files"
]
},
"resourceTokenList": {
"description": "Resource Tokens List",
"type": "object",
"properties": {
"total": {
"type": "integer",
"description": "Total number of tokens documents that matched your query.",
"x-example": 5,
"format": "int32"
},
"tokens": {
"type": "array",
"description": "List of tokens.",
"items": {
"$ref": "#\/components\/schemas\/resourceToken"
},
"x-example": ""
}
},
"required": [
"total",
"tokens"
]
},
"teamList": {
"description": "Teams List",
"type": "object",
@@ -8894,6 +9363,50 @@
"chunksUploaded"
]
},
"resourceToken": {
"description": "ResourceToken",
"type": "object",
"properties": {
"$id": {
"type": "string",
"description": "Token ID.",
"x-example": "bb8ea5c16897e"
},
"$createdAt": {
"type": "string",
"description": "Token creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"resourceId": {
"type": "string",
"description": "Resource ID.",
"x-example": "5e5ea5c168bb8:5e5ea5c168bb8"
},
"resourceInternalId": {
"type": "string",
"description": "File ID.",
"x-example": "1:1"
},
"resourceType": {
"type": "string",
"description": "Resource type.",
"x-example": "file"
},
"expire": {
"type": "string",
"description": "Token expiration date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
"$id",
"$createdAt",
"resourceId",
"resourceInternalId",
"resourceType",
"expire"
]
},
"team": {
"description": "Team",
"type": "object",
File diff suppressed because it is too large Load Diff
+576 -51
View File
@@ -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,
@@ -21127,6 +21127,463 @@
}
}
},
"\/tokens\/buckets\/{bucketId}\/files\/{fileId}": {
"get": {
"summary": "List tokens",
"operationId": "tokensList",
"tags": [
"tokens"
],
"description": "List all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"responses": {
"200": {
"description": "Resource Tokens List",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceTokenList"
}
}
}
}
},
"x-appwrite": {
"method": "list",
"weight": 432,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/list.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterList all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"schema": {
"type": "string",
"x-example": "<BUCKET_ID>"
},
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<FILE_ID>"
},
"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: expire",
"required": false,
"schema": {
"type": "string",
"default": []
},
"in": "query"
}
]
},
"post": {
"summary": "Create file token",
"operationId": "tokensCreateFileToken",
"tags": [
"tokens"
],
"description": "Create a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"responses": {
"201": {
"description": "ResourceToken",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceToken"
}
}
}
}
},
"x-appwrite": {
"method": "createFileToken",
"weight": 429,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/create-file-token.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"schema": {
"type": "string",
"x-example": "<BUCKET_ID>"
},
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<FILE_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "Token expiry date",
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
},
"\/tokens\/{tokenId}": {
"get": {
"summary": "Get token",
"operationId": "tokensGet",
"tags": [
"tokens"
],
"description": "Get a token by its unique ID.",
"responses": {
"200": {
"description": "ResourceToken",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceToken"
}
}
}
}
},
"x-appwrite": {
"method": "get",
"weight": 430,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a token by its unique ID.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
]
},
"patch": {
"summary": "Update token",
"operationId": "tokensUpdate",
"tags": [
"tokens"
],
"description": "Update a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"responses": {
"200": {
"description": "ResourceToken",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/resourceToken"
}
}
}
}
},
"x-appwrite": {
"method": "update",
"weight": 433,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/update.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterUpdate a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token unique ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "File token expiry date",
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"delete": {
"summary": "Delete token",
"operationId": "tokensDelete",
"tags": [
"tokens"
],
"description": "Delete a token by its unique ID.",
"responses": {
"204": {
"description": "No content"
}
},
"x-appwrite": {
"method": "delete",
"weight": 434,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/delete.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterDelete a token by its unique ID.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
]
}
},
"\/tokens\/{tokenId}\/jwt": {
"get": {
"summary": "Get token as JWT",
"operationId": "tokensGetJWT",
"tags": [
"tokens"
],
"description": "Get a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"responses": {
"200": {
"description": "JWT",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/jwt"
}
}
}
}
},
"x-appwrite": {
"method": "getJWT",
"weight": 431,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get-j-w-t.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "File token ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<TOKEN_ID>"
},
"in": "path"
}
]
}
},
"\/users": {
"get": {
"summary": "List users",
@@ -24706,6 +25163,30 @@
"buckets"
]
},
"resourceTokenList": {
"description": "Resource Tokens List",
"type": "object",
"properties": {
"total": {
"type": "integer",
"description": "Total number of tokens documents that matched your query.",
"x-example": 5,
"format": "int32"
},
"tokens": {
"type": "array",
"description": "List of tokens.",
"items": {
"$ref": "#\/components\/schemas\/resourceToken"
},
"x-example": ""
}
},
"required": [
"total",
"tokens"
]
},
"teamList": {
"description": "Teams List",
"type": "object",
@@ -27273,6 +27754,50 @@
"antivirus"
]
},
"resourceToken": {
"description": "ResourceToken",
"type": "object",
"properties": {
"$id": {
"type": "string",
"description": "Token ID.",
"x-example": "bb8ea5c16897e"
},
"$createdAt": {
"type": "string",
"description": "Token creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"resourceId": {
"type": "string",
"description": "Resource ID.",
"x-example": "5e5ea5c168bb8:5e5ea5c168bb8"
},
"resourceInternalId": {
"type": "string",
"description": "File ID.",
"x-example": "1:1"
},
"resourceType": {
"type": "string",
"description": "Resource type.",
"x-example": "file"
},
"expire": {
"type": "string",
"description": "Token expiration date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
"$id",
"$createdAt",
"resourceId",
"resourceInternalId",
"resourceType",
"expire"
]
},
"team": {
"description": "Team",
"type": "object",
+522 -8
View File
@@ -4918,7 +4918,7 @@
"tags": [
"functions"
],
"description": "",
"description": "Get a list of all the current user function execution logs. You can use the query params to filter your results.",
"responses": {
"200": {
"description": "Executions List",
@@ -4929,7 +4929,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 383,
"weight": 384,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4991,7 +4991,7 @@
"tags": [
"functions"
],
"description": "",
"description": "Trigger a function execution. The returned object will return you the current execution status. You can ping the `Get Execution` endpoint to get updates on the current execution status. Once this endpoint is called, your function execution process will start asynchronously.",
"responses": {
"201": {
"description": "Execution",
@@ -5002,7 +5002,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 381,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5109,7 +5109,7 @@
"tags": [
"functions"
],
"description": "",
"description": "Get a function execution log by its unique ID.",
"responses": {
"200": {
"description": "Execution",
@@ -5120,7 +5120,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 382,
"weight": 383,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5761,7 +5761,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 348,
"weight": 349,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5845,7 +5845,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 352,
"weight": 353,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7653,6 +7653,451 @@
}
]
}
},
"\/tokens\/buckets\/{bucketId}\/files\/{fileId}": {
"get": {
"summary": "List tokens",
"operationId": "tokensList",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "List all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"responses": {
"200": {
"description": "Resource Tokens List",
"schema": {
"$ref": "#\/definitions\/resourceTokenList"
}
}
},
"x-appwrite": {
"method": "list",
"weight": 432,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/list.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterList all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"type": "string",
"x-example": "<BUCKET_ID>",
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"type": "string",
"x-example": "<FILE_ID>",
"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: expire",
"required": false,
"type": "array",
"collectionFormat": "multi",
"items": {
"type": "string"
},
"default": [],
"in": "query"
}
]
},
"post": {
"summary": "Create file token",
"operationId": "tokensCreateFileToken",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Create a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"responses": {
"201": {
"description": "ResourceToken",
"schema": {
"$ref": "#\/definitions\/resourceToken"
}
}
},
"x-appwrite": {
"method": "createFileToken",
"weight": 429,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/create-file-token.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"type": "string",
"x-example": "<BUCKET_ID>",
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"type": "string",
"x-example": "<FILE_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "Token expiry date",
"default": null,
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"default": [],
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
]
}
},
"\/tokens\/{tokenId}": {
"get": {
"summary": "Get token",
"operationId": "tokensGet",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Get a token by its unique ID.",
"responses": {
"200": {
"description": "ResourceToken",
"schema": {
"$ref": "#\/definitions\/resourceToken"
}
}
},
"x-appwrite": {
"method": "get",
"weight": 430,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a token by its unique ID.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
}
]
},
"patch": {
"summary": "Update token",
"operationId": "tokensUpdate",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Update a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"responses": {
"200": {
"description": "ResourceToken",
"schema": {
"$ref": "#\/definitions\/resourceToken"
}
}
},
"x-appwrite": {
"method": "update",
"weight": 433,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/update.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterUpdate a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token unique ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "File token expiry date",
"default": null,
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"default": null,
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
]
},
"delete": {
"summary": "Delete token",
"operationId": "tokensDelete",
"consumes": [
"application\/json"
],
"produces": [],
"tags": [
"tokens"
],
"description": "Delete a token by its unique ID.",
"responses": {
"204": {
"description": "No content"
}
},
"x-appwrite": {
"method": "delete",
"weight": 434,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/delete.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterDelete a token by its unique ID.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
}
]
}
},
"\/tokens\/{tokenId}\/jwt": {
"get": {
"summary": "Get token as JWT",
"operationId": "tokensGetJWT",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Get a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"responses": {
"200": {
"description": "JWT",
"schema": {
"$ref": "#\/definitions\/jwt"
}
}
},
"x-appwrite": {
"method": "getJWT",
"weight": 431,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get-j-w-t.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "File token ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
}
]
}
}
},
"tags": [
@@ -7875,6 +8320,31 @@
"files"
]
},
"resourceTokenList": {
"description": "Resource Tokens List",
"type": "object",
"properties": {
"total": {
"type": "integer",
"description": "Total number of tokens documents that matched your query.",
"x-example": 5,
"format": "int32"
},
"tokens": {
"type": "array",
"description": "List of tokens.",
"items": {
"type": "object",
"$ref": "#\/definitions\/resourceToken"
},
"x-example": ""
}
},
"required": [
"total",
"tokens"
]
},
"teamList": {
"description": "Teams List",
"type": "object",
@@ -9082,6 +9552,50 @@
"chunksUploaded"
]
},
"resourceToken": {
"description": "ResourceToken",
"type": "object",
"properties": {
"$id": {
"type": "string",
"description": "Token ID.",
"x-example": "bb8ea5c16897e"
},
"$createdAt": {
"type": "string",
"description": "Token creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"resourceId": {
"type": "string",
"description": "Resource ID.",
"x-example": "5e5ea5c168bb8:5e5ea5c168bb8"
},
"resourceInternalId": {
"type": "string",
"description": "File ID.",
"x-example": "1:1"
},
"resourceType": {
"type": "string",
"description": "Resource type.",
"x-example": "file"
},
"expire": {
"type": "string",
"description": "Token expiration date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
"$id",
"$createdAt",
"resourceId",
"resourceInternalId",
"resourceType",
"expire"
]
},
"team": {
"description": "Team",
"type": "object",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+517 -3
View File
@@ -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,
@@ -7653,6 +7653,451 @@
}
]
}
},
"\/tokens\/buckets\/{bucketId}\/files\/{fileId}": {
"get": {
"summary": "List tokens",
"operationId": "tokensList",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "List all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"responses": {
"200": {
"description": "Resource Tokens List",
"schema": {
"$ref": "#\/definitions\/resourceTokenList"
}
}
},
"x-appwrite": {
"method": "list",
"weight": 432,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/list.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterList all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"type": "string",
"x-example": "<BUCKET_ID>",
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"type": "string",
"x-example": "<FILE_ID>",
"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: expire",
"required": false,
"type": "array",
"collectionFormat": "multi",
"items": {
"type": "string"
},
"default": [],
"in": "query"
}
]
},
"post": {
"summary": "Create file token",
"operationId": "tokensCreateFileToken",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Create a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"responses": {
"201": {
"description": "ResourceToken",
"schema": {
"$ref": "#\/definitions\/resourceToken"
}
}
},
"x-appwrite": {
"method": "createFileToken",
"weight": 429,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/create-file-token.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"type": "string",
"x-example": "<BUCKET_ID>",
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"type": "string",
"x-example": "<FILE_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "Token expiry date",
"default": null,
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"default": [],
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
]
}
},
"\/tokens\/{tokenId}": {
"get": {
"summary": "Get token",
"operationId": "tokensGet",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Get a token by its unique ID.",
"responses": {
"200": {
"description": "ResourceToken",
"schema": {
"$ref": "#\/definitions\/resourceToken"
}
}
},
"x-appwrite": {
"method": "get",
"weight": 430,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a token by its unique ID.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
}
]
},
"patch": {
"summary": "Update token",
"operationId": "tokensUpdate",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Update a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"responses": {
"200": {
"description": "ResourceToken",
"schema": {
"$ref": "#\/definitions\/resourceToken"
}
}
},
"x-appwrite": {
"method": "update",
"weight": 433,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/update.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterUpdate a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token unique ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "File token expiry date",
"default": null,
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"default": null,
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
]
},
"delete": {
"summary": "Delete token",
"operationId": "tokensDelete",
"consumes": [
"application\/json"
],
"produces": [],
"tags": [
"tokens"
],
"description": "Delete a token by its unique ID.",
"responses": {
"204": {
"description": "No content"
}
},
"x-appwrite": {
"method": "delete",
"weight": 434,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/delete.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterDelete a token by its unique ID.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
}
]
}
},
"\/tokens\/{tokenId}\/jwt": {
"get": {
"summary": "Get token as JWT",
"operationId": "tokensGetJWT",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Get a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"responses": {
"200": {
"description": "JWT",
"schema": {
"$ref": "#\/definitions\/jwt"
}
}
},
"x-appwrite": {
"method": "getJWT",
"weight": 431,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get-j-w-t.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "File token ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
}
]
}
}
},
"tags": [
@@ -7875,6 +8320,31 @@
"files"
]
},
"resourceTokenList": {
"description": "Resource Tokens List",
"type": "object",
"properties": {
"total": {
"type": "integer",
"description": "Total number of tokens documents that matched your query.",
"x-example": 5,
"format": "int32"
},
"tokens": {
"type": "array",
"description": "List of tokens.",
"items": {
"type": "object",
"$ref": "#\/definitions\/resourceToken"
},
"x-example": ""
}
},
"required": [
"total",
"tokens"
]
},
"teamList": {
"description": "Teams List",
"type": "object",
@@ -9082,6 +9552,50 @@
"chunksUploaded"
]
},
"resourceToken": {
"description": "ResourceToken",
"type": "object",
"properties": {
"$id": {
"type": "string",
"description": "Token ID.",
"x-example": "bb8ea5c16897e"
},
"$createdAt": {
"type": "string",
"description": "Token creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"resourceId": {
"type": "string",
"description": "Resource ID.",
"x-example": "5e5ea5c168bb8:5e5ea5c168bb8"
},
"resourceInternalId": {
"type": "string",
"description": "File ID.",
"x-example": "1:1"
},
"resourceType": {
"type": "string",
"description": "Resource type.",
"x-example": "file"
},
"expire": {
"type": "string",
"description": "Token expiration date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
"$id",
"$createdAt",
"resourceId",
"resourceInternalId",
"resourceType",
"expire"
]
},
"team": {
"description": "Team",
"type": "object",
File diff suppressed because it is too large Load Diff
+577 -51
View File
@@ -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,
@@ -21628,6 +21628,463 @@
]
}
},
"\/tokens\/buckets\/{bucketId}\/files\/{fileId}": {
"get": {
"summary": "List tokens",
"operationId": "tokensList",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "List all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"responses": {
"200": {
"description": "Resource Tokens List",
"schema": {
"$ref": "#\/definitions\/resourceTokenList"
}
}
},
"x-appwrite": {
"method": "list",
"weight": 432,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/list.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterList all the tokens created for a specific file or bucket. You can use the query params to filter your results.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"type": "string",
"x-example": "<BUCKET_ID>",
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"type": "string",
"x-example": "<FILE_ID>",
"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: expire",
"required": false,
"type": "array",
"collectionFormat": "multi",
"items": {
"type": "string"
},
"default": [],
"in": "query"
}
]
},
"post": {
"summary": "Create file token",
"operationId": "tokensCreateFileToken",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Create a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"responses": {
"201": {
"description": "ResourceToken",
"schema": {
"$ref": "#\/definitions\/resourceToken"
}
}
},
"x-appwrite": {
"method": "createFileToken",
"weight": 429,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/create-file-token.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "bucketId",
"description": "Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https:\/\/appwrite.io\/docs\/server\/storage#createBucket).",
"required": true,
"type": "string",
"x-example": "<BUCKET_ID>",
"in": "path"
},
{
"name": "fileId",
"description": "File unique ID.",
"required": true,
"type": "string",
"x-example": "<FILE_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "Token expiry date",
"default": null,
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"default": [],
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
]
}
},
"\/tokens\/{tokenId}": {
"get": {
"summary": "Get token",
"operationId": "tokensGet",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Get a token by its unique ID.",
"responses": {
"200": {
"description": "ResourceToken",
"schema": {
"$ref": "#\/definitions\/resourceToken"
}
}
},
"x-appwrite": {
"method": "get",
"weight": 430,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a token by its unique ID.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
}
]
},
"patch": {
"summary": "Update token",
"operationId": "tokensUpdate",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Update a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"responses": {
"200": {
"description": "ResourceToken",
"schema": {
"$ref": "#\/definitions\/resourceToken"
}
}
},
"x-appwrite": {
"method": "update",
"weight": 433,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/update.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterUpdate a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token unique ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"expire": {
"type": "string",
"description": "File token expiry date",
"default": null,
"x-example": null,
"x-nullable": true
},
"permissions": {
"type": "array",
"description": "An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"default": null,
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
}
}
}
}
]
},
"delete": {
"summary": "Delete token",
"operationId": "tokensDelete",
"consumes": [
"application\/json"
],
"produces": [],
"tags": [
"tokens"
],
"description": "Delete a token by its unique ID.",
"responses": {
"204": {
"description": "No content"
}
},
"x-appwrite": {
"method": "delete",
"weight": 434,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/delete.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterDelete a token by its unique ID.",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "tokens.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "Token ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
}
]
}
},
"\/tokens\/{tokenId}\/jwt": {
"get": {
"summary": "Get token as JWT",
"operationId": "tokensGetJWT",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"tokens"
],
"description": "Get a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"responses": {
"200": {
"description": "JWT",
"schema": {
"$ref": "#\/definitions\/jwt"
}
}
},
"x-appwrite": {
"method": "getJWT",
"weight": 431,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "tokens\/get-j-w-t.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterGet a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "tokens.read",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Session": []
}
},
"security": [
{
"Project": [],
"Session": [],
"Key": [],
"JWT": []
}
],
"parameters": [
{
"name": "tokenId",
"description": "File token ID.",
"required": true,
"type": "string",
"x-example": "<TOKEN_ID>",
"in": "path"
}
]
}
},
"\/users": {
"get": {
"summary": "List users",
@@ -25231,6 +25688,31 @@
"buckets"
]
},
"resourceTokenList": {
"description": "Resource Tokens List",
"type": "object",
"properties": {
"total": {
"type": "integer",
"description": "Total number of tokens documents that matched your query.",
"x-example": 5,
"format": "int32"
},
"tokens": {
"type": "array",
"description": "List of tokens.",
"items": {
"type": "object",
"$ref": "#\/definitions\/resourceToken"
},
"x-example": ""
}
},
"required": [
"total",
"tokens"
]
},
"teamList": {
"description": "Teams List",
"type": "object",
@@ -27822,6 +28304,50 @@
"antivirus"
]
},
"resourceToken": {
"description": "ResourceToken",
"type": "object",
"properties": {
"$id": {
"type": "string",
"description": "Token ID.",
"x-example": "bb8ea5c16897e"
},
"$createdAt": {
"type": "string",
"description": "Token creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"resourceId": {
"type": "string",
"description": "Resource ID.",
"x-example": "5e5ea5c168bb8:5e5ea5c168bb8"
},
"resourceInternalId": {
"type": "string",
"description": "File ID.",
"x-example": "1:1"
},
"resourceType": {
"type": "string",
"description": "Resource type.",
"x-example": "file"
},
"expire": {
"type": "string",
"description": "Token expiration date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
"$id",
"$createdAt",
"resourceId",
"resourceInternalId",
"resourceType",
"expire"
]
},
"team": {
"description": "Team",
"type": "object",
+7 -7
View File
@@ -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')
+42 -6
View File
@@ -4,6 +4,7 @@ use Appwrite\Auth\Auth;
use Appwrite\Event\Event;
use Appwrite\Event\Migration;
use Appwrite\Extend\Exception;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -27,8 +28,11 @@ use Utopia\Migration\Sources\Firebase;
use Utopia\Migration\Sources\NHost;
use Utopia\Migration\Sources\Supabase;
use Utopia\Migration\Transfer;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Compression\Algorithms\Zstd;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Device;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
use Utopia\Validator\Text;
@@ -345,18 +349,50 @@ App::post('/v1/migrations/csv')
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
}
if (!empty($file->getAttribute('openSSLCipher')) || $file->getAttribute('algorithm', Compression::NONE) !== Compression::NONE) {
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED, "Only uncompressed, unencrypted CSV files can be used for document import.");
}
// no encryption, compression on files above 20MB.
$hasEncryption = !empty($file->getAttribute('openSSLCipher'));
$compression = $file->getAttribute('algorithm', Compression::NONE);
$hasCompression = $compression !== Compression::NONE;
// copy to temporary folder
$migrationId = ID::unique();
$newPath = $deviceForImports->getPath('/' . $migrationId . '_' . $fileId . '.csv');
if (!$deviceForFiles->transfer($path, $newPath, $deviceForImports)) {
if ($hasEncryption || $hasCompression) {
$source = $deviceForFiles->read($path);
// 1. decrypt
if ($hasEncryption) {
$source = OpenSSL::decrypt(
$source,
$file->getAttribute('openSSLCipher'),
System::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
hex2bin($file->getAttribute('openSSLIV')),
hex2bin($file->getAttribute('openSSLTag'))
);
}
// 2. decompress
if ($hasCompression) {
switch ($compression) {
case Compression::ZSTD:
$source = (new Zstd())->decompress($source);
break;
case Compression::GZIP:
$source = (new GZIP())->decompress($source);
break;
}
}
// manual write after decryption and/or decompression
if (! $deviceForImports->write($newPath, $source, 'text/csv')) {
throw new \Exception("Unable to copy file");
}
} elseif (! $deviceForFiles->transfer($path, $newPath, $deviceForImports)) {
throw new \Exception("Unable to copy file");
}
$fileSize = $deviceForImports->getFileSize($path);
$fileSize = $deviceForImports->getFileSize($newPath);
$resources = Transfer::extractServices([Transfer::GROUP_DATABASES]);
$migration = $dbForProject->createDocument('migrations', new Document([
+10 -3
View File
@@ -936,9 +936,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true)
->inject('response')
->inject('dbForProject')
->inject('resourceToken')
->inject('deviceForFiles')
->inject('deviceForLocal')
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Response $response, Database $dbForProject, Device $deviceForFiles, Device $deviceForLocal) {
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal) {
if (!\extension_loaded('imagick')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
@@ -953,19 +954,24 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getInternalId();
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
if (!$fileSecurity && !$valid && !$isToken) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid) {
if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getInternalId()) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
@@ -1765,6 +1771,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
$response->noContent();
});
/** Storage usage */
App::get('/v1/storage/usage')
->desc('Get storage usage stats')
->groups(['api', 'storage'])
+2 -1
View File
@@ -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')
+21 -8
View File
@@ -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,
@@ -1543,11 +1555,12 @@ foreach (Config::getParam('services', []) as $service) {
}
}
// Modules
$platform = new Appwrite();
$platform->init(Service::TYPE_HTTP);
// Check for any errors found while we were initialising the SDK Methods.
if (!empty(Method::getErrors())) {
throw new \Exception('Errors found during SDK initialization:' . PHP_EOL . implode(PHP_EOL, Method::getErrors()));
}
// Modules
$platform = new Appwrite();
$platform->init(Service::TYPE_HTTP);
+19 -9
View File
@@ -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());
@@ -399,10 +399,12 @@ App::init()
->inject('queueForStatsUsage')
->inject('dbForProject')
->inject('timelimit')
->inject('resourceToken')
->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, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey) use ($usageDatabaseListener, $eventDatabaseListener) {
$route = $utopia->getRoute();
@@ -469,6 +471,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);
@@ -552,6 +555,10 @@ App::init()
$bucketId = $parts[1] ?? null;
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getInternalId();
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
@@ -559,20 +566,23 @@ App::init()
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
if (!$fileSecurity && !$valid && !$isToken) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$parts = explode('/', $cacheLog->getAttribute('resource'));
$fileId = $parts[1] ?? null;
if ($fileSecurity && !$valid) {
if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getInternalId()) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
@@ -798,7 +808,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([
+14
View File
@@ -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) {
+88
View File
@@ -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') {
@@ -852,3 +897,46 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key
}, ['request', 'project']);
App::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
App::setResource('resourceToken', function ($project, $dbForProject, $request) {
$tokenJWT = $request->getParam('token');
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
try {
$payload = $jwt->decode($tokenJWT);
} catch (JWTException $error) {
return new Document([]);
}
$tokenId = $payload['tokenId'] ?? '';
$secret = $payload['secret'] ?? '';
if (empty($tokenId) || empty($secret)) {
return new Document([]);
}
$token = Authorization::skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
if ($token->isEmpty() || $token->getAttribute('secret') !== $secret) {
return new Document([]);
}
if ($token->getAttribute('resourceType') === 'file') {
$internalIds = explode(':', $token->getAttribute('resourceInternalId'));
$ids = explode(':', $token->getAttribute('resourceId'));
if (count($internalIds) !== 2 || count($ids) !== 2) {
return new Document([]);
}
return new Document([
'bucketId' => $ids[0],
'fileId' => $ids[1],
'bucketInternalId' => $internalIds[0],
'fileInternalId' => $internalIds[1],
]);
}
}
return new Document([]);
}, ['project', 'dbForProject', 'request']);
+13
View File
@@ -1,4 +1,6 @@
<?php
use Utopia\System\System;
$development = $this->getParam('development', false);
$type = $this->getParam('type', 'general_server_error');
$code = $this->getParam('code', 500);
@@ -12,6 +14,15 @@ $label = '';
$labelClass = '';
$buttons = [];
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
// TODO: remove this later
if (System::getEnv('_APP_ENV') === 'development') {
$hostname = 'localhost';
}
$url = $protocol . '://' . $hostname;
if($exception !== null && method_exists($exception, 'getCTAs')) {
foreach ($exception->getCTAs() as $index => $cta) {
$class = ($index === 0) ? 'bordered-button' : 'button';
@@ -94,6 +105,8 @@ switch ($type) {
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
<link rel="icon" type="image/svg+xml" href="<?php echo $url; ?>/images/logos/appwrite-icon.svg" />
<link rel="mask-icon" type="image/png" href="<?php echo $url; ?>/images/logos/appwrite-icon.png" />
<link
rel="preload"
href="/fonts/inter/inter-v8-latin-600.woff2"
+1 -1
View File
@@ -53,7 +53,7 @@
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/detector": "0.1.*",
"utopia-php/database": "0.64.*",
"utopia-php/database": "0.66.*",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
Generated
+15 -15
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e7875026636ccec909f9aa4d79091d5b",
"content-hash": "51959289a3f882160f5a9eeb605d41d7",
"packages": [
{
"name": "adhocore/jwt",
@@ -3497,16 +3497,16 @@
},
{
"name": "utopia-php/database",
"version": "0.64.2",
"version": "0.66.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "dc9c4a68c93e8bea2dfaa76d1ba308be539998bd"
"reference": "67d2ab418efba31dc76b3564cf043e2b3f98d027"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/dc9c4a68c93e8bea2dfaa76d1ba308be539998bd",
"reference": "dc9c4a68c93e8bea2dfaa76d1ba308be539998bd",
"url": "https://api.github.com/repos/utopia-php/database/zipball/67d2ab418efba31dc76b3564cf043e2b3f98d027",
"reference": "67d2ab418efba31dc76b3564cf043e2b3f98d027",
"shasum": ""
},
"require": {
@@ -3547,9 +3547,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.64.2"
"source": "https://github.com/utopia-php/database/tree/0.66.0"
},
"time": "2025-04-09T07:53:05+00:00"
"time": "2025-04-16T07:10:27+00:00"
},
{
"name": "utopia-php/detector",
@@ -4811,16 +4811,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.40.12",
"version": "0.40.13",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "182ec17848f81b78c336379bac94ff92b7a73365"
"reference": "c4bc0ff4589969252b99354368743a99997f0428"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/182ec17848f81b78c336379bac94ff92b7a73365",
"reference": "182ec17848f81b78c336379bac94ff92b7a73365",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/c4bc0ff4589969252b99354368743a99997f0428",
"reference": "c4bc0ff4589969252b99354368743a99997f0428",
"shasum": ""
},
"require": {
@@ -4856,9 +4856,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.12"
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.13"
},
"time": "2025-04-02T23:36:11+00:00"
"time": "2025-04-23T11:59:44+00:00"
},
{
"name": "doctrine/annotations",
@@ -8229,7 +8229,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@@ -8253,5 +8253,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+11
View File
@@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_28_719)">
<path d="M24.4429 17.4322V22.9096H10.7519C6.76318 22.9096 3.28044 20.7067 1.4171 17.4322C1.14622 16.9561 0.909137 16.4567 0.710264 15.9383C0.319864 14.9225 0.0744552 13.8325 0 12.6952V11.2143C0.0161646 10.9609 0.0416361 10.7094 0.0749451 10.4609C0.143032 9.95105 0.245898 9.45211 0.381093 8.96711C1.66006 4.36909 5.81877 1 10.7519 1C15.6851 1 19.8433 4.36909 21.1223 8.96711H15.2682C14.3072 7.4683 12.6437 6.4774 10.7519 6.4774C8.86017 6.4774 7.19668 7.4683 6.23562 8.96711C5.9427 9.42274 5.71542 9.92516 5.56651 10.4609C5.43425 10.936 5.36371 11.4369 5.36371 11.9548C5.36371 13.5248 6.01324 14.94 7.05463 15.9383C8.01961 16.865 9.32061 17.4322 10.7519 17.4322H24.4429Z" fill="#FD366E" />
<path d="M24.4429 10.4609V15.9383H14.4492C15.4906 14.94 16.1401 13.5248 16.1401 11.9548C16.1401 11.4369 16.0696 10.936 15.9373 10.4609H24.4429Z" fill="#FD366E" />
</g>
<defs>
<clipPath id="clip0_28_719">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+4
View File
@@ -318,6 +318,10 @@ class Exception extends \Exception
/** Schedules */
public const SCHEDULE_NOT_FOUND = 'schedule_not_found';
/** Tokens */
public const TOKEN_NOT_FOUND = 'token_not_found';
public const TOKEN_EXPIRED = 'token_expired';
protected string $type = '';
protected array $errors = [];
+4
View File
@@ -5,8 +5,10 @@ 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 Appwrite\Platform\Modules\Storage;
use Utopia\Platform\Platform;
class Appwrite extends Platform
@@ -14,9 +16,11 @@ 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());
$this->addModule(new Proxy\Module());
$this->addModule(new Storage\Module());
}
}
@@ -0,0 +1,96 @@
<?php
namespace Appwrite\Platform\Modules\Projects\Http\DevKeys;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class Create extends Action
{
use HTTP;
public static function getName()
{
return 'createDevKey';
}
public function __construct()
{
$this
->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: <<<EOT
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.
EOT,
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_DEV_KEY
)
],
contentType: ContentType::JSON
))
->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);
}
}
@@ -0,0 +1,75 @@
<?php
namespace Appwrite\Platform\Modules\Projects\Http\DevKeys;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Delete extends Action
{
use HTTP;
public static function getName()
{
return 'deleteDevKey';
}
public function __construct()
{
$this
->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: <<<EOT
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.
EOT,
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE
)
],
contentType: ContentType::NONE
))
->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();
}
}
@@ -0,0 +1,71 @@
<?php
namespace Appwrite\Platform\Modules\Projects\Http\DevKeys;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Action
{
use HTTP;
public static function getName()
{
return 'getDevKey';
}
public function __construct()
{
$this
->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: <<<EOT
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.
EOT,
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_DEV_KEY
)
],
contentType: ContentType::JSON
))
->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);
}
}
@@ -0,0 +1,82 @@
<?php
namespace Appwrite\Platform\Modules\Projects\Http\DevKeys;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class Update extends Action
{
use HTTP;
public static function getName()
{
return 'updateDevKey';
}
public function __construct()
{
$this->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: <<<EOT
Update a project\'s dev key by its unique ID. Use this endpoint to update a project\'s dev key name or expiration time.'
EOT,
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_DEV_KEY
)
],
contentType: ContentType::JSON
))
->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);
}
}
@@ -0,0 +1,88 @@
<?php
namespace Appwrite\Platform\Modules\Projects\Http\DevKeys;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\DevKeys;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class XList extends Action
{
use HTTP;
public static function getName()
{
return 'listDevKeys';
}
public function __construct()
{
$this
->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: <<<EOT
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.'
EOT,
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_DEV_KEY_LIST
)
],
contentType: ContentType::JSON
))
->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);
}
}
@@ -0,0 +1,14 @@
<?php
namespace Appwrite\Platform\Modules\Projects;
use Appwrite\Platform\Modules\Projects\Services\Http;
use Utopia\Platform\Module as Base;
class Module extends Base
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -0,0 +1,23 @@
<?php
namespace Appwrite\Platform\Modules\Projects\Services;
use Appwrite\Platform\Modules\Projects\Http\DevKeys\Create as CreateDevKey;
use Appwrite\Platform\Modules\Projects\Http\DevKeys\Delete as DeleteDevKey;
use Appwrite\Platform\Modules\Projects\Http\DevKeys\Get as GetDevKey;
use Appwrite\Platform\Modules\Projects\Http\DevKeys\Update as UpdateDevKey;
use Appwrite\Platform\Modules\Projects\Http\DevKeys\XList as ListDevKeys;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->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());
}
}
@@ -0,0 +1,45 @@
<?php
namespace Appwrite\Platform\Modules\Storage\Http\Tokens\Buckets\Files;
use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action as UtopiaAction;
class Action extends UtopiaAction
{
protected function getFileAndBucket(Database $dbForProject, string $bucketId, string $fileId): array
{
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
if ($fileSecurity) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
return [
'bucket' => $bucket,
'file' => $file,
];
}
}
@@ -0,0 +1,116 @@
<?php
namespace Appwrite\Platform\Modules\Storage\Http\Tokens\Buckets\Files;
use Appwrite\Auth\Auth;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Nullable;
class Create extends Action
{
use HTTP;
public static function getName()
{
return 'createFileToken';
}
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/tokens/buckets/:bucketId/files/:fileId')
->desc('Create file token')
->groups(['api', 'token'])
->label('scope', 'tokens.write')
->label('event', 'tokens.[tokenId].create')
->label('audits.event', 'token.create')
->label('audits.resource', 'token/{response.$id}')
->label('usage.metric', 'tokens.{scope}.requests.create')
->label('usage.params', ['resourceId:{request.resourceId}', 'resourceType:{request.resourceType}'])
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk', new Method(
namespace: 'tokens',
name: 'createFileToken',
description: <<<EOT
Create a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.
EOT,
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_RESOURCE_TOKEN,
)
],
contentType: ContentType::JSON
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Token expiry date', true)
->param('permissions', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('user')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(string $bucketId, string $fileId, ?string $expire, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents)
{
/**
* @var Document $bucket
* @var Document $file
*/
['bucket' => $bucket, 'file' => $file] = $this->getFileAndBucket($dbForProject, $bucketId, $fileId);
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_UPDATE);
$bucketPermission = $validator->isValid($bucket->getUpdate());
if ($fileSecurity) {
$filePermission = $validator->isValid($file->getUpdate());
if (!$bucketPermission && !$filePermission) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
} elseif (!$bucketPermission) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$token = $dbForProject->createDocument('resourceTokens', new Document([
'$id' => ID::unique(),
'secret' => Auth::tokenGenerator(128),
'resourceId' => $bucketId . ':' . $fileId,
'resourceInternalId' => $bucket->getInternalId() . ':' . $file->getInternalId(),
'resourceType' => 'files',
'expire' => $expire,
'$permissions' => $permissions
]));
$queueForEvents
->setParam('bucketId', $bucket->getId())
->setParam('fileId', $file->getId())
->setParam('tokenId', $token->getId())
->setContext('bucket', $bucket)
;
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($token, Response::MODEL_RESOURCE_TOKEN);
}
}
@@ -0,0 +1,91 @@
<?php
namespace Appwrite\Platform\Modules\Storage\Http\Tokens\Buckets\Files;
use Appwrite\Extend\Exception as ExtendException;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\FileTokens;
use Appwrite\Utopia\Response;
use Exception;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
class XList extends Action
{
use HTTP;
public static function getName()
{
return 'listFileTokens';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/tokens/buckets/:bucketId/files/:fileId')
->desc('List tokens')
->groups(['api', 'tokens'])
->label('scope', 'tokens.read')
->label('usage.metric', 'tokens.requests.read')
->label('sdk', new Method(
namespace: 'tokens',
name: 'list',
description: <<<EOT
List all the tokens created for a specific file or bucket. You can use the query params to filter your results.
EOT,
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_RESOURCE_TOKEN_LIST,
)
],
contentType: ContentType::JSON
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->param('queries', [], new FileTokens(), '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(', ', FileTokens::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $bucketId, string $fileId, array $queries, Response $response, Database $dbForProject)
{
['bucket' => $bucket, 'file' => $file] = $this->getFileAndBucket($dbForProject, $bucketId, $fileId);
$queries = Query::parseQueries($queries);
$queries[] = Query::equal('resourceType', ["files"]);
$queries[] = Query::equal('resourceId', [$bucket->getInternalId() . ':' . $file->getInternalId()]);
// Get cursor document if there was a cursor query
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$tokenId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('resourceTokens', $tokenId);
if ($cursorDocument->isEmpty()) {
throw new Exception(ExtendException::GENERAL_CURSOR_NOT_FOUND, "File token '{$tokenId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$response->dynamic(new Document([
'tokens' => $dbForProject->find('resourceTokens', $queries),
'total' => $dbForProject->count('resourceTokens', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_RESOURCE_TOKEN_LIST);
}
}
@@ -0,0 +1,79 @@
<?php
namespace Appwrite\Platform\Modules\Storage\Http\Tokens;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Delete extends Action
{
use HTTP;
public static function getName()
{
return 'deleteToken';
}
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE)
->setHttpPath('/v1/tokens/:tokenId')
->desc('Delete token')
->groups(['api', 'tokens'])
->label('scope', 'tokens.write')
->label('event', 'tokens.[tokenId].delete')
->label('audits.event', 'tokens.delete')
->label('audits.resource', 'token/{request.tokenId}')
->label('usage.metric', 'tokens.{scope}.requests.delete')
->label('usage.params', ['tokenId:{request.tokenId}'])
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk', new Method(
namespace: 'tokens',
name: 'delete',
description: <<<EOT
Delete a token by its unique ID.
EOT,
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('tokenId', '', new UID(), 'Token ID.')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(string $tokenId, Response $response, Database $dbForProject, Event $queueForEvents)
{
$token = $dbForProject->getDocument('resourceTokens', $tokenId);
if ($token->isEmpty()) {
throw new Exception(Exception::TOKEN_NOT_FOUND);
}
$dbForProject->deleteDocument('resourceTokens', $tokenId);
$queueForEvents
->setParam('tokenId', $token->getId())
->setPayload($response->output($token, Response::MODEL_RESOURCE_TOKEN))
;
$response->noContent();
}
}
@@ -0,0 +1,65 @@
<?php
namespace Appwrite\Platform\Modules\Storage\Http\Tokens;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Action
{
use HTTP;
public static function getName()
{
return 'getToken';
}
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/tokens/:tokenId')
->desc('Get token')
->groups(['api', 'tokens'])
->label('scope', 'tokens.read')
->label('usage.metric', 'tokens.{scope}.requests.read')
->label('usage.params', ['tokenId:{request.tokenId}'])
->label('sdk', new Method(
namespace: 'tokens',
name: 'get',
description: <<<EOT
Get a token by its unique ID.
EOT,
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_RESOURCE_TOKEN,
)
],
contentType: ContentType::JSON
))
->param('tokenId', '', new UID(), 'Token ID.')
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $tokenId, Response $response, Database $dbForProject)
{
$token = $dbForProject->getDocument('resourceTokens', $tokenId);
if ($token->isEmpty()) {
throw new Exception(Exception::TOKEN_NOT_FOUND);
}
$response->dynamic($token, Response::MODEL_RESOURCE_TOKEN);
}
}
@@ -0,0 +1,90 @@
<?php
namespace Appwrite\Platform\Modules\Storage\Http\Tokens\JWT;
use Ahc\Jwt\JWT;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
class Get extends Action
{
use HTTP;
public static function getName()
{
return 'getTokenJWT';
}
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/tokens/:tokenId/jwt')
->desc('Get token as JWT')
->groups(['api', 'tokens'])
->label('scope', 'tokens.read')
->label('usage.metric', 'tokens.{scope}.requests.read')
->label('usage.params', ['tokenId:{request.tokenId}'])
->label('sdk', new Method(
namespace: 'tokens',
name: 'getJWT',
description: <<<EOT
Get a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.
EOT,
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_JWT,
)
],
contentType: ContentType::JSON
))
->param('tokenId', '', new UID(), 'File token ID.')
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $tokenId, Response $response, Database $dbForProject)
{
$token = $dbForProject->getDocument('resourceTokens', $tokenId);
if ($token->isEmpty()) {
throw new Exception(Exception::TOKEN_NOT_FOUND);
}
// calculate maxAge based on expiry date
$maxAge = PHP_INT_MAX;
$expire = $token->getAttribute('expire');
if ($expire !== null) {
$now = new \DateTime();
$expiryDate = new \DateTime($expire);
if ($expiryDate < $now) {
throw new Exception(Exception::TOKEN_EXPIRED);
}
$maxAge = $expiryDate->getTimestamp() - $now->getTimestamp();
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $maxAge, 10); // Instantiate with key, algo, maxAge and leeway.
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document(['jwt' => $jwt->encode([
'resourceType' => $token->getAttribute('resourceType'),
'resourceId' => $token->getAttribute('resourceId'),
'resourceInternalId' => $token->getAttribute('resourceInternalId'),
'tokenId' => $token->getId(),
'secret' => $token->getAttribute('secret')
])]), Response::MODEL_JWT);
}
}
@@ -0,0 +1,124 @@
<?php
namespace Appwrite\Platform\Modules\Storage\Http\Tokens;
use Appwrite\Auth\Auth;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Nullable;
class Update extends Action
{
use HTTP;
public static function getName()
{
return 'updateToken';
}
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/tokens/:tokenId')
->desc('Update token')
->groups(['api', 'tokens'])
->label('scope', 'tokens.write')
->label('event', 'tokens.[tokenId].update')
->label('audits.event', 'tokens.update')
->label('audits.resource', 'tokens/{response.$id}')
->label('usage.metric', 'tokens.{scope}.requests.update')
->label('usage.params', ['tokenId:{request.tokenId}'])
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk', new Method(
namespace: 'tokens',
name: 'update',
description: <<<EOT
Update a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.
EOT,
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_RESOURCE_TOKEN,
)
],
contentType: ContentType::JSON
))
->param('tokenId', '', new UID(), 'Token unique ID.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'File token expiry date', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(string $tokenId, ?string $expire, ?array $permissions, Response $response, Database $dbForProject, Event $queueForEvents)
{
$token = $dbForProject->getDocument('resourceTokens', $tokenId);
if ($token->isEmpty()) {
throw new Exception(Exception::TOKEN_NOT_FOUND);
}
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission::aggregate($permissions, [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
]);
// Users can only manage their own roles, API keys and Admin users can manage any
$roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles) && !\is_null($permissions)) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
if ($permission->getPermission() != $type) {
continue;
}
$role = (new Role(
$permission->getRole(),
$permission->getIdentifier(),
$permission->getDimension()
))->toString();
if (!Authorization::isRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
}
}
if (\is_null($permissions)) {
$permissions = $token->getPermissions() ?? [];
}
$token
->setAttribute('expire', $expire)
->setAttribute('$permissions', $permissions);
$token = $dbForProject->updateDocument('resourceTokens', $tokenId, $token);
$queueForEvents
->setParam('tokenId', $token->getId())
;
$response->dynamic($token, Response::MODEL_RESOURCE_TOKEN);
}
}
@@ -0,0 +1,14 @@
<?php
namespace Appwrite\Platform\Modules\Storage;
use Appwrite\Platform\Modules\Storage\Services\Http;
use Utopia\Platform;
class Module extends Platform\Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -0,0 +1,28 @@
<?php
namespace Appwrite\Platform\Modules\Storage\Services;
use Appwrite\Platform\Modules\Storage\Http\Tokens\Buckets\Files\Create as CreateFileToken;
use Appwrite\Platform\Modules\Storage\Http\Tokens\Buckets\Files\XList as ListFileTokens;
use Appwrite\Platform\Modules\Storage\Http\Tokens\Delete as DeleteToken;
use Appwrite\Platform\Modules\Storage\Http\Tokens\Get as GetToken;
use Appwrite\Platform\Modules\Storage\Http\Tokens\JWT\Get as GetTokenJWT;
use Appwrite\Platform\Modules\Storage\Http\Tokens\Update as UpdateToken;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->type = Service::TYPE_HTTP;
$this
->addAction(CreateFileToken::getName(), new CreateFileToken())
->addAction(GetToken::getName(), new GetToken())
->addAction(GetTokenJWT::getName(), new GetTokenJWT())
->addAction(ListFileTokens::getName(), new ListFileTokens())
->addAction(UpdateToken::getName(), new UpdateToken())
->addAction(DeleteToken::getName(), new DeleteToken())
;
}
}
+1 -1
View File
@@ -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));
+4 -1
View File
@@ -216,7 +216,10 @@ class Migrations extends Action
'databases.read',
'collections.read',
'documents.read',
],
'documents.write',
'tokens.read',
'tokens.write',
]
]);
return API_KEY_DYNAMIC . '_' . $apiKey;
@@ -0,0 +1,20 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class DevKeys extends Base
{
public const ALLOWED_ATTRIBUTES = [
'accessedAt',
'expire',
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('devKeys', self::ALLOWED_ATTRIBUTES);
}
}
@@ -0,0 +1,19 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class FileTokens extends Base
{
public const ALLOWED_ATTRIBUTES = [
'expire',
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('files', self::ALLOWED_ATTRIBUTES);
}
}
+10
View File
@@ -39,6 +39,7 @@ use Appwrite\Utopia\Response\Model\Database;
use Appwrite\Utopia\Response\Model\Deployment;
use Appwrite\Utopia\Response\Model\DetectionFramework;
use Appwrite\Utopia\Response\Model\DetectionRuntime;
use Appwrite\Utopia\Response\Model\DevKey;
use Appwrite\Utopia\Response\Model\Document as ModelDocument;
use Appwrite\Utopia\Response\Model\Error;
use Appwrite\Utopia\Response\Model\ErrorDev;
@@ -85,6 +86,7 @@ use Appwrite\Utopia\Response\Model\Provider;
use Appwrite\Utopia\Response\Model\ProviderRepository;
use Appwrite\Utopia\Response\Model\ProviderRepositoryFramework;
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntime;
use Appwrite\Utopia\Response\Model\ResourceToken;
use Appwrite\Utopia\Response\Model\Rule;
use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\Session;
@@ -210,6 +212,8 @@ class Response extends SwooleResponse
public const MODEL_FILE_LIST = 'fileList';
public const MODEL_BUCKET = 'bucket';
public const MODEL_BUCKET_LIST = 'bucketList';
public const MODEL_RESOURCE_TOKEN = 'resourceToken';
public const MODEL_RESOURCE_TOKEN_LIST = 'resourceTokenList';
// Locale
public const MODEL_LOCALE = 'locale';
@@ -306,6 +310,8 @@ class Response extends SwooleResponse
public const MODEL_WEBHOOK_LIST = 'webhookList';
public const MODEL_KEY = 'key';
public const MODEL_KEY_LIST = 'keyList';
public const MODEL_DEV_KEY = 'devKey';
public const MODEL_DEV_KEY_LIST = 'devKeyList';
public const MODEL_MOCK_NUMBER = 'mockNumber';
public const MODEL_AUTH_PROVIDER = 'authProvider';
public const MODEL_AUTH_PROVIDER_LIST = 'authProviderList';
@@ -373,6 +379,7 @@ class Response extends SwooleResponse
->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG))
->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE))
->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET))
->setModel(new BaseList('Resource Tokens List', self::MODEL_RESOURCE_TOKEN_LIST, 'tokens', self::MODEL_RESOURCE_TOKEN))
->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM))
->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP))
->setModel(new BaseList('Sites List', self::MODEL_SITE_LIST, 'sites', self::MODEL_SITE))
@@ -390,6 +397,7 @@ class Response extends SwooleResponse
->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))
@@ -447,6 +455,7 @@ class Response extends SwooleResponse
->setModel(new LocaleCode())
->setModel(new File())
->setModel(new Bucket())
->setModel(new ResourceToken())
->setModel(new Team())
->setModel(new Membership())
->setModel(new Site())
@@ -472,6 +481,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())
@@ -0,0 +1,89 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class DevKey extends Model
{
/**
* @var bool
*/
protected bool $public = false;
public function __construct()
{
$this
->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;
}
}
@@ -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',
@@ -0,0 +1,71 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class ResourceToken extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Token ID.',
'default' => '',
'example' => 'bb8ea5c16897e',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Token creation date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('resourceId', [
'type' => self::TYPE_STRING,
'description' => 'Resource ID.',
'default' => '',
'example' => '5e5ea5c168bb8:5e5ea5c168bb8',
])
->addRule('resourceInternalId', [
'type' => self::TYPE_STRING,
'description' => 'File ID.',
'default' => '',
'example' => '1:1',
])
->addRule('resourceType', [
'type' => self::TYPE_STRING,
'description' => 'Resource type.',
'default' => '',
'example' => 'file',
])
->addRule('expire', [
'type' => self::TYPE_DATETIME,
'description' => 'Token expiration date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'ResourceToken';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_RESOURCE_TOKEN;
}
}
+19 -1
View File
@@ -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;
@@ -102,7 +103,9 @@ trait ProjectCustom
'subscribers.write',
'subscribers.read',
'migrations.write',
'migrations.read'
'migrations.read',
'tokens.read',
'tokens.write',
],
]);
@@ -110,6 +113,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 +166,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;
}
+1 -1
View File
@@ -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
+7 -2
View File
@@ -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;
}
/**
+1 -1
View File
@@ -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',
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Tests\E2E\Scopes;
trait SideNone
{
public function getHeaders(): array
public function getHeaders(bool $devKey = true): array
{
return [];
}
+1 -1
View File
@@ -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']
+7 -3
View File
@@ -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,
@@ -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,
@@ -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,
+1 -1
View File
@@ -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);
@@ -971,7 +971,6 @@ trait MigrationsBase
$this->assertEquals($response['body']['required'], true);
// make a bucket, upload a file to it!
// 1. enable compression, encryption
$bucketOne = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -989,33 +988,11 @@ trait MigrationsBase
$bucketOneId = $bucketOne['body']['$id'];
// 2. no compression and encryption
$bucketTwo = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'bucketId' => ID::unique(),
'name' => 'Test Bucket 2',
'maximumFileSize' => 2000000, //2MB
'allowedFileExtensions' => ['csv'],
'compression' => 'none',
'encryption' => false
]);
$this->assertNotEmpty($bucketTwo['body']['$id']);
$this->assertEquals(201, $bucketTwo['headers']['status-code']);
$bucketTwoId = $bucketTwo['body']['$id'];
$bucketIds = [
'compressed' => $bucketOneId,
'uncompressed' => $bucketTwoId,
// in uncompressed buckets!
'missing-row' => $bucketTwoId,
'missing-column' => $bucketTwoId,
'irrelevant-column' => $bucketTwoId,
'default' => $bucketOneId,
'missing-row' => $bucketOneId,
'missing-column' => $bucketOneId,
'irrelevant-column' => $bucketOneId,
];
$fileIds = [];
@@ -1049,20 +1026,6 @@ trait MigrationsBase
$fileIds[$label] = $response['body']['$id'];
}
// compressed, fail.
$compressed = $this->performCsvMigration(
[
'fileId' => $fileIds['compressed'],
'bucketId' => $bucketIds['compressed'],
'resourceId' => $databaseId . ':' . $collectionId,
]
);
// fail on compressed, encrypted buckets!
$this->assertEquals(400, $compressed['body']['code']);
$this->assertEquals('storage_file_type_unsupported', $compressed['body']['type']);
$this->assertEquals('Only uncompressed, unencrypted CSV files can be used for document import.', $compressed['body']['message']);
// missing attribute, fail in worker.
$missingColumn = $this->performCsvMigration(
[
@@ -1150,12 +1113,12 @@ trait MigrationsBase
);
}, 60000, 500);
// no compression, no encryption, pass.
// all data exists, pass/
$migration = $this->performCsvMigration(
[
'endpoint' => 'http://localhost/v1',
'fileId' => $fileIds['uncompressed'],
'bucketId' => $bucketIds['uncompressed'],
'fileId' => $fileIds['default'],
'bucketId' => $bucketIds['default'],
'resourceId' => $databaseId . ':' . $collectionId,
]
);
@@ -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'],
];
}
}
@@ -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 ------------------------------------------------
*/
}
@@ -13,6 +13,9 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator;
trait StorageBase
{
/**
* @group fileTokens
*/
public function testCreateBucketFile(): array
{
/**
@@ -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,
@@ -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
*/
+7
View File
@@ -0,0 +1,7 @@
<?php
namespace Tests\E2E\Services\Tokens;
trait TokensBase
{
}
@@ -0,0 +1,14 @@
<?php
namespace Tests\E2E\Services\Tokens;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideConsole;
class TokensConsoleClientTest extends Scope
{
use SideConsole;
use TokensBase;
use ProjectCustom;
}
@@ -0,0 +1,15 @@
<?php
namespace Tests\E2E\Services\Tokens;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
class TokensCustomClientTest extends Scope
{
use TokensBase;
use ProjectCustom;
use SideClient;
}
@@ -0,0 +1,112 @@
<?php
namespace Tests\E2E\Services\Tokens;
use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
use Utopia\Database\DateTime;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
class TokensCustomServerTest extends Scope
{
use TokensBase;
use ProjectCustom;
use SideServer;
public function testCreateToken(): array
{
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'bucketId' => ID::unique(),
'name' => 'Test Bucket',
'fileSecurity' => true,
'maximumFileSize' => 2000000, //2MB
'allowedFileExtensions' => ['jpg', 'png', 'jfif'],
'permissions' => [
Permission::read(Role::any()),
Permission::create(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]);
$this->assertEquals(201, $bucket['headers']['status-code']);
$this->assertNotEmpty($bucket['body']['$id']);
$bucketId = $bucket['body']['$id'];
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'fileId' => ID::unique(),
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'),
'permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]);
$this->assertEquals(201, $file['headers']['status-code']);
$this->assertNotEmpty($file['body']['$id']);
$fileId = $file['body']['$id'];
$res = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(201, $res['headers']['status-code']);
$this->assertEquals('files', $res['body']['resourceType']);
$data = [];
$data['fileId'] = $fileId;
$data['bucketId'] = $bucketId;
$data['tokenId'] = $res['body']['$id'];
return $data;
}
/**
* @depends testCreateToken
*/
public function testUpdateToken(array $data): array
{
$tokenId = $data['tokenId'];
$expiry = DateTime::now();
$res = $this->client->call(Client::METHOD_PATCH, '/tokens/' . $tokenId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'expire' => $expiry,
]);
$this->assertEquals($expiry, $res['body']['expire']);
return $data;
}
/**
* @depends testUpdateToken
*/
public function testDeleteToken(array $data): array
{
$tokenId = $data['tokenId'];
$res = $this->client->call(Client::METHOD_DELETE, '/tokens/' . $tokenId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(204, $res['headers']['status-code']);
return $data;
}
}