Compare commits

...
Author SHA1 Message Date
Jake Barnby b343158d19 Merge remote-tracking branch 'origin/main' into 1.6.x
# Conflicts:
#	.gitmodules
#	app/console
#	composer.lock
#	tests/e2e/Services/Health/HealthCustomServerTest.php
2024-07-10 19:11:04 +12:00
Torsten DittmannandGitHub c8ee399cb7 Merge pull request #8357 from appwrite/chore-add-test-for-duration
Fix execution duration
2024-07-03 16:00:23 +02:00
Torsten DittmannandGitHub 8bc3fb18e2 Merge pull request #8356 from appwrite/chore-16x-specs
Chores: Update specs
2024-07-03 15:41:12 +02:00
Binyamin Yawitz c5650ba5f0 fix: Ensuring new logger syntax 2024-07-03 09:29:35 -04:00
Khushboo Verma d6a1ef80b3 Remove wrong test 2024-07-03 18:24:46 +05:30
Khushboo Verma 4ef42c9cf8 Fix execution duration and add test 2024-07-03 18:05:08 +05:30
Matej Bačo 7549b81a2d Upgrade Console 2024-07-03 12:14:07 +00:00
Matej Bačo b49a5fe03b Upgrade executor 2024-07-03 12:03:39 +00:00
Matej Bačo 88aefe230c QA changes with Console 2024-07-03 11:36:56 +00:00
Matej Bačo 914886d79f Update specs 2024-07-03 10:32:13 +00:00
Matej Bačo 70fc5e6676 Merge remote-tracking branch 'origin/1.6.x' into chore-16x-specs 2024-07-03 10:24:34 +00:00
Matej Bačo 3e62ca477d Update specs 2024-07-03 10:24:28 +00:00
Damodar LohaniandGitHub d2c5e55ca7 Merge pull request #8355 from appwrite/feat-scheduled-executions
Feat scheduled executions
2024-07-03 16:08:48 +05:45
Damodar LohaniandGitHub 71a9694f86 Merge pull request #7565 from appwrite/mock-numbers
Mock OTP and phone numbers
2024-07-03 15:21:58 +05:45
Matej Bačo b5fc9d5789 PR review changes 2024-07-03 09:35:56 +00:00
Matej Bačo 2262d516f1 Merge remote-tracking branch 'origin/1.6.x' into mock-numbers 2024-07-03 09:30:11 +00:00
Damodar LohaniandGitHub 75cd558670 Merge pull request #8323 from appwrite/feat-opr-v4-support
OPR v4 support
2024-07-03 15:11:54 +05:45
Damodar LohaniandGitHub 1c0490aa91 Merge pull request #8315 from appwrite/feat-new-session-alert
Feat new session alert
2024-07-03 14:58:16 +05:45
Damodar LohaniandGitHub 5f84edd983 Merge pull request #8337 from appwrite/feat-delete-execution
Feat delete execution
2024-07-03 14:55:57 +05:45
Damodar LohaniandGitHub b3ff29dca5 Merge pull request #8243 from appwrite/feat-scheduled-executions
Feat: Scheduled executions (execute later)
2024-07-03 14:54:58 +05:45
Matej Bačo 3a570b1f73 PR review changes 2024-07-03 08:27:54 +00:00
Matej Bačo f59a6cf22e Fix test 2024-07-03 08:02:16 +00:00
Matej Bačo 5fb41fd216 Fix executions 2024-07-03 08:00:17 +00:00
Matej Bačo 5ef196ec9a PR review changes 2024-07-03 07:11:15 +00:00
Matej Bačo 1c83668568 PR review changes 2024-07-03 07:02:41 +00:00
Jake BarnbyandGitHub ae87033974 Merge pull request #8327 from appwrite/chore-sync-1.5.x-into-main
Sync 1.5.x into main
2024-07-03 14:31:08 +12:00
Binyamin YawitzandGitHub c7d030fef4 Merge branch 'feat-scheduled-executions' into feat-delete-execution 2024-07-02 16:21:20 -04:00
Binyamin YawitzandGitHub 5ba37f2fa5 Merge branch '1.6.x' into feat-scheduled-executions 2024-07-02 16:21:16 -04:00
Binyamin YawitzandGitHub efdf278254 Merge branch '1.6.x' into feat-new-session-alert 2024-07-02 16:21:12 -04:00
Binyamin YawitzandGitHub deaec44264 Merge branch '1.6.x' into mock-numbers 2024-07-02 16:21:07 -04:00
Binyamin YawitzandGitHub 1ce4eee34c Merge branch '1.6.x' into feat-opr-v4-support 2024-07-02 16:20:55 -04:00
Eldad A. FuxandGitHub 34076b903f Merge pull request #8350 from appwrite/chore-16x-tests
Tests: Ensure CI/CD works
2024-07-02 16:18:48 -04:00
Matej Bačo acc97ae52d Remove Leftover 2024-07-02 19:41:49 +00:00
Matej Bačo c7ccc38768 Fix tests 2024-07-02 19:08:31 +00:00
Matej Bačo 263981c927 Fix function tests 2024-07-02 18:39:54 +00:00
Matej Bačo 23b522da98 Empty commit 2024-07-02 17:17:22 +00:00
Matej Bačo 7dec126987 Revert unwanted change 2024-07-02 17:16:53 +00:00
Matej Bačo f1603ecb6a Upgrade executor 2024-07-02 17:15:20 +00:00
Matej Bačo cc176a4e7d Merge remote-tracking branch 'origin/1.6.x' into feat-scheduled-executions 2024-07-02 15:56:54 +00:00
Khushboo Verma e9034a9d9d Fix missing closing bracket 2024-07-02 17:08:59 +05:30
Khushboo Verma aab4665701 Resolve merge conflict 2024-07-02 15:02:58 +05:30
Khushboo Verma ead0b93900 Resolve merge conflict 2024-07-02 14:59:15 +05:30
Khushboo VermaandGitHub f72f9c7881 Merge branch '1.6.x' into feat-new-session-alert 2024-07-02 14:50:11 +05:30
Matej Bačo 6fdc5a3425 PR review changes 2024-07-02 08:43:04 +00:00
Binyamin Yawitz 30db70fe80 fix: reviews 2024-07-01 10:23:39 -04:00
Eldad A. FuxandGitHub 2ec038ed26 Merge pull request #8336 from appwrite/feat-add-appwrite-version
Add appwrite version in function variables
2024-07-01 10:18:38 -04:00
Eldad A. FuxandGitHub 23d91e3790 Merge pull request #8333 from appwrite/fix-16x-tests
Fix: 1.6.x failing tests
2024-07-01 10:15:30 -04:00
loks0n fef22825fb chore: delete schedule if not active 2024-07-01 14:35:37 +01:00
Khushboo Verma f8427155f9 Merge branch '1.6.x' into mock-numbers 2024-07-01 17:02:41 +05:30
Matej Bačo f87e2bc879 Binary support & tests for Router 2024-07-01 10:35:06 +02:00
Matej Bačo 7bdcd5c436 Multipoart support 2024-07-01 06:57:18 +00:00
loks0n bb3ee81065 chore: rename metadata to data 2024-06-28 22:42:55 +01:00
Binyamin Yawitz 87c7da0efa fix: unnecessary space 2024-06-28 11:12:24 -04:00
Khushboo Verma 66c761b73e Create execution even when logging is disabled 2024-06-28 19:51:33 +05:30
Binyamin Yawitz d58a352313 fix: Removing debug var_dump 2024-06-27 18:13:17 -04:00
Binyamin Yawitz b19d7cb35f feat: Update scheduled when deleting an execution 2024-06-27 18:12:23 -04:00
Binyamin Yawitz 3aee3122a9 Merge remote-tracking branch 'refs/remotes/origin/feat-scheduled-executions' into feat-delete-execution 2024-06-27 15:10:51 -04:00
Binyamin Yawitz 010f8d3fb8 review: Delete execution 2024-06-27 12:36:24 -04:00
Binyamin Yawitz 9172ff6b14 feat: Delete execution 2024-06-27 11:39:27 -04:00
Binyamin Yawitz 366d75fe39 feat: Delete execution 2024-06-27 11:17:14 -04:00
Khushboo Verma 0a335ed8ac Add appwrite version 2024-06-27 18:13:15 +05:30
Matej Bačo b041db0658 Fix tests 2024-06-27 10:17:01 +00:00
Matej Bačo 6411dd9fb4 Mark domain as traefik 2024-06-27 08:24:12 +00:00
loks0n d0125398f1 fix: test 2024-06-26 15:46:12 +01:00
loks0n fd12449cc3 test: fix 2024-06-26 14:01:53 +01:00
loks0n e884009075 fix: test 2024-06-26 13:14:07 +01:00
Luke B. SilverandGitHub ecf761beab Merge branch '1.6.x' into feat-new-session-alert 2024-06-26 12:44:49 +01:00
loks0n 9eb8f02b53 fix: test 2024-06-26 12:42:35 +01:00
loks0n d9434517dc Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-scheduled-executions 2024-06-26 12:30:48 +01:00
loks0n 303ce498be feat: allow custom path method body 2024-06-26 12:30:23 +01:00
Torsten DittmannandGitHub c64e9e1fab Merge pull request #8330 from appwrite/feat-vcs-get-contents
Feat: vcs.getRepositoryContents()
2024-06-26 13:13:40 +02:00
Matej Bačo d7d5fb0bd7 Improve VCS tests 2024-06-26 10:27:24 +00:00
Matej Bačo 186cef458b Implemented vcs.getRepositoryContents() 2024-06-26 10:03:27 +00:00
loks0n 878f6c86df chore: matej review p1 2024-06-26 11:01:43 +01:00
loks0n 6353de6f10 chore: fix template 2024-06-26 09:42:01 +01:00
Steven NguyenandGitHub 636497304e Merge branch '1.5.x' into chore-sync-1.5.x-into-main 2024-06-25 23:47:37 +00:00
Luke B. SilverandGitHub dab0a17082 Merge branch '1.6.x' into feat-scheduled-executions 2024-06-25 17:21:44 +01:00
Christy Jacob fa93d9d1cc chore: linter 2024-06-25 15:31:02 +00:00
Christy Jacob 7cec278d27 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into mock-numbers 2024-06-25 15:17:22 +00:00
Christy JacobandGitHub 91fe8b7a8b Merge pull request #8295 from appwrite/sync-with-main
Sync with main
2024-06-25 19:13:32 +04:00
Christy JacobandGitHub c692661429 Merge pull request #8215 from appwrite/feat-func-deploy-type
feat: Adding cli to the function deployment type
2024-06-25 17:59:42 +04:00
Khushboo Verma e5fd3619f7 Merge branch '1.6.x' into feat-opr-v4-support 2024-06-25 15:13:43 +05:30
Christy JacobandGitHub 8e5779c168 Merge pull request #8292 from appwrite/fix-certificate-emails
Fix certificate emails
2024-06-25 13:35:43 +04:00
Khushboo Verma 0edbec4f44 OPR v4 support 2024-06-25 15:03:07 +05:30
Christy JacobandGitHub c38eda3cea Merge branch '1.6.x' into feat-func-deploy-type 2024-06-25 13:31:54 +04:00
Christy Jacob c774b952de chore: flaky function test 2024-06-25 09:31:09 +00:00
loks0n 0d987045bf chore: revert fmt 2024-06-25 08:46:51 +01:00
Jake BarnbyandGitHub c63b1785ed Merge pull request #8321 from appwrite/chore-bump-console-4.3.14
Bump console to version 4.3.14
2024-06-25 15:23:54 +12:00
Steven NguyenandGitHub 827ad1446b Bump console to version 4.3.14 2024-06-25 02:37:55 +00:00
Jake BarnbyandGitHub 4ad987e4ef Merge pull request #8308 from appwrite/fix-8280-expire-error-when-creating-session
Fix 'Missing required attribute "expire"' on `users.createSession()`
2024-06-25 13:43:03 +12:00
Jake BarnbyandGitHub ef203dc075 Merge pull request #8297 from appwrite/fix-subscribers
Disable validation for subquery to prevent error
2024-06-25 13:38:47 +12:00
Jake BarnbyandGitHub 83cb303f03 Merge pull request #8317 from appwrite/fix-flaky-function-test
Fix flaky function test
2024-06-25 13:35:00 +12:00
Jake BarnbyandGitHub dc9f06976c Merge pull request #8316 from appwrite/fix-flaky-cert-test
Fix flaky certificate test
2024-06-25 13:33:50 +12:00
Steven NguyenandGitHub 3a3a0594dc fix(messaging): disable validation for subquery to prevent error
If there are more than 100 targets, the validation throws an error. This
change skips the validation so no error is thrown.
2024-06-24 23:38:41 +00:00
Steven NguyenandGitHub c6489d172f chore: bump utopia-php/database and related packages
utopia-php/database version 0.50.0 includes a new skipValidation()
function we can use to skip validation when making queries.
2024-06-24 23:38:40 +00:00
Steven NguyenandGitHub 13027fac5b fix(users): update session secret to be longer
The create session endpoint created a 6 character secret which is too
short. This changes the secret to be 256 characters which is in line
with the secret for `account.createEmailPasswordSession()`.
2024-06-24 23:21:45 +00:00
Steven NguyenandGitHub 34b2e15243 fix(users): fix expire error when creating user session
Before this, the Create session API call would throw:

> Invalid document structure: Missing required attribute "expire"

This is because the `expire` attribute is required, but it was omitted
from the document. This PR ensures the `expire` attribute is set when
creating the session document.
2024-06-24 23:21:44 +00:00
Steven NguyenandGitHub 9ccf4991e3 fix(functions): fix flaky function test
The execution is async so give the function a bit more time to execute.
2024-06-24 19:15:41 +00:00
Steven NguyenandGitHub 01fc6a699a fix(health): fix flaky certificate test
There seems to be a load balancer in front of www.google.com that has
either a certificate issuer of "Google Trust Services LLC" or
"Google Trust Services". This causes the test to fail intermittently.

This PR updates the test to check for the substring
"Google Trust Services" instead of the exact string
"Google Trust Services LLC".
2024-06-24 19:02:44 +00:00
loks0n edf7af34f1 test: session alerts 2024-06-24 14:39:47 +01:00
loks0n 0ea2438069 fix: implementation 2024-06-24 14:12:09 +01:00
loks0n baf5e5ade0 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-new-session-alert 2024-06-24 10:34:11 +01:00
loks0n fbeca34df8 fix: func 2024-06-24 10:34:06 +01:00
loks0n f3f233eb14 feat: new session alert 2024-06-21 19:21:05 +01:00
Christy Jacob 7b63f68fc3 chore: fix tests 2024-06-21 18:02:01 +00:00
Christy Jacob bf8b2ea7ae Merge branch '1.6.x' of https://github.com/appwrite/appwrite into sync-with-main 2024-06-21 16:18:19 +00:00
Steven NguyenandGitHub ae3bcb2efe Merge pull request #8271 from ItzNotABug/fix-7892-add-defaults-to-migrations
Add `default` to Collection Attributes in Migration
2024-06-21 08:46:46 -07:00
Christy JacobandGitHub 5ba99ed4e1 Merge pull request #8304 from appwrite/1.6.x-specs
1.6.x specs
2024-06-21 19:41:13 +05:30
Binyamin Yawitz 5e0000376e chore: generate specs 2024-06-21 09:56:19 -04:00
Christy Jacob a2f27ec15f Merge branch '1.5.x' of https://github.com/appwrite/appwrite into sync-with-main 2024-06-21 13:11:35 +00:00
Christy JacobandGitHub 8764a7491a Merge pull request #8296 from appwrite/refactor-usage-sn
Sync refactor-usage-sn into 1.5.x
2024-06-21 12:09:16 +05:30
Jake BarnbyandGitHub ba43358ac4 Merge pull request #8298 from appwrite/fix-migrate-validation
Disable validation for project DBs during migration
2024-06-21 13:46:55 +12:00
Steven NguyenandGitHub 72f8b2001f fix: Disable validation for project DBs during migration
Skip the validation so that queries that typically fail can pass. This
is fine because the validation is failing on internal attributes such
as an $id for cache key with * and $id for attributes that are too long
because we prefix the $id with the internal database and collection id.
2024-06-20 23:00:49 +00:00
Christy JacobandGitHub 991a22f642 Merge pull request #8290 from appwrite/1.5.x
Sync 1.5.x into refactor-usage-sn
2024-06-21 00:38:21 +05:30
Christy Jacob 330e7a93c6 chore: composer update 2024-06-20 15:27:16 +00:00
Christy Jacob fdf44c7bab Merge branch 'main' of https://github.com/appwrite/appwrite into sync-with-main 2024-06-20 15:26:17 +00:00
Christy Jacob c5fa7eb7dc Merge branch '1.6.x' of github.com:appwrite/appwrite into mock-numbers 2024-06-20 20:53:06 +05:30
Christy Jacob 3b791f0b6f chore: linter 2024-06-20 20:44:02 +05:30
Christy Jacob 814b59a13a Merge branch 'main' of github.com:appwrite/appwrite into mock-numbers 2024-06-20 20:42:29 +05:30
Christy Jacob 8d5b79c13d fix: undefined variable 2024-06-20 20:31:54 +05:30
Christy Jacob 1b1cfc38c8 fix: undefined variable 2024-06-20 20:31:20 +05:30
Christy Jacob 392aaca681 chore: fix tests 2024-06-20 20:19:56 +05:30
Christy Jacob e436ad721f chore: linter 2024-06-20 20:02:36 +05:30
Christy Jacob e32e7063bb chore: linter 2024-06-20 20:01:33 +05:30
Bradley Schofield ea95e80cbf Use localised subject string 2024-06-20 17:47:16 +09:00
ItzNotABug 95e463a7ae Merge branch 'fix-7892-add-defaults-to-migrations' of https://github.com/ItzNotABug/appwrite.source into fix-7892-add-defaults-to-migrations 2024-06-20 14:15:17 +05:30
DarshanandGitHub 441811e808 Merge branch '1.5.x' into fix-7892-add-defaults-to-migrations 2024-06-20 14:15:09 +05:30
ItzNotABug ee6137db3d Merge branch 'fix-7892-add-defaults-to-migrations' of https://github.com/ItzNotABug/appwrite.source into fix-7892-add-defaults-to-migrations 2024-06-20 14:14:48 +05:30
ItzNotABug eb36ae5c61 add: default to V20 migration. 2024-06-20 14:14:21 +05:30
Bradley Schofield dbfae94552 Fix certificate emails 2024-06-20 17:28:21 +09:00
Steven NguyenandGitHub e3bc1887a4 Merge pull request #8263 from appwrite/docs-clarify-optional-url
docs: update text description in request table
2024-06-19 09:45:53 -07:00
Christy JacobandGitHub 691ce68636 Merge pull request #8249 from appwrite/update-platform
update to use latest platform with module
2024-06-19 12:21:07 +05:30
Christy JacobandGitHub c5e788c2ef Merge pull request #7888 from appwrite/feat-security-emails
New environment for certificates and security emails
2024-06-19 12:20:55 +05:30
Christy JacobandGitHub 868e57a9a3 Merge pull request #8281 from appwrite/main
Sync main into 1.5.x
2024-06-19 12:18:12 +05:30
DarshanandGitHub e0779013d0 Merge branch 'main' into fix-7892-add-defaults-to-migrations 2024-06-19 12:10:24 +05:30
RichardandGitHub 0dc553f31d Merge branch 'main' into docs-clarify-optional-url 2024-06-18 09:12:32 -04:00
Steven NguyenandGitHub 4b24848f22 Merge pull request #8236 from 2002Bishwajeet/fix-user-model
Fix: Don't set the target field if the existing target document is false
2024-06-17 17:11:27 -07:00
Steven NguyenandGitHub 2e187107da Merge pull request #8269 from appwrite/docs-fix-storage-hyperlink
docs: fix hyperlink in storage docs
2024-06-17 16:58:51 -07:00
Steven NguyenandGitHub 120b5aee47 Merge pull request #8228 from ItzNotABug/add-test-for-user-id
Add Test to Validate Headers aren't Overridden
2024-06-17 16:09:50 -07:00
Binyamin Yawitz 74360abdb0 test: Test function creation type from CLI 2024-06-17 14:18:01 -04:00
Binyamin YawitzandGitHub 6cfada946f Merge branch '1.6.x' into feat-func-deploy-type 2024-06-17 13:53:21 -04:00
loks0n 67fddeb4fa fix: path and method 2024-06-17 14:23:14 +01:00
loks0n 5f144f91ad chore: matej review 2024-06-17 14:12:02 +01:00
loks0n 83ffc41d92 chore: fmt 2024-06-17 13:46:18 +01:00
loks0n 2f0f7bf9c7 fix: test 2024-06-17 13:44:12 +01:00
Christy Jacob fa070c5e1e chore: sync & updates 2024-06-16 22:41:13 +00:00
Christy Jacob 9e1541d96a Merge branch 'main' of https://github.com/appwrite/appwrite into mock-numbers 2024-06-16 07:28:22 +00:00
Christy Jacob a912bbe374 chore: update variable name 2024-06-16 07:19:10 +00:00
Luke B. SilverandGitHub 9341c9d76b Merge branch '1.6.x' into feat-scheduled-executions 2024-06-14 14:07:41 +01:00
loks0n 10e37bb8ca test: fix 2024-06-14 14:01:04 +01:00
ItzNotABug 4fd94fd7b1 fix: add defaults to collection attributes. 2024-06-14 15:37:30 +05:30
choir27 84d9b4befe docs: fix hyperlink in storage code snippet 2024-06-13 09:59:20 -04:00
Christy JacobandGitHub 83d60612f2 Merge pull request #7605 from appwrite/feat-cancel-build-deployment
Added cancel build endpoint
2024-06-13 19:06:03 +05:30
loks0n 6f38ef3a18 test: poll scheduled function 2024-06-13 14:22:38 +01:00
loks0n dff9bed882 test: increase delay 2024-06-13 13:41:37 +01:00
loks0n a83d125f54 chore: adjust timers 2024-06-13 13:34:10 +01:00
loks0n cfdd40dbeb test: execution is completed 2024-06-13 09:36:01 +01:00
loks0n 865b12ba9d chore: exception type 2024-06-13 09:24:51 +01:00
choir27 897ae1c859 docs: update text description in request table 2024-06-12 10:47:48 -04:00
loks0n 0a78d41ecb chore: remove endTime 2024-06-12 13:50:18 +01:00
loks0n 7559c9af68 chore: us spelling of canceled 2024-06-12 11:21:48 +01:00
loks0n d6bfe084c1 fix: tests 2024-06-11 17:27:22 +01:00
loks0n 682b854aad test: fix functions test 2024-06-11 17:01:56 +01:00
Bishwajeet ParhiandGitHub eca2bbdb5d Merge branch 'main' into fix-user-model 2024-06-11 21:18:11 +05:30
Bishwajeet Parhi 190560b8ef chore: append attributes instead of replace 2024-06-11 21:17:25 +05:30
Steven NguyenandGitHub f91a8a9135 Merge pull request #8179 from navjotNSK/bug-7880-location_hk_issue
Adding hk in translations
2024-06-11 11:00:00 -04:00
loks0n 1269397475 chore: fmt 2024-06-11 15:50:50 +01:00
loks0n 853ffe804e feat: improve duration calc 2024-06-11 15:48:55 +01:00
loks0n 67b32ecb05 test: cancel deployment 2024-06-11 15:41:13 +01:00
Steven NguyenandGitHub 0376189dbc Merge pull request #8239 from ItzNotABug/8159-delete-expired-targets-per-project
Delete Expired Targets Per Project
2024-06-11 10:24:14 -04:00
loks0n 1e9ced2878 test: scheduled status 2024-06-11 13:57:03 +01:00
loks0n 7e8f72d267 feat: implement scheduledAt in schedule 2024-06-11 13:39:38 +01:00
Damodar Lohani 26413566dc fix format 2024-06-09 11:36:35 +00:00
Damodar Lohani c179e89fdf update to use latest platform with module 2024-06-09 01:03:40 +00:00
Matej Bačo b8b81a9bd1 WIP: Schedulded executions 2024-06-07 19:05:29 +00:00
Khushboo Verma 97786a05ff Updated cancelled build duration 2024-06-07 20:29:24 +05:30
Khushboo Verma d233f1ac64 Update duration and endTime 2024-06-07 16:53:33 +05:30
Khushboo Verma c2c6d337ed Resolved merge conflict 2024-06-06 19:39:16 +05:30
Christy JacobandGitHub 3e47b43908 Merge pull request #8214 from appwrite/chore-update-create-build-endpoint
Remove build id from api endpoint
2024-06-06 17:53:05 +05:30
ItzNotABug a34ec72048 fix: delete expired targets per project. 2024-06-06 12:01:32 +05:30
Bishwajeet Parhi 485b8eeb8b chore: misc test assertions improvements 2024-06-05 23:53:31 +05:30
Bishwajeet Parhi cccda2a46c fix: Don't set target attribute if no existing Target found 2024-06-05 23:34:01 +05:30
Eldad A. FuxandGitHub f91a68c8a0 Merge pull request #8207 from appwrite/feat-users-create-jwt
Feat: users.createJWT()
2024-06-04 07:00:32 -04:00
Eldad A. FuxandGitHub 2dd8c49698 Merge pull request #8213 from appwrite/feat-projects-createJWT
Feat:  projects.createJWT (dynamic keys)
2024-06-04 07:00:18 -04:00
Matej BačoandGitHub 9279468c27 Merge branch '1.6.x' into feat-users-create-jwt 2024-06-04 09:43:14 +02:00
Matej BačoandGitHub c279745259 Merge branch '1.6.x' into feat-projects-createJWT 2024-06-04 09:43:12 +02:00
Damodar Lohani f762d66c5e fix typo 2024-06-04 01:31:05 +00:00
Damodar LohaniandGitHub 66ac96580b Merge branch 'main' into feat-security-emails 2024-06-04 06:51:24 +05:45
ItzNotABug 37da4a0cd3 review comments. 2024-06-03 14:19:59 +05:30
ItzNotABug b54a599960 update test name for clarity. 2024-06-03 14:13:08 +05:30
ItzNotABug 52f5a5c40c update test. 2024-06-03 14:10:20 +05:30
Khushboo Verma d7a40e2b4d Remove the code to fetch build document 2024-06-02 23:14:41 +05:30
Khushboo Verma c60b8b05a3 Merge branch 'feat-cancel-build-deployment' of github.com:appwrite/appwrite into feat-cancel-build-deployment 2024-06-02 23:05:18 +05:30
Khushboo Verma 2643202c97 Replaced build1 with build 2024-06-02 23:04:49 +05:30
ItzNotABug 1a42d118f6 add: test to validate user-id isn't overridden. 2024-06-01 19:18:35 +05:30
Navjot SinghandGitHub c07c7889d3 Merge branch 'main' into bug-7880-location_hk_issue 2024-06-01 17:29:57 +05:30
Khushboo VermaandGitHub beb3fe1d2b Merge branch '1.6.x' into chore-update-create-build-endpoint 2024-05-31 14:17:34 +05:30
Khushboo VermaandGitHub 1beaeba214 Merge branch '1.6.x' into feat-cancel-build-deployment 2024-05-31 14:16:14 +05:30
Christy JacobandGitHub b135c69bd6 Merge pull request #8187 from appwrite/feat-unified-app-logger-config
feat(logger): Unifying logger environment variable and adapting to th…
2024-05-31 03:38:54 +04:00
Khushboo Verma 0eabff5faf Refactor builds document creation 2024-05-31 01:53:29 +05:30
Khushboo Verma cfec6c974f Updated spec 2024-05-30 22:40:09 +05:30
Khushboo Verma d3c2e9db1f Add comment for clarity 2024-05-30 22:33:37 +05:30
Binyamin Yawitz ca7f222640 feat: Adding cli to the function deployment type 2024-05-29 12:55:14 -04:00
Khushboo Verma 94e7669b41 Remove build id from api endpoint 2024-05-29 17:48:52 +05:30
Matej Bačo 72e3898f09 CI/CD trigger 2024-05-29 11:45:32 +02:00
Matej Bačo 49b2bcf462 Implement JWT dynamic keys test 2024-05-29 09:33:39 +00:00
Khushboo Verma edafeece9b Add console info before return 2024-05-29 14:39:57 +05:30
Matej Bačo b0317fb19d Implement project dynamic (JWT) keys 2024-05-29 08:57:25 +00:00
Matej Bačo 3b8799353d PR review changes 2024-05-29 07:51:51 +00:00
Matej BačoandGitHub d9745ca249 Merge branch '1.6.x' into feat-users-create-jwt 2024-05-29 09:33:09 +02:00
11827216ff Update tests/e2e/Services/Users/UsersBase.php
Co-authored-by: Steven Nguyen <1477010+stnguyen90@users.noreply.github.com>
2024-05-29 09:32:58 +02:00
2dac8bc7ed Update app/controllers/api/users.php
Co-authored-by: Steven Nguyen <1477010+stnguyen90@users.noreply.github.com>
2024-05-29 09:31:08 +02:00
Binyamin YawitzandGitHub ccafc06d93 Merge branch '1.6.x' into feat-unified-app-logger-config 2024-05-28 10:18:39 -04:00
Binyamin Yawitz b98930e5a0 feat(logger): Adding fallback comment 2024-05-28 10:18:29 -04:00
Khushboo Verma a465b23716 Merge branch '1.6.x' into feat-cancel-build-deployment 2024-05-28 19:06:09 +05:30
Christy JacobandGitHub 1cb490a36e Merge pull request #8210 from appwrite/feat-track-build-metrics-1
Add metrics for successful and failed builds
2024-05-28 17:28:27 +04:00
Khushboo Verma 0b3c188bbf Add metrics for successful and failed builds 2024-05-28 18:29:28 +05:30
Matej Bačo 711e26c605 Relay on lib to verify JWT expiry 2024-05-28 10:59:53 +00:00
Matej Bačo b1ff989c3f Implement tests, fix JWT maxAge 2024-05-28 09:25:54 +00:00
Matej Bačo 613677e9f7 Implement users.createJWT 2024-05-27 20:04:50 +00:00
Khushboo Verma 93b4bfe4df Add one more cancel check 2024-05-27 18:31:35 +05:30
Khushboo Verma 63a5b9eed3 Revert changes in app/console 2024-05-27 18:25:11 +05:30
Khushboo Verma b880a0a7b2 Remove :buildId from endpoint as discussed 2024-05-27 18:21:28 +05:30
Khushboo Verma 4e8855fad3 Merge branch '1.6.x' into feat-cancel-build-deployment 2024-05-27 18:03:59 +05:30
Christy JacobandGitHub 3ae4ed2604 Merge pull request #7512 from appwrite/feat-function-tokens
Feat: API Tokens
2024-05-24 14:45:11 +04:00
Navjot SinghandGitHub ade0472a5f Merge branch 'main' into bug-7880-location_hk_issue 2024-05-24 12:24:08 +05:30
Binyamin Yawitz ebe386acd9 feat(logger): Unifying logger environment variable and adapting to the latest Logger version 2024-05-23 12:44:19 -04:00
Khushboo Verma f0de4c6eac Resolved merge conflict 2024-05-23 18:34:48 +05:30
navjotNSK fcbab74f04 All translations added. 2024-05-23 15:55:57 +05:30
navjotNSK 2621d948c6 All translations added. 2024-05-23 15:53:59 +05:30
navjotNSK c29fc5e72e hk added till cs.json 2024-05-22 16:43:40 +05:30
Matej Bačo a115f878b3 Merge remote-tracking branch 'origin/1.6.x' into feat-function-tokens 2024-05-21 10:28:21 +00:00
Christy JacobandGitHub 6eef9a8394 Merge pull request #8163 from appwrite/sync-main
Sync 1.6.x with main
2024-05-21 14:05:48 +04:00
Christy Jacob 984da4f4ac Merge branch 'main' of https://github.com/appwrite/appwrite into sync-main 2024-05-20 11:05:24 +00:00
Matej BačoandGitHub d57c459e6f Merge branch '1.6.x' into feat-function-tokens 2024-05-17 18:16:43 +02:00
Matej Bačo 2e1f67245d Fix bugs, add old api key test 2024-05-16 08:39:15 +00:00
Matej Bačo 37176e7865 Backwards compatible approach 2024-05-14 12:33:49 +00:00
Matej Bačo 4d249c5d36 Bug fix 2024-05-14 12:27:35 +00:00
Matej Bačo 9846b573bd Merge remote-tracking branch 'origin/main' into feat-function-tokens 2024-05-14 11:59:43 +00:00
Matej Bačo 54c953d559 Add prefix of api key types 2024-05-14 11:58:31 +00:00
Matej Bačo 5002b0f3fa PR review changes 2024-05-09 11:50:45 +00:00
Matej Bačo 9252b66493 Add scope tests 2024-05-06 11:27:28 +00:00
Matej Bačo 3a3d5b61a6 PR review changes 2024-05-06 09:55:59 +00:00
Matej Bačo 448b0a5607 Re-add api token auth 2024-05-06 08:35:27 +00:00
Matej Bačo 9a0d9cee15 Merge remote-tracking branch 'origin/main' into feat-function-tokens 2024-05-06 08:35:14 +00:00
Damodar Lohani 14a0dd3166 Merge remote-tracking branch 'origin/main' into feat-security-emails 2024-05-06 01:04:44 +00:00
Damodar Lohani be1c147658 update console 2024-05-06 01:00:20 +00:00
Damodar Lohani b61e0f08f6 make required 2024-05-06 00:58:23 +00:00
Khushboo Verma fba60f974e Throw exception for cancellation if build is already complete 2024-04-30 15:46:58 +05:30
Khushboo Verma ea9c71757c Fix tests 2024-04-30 00:19:14 +05:30
Khushboo Verma e2b2df1e31 Fix formatting 2024-04-29 21:18:36 +05:30
Khushboo Verma 0dd908b14c Fix formatting 2024-04-29 19:47:29 +05:30
Khushboo Verma 3123bf6b72 Fix formatting 2024-04-29 19:16:17 +05:30
Khushboo Verma 2b69f0b7e2 Fix formatting 2024-04-29 19:14:37 +05:30
Khushboo Verma 564fe96eec Resolve merge conflicts 2024-04-29 19:05:51 +05:30
Damodar Lohani 00c45c2507 new environment for certificates and security emails 2024-03-31 03:32:55 +00:00
Christy JacobandGitHub 88abc6511a Merge pull request #7607 from appwrite/feat-function-execution-filters
Allow execution filter attributes
2024-03-25 19:28:24 +05:30
Khushboo Verma f4cf7ba414 Merge branch 'main' into feat-function-execution-filters 2024-03-20 23:51:27 +05:30
Khushboo Verma 974104f8b0 address PR comments 2024-03-04 21:30:06 +01:00
Khushboo Verma a2b19ef116 Merge branch 'main' into feat-cancel-build-deployment 2024-03-01 18:27:34 +05:30
Khushboo Verma b552d052da Update index length 2024-02-29 19:51:13 +05:30
Khushboo Verma 1d282da6b3 Update index length 2024-02-29 19:35:58 +05:30
Khushboo Verma f8f54271d1 Fix execution indexes 2024-02-29 18:39:38 +05:30
Khushboo Verma c105cfda1a Merge branch 'main' into feat-function-execution-filters 2024-02-29 18:20:26 +05:30
Khushboo Verma 56760b14cb Restore create build endpoint 2024-02-26 15:37:47 +05:30
Khushboo Verma 727fe2c9a9 Update newBuild condition 2024-02-26 12:33:54 +05:30
Khushboo Verma 353092ba00 Remove create build endpoint 2024-02-26 00:05:00 +05:30
Khushboo Verma ed4773c2d9 Merge branch 'main' into feat-cancel-build-deployment 2024-02-25 19:14:42 +05:30
Khushboo Verma 0e3a354f1f Create build document in API instead of worker 2024-02-25 19:13:55 +05:30
Khushboo Verma c2e7497c76 Merge branch 'main' into feat-function-execution-filters 2024-02-25 16:00:14 +05:30
Khushboo Verma f1c6790240 Add indexes for execution attributes 2024-02-25 15:57:39 +05:30
Khushboo Verma a02a03e322 Fetch build document from DB 2024-02-24 00:13:11 +05:30
Christy JacobandGitHub 35562715ad Merge branch 'main' into mock-numbers 2024-02-20 12:27:14 +05:30
Khushboo Verma 3471359735 Allow execution filter attributes 2024-02-19 10:24:12 +05:30
Khushboo Verma ada1f2abb7 Mark cancelled first, then kill container 2024-02-19 00:44:21 +05:30
Khushboo Verma 12f2e761ef Updated description and response model 2024-02-18 23:25:57 +05:30
Khushboo Verma 6ebca949c0 Fixed composer error 2024-02-18 18:04:16 +05:30
Khushboo Verma f5919aad4a Merge branch 'main' into feat-cancel-build-deployment 2024-02-18 18:03:21 +05:30
Khushboo Verma ed3d598f2e Added cancel build endpoint 2024-02-18 18:01:25 +05:30
Christy Jacob abad778a1e chore: linter 2024-02-14 17:22:43 +00:00
Christy Jacob 48e87cdc7c Merge branch 'main' of github.com:appwrite/appwrite into mock-numbers 2024-02-14 21:24:53 +05:30
Christy Jacob 28a16173ab feat: add tests 2024-02-14 12:10:19 +05:30
Christy Jacob be92aa92e5 chore: rename files 2024-02-11 20:28:05 +05:30
Christy Jacob 89dcc34453 feat: initial commit 2024-02-11 20:21:19 +05:30
Matej Bačo ba6f5c1a57 Revert unnessessary changes 2024-01-29 11:21:51 +00:00
Matej Bačo 36ce992e9f Add API Tokens 2024-01-29 11:15:07 +00:00
148 changed files with 3351 additions and 480 deletions
+5 -4
View File
@@ -4,19 +4,21 @@ _APP_LOCALE=en
_APP_WORKER_PER_CORE=6
_APP_CONSOLE_WHITELIST_ROOT=disabled
_APP_CONSOLE_WHITELIST_EMAILS=
_APP_CONSOLE_SESSION_ALERTS=enabled
_APP_CONSOLE_WHITELIST_IPS=
_APP_CONSOLE_COUNTRIES_DENYLIST=AQ
_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io
_APP_EMAIL_SECURITY=security@appwrite.io
_APP_EMAIL_CERTIFICATES=certificates@appwrite.io
_APP_SYSTEM_RESPONSE_FORMAT=
_APP_OPTIONS_ABUSE=disabled
_APP_OPTIONS_ROUTER_PROTECTION=disabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=localhost
_APP_DOMAIN=traefik
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_TARGET=localhost
_APP_REDIS_HOST=redis
@@ -84,7 +86,6 @@ _APP_USAGE_AGGREGATION_INTERVAL=30
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_USAGE_STATS=enabled
_APP_LOGGING_PROVIDER=
_APP_LOGGING_CONFIG=
_APP_GRAPHQL_MAX_BATCH_SIZE=10
_APP_GRAPHQL_MAX_COMPLEXITY=250
@@ -104,4 +105,4 @@ _APP_MESSAGE_SMS_TEST_DSN=
_APP_MESSAGE_EMAIL_TEST_DSN=
_APP_MESSAGE_PUSH_TEST_DSN=
_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
_APP_PROJECT_REGIONS=default
_APP_PROJECT_REGIONS=default
+1 -1
View File
@@ -1,4 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 4.3.5
branch = 1.6.x
+1
View File
@@ -79,6 +79,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/migrate && \
chmod +x /usr/local/bin/realtime && \
chmod +x /usr/local/bin/schedule-functions && \
chmod +x /usr/local/bin/schedule-executions && \
chmod +x /usr/local/bin/schedule-messages && \
chmod +x /usr/local/bin/sdks && \
chmod +x /usr/local/bin/specs && \
+45 -2
View File
@@ -3029,7 +3029,7 @@ $projectCollections = array_merge([
'size' => 8,
'signed' => true,
'required' => false,
'default' => 'v3',
'default' => 'v4',
'array' => false,
'filters' => [],
],
@@ -3054,7 +3054,18 @@ $projectCollections = array_merge([
'required' => false,
'default' => null,
'filters' => [],
]
],
[
'$id' => ID::custom('scopes'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
@@ -3867,6 +3878,27 @@ $projectCollections = array_merge([
'lengths' => [32],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_requestMethod'),
'type' => Database::INDEX_KEY,
'attributes' => ['requestMethod'],
'lengths' => [128],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_requestPath'),
'type' => Database::INDEX_KEY,
'attributes' => ['requestPath'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_deployment'),
'type' => Database::INDEX_KEY,
'attributes' => ['deploymentId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_responseStatusCode'),
'type' => Database::INDEX_KEY,
@@ -4518,6 +4550,17 @@ $consoleCollections = array_merge([
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('data'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => new \stdClass(),
'array' => false,
'filters' => ['json', 'encrypt'],
],
[
'$id' => ID::custom('active'),
'type' => Database::VAR_BOOLEAN,
+16
View File
@@ -332,6 +332,11 @@ return [
'description' => 'API key and session used in the same request. Use either `setSession` or `setKey`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
'code' => 403,
],
Exception::API_KEY_EXPIRED => [
'name' => Exception::API_KEY_EXPIRED,
'description' => 'The dynamic API key has expired. Please don\'t use dynamic API keys for more than duration of the execution.',
'code' => 401,
],
/** Teams */
Exception::TEAM_NOT_FOUND => [
@@ -541,6 +546,11 @@ return [
'description' => 'Build with the requested ID is already in progress. Please wait before you can retry.',
'code' => 400,
],
Exception::BUILD_ALREADY_COMPLETED => [
'name' => Exception::BUILD_ALREADY_COMPLETED,
'description' => 'Build with the requested ID is already completed and cannot be canceled.',
'code' => 400,
],
/** Deployments */
Exception::DEPLOYMENT_NOT_FOUND => [
@@ -556,6 +566,12 @@ return [
'code' => 404,
],
Exception::EXECUTION_IN_PROGRESS => [
'name' => Exception::EXECUTION_IN_PROGRESS,
'description' => 'Can\'t delete ongoing execution. Please wait for execution to finish before deleting it.',
'code' => 400,
],
/** Databases */
Exception::DATABASE_NOT_FOUND => [
'name' => Exception::DATABASE_NOT_FOUND,
@@ -4,7 +4,9 @@
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
<tr>
<td align="center" style="border-radius: 8px; background-color: #ffffff;">
<p style="text-align: start; font-size: 14px; font-family: Inter; color: #414146; text-decoration: none; border-radius: 8px; padding: 32px; border: 1px solid #EDEDF0; display: inline-block; word-break: break-word;">{{error}}</p>
<p style="text-align: start; font-size: 14px; font-family: Inter; color: #414146; text-decoration: none; border-radius: 8px; padding: 32px; border: 1px solid #EDEDF0; display: inline-block; word-break: break-word;">
{{error}}
</p>
</td>
</tr>
</table>
@@ -0,0 +1,14 @@
<p>{{hello}},</p>
<p>{{body}}</p>
<ol>
<li>{{listDevice}}</li>
<li>{{listIpAddress}}</li>
<li>{{listCountry}}</li>
</ol>
<p>{{footer}}</p>
<p style="margin-bottom: 0px;">{{thanks}}</p>
<p style="margin-top: 0px;">{{signature}}</p>
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroasië",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "غرينادا",
"countries.gt": "غواتيمالا",
"countries.gy": "غيانا",
"countries.hk": "هونغ كونغ",
"countries.hn": "هندوراس",
"countries.hr": "كرواتيا",
"countries.ht": "هايتي",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "গ্ৰেনাডা",
"countries.gt": "গুয়াতেমালা",
"countries.gy": "গায়ানা",
"countries.hk": "হং কং",
"countries.hn": "হণ্ডুৰাছ",
"countries.hr": "ক্ৰোৱেছিয়া",
"countries.ht": "হাইতি",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Qrenada",
"countries.gt": "Qvatemala",
"countries.gy": "Guyana",
"countries.hk": "Honq Konq",
"countries.hn": "Honduras",
"countries.hr": "Xorvatiya",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Грэнада",
"countries.gt": "Гватэмала",
"countries.gy": "Гаяна",
"countries.hk": "Гонконг",
"countries.hn": "Гандурас",
"countries.hr": "Харватыя",
"countries.ht": "Гаіці",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Гренада",
"countries.gt": "Гватемала",
"countries.gy": "Гвиана",
"countries.hk": "Хонг Конг",
"countries.hn": "Хондурас",
"countries.hr": "Хърватия",
"countries.ht": "Хаити",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ग्रेनाडा",
"countries.gt": "ग्वाटेमाला",
"countries.gy": "गयाना",
"countries.hk": "हांगकांग",
"countries.hn": "होंडुरस",
"countries.hr": "क्रोएशिया",
"countries.ht": "हैती",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "গ্রেনাডা",
"countries.gt": "গুয়াতেমালা",
"countries.gy": "গায়ানা",
"countries.hk": "হংকং",
"countries.hn": "হন্ডুরাস",
"countries.hr": "ক্রোয়েশিয়া",
"countries.ht": "হাইতি",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Gvajana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Hrvatska",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Granada",
"countries.gt": "Guatemala",
"countries.gy": "Guaiana",
"countries.hk": "Hong Kong",
"countries.hn": "Hondures",
"countries.hr": "Croàcia",
"countries.ht": "Haití",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Chorvatsko",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroatien",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroatien",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Γρενάδα",
"countries.gt": "Γουατεμάλα",
"countries.gy": "Γουιάνα",
"countries.hk": "Χονγκ Κονγκ",
"countries.hn": "Ονδούρα",
"countries.hr": "Κροατία",
"countries.ht": "Αϊτή",
+17 -1
View File
@@ -18,6 +18,15 @@
"emails.magicSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.",
"emails.magicSession.thanks": "Thanks,",
"emails.magicSession.signature": "{{project}} team",
"emails.sessionAlert.subject": "New session alert for {{project}}",
"emails.sessionAlert.hello":"Hello {{user}}",
"emails.sessionAlert.body": "We're writing to inform you that a new session has been initiated on your {{b}}{{project}}{{/b}} account, on {{b}}{{dateTime}}{{/b}}. \nHere are the details of the new session: ",
"emails.sessionAlert.listDevice": "Device: {{b}}{{device}}{{/b}}",
"emails.sessionAlert.listIpAddress": "IP Address: {{b}}{{ipAddress}}{{/b}}",
"emails.sessionAlert.listCountry": "Country: {{b}}{{country}}{{/b}}",
"emails.sessionAlert.footer": "If you didn't request the sign in, you can safely ignore this email. If you suspect unauthorized activity, please secure your account immediately.",
"emails.sessionAlert.thanks": "Thanks,",
"emails.sessionAlert.signature": "{{project}} team",
"emails.otpSession.subject": "OTP for {{project}} Login",
"emails.otpSession.hello": "Hello {{user}}",
"emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.",
@@ -34,7 +43,7 @@
"emails.recovery.subject": "Password Reset",
"emails.recovery.hello": "Hello {{user}}",
"emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.",
"emails.recovery.footer": "If you didnt ask to reset your password, you can ignore this message.",
"emails.recovery.footer": "If you didn't ask to reset your password, you can ignore this message.",
"emails.recovery.thanks": "Thanks",
"emails.recovery.signature": "{{project}} team",
"emails.invitation.subject": "Invitation to %s Team at %s",
@@ -43,6 +52,12 @@
"emails.invitation.footer": "If you are not interested, you can ignore this message.",
"emails.invitation.thanks": "Thanks",
"emails.invitation.signature": "{{project}} team",
"emails.certificate.subject": "Certificate failure for %s",
"emails.certificate.hello": "Hello",
"emails.certificate.body": "Certificate for your domain '{{domain}}' could not be generated. This is attempt no. {{attempt}}, and the failure was caused by: {{error}}",
"emails.certificate.footer": "Your previous certificate will be valid for 30 days since the first failure. We highly recommend investigating this case, otherwise your domain will end up without a valid SSL communication.",
"emails.certificate.thanks": "Thanks",
"emails.certificate.signature": "{{project}} team",
"sms.verification.body": "{{secret}}",
"locale.country.unknown": "Unknown",
"countries.af": "Afghanistan",
@@ -117,6 +132,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croatia",
"countries.ht": "Haiti",
+1
View File
@@ -99,6 +99,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croatia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Granada",
"countries.gt": "Guatemala",
"countries.gy": "Guayana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croacia",
"countries.ht": "Haití",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "گرنادا",
"countries.gt": "گواتمالا",
"countries.gy": "گویان",
"countries.hk": "هنگ کنگ",
"countries.hn": "هندوراس",
"countries.hr": "کرواسی",
"countries.ht": "هائیتی",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroatia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Gujana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroatia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenade",
"countries.gt": "Guatemala",
"countries.gy": "Guyane",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croatie",
"countries.ht": "Haïti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Greanáda",
"countries.gt": "Guatamala",
"countries.gy": "An Ghuáin",
"countries.hk": "Hong Kong",
"countries.hn": "Hondúras",
"countries.hr": "An Chróit",
"countries.ht": "Háítí",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ગ્રેનાડા",
"countries.gt": "ગ્વાટેમાલા",
"countries.gy": "ગુયાના",
"countries.hk": "હોંગ કોંગ",
"countries.hn": "હોન્ડુરાસ",
"countries.hr": "ક્રોએશિયા",
"countries.ht": "હૈતી",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "גרנדה",
"countries.gt": "גואטמלה",
"countries.gy": "גיאנה",
"countries.hk": "הונג קונג",
"countries.hn": "הונדורס",
"countries.hr": "קרואטיה",
"countries.ht": "האיטי",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ग्रेनाडा",
"countries.gt": "ग्वाटेमाला",
"countries.gy": "गयाना",
"countries.hk": "हांग कांग",
"countries.hn": "होंडुरस",
"countries.hr": "क्रोएशिया",
"countries.ht": "हैती",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Gvatemala",
"countries.gy": "Gvajana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Hrvatska",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Horvátország",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Գրենադա",
"countries.gt": "Գվատեմալա",
"countries.gy": "Գայանա",
"countries.hk": "Հոնգ Կոնգ",
"countries.hn": "Գոնդուրաս",
"countries.hr": "Խորվաթիա",
"countries.ht": "Հաիթի",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroasia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Gvatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Hondúras",
"countries.hr": "Króatía",
"countries.ht": "Haítí",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croazia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "グレナダ",
"countries.gt": "グアテマラ",
"countries.gy": "ガイアナ",
"countries.hk": "香港",
"countries.hn": "ホンジュラス",
"countries.hr": "クロアチア",
"countries.ht": "ハイチ",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croatia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ហ្គ្រេណាដា",
"countries.gt": "ហ្គាតេម៉ាឡា",
"countries.gy": "ហ្គីយ៉ាណា",
"countries.hk": "ហុងកុង",
"countries.hn": "ហុងឌូរ៉ាស",
"countries.hr": "ក្រូអាត",
"countries.ht": "ហៃទី",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ಗ್ರೆನಡಾ",
"countries.gt": "ಗ್ವಾಟೆಮಾಲಾ",
"countries.gy": "ಗಯಾನಾ",
"countries.hk": "ಹಾಂಗ್ ಕಾಂಗ್",
"countries.hn": "ಹೊಂಡುರಾಸ್",
"countries.hr": "ಕ್ರೊಯೇಷಿಯಾ",
"countries.ht": "ಹೈಟಿ",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "그레나다",
"countries.gt": "과테말라",
"countries.gy": "기아나",
"countries.hk": "홍콩",
"countries.hn": "온두라스",
"countries.hr": "크로아티아",
"countries.ht": "아이티",
+2 -1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croatia",
"countries.ht": "Haiti",
@@ -250,4 +251,4 @@
"emails.certificate.thanks": "Gratias",
"emails.certificate.signature": "team {{project}}",
"sms.verification.body": "{{secret}}"
}
}
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Gvatemala",
"countries.gy": "Gajana",
"countries.hk": "Honkongas",
"countries.hn": "Hondūras",
"countries.hr": "Kroatija",
"countries.ht": "Haitis",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Gvatemala",
"countries.gy": "Gajāna",
"countries.hk": "Honkonga",
"countries.hn": "Hondurasa",
"countries.hr": "Horvātija",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ഗ്രെനാഡ",
"countries.gt": "ഗ്വാട്ടിമാല",
"countries.gy": "ഗയാന",
"countries.hk": "ഹോങ്കോങ്",
"countries.hn": "ഹോണ്ടുറാസ്",
"countries.hr": "ക്രോയേഷ്യ",
"countries.ht": "ഹെയ്തി",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ग्रेनाडा",
"countries.gt": "ग्वाटेमाला",
"countries.gy": "गुयाना",
"countries.hk": "हाँगकांग",
"countries.hn": "होंडुरास",
"countries.hr": "क्रोएशिया",
"countries.ht": "हैती",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Granada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroatia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroatia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ग्रेनेडा",
"countries.gt": "ग्वाटेमाला",
"countries.gy": "गुयाना",
"countries.hk": "हाँगकाँग",
"countries.hn": "होन्डुरस",
"countries.hr": "क्रोएशिया",
"countries.ht": "हैती",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hongkong",
"countries.hn": "Honduras",
"countries.hr": "Croatië",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroatia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ଗ୍ରେନାଡା",
"countries.gt": "ଗୁଆଟେମାଲା",
"countries.gy": "ଗୁଇନ୍ଦା",
"countries.hk": "ହଂ କଙ୍ଗ",
"countries.hn": "ହୋଣ୍ଡୁରାସ୍",
"countries.hr": "କ୍ରୋଏସିଆ",
"countries.ht": "ହାଇତି",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ਗ੍ਰੇਨਾਡਾ",
"countries.gt": "ਗੁਆਟੇਮਾਲਾ",
"countries.gy": "ਗੇਆਨਾ",
"countries.hk": "ਹਾਂਗ ਕਾਂਗ",
"countries.hn": "ਹੌਂਡੂਰਸ",
"countries.hr": "ਕਰੋਸ਼ੀਆ",
"countries.ht": "ਹੈਤੀ",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Gwatemala",
"countries.gy": "Gujana",
"countries.hk": "Hongkong",
"countries.hn": "Honduras",
"countries.hr": "Chorwacja",
"countries.ht": "Haiti",
@@ -100,6 +100,7 @@
"countries.gd": "Granada",
"countries.gt": "Guatemala",
"countries.gy": "Guiana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croácia",
"countries.ht": "Haiti",
@@ -100,6 +100,7 @@
"countries.gd": "Granada",
"countries.gt": "Guatemala",
"countries.gy": "Guiana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croácia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croația",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Гренада",
"countries.gt": "Гватемала",
"countries.gy": "Гайана",
"countries.hk": "Гонконг",
"countries.hn": "Гондурас",
"countries.hr": "Хорватия",
"countries.ht": "Гаити",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ग्रेनेडा",
"countries.gt": "ग्वाटेमाला",
"countries.gy": "गुयाना",
"countries.hk": "हांगकांग",
"countries.hn": "होंडुरस्‌",
"countries.hr": "क्रोएशिया",
"countries.ht": "हैती",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "گرينڊا",
"countries.gt": "گٽيمالا",
"countries.gy": "گيانا",
"countries.hk": "هانگ کانگ",
"countries.hn": "هونڊرس",
"countries.hr": "ڪوريٽيا",
"countries.ht": "هيٽي",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "ග්‍රෙනාඩා",
"countries.gt": "ග්වාතමාලාව",
"countries.gy": "ගයනා",
"countries.hk": "හොංකොං",
"countries.hn": "හොන්ඩුරාස්",
"countries.hr": "ක්‍රොඒෂියාව",
"countries.ht": "හයිටි",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hongkong",
"countries.hn": "Honduras",
"countries.hr": "Chorvátsko",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Gvatemala",
"countries.gy": "Gvajana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Hrvaška",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croatia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guajana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroacia",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroatien",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "கிரெனடா",
"countries.gt": "குவாத்தமாலா",
"countries.gy": "கயானா",
"countries.hk": "ஹாங்காங்",
"countries.hn": "ஹொண்டூராஸ்",
"countries.hr": "குரோவாசியா",
"countries.ht": "ஹைத்தி",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "గ్రెనడా",
"countries.gt": "గ్వాటెమాల",
"countries.gy": "గయానా",
"countries.hk": "హాంగ్ కొంగ",
"countries.hn": "హోండురాస్",
"countries.hr": "క్రొయేషియా",
"countries.ht": "హైతీ",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "เกรเนดา",
"countries.gt": "กัวเตมาลา",
"countries.gy": "กายอานา",
"countries.hk": "ฮ่องกง",
"countries.hn": "ฮอนดูรัส",
"countries.hr": "โครเอเชีย",
"countries.ht": "ไฮติ",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guwatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Kroasya",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Hırvatistan",
"countries.ht": "Haiti",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Гренада",
"countries.gt": "Гватемала",
"countries.gy": "Гайана",
"countries.hk": "Гонконг",
"countries.hn": "Гондурас",
"countries.hr": "Хорватія",
"countries.ht": "Гаїті",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "گریناڈا",
"countries.gt": "گوئٹے مالا",
"countries.gy": "گیانا",
"countries.hk": "ہانگ کانگ",
"countries.hn": "ہونڈوراس",
"countries.hr": "کروشیا",
"countries.ht": "ہیٹی",
+1
View File
@@ -100,6 +100,7 @@
"countries.gd": "Grenada",
"countries.gt": "Guatemala",
"countries.gy": "Guyana",
"countries.hk": "Hong Kong",
"countries.hn": "Honduras",
"countries.hr": "Croatia",
"countries.ht": "Haiti",
@@ -100,6 +100,7 @@
"countries.gd": "格林纳达",
"countries.gt": "危地马拉",
"countries.gy": "圭亚那",
"countries.hk": "香港",
"countries.hn": "洪都拉斯",
"countries.hr": "克罗地亚",
"countries.ht": "海地",
@@ -100,6 +100,7 @@
"countries.gd": "格瑞那達",
"countries.gt": "瓜地馬拉",
"countries.gy": "蓋亞那",
"countries.hk": "香港",
"countries.hn": "宏都拉斯",
"countries.hr": "克羅埃西亞",
"countries.ht": "海地",
+1 -1
View File
@@ -6,4 +6,4 @@
use Appwrite\Runtimes\Runtimes;
return (new Runtimes('v3'))->getAll();
return (new Runtimes('v4'))->getAll();
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+30 -3
View File
@@ -162,13 +162,31 @@ return [
],
[
'name' => '_APP_SYSTEM_SECURITY_EMAIL_ADDRESS',
'description' => 'This is the email address used to issue SSL certificates for custom domains or the user agent in your webhooks payload.',
'description' => 'Deprecated since 1.5.1 use _APP_EMAIL_SECURITY and _APP_EMAIL_CERTIFICATES instead',
'introduction' => '0.7.0',
'default' => 'certs@appwrite.io',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_EMAIL_SECURITY',
'description' => 'This is the email address used as the user agent in your webhooks payload.',
'introduction' => '1.5.1',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_EMAIL_CERTIFICATES',
'description' => 'This is the email address used to issue SSL certificates for custom domains',
'introduction' => '1.5.1',
'default' => '',
'required' => true,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_USAGE_STATS',
'description' => 'This variable allows you to disable the collection and displaying of usage stats. This value is set to \'enabled\' by default, to disable the usage stats set the value to \'disabled\'. When disabled, it\'s recommended to turn off the Worker Usage container to reduce resource usage.',
@@ -180,7 +198,7 @@ return [
],
[
'name' => '_APP_LOGGING_PROVIDER',
'description' => 'This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, set the value to one of \'sentry\', \'raygun\', \'appSignal\', \'logOwl\' to enable the logger.',
'description' => 'Deprecated since 1.6.0, use `_APP_LOGGING_CONFIG` with DSN value instead. This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, set the value to one of \'sentry\', \'raygun\', \'appSignal\', \'logOwl\' to enable the logger.',
'introduction' => '0.12.0',
'default' => '',
'required' => false,
@@ -189,7 +207,7 @@ return [
],
[
'name' => '_APP_LOGGING_CONFIG',
'description' => 'This variable configures authentication to 3rd party error logging providers. If using Sentry, this should be \'SENTRY_API_KEY;SENTRY_APP_ID\'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key. If using LogOwl, this should be LogOwl Service Ticket.',
'description' => 'This variable allows you to enable logging errors to third party providers. This value is empty by default, set a DSN value to one of the following `sentry://PROJECT_ID:SENTRY_API_KEY@SENTRY_HOST/`, , `logowl://SERVICE_TICKET@SERIVCE_HOST/` `raygun://RAYGUN_API_KEY/`, `appSignal://API_KEY/` to enable the logger.\n\nFor versions prior `1.5.6` you can use the old syntax.\n\nOld syntax: If using Sentry, this should be \'SENTRY_API_KEY;SENTRY_APP_ID\'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key. If using LogOwl, this should be LogOwl Service Ticket.',
'introduction' => '0.12.0',
'default' => '',
'required' => false,
@@ -232,6 +250,15 @@ return [
'question' => '',
'filter' => ''
],
[
'name' => '_APP_CONSOLE_SESSION_ALERTS',
'description' => 'This option allows you configure if a new login in the Appwrite Console should send an alert email to the user. It\'s disabled by default with value "disabled", and to enable it, pass value "enabled".',
'introduction' => '1.6.0',
'default' => 'disabled',
'required' => false,
'question' => '',
'filter' => ''
],
],
],
[
+188 -64
View File
@@ -58,7 +58,92 @@ use Utopia\Validator\WhiteList;
$oauthDefaultSuccess = '/auth/oauth2/success';
$oauthDefaultFailure = '/auth/oauth2/failure';
$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) {
function sendSessionAlert(Locale $locale, Document $user, Document $project, Document $session, Mail $queueForMails)
{
$subject = $locale->getText("emails.sessionAlert.subject");
$customTemplate = $project->getAttribute('templates', [])['email.sessionAlert-' . $locale->default] ?? [];
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-session-alert.tpl');
$message
->setParam('{{hello}}', $locale->getText("emails.sessionAlert.hello"))
->setParam('{{body}}', $locale->getText("emails.sessionAlert.body"))
->setParam('{{listDevice}}', $locale->getText("emails.sessionAlert.listDevice"))
->setParam('{{listIpAddress}}', $locale->getText("emails.sessionAlert.listIpAddress"))
->setParam('{{listCountry}}', $locale->getText("emails.sessionAlert.listCountry"))
->setParam('{{footer}}', $locale->getText("emails.sessionAlert.footer"))
->setParam('{{thanks}}', $locale->getText("emails.sessionAlert.thanks"))
->setParam('{{signature}}', $locale->getText("emails.sessionAlert.signature"));
$body = $message->render();
$smtp = $project->getAttribute('smtp', []);
$smtpEnabled = $smtp['enabled'] ?? false;
$senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
$replyTo = "";
if ($smtpEnabled) {
if (!empty($smtp['senderEmail'])) {
$senderEmail = $smtp['senderEmail'];
}
if (!empty($smtp['senderName'])) {
$senderName = $smtp['senderName'];
}
if (!empty($smtp['replyTo'])) {
$replyTo = $smtp['replyTo'];
}
$queueForMails
->setSmtpHost($smtp['host'] ?? '')
->setSmtpPort($smtp['port'] ?? '')
->setSmtpUsername($smtp['username'] ?? '')
->setSmtpPassword($smtp['password'] ?? '')
->setSmtpSecure($smtp['secure'] ?? '');
if (!empty($customTemplate)) {
if (!empty($customTemplate['senderEmail'])) {
$senderEmail = $customTemplate['senderEmail'];
}
if (!empty($customTemplate['senderName'])) {
$senderName = $customTemplate['senderName'];
}
if (!empty($customTemplate['replyTo'])) {
$replyTo = $customTemplate['replyTo'];
}
$body = $customTemplate['message'] ?? '';
$subject = $customTemplate['subject'] ?? $subject;
}
$queueForMails
->setSmtpReplyTo($replyTo)
->setSmtpSenderEmail($senderEmail)
->setSmtpSenderName($senderName);
}
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
'dateTime' => DateTime::format(new \DateTime(), 'Y-m-d H:i:s'),
'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name'),
'device' => $session->getAttribute('clientName'),
'ipAddress' => $session->getAttribute('ip'),
'country' => $locale->getText('countries.' . $session->getAttribute('countryCode'), $locale->getText('locale.country.unknown')),
];
$email = $user->getAttribute('email');
$queueForMails
->setSubject($subject)
->setBody($body)
->setVariables($emailVariables)
->setRecipient($email)
->trigger();
};
$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails) {
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -138,6 +223,10 @@ $createSession = function (string $userId, string $secret, Request $request, Res
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed saving user to DB');
}
if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
}
$queueForEvents
->setParam('userId', $user->getId())
->setParam('sessionId', $session->getId());
@@ -290,7 +379,9 @@ App::post('/v1/account')
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]);
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
if($existingTarget) {
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
}
}
$dbForProject->purgeCachedDocument('users', $user->getId());
@@ -717,8 +808,9 @@ App::post('/v1/account/sessions/email')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
->inject('queueForMails')
->inject('hooks')
->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Hooks $hooks) {
->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks) {
$email = \strtolower($email);
$protocol = $request->getProtocol();
@@ -811,6 +903,10 @@ App::post('/v1/account/sessions/email')
->setParam('sessionId', $session->getId())
;
if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
}
$response->dynamic($session, Response::MODEL_SESSION);
});
@@ -979,6 +1075,7 @@ App::post('/v1/account/sessions/token')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
->inject('queueForMails')
->action($createSession);
App::get('/v1/account/sessions/oauth2/:provider')
@@ -2140,6 +2237,7 @@ App::put('/v1/account/sessions/magic-url')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
->inject('queueForMails')
->action($createSession);
App::put('/v1/account/sessions/phone')
@@ -2170,6 +2268,7 @@ App::put('/v1/account/sessions/phone')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
->inject('queueForMails')
->action($createSession);
App::post('/v1/account/tokens/phone')
@@ -2272,7 +2371,18 @@ App::post('/v1/account/tokens/phone')
$dbForProject->purgeCachedDocument('users', $user->getId());
}
$secret = Auth::codeGenerator();
$secret = null;
$sendSMS = true;
$mockNumbers = $project->getAttribute('auths', [])['mockNumbers'] ?? [];
foreach ($mockNumbers as $mockNumber) {
if ($mockNumber['phone'] === $phone) {
$secret = $mockNumber['otp'];
$sendSMS = false;
break;
}
}
$secret ??= Auth::codeGenerator();
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_OTP));
$token = new Document([
@@ -2297,35 +2407,37 @@ App::post('/v1/account/tokens/phone')
$dbForProject->purgeCachedDocument('users', $user->getId());
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
if ($sendSMS) {
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
$customTemplate = $project->getAttribute('templates', [])['sms.login-' . $locale->default] ?? [];
if (!empty($customTemplate)) {
$message = $customTemplate['message'] ?? $message;
$customTemplate = $project->getAttribute('templates', [])['sms.login-' . $locale->default] ?? [];
if (!empty($customTemplate)) {
$message = $customTemplate['message'] ?? $message;
}
$messageContent = Template::fromString($locale->getText("sms.verification.body"));
$messageContent
->setParam('{{project}}', $project->getAttribute('name'))
->setParam('{{secret}}', $secret);
$messageContent = \strip_tags($messageContent->render());
$message = $message->setParam('{{token}}', $messageContent);
$message = $message->render();
$messageDoc = new Document([
'$id' => $token->getId(),
'data' => [
'content' => $message,
],
]);
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_INTERNAL)
->setMessage($messageDoc)
->setRecipients([$phone])
->setProviderType(MESSAGE_TYPE_SMS);
}
$messageContent = Template::fromString($locale->getText("sms.verification.body"));
$messageContent
->setParam('{{project}}', $project->getAttribute('name'))
->setParam('{{secret}}', $secret);
$messageContent = \strip_tags($messageContent->render());
$message = $message->setParam('{{token}}', $messageContent);
$message = $message->render();
$messageDoc = new Document([
'$id' => $token->getId(),
'data' => [
'content' => $message,
],
]);
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_INTERNAL)
->setMessage($messageDoc)
->setRecipients([$phone])
->setProviderType(MESSAGE_TYPE_SMS);
// Set to unhashed secret for events and server responses
$token->setAttribute('secret', $secret);
@@ -2340,7 +2452,8 @@ App::post('/v1/account/tokens/phone')
->dynamic($token, Response::MODEL_TOKEN);
});
App::post('/v1/account/jwt')
App::post('/v1/account/jwts')
->alias('/v1/account/jwt')
->desc('Create JWT')
->groups(['api', 'account', 'auth'])
->label('scope', 'account')
@@ -2373,15 +2486,11 @@ App::post('/v1/account/jwt')
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic(new Document(['jwt' => $jwt->encode([
// 'uid' => 1,
// 'aud' => 'http://site.com',
// 'scopes' => ['user'],
// 'iss' => 'http://api.mysite.com',
'userId' => $user->getId(),
'sessionId' => $current->getId(),
])]), Response::MODEL_JWT);
@@ -3343,7 +3452,8 @@ App::post('/v1/account/verification/phone')
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
if (empty($user->getAttribute('phone'))) {
$phone = $user->getAttribute('phone');
if (empty($phone)) {
throw new Exception(Exception::USER_PHONE_NOT_FOUND);
}
@@ -3354,7 +3464,19 @@ App::post('/v1/account/verification/phone')
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$secret = Auth::codeGenerator();
$secret = null;
$sendSMS = true;
$mockNumbers = $project->getAttribute('auths', [])['mockNumbers'] ?? [];
foreach ($mockNumbers as $mockNumber) {
if ($mockNumber['phone'] === $phone) {
$secret = $mockNumber['otp'];
$sendSMS = false;
break;
}
}
$secret ??= Auth::codeGenerator();
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM);
$verification = new Document([
@@ -3379,35 +3501,37 @@ App::post('/v1/account/verification/phone')
$dbForProject->purgeCachedDocument('users', $user->getId());
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
if ($sendSMS) {
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
$customTemplate = $project->getAttribute('templates', [])['sms.verification-' . $locale->default] ?? [];
if (!empty($customTemplate)) {
$message = $customTemplate['message'] ?? $message;
$customTemplate = $project->getAttribute('templates', [])['sms.verification-' . $locale->default] ?? [];
if (!empty($customTemplate)) {
$message = $customTemplate['message'] ?? $message;
}
$messageContent = Template::fromString($locale->getText("sms.verification.body"));
$messageContent
->setParam('{{project}}', $project->getAttribute('name'))
->setParam('{{secret}}', $secret);
$messageContent = \strip_tags($messageContent->render());
$message = $message->setParam('{{token}}', $messageContent);
$message = $message->render();
$messageDoc = new Document([
'$id' => $verification->getId(),
'data' => [
'content' => $message,
],
]);
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_INTERNAL)
->setMessage($messageDoc)
->setRecipients([$user->getAttribute('phone')])
->setProviderType(MESSAGE_TYPE_SMS);
}
$messageContent = Template::fromString($locale->getText("sms.verification.body"));
$messageContent
->setParam('{{project}}', $project->getAttribute('name'))
->setParam('{{secret}}', $secret);
$messageContent = \strip_tags($messageContent->render());
$message = $message->setParam('{{token}}', $messageContent);
$message = $message->render();
$messageDoc = new Document([
'$id' => $verification->getId(),
'data' => [
'content' => $message,
],
]);
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_INTERNAL)
->setMessage($messageDoc)
->setRecipients([$user->getAttribute('phone')])
->setProviderType(MESSAGE_TYPE_SMS);
// Set to unhashed secret for events and server responses
$verification->setAttribute('secret', $secret);
+1 -1
View File
@@ -321,7 +321,7 @@ App::get('/v1/avatars/favicon')
->setUserAgent(\sprintf(
APP_USERAGENT,
System::getEnv('_APP_VERSION', 'UNKNOWN'),
System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
))
->fetch($url);
} catch (\Throwable) {
+234 -44
View File
@@ -11,6 +11,7 @@ use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Tasks\ScheduleExecutions;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
@@ -33,6 +34,7 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
use Utopia\Storage\Device;
@@ -151,6 +153,7 @@ App::post('/v1/functions')
->param('logging', true, new Boolean(), 'Whether executions will be logged. When set to false, executions will not be logged, but will reduce resource used by your Appwrite project.', true)
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true)
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function.', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true)
@@ -169,7 +172,7 @@ App::post('/v1/functions')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
@@ -219,8 +222,9 @@ App::post('/v1/functions')
'timeout' => $timeout,
'entrypoint' => $entrypoint,
'commands' => $commands,
'scopes' => $scopes,
'search' => implode(' ', [$functionId, $name, $runtime]),
'version' => 'v3',
'version' => 'v4',
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
@@ -682,6 +686,7 @@ App::put('/v1/functions/:functionId')
->param('logging', true, new Boolean(), 'Whether executions will be logged. When set to false, executions will not be logged, but will reduce resource used by your Appwrite project.', true)
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API Key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Controle System) deployment.', true)
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
@@ -695,7 +700,7 @@ App::put('/v1/functions/:functionId')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
// TODO: If only branch changes, re-deploy
$function = $dbForProject->getDocument('functions', $functionId);
@@ -807,6 +812,7 @@ App::put('/v1/functions/:functionId')
'logging' => $logging,
'entrypoint' => $entrypoint,
'commands' => $commands,
'scopes' => $scopes,
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
@@ -1160,6 +1166,7 @@ App::post('/v1/functions/:functionId/deployments')
}
$activate = (bool) filter_var($activate, FILTER_VALIDATE_BOOLEAN);
$type = $request->getHeader('x-sdk-language') === 'cli' ? 'cli' : 'manual';
if ($chunksUploaded === $chunks) {
if ($activate) {
@@ -1197,7 +1204,7 @@ App::post('/v1/functions/:functionId/deployments')
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => $activate,
'metadata' => $metadata,
'type' => 'manual'
'type' => $type
]));
} else {
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('size', $fileSize)->setAttribute('metadata', $metadata));
@@ -1230,7 +1237,7 @@ App::post('/v1/functions/:functionId/deployments')
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => $activate,
'metadata' => $metadata,
'type' => 'manual'
'type' => $type
]));
} else {
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('chunksUploaded', $chunksUploaded)->setAttribute('metadata', $metadata));
@@ -1430,9 +1437,10 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId')
$response->noContent();
});
App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
->alias('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
->groups(['api', 'functions'])
->desc('Create build')
->desc('Rebuild deployment')
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
->label('audits.event', 'deployment.update')
@@ -1440,12 +1448,11 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'createBuild')
->label('sdk.description', '/docs/references/functions/create-build.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->param('buildId', '', new UID(), 'Build unique ID.')
->param('buildId', '', new UID(), 'Build unique ID.', true) // added as optional param for backward compatibility
->inject('request')
->inject('response')
->inject('dbForProject')
@@ -1453,25 +1460,17 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
->inject('queueForEvents')
->inject('queueForBuilds')
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $buildId));
if ($build->isEmpty()) {
throw new Exception(Exception::BUILD_NOT_FOUND);
}
$deploymentId = ID::unique();
$deployment->removeAttribute('$internalId');
@@ -1496,6 +1495,86 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
$response->noContent();
});
App::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
->groups(['api', 'functions'])
->desc('Cancel deployment')
->label('scope', 'functions.write')
->label('audits.event', 'deployment.update')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'updateDeploymentBuild')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUILD)
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
if ($build->isEmpty()) {
$buildId = ID::unique();
$build = $dbForProject->createDocument('builds', new Document([
'$id' => $buildId,
'$permissions' => [],
'startTime' => DateTime::now(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
'status' => 'canceled',
'path' => '',
'runtime' => $function->getAttribute('runtime'),
'source' => $deployment->getAttribute('path', ''),
'sourceType' => '',
'logs' => '',
'duration' => 0,
'size' => 0
]));
$deployment->setAttribute('buildId', $build->getId());
$deployment->setAttribute('buildInternalId', $build->getInternalId());
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
} else {
if (\in_array($build->getAttribute('status'), ['ready', 'failed'])) {
throw new Exception(Exception::BUILD_ALREADY_COMPLETED);
}
$startTime = new \DateTime($build->getAttribute('startTime'));
$endTime = new \DateTime('now');
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttributes([
'endTime' => DateTime::now(),
'duration' => $duration,
'status' => 'canceled'
]));
}
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
$deleteBuild = $executor->deleteRuntime($project->getId(), $deploymentId . "-build");
$queueForEvents
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
$response->dynamic($build, Response::MODEL_BUILD);
});
App::post('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
->desc('Create execution')
@@ -1514,16 +1593,21 @@ App::post('/v1/functions/:functionId/executions')
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true)
->param('headers', [], new Assoc(), 'HTTP headers of execution. Defaults to empty.', true)
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->inject('user')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('mode')
->inject('queueForFunctions')
->inject('geodb')
->action(function (string $functionId, string $body, bool $async, string $path, string $method, array $headers, Response $response, Document $project, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode, Func $queueForFunctions, Reader $geodb) {
->action(function (string $functionId, string $body, bool $async, string $path, string $method, array $headers, ?string $scheduledAt, Response $response, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) {
if(!$async && !is_null($scheduledAt)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Scheduled executions must run asynchronously. Set scheduledAt to a future date, or set async to true.');
}
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
@@ -1582,7 +1666,8 @@ App::post('/v1/functions/:functionId/executions')
}
if (!$current->isEmpty()) {
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
$jwtExpiry = $function->getAttribute('timeout', 900);
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
$jwt = $jwtObj->encode([
'userId' => $user->getId(),
'sessionId' => $current->getId(),
@@ -1590,6 +1675,14 @@ App::post('/v1/functions/:functionId/executions')
}
}
$jwtExpiry = $function->getAttribute('timeout', 900);
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
$apiKey = $jwtObj->encode([
'projectId' => $project->getId(),
'scopes' => $function->getAttribute('scopes', [])
]);
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
$headers['x-appwrite-user-jwt'] = $jwt ?? '';
@@ -1619,6 +1712,12 @@ App::post('/v1/functions/:functionId/executions')
$executionId = ID::unique();
$status = $async ? 'waiting' : 'processing';
if(!is_null($scheduledAt)) {
$status = 'scheduled';
}
$execution = new Document([
'$id' => $executionId,
'$permissions' => !$user->isEmpty() ? [Permission::read(Role::user($user->getId()))] : [],
@@ -1626,8 +1725,8 @@ App::post('/v1/functions/:functionId/executions')
'functionId' => $function->getId(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
'trigger' => 'http', // http / schedule / event
'status' => $async ? 'waiting' : 'processing', // waiting / processing / completed / failed
'trigger' => (!is_null($scheduledAt)) ? 'schedule' : 'http',
'status' => $status, // waiting / processing / completed / failed / scheduled
'responseStatusCode' => 0,
'responseHeaders' => [],
'requestPath' => $path,
@@ -1645,25 +1744,44 @@ App::post('/v1/functions/:functionId/executions')
->setContext('function', $function);
if ($async) {
if ($function->getAttribute('logging')) {
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
$queueForFunctions
->setType('http')
->setExecution($execution)
->setFunction($function)
->setBody($body)
->setHeaders($headers)
->setPath($path)
->setMethod($method)
->setJWT($jwt)
->setProject($project)
->setUser($user)
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
->trigger();
if(is_null($scheduledAt)) {
$queueForFunctions
->setType('http')
->setExecution($execution)
->setFunction($function)
->setBody($body)
->setHeaders($headers)
->setPath($path)
->setMethod($method)
->setJWT($jwt)
->setProject($project)
->setUser($user)
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
->trigger();
} else {
$data = [
'headers' => $headers,
'path' => $path,
'method' => $method,
'body' => $body,
'jwt' => $jwt,
];
$dbForConsole->createDocument('schedules', new Document([
'region' => System::getEnv('_APP_REGION', 'default'),
'resourceType' => ScheduleExecutions::getSupportedResource(),
'resourceId' => $execution->getId(),
'resourceInternalId' => $execution->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
'data' => $data,
'active' => true,
]));
}
return $response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
@@ -1694,14 +1812,20 @@ App::post('/v1/functions/:functionId/executions')
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$endpoint = $protocol . '://' . $hostname . "/v1";
// Appwrite vars
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
'APPWRITE_FUNCTION_ID' => $functionId,
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_VERSION' => APP_VERSION_STABLE
]);
/** Execute function */
@@ -1724,6 +1848,7 @@ App::post('/v1/functions/:functionId/executions')
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
);
@@ -1763,10 +1888,7 @@ App::post('/v1/functions/:functionId/executions')
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
;
if ($function->getAttribute('logging')) {
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
$roles = Authorization::getRoles();
@@ -1919,6 +2041,74 @@ App::get('/v1/functions/:functionId/executions/:executionId')
$response->dynamic($execution, Response::MODEL_EXECUTION);
});
App::delete('/v1/functions/:functionId/executions/:executionId')
->groups(['api', 'functions'])
->desc('Delete execution')
->label('scope', 'execution.write')
->label('event', 'functions.[functionId].executions.[executionId].delete')
->label('audits.event', 'executions.delete')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'deleteExecution')
->label('sdk.description', '/docs/references/functions/delete-execution.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('functionId', '', new UID(), 'Function ID.')
->param('executionId', '', new UID(), 'Execution ID.')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
->inject('queueForEvents')
->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$execution = $dbForProject->getDocument('executions', $executionId);
if ($execution->isEmpty()) {
throw new Exception(Exception::EXECUTION_NOT_FOUND);
}
if ($execution->getAttribute('functionId') !== $function->getId()) {
throw new Exception(Exception::EXECUTION_NOT_FOUND);
}
$status = $execution->getAttribute('status');
if (!in_array($status, ['completed', 'failed', 'scheduled'])) {
throw new Exception(Exception::EXECUTION_IN_PROGRESS);
}
if (!$dbForProject->deleteDocument('executions', $execution->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove execution from DB');
}
if ($status === 'scheduled') {
$schedule = $dbForConsole->findOne('schedules', [
Query::equal('resourceId', [$execution->getId()]),
Query::equal('resourceType', [ScheduleExecutions::getSupportedResource()]),
Query::equal('active', [true]),
]);
if ($schedule && !$schedule->isEmpty()) {
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('active', false);
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
}
}
$queueForEvents
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
->setPayload($response->output($execution, Response::MODEL_EXECUTION));
$response->noContent();
});
// Variables
App::post('/v1/functions/:functionId/variables')
+5 -9
View File
@@ -2697,7 +2697,7 @@ App::post('/v1/messaging/messages/email')
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
'schedule' => $scheduledAt,
'active' => true,
]));
@@ -2813,7 +2813,7 @@ App::post('/v1/messaging/messages/sms')
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
'schedule' => $scheduledAt,
'active' => true,
]));
@@ -2939,11 +2939,9 @@ App::post('/v1/messaging/messages/push')
$expiry = (new \DateTime())->add(new \DateInterval('P15D'))->format('U');
}
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'));
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', \intval($expiry), 0);
$jwt = $encoder->encode([
'iat' => \time(),
'exp' => $expiry,
'bucketId' => $bucket->getId(),
'fileId' => $file->getId(),
'projectId' => $project->getId(),
@@ -2991,7 +2989,7 @@ App::post('/v1/messaging/messages/push')
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
'schedule' => $scheduledAt,
'active' => true,
]));
@@ -3801,11 +3799,9 @@ App::patch('/v1/messaging/messages/push/:messageId')
$expiry = (new \DateTime())->add(new \DateInterval('P15D'))->format('U');
}
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'));
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', \intval($expiry), 0);
$jwt = $encoder->encode([
'iat' => \time(),
'exp' => $expiry,
'bucketId' => $bucket->getId(),
'fileId' => $file->getId(),
'projectId' => $project->getId(),
+105 -3
View File
@@ -1,6 +1,8 @@
<?php
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Validator\MockNumber;
use Appwrite\Event\Delete;
use Appwrite\Event\Mail;
use Appwrite\Event\Validator\Event;
@@ -103,8 +105,11 @@ App::post('/v1/projects')
'passwordHistory' => 0,
'passwordDictionary' => false,
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
'personalDataCheck' => false
'personalDataCheck' => false,
'mockNumbers' => [],
'sessionAlerts' => false,
];
foreach ($auth as $method) {
$auths[$method['key'] ?? ''] = true;
}
@@ -361,7 +366,7 @@ App::patch('/v1/projects/:projectId')
});
App::patch('/v1/projects/:projectId/team')
->desc('Update Project Team')
->desc('Update project team')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -602,6 +607,37 @@ App::patch('/v1/projects/:projectId/oauth2')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/session-alerts')
->desc('Update project sessions emails')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateSessionAlerts')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('alerts', false, new Boolean(true), 'Set to true to enable session emails.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $alerts, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
$auths['sessionAlerts'] = $alerts;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/limit')
->desc('Update project users limit')
->groups(['api', 'projects'])
@@ -822,6 +858,37 @@ App::patch('/v1/projects/:projectId/auth/max-sessions')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/mock-numbers')
->desc('Update the mock numbers for the project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateMockNumbers')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('numbers', '', new ArrayList(new MockNumber(), 10), 'An array of mock numbers and their corresponding verification codes (OTPs). Each number should be a valid E.164 formatted phone number. Maximum of 10 numbers are allowed.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, array $numbers, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
$auths['mockNumbers'] = $numbers;
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::delete('/v1/projects/:projectId')
->desc('Delete project')
->groups(['api', 'projects'])
@@ -1155,7 +1222,7 @@ App::post('/v1/projects/:projectId/keys')
'expire' => $expire,
'sdks' => [],
'accessedAt' => null,
'secret' => \bin2hex(\random_bytes(128)),
'secret' => API_KEY_STANDARD . '_' . \bin2hex(\random_bytes(128)),
]);
$key = $dbForConsole->createDocument('keys', $key);
@@ -1316,6 +1383,41 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
$response->noContent();
});
// JWT Keys
App::post('/v1/projects/:projectId/jwts')
->groups(['api', 'projects'])
->desc('Create JWT')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createJWT')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_JWT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, array $scopes, int $duration, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 0);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic(new Document(['jwt' => API_KEY_DYNAMIC . '_' . $jwt->encode([
'projectId' => $project->getId(),
'scopes' => $scopes
])]), Response::MODEL_JWT);
});
// Platforms
App::post('/v1/projects/:projectId/platforms')
+3 -4
View File
@@ -351,7 +351,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
->label('sdk.response.model', Response::MODEL_FILE)
->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 CustomId(), 'File 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('file', [], new File(), 'Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/storage#file-input).', skipValidation: true)
->param('file', [], new File(), 'Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/products/storage/upload-download#input-file).', skipValidation: 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 strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->inject('request')
->inject('response')
@@ -1309,7 +1309,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'));
$decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
try {
$decoded = $decoder->decode($jwt);
@@ -1320,8 +1320,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
if (
$decoded['projectId'] !== $project->getId() ||
$decoded['bucketId'] !== $bucketId ||
$decoded['fileId'] !== $fileId ||
$decoded['exp'] < \time()
$decoded['fileId'] !== $fileId
) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
+1 -1
View File
@@ -387,7 +387,7 @@ App::post('/v1/teams/:teamId/memberships')
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('roles', [], 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.')
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation 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.', true, ['clients']) // TODO add our own built-in confirm page
->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('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
->inject('response')
->inject('project')
+64 -4
View File
@@ -1,5 +1,6 @@
<?php
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\MFA\Type;
use Appwrite\Auth\MFA\Type\TOTP;
@@ -39,6 +40,7 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Boolean;
@@ -138,7 +140,9 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]);
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
if($existingTarget) {
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
}
}
}
@@ -160,7 +164,9 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$phone]),
]);
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
if($existingTarget) {
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
}
}
}
@@ -1780,7 +1786,7 @@ App::post('/v1/users/:userId/sessions')
throw new Exception(Exception::USER_NOT_FOUND);
}
$secret = Auth::codeGenerator();
$secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION);
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
@@ -1797,6 +1803,7 @@ App::post('/v1/users/:userId/sessions')
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
'expire' => $expire,
],
$detector->getOS(),
$detector->getClient(),
@@ -1808,7 +1815,6 @@ App::post('/v1/users/:userId/sessions')
$session = $dbForProject->createDocument('sessions', $session);
$session
->setAttribute('secret', $secret)
->setAttribute('expire', $expire)
->setAttribute('countryName', $countryName);
$queueForEvents
@@ -2091,6 +2097,60 @@ App::delete('/v1/users/identities/:identityId')
return $response->noContent();
});
App::post('/v1/users/:userId/jwts')
->desc('Create user JWT')
->groups(['api', 'users'])
->label('scope', 'users.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createJWT')
->label('sdk.description', '/docs/references/users/create-user-jwt.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_JWT)
->param('userId', '', new UID(), 'User ID.')
->param('sessionId', 'recent', new UID(), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true)
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $userId, string $sessionId, int $duration, Response $response, Database $dbForProject) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$sessions = $user->getAttribute('sessions', []);
$session = new Document();
if($sessionId === 'recent') {
// Get most recent
$session = \count($sessions) > 0 ? $sessions[\count($sessions) - 1] : new Document();
} else {
// Find by ID
foreach ($sessions as $loopSession) { /** @var Utopia\Database\Document $loopSession */
if ($loopSession->getId() == $sessionId) {
$session = $loopSession;
break;
}
}
}
if ($session->isEmpty()) {
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 0);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic(new Document(['jwt' => $jwt->encode([
'userId' => $user->getId(),
'sessionId' => $session->getId()
])]), Response::MODEL_JWT);
});
App::get('/v1/users/usage')
->desc('Get users usage stats')
->groups(['api', 'users'])
+63
View File
@@ -464,6 +464,67 @@ App::get('/v1/vcs/github/callback')
->redirect($redirectSuccess);
});
App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents')
->desc('Get files and directories of a VCS repository')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'getRepositoryContents')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VCS_CONTENT_LIST)
->param('installationId', '', new Text(256), 'Installation Id')
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
->param('providerRootDirectory', '', new Text(256, 0), 'Path to get contents of nested directory', true)
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$providerInstallationId = $installation->getAttribute('providerInstallationId');
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$contents = $github->listRepositoryContents($owner, $repositoryName, $providerRootDirectory);
$vcsContents = [];
foreach ($contents as $content) {
$isDirectory = false;
if($content['type'] === GitHub::CONTENTS_DIRECTORY) {
$isDirectory = true;
}
$vcsContents[] = new Document([
'isDirectory' => $isDirectory,
'name' => $content['name'] ?? '',
'size' => $content['size'] ?? 0
]);
}
$response->dynamic(new Document([
'contents' => $vcsContents
]), Response::MODEL_VCS_CONTENT_LIST);
});
App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection')
->desc('Detect runtime settings from source code')
->groups(['api', 'vcs'])
@@ -505,6 +566,7 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
}
$files = $github->listRepositoryContents($owner, $repositoryName, $providerRootDirectory);
$files = \array_column($files, 'name');
$languages = $github->listRepositoryLanguages($owner, $repositoryName);
$detectorFactory = new Detector($files, $languages);
@@ -586,6 +648,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
return function () use ($repo, $github) {
try {
$files = $github->listRepositoryContents($repo['organization'], $repo['name'], '');
$files = \array_column($files, 'name');
$languages = $github->listRepositoryLanguages($repo['organization'], $repo['name']);
$detectorFactory = new Detector($files, $languages);
+4 -9
View File
@@ -250,6 +250,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_VERSION' => APP_VERSION_STABLE
]);
/** Execute function */
@@ -272,6 +273,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
);
@@ -331,13 +333,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$body = $execution['responseBody'] ?? '';
$encodingKey = \array_search('x-open-runtimes-encoding', \array_column($execution['responseHeaders'], 'name'));
if ($encodingKey !== false) {
if (($execution['responseHeaders'][$encodingKey]['value'] ?? '') === 'base64') {
$body = \base64_decode($body);
}
}
$contentType = 'text/plain';
foreach ($execution['responseHeaders'] as $header) {
if (\strtolower($header['name']) === 'content-type') {
@@ -901,7 +896,7 @@ App::get('/robots.txt')
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
if ($host === $mainDomain) {
if ($host === $mainDomain || $host === 'localhost') {
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
@@ -926,7 +921,7 @@ App::get('/humans.txt')
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
if ($host === $mainDomain) {
if ($host === $mainDomain || $host === 'localhost') {
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
+50
View File
@@ -6,6 +6,7 @@ use Appwrite\Extend\Exception;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
@@ -154,6 +155,55 @@ App::patch('/v1/mock/functions-v2')
$response->noContent();
});
App::post('/v1/mock/api-key-unprefixed')
->desc('Create API Key (without standard prefix)')
->groups(['mock', 'api', 'projects'])
->label('scope', 'projects.write')
->label('docs', false)
->param('projectId', '', new UID(), 'Project ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, Response $response, Database $dbForConsole) {
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
if (!$isDevelopment) {
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
}
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$scopes = array_keys(Config::getParam('scopes'));
$key = new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'projectInternalId' => $project->getInternalId(),
'projectId' => $project->getId(),
'name' => 'Outdated key',
'scopes' => $scopes,
'expire' => null,
'sdks' => [],
'accessedAt' => null,
'secret' => \bin2hex(\random_bytes(128)),
]);
$key = $dbForConsole->createDocument('keys', $key);
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($key, Response::MODEL_KEY);
});
App::get('/v1/mock/github/callback')
->desc('Create installation document using GitHub installation id')
->groups(['mock', 'api', 'vcs'])
+78 -33
View File
@@ -1,5 +1,7 @@
<?php
use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Auth;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Event\Audit;
@@ -195,56 +197,99 @@ App::init()
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
$authKey = $request->getHeader('x-appwrite-key', '');
$apiKey = $request->getHeader('x-appwrite-key', '');
if (!empty($authKey)) { // API Key authentication
// API Key authentication
if (!empty($apiKey)) {
// Do not allow API key and session to be set at the same time
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
// Check if given key match project API keys
$key = $project->find('secret', $authKey, 'keys');
if ($key) {
$user = new Document([
'$id' => '',
'status' => true,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
]);
if(!\str_contains($apiKey, '_')) {
$keyType = API_KEY_STANDARD;
$authKey = $apiKey;
} else {
[ $keyType, $authKey ] = \explode('_', $apiKey, 2);
}
$role = Auth::USER_ROLE_APPS;
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
if($keyType === API_KEY_DYNAMIC) {
// Dynamic key
$expire = $key->getAttribute('expire');
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
try {
$payload = $jwtObj->decode($authKey);
} catch (JWTException $error) {
throw new Exception(Exception::API_KEY_EXPIRED);
}
Authorization::setRole(Auth::USER_ROLE_APPS);
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
$projectId = $payload['projectId'] ?? '';
$tokenScopes = $payload['scopes'] ?? [];
$accessedAt = $key->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCCESS)) > $accessedAt) {
$key->setAttribute('accessedAt', DateTime::now());
$dbForConsole->updateDocument('keys', $key->getId(), $key);
$dbForConsole->purgeCachedDocument('projects', $project->getId());
// JWT includes project ID for better security
if ($projectId === $project->getId()) {
$user = new Document([
'$id' => '',
'status' => true,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
]);
$role = Auth::USER_ROLE_APPS;
$scopes = \array_merge($roles[$role]['scopes'], $tokenScopes);
Authorization::setRole(Auth::USER_ROLE_APPS);
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
}
} elseif($keyType === API_KEY_STANDARD) {
// No underline means no prefix. Backwards compatibility.
// Regular key
$sdkValidator = new WhiteList($servers, true);
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
if ($sdkValidator->isValid($sdk)) {
$sdks = $key->getAttribute('sdks', []);
if (!in_array($sdk, $sdks)) {
array_push($sdks, $sdk);
$key->setAttribute('sdks', $sdks);
// Check if given key match project API keys
$key = $project->find('secret', $apiKey, 'keys');
if ($key) {
$user = new Document([
'$id' => '',
'status' => true,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
]);
/** Update access time as well */
$key->setAttribute('accessedAt', Datetime::now());
$role = Auth::USER_ROLE_APPS;
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
$expire = $key->getAttribute('expire');
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
}
Authorization::setRole(Auth::USER_ROLE_APPS);
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
$accessedAt = $key->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCCESS)) > $accessedAt) {
$key->setAttribute('accessedAt', DateTime::now());
$dbForConsole->updateDocument('keys', $key->getId(), $key);
$dbForConsole->purgeCachedDocument('projects', $project->getId());
}
$sdkValidator = new WhiteList($servers, true);
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
if ($sdkValidator->isValid($sdk)) {
$sdks = $key->getAttribute('sdks', []);
if (!in_array($sdk, $sdks)) {
array_push($sdks, $sdk);
$key->setAttribute('sdks', $sdks);
/** Update access time as well */
$key->setAttribute('accessedAt', Datetime::now());
$dbForConsole->updateDocument('keys', $key->getId(), $key);
$dbForConsole->purgeCachedDocument('projects', $project->getId());
}
}
}
}
}
+51 -18
View File
@@ -62,6 +62,10 @@ use Utopia\Database\Validator\Structure;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\DSN\DSN;
use Utopia\Locale\Locale;
use Utopia\Logger\Adapter\AppSignal;
use Utopia\Logger\Adapter\LogOwl;
use Utopia\Logger\Adapter\Raygun;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\Pools\Group;
@@ -112,7 +116,7 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 443;
const APP_CACHE_BUSTER = 4314;
const APP_VERSION_STABLE = '1.5.7';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
@@ -208,6 +212,9 @@ const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
const MESSAGE_TYPE_EMAIL = 'email';
const MESSAGE_TYPE_SMS = 'sms';
const MESSAGE_TYPE_PUSH = 'push';
// API key types
const API_KEY_STANDARD = 'standard';
const API_KEY_DYNAMIC = 'dynamic';
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
@@ -229,11 +236,19 @@ const METRIC_FUNCTIONS = 'functions';
const METRIC_DEPLOYMENTS = 'deployments';
const METRIC_DEPLOYMENTS_STORAGE = 'deployments.storage';
const METRIC_BUILDS = 'builds';
const METRIC_BUILDS_SUCCESS = 'builds.success';
const METRIC_BUILDS_FAILED = 'builds.failed';
const METRIC_BUILDS_STORAGE = 'builds.storage';
const METRIC_BUILDS_COMPUTE = 'builds.compute';
const METRIC_BUILDS_COMPUTE_SUCCESS = 'builds.compute.success';
const METRIC_BUILDS_COMPUTE_FAILED = 'builds.compute.failed';
const METRIC_FUNCTION_ID_BUILDS = '{functionInternalId}.builds';
const METRIC_FUNCTION_ID_BUILDS_SUCCESS = '{functionInternalId}.builds.success';
const METRIC_FUNCTION_ID_BUILDS_FAILED = '{functionInternalId}.builds.failed';
const METRIC_FUNCTION_ID_BUILDS_STORAGE = '{functionInternalId}.builds.storage';
const METRIC_FUNCTION_ID_BUILDS_COMPUTE = '{functionInternalId}.builds.compute';
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS = '{functionInternalId}.builds.compute.success';
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED = '{functionInternalId}.builds.compute.failed';
const METRIC_FUNCTION_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
const METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
const METRIC_EXECUTIONS = 'executions';
@@ -341,8 +356,7 @@ Database::addFilter(
if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
$attribute
->setAttribute('min', $formatOptions['min'])
->setAttribute('max', $formatOptions['max'])
;
->setAttribute('max', $formatOptions['max']);
}
return $value;
@@ -607,9 +621,9 @@ Database::addFilter(
])
));
if (\count($targetIds) > 0) {
return $database->find('targets', [
return $database->skipValidation(fn () => $database->find('targets', [
Query::equal('$internalId', $targetIds)
]);
]));
}
return [];
}
@@ -725,6 +739,26 @@ $register->set('logger', function () {
$providerName = System::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = System::getEnv('_APP_LOGGING_CONFIG', '');
try {
$loggingProvider = new DSN($providerConfig ?? '');
$providerName = $loggingProvider->getScheme();
$providerConfig = match ($providerName) {
'sentry' => ['key' => $loggingProvider->getPassword(), 'projectId' => $loggingProvider->getUser() ?? '', 'host' => $loggingProvider->getHost()],
'logowl' => ['ticket' => $loggingProvider->getUser() ?? '', 'host' => $loggingProvider->getHost()],
default => ['key' => $loggingProvider->getHost()],
};
} catch (Throwable) {
// Fallback for older Appwrite versions up to 1.5.x that use _APP_LOGGING_PROVIDER and _APP_LOGGING_CONFIG environment variables
$configChunks = \explode(";", $providerConfig);
$providerConfig = match ($providerName) {
'sentry' => [ 'key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',],
'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''],
default => ['key' => $providerConfig],
};
}
if (empty($providerName) || empty($providerConfig)) {
return;
}
@@ -733,20 +767,17 @@ $register->set('logger', function () {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled");
}
// Old Sentry Format conversion. Fallback until the old syntax is completely deprecated.
if (str_contains($providerConfig, ';') && strtolower($providerName) == 'sentry') {
$configChunks = \explode(";", $providerConfig);
$adapter = match ($providerName) {
'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']),
'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']),
'raygun' => new Raygun($providerConfig['key']),
'appsignal' => new AppSignal($providerConfig['key']),
default => throw new Exception('Provider "' . $providerName . '" not supported.')
};
$sentryKey = $configChunks[0];
$projectId = $configChunks[1];
$providerConfig = 'https://' . $sentryKey . '@sentry.io/' . $projectId;
}
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
$adapter = new $classname($providerConfig);
return new Logger($adapter);
});
$register->set('pools', function () {
$group = new Group();
@@ -1008,7 +1039,7 @@ foreach ($locales as $locale) {
'user_agent' => \sprintf(
APP_USERAGENT,
System::getEnv('_APP_VERSION', 'UNKNOWN'),
System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
),
'timeout' => 2,
],
@@ -1201,7 +1232,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$authJWT = $request->getHeader('x-appwrite-jwt', '');
if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
try {
$payload = $jwt->decode($authJWT);
@@ -1290,9 +1321,11 @@ App::setResource('console', function () {
'legalAddress' => '',
'legalTaxId' => '',
'auths' => [
'mockNumbers' => [],
'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled'
],
'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],

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