Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
648 commits
Select commit Hold shift + click to select a range
79a32e5
Update database
abnegate Jan 15, 2025
2b52bb3
Merge pull request #95 from utopia-php/chore-update-database
abnegate Jan 15, 2025
6f66803
Update database
abnegate Jan 23, 2025
8387c65
Merge pull request #96 from utopia-php/chore-update-database
abnegate Jan 23, 2025
1bd0a16
Update database to 0.58
PineappleIOnic Feb 4, 2025
6403c8e
Update version to fix 8.1
PineappleIOnic Feb 4, 2025
76612c2
Merge pull request #97 from utopia-php/chore-update-database
abnegate Feb 4, 2025
f2f9cc0
Update database
abnegate Feb 12, 2025
3ff6781
Merge pull request #98 from utopia-php/chore-update-database
abnegate Feb 12, 2025
3763248
Update database
abnegate Feb 17, 2025
661687b
Merge pull request #99 from utopia-php/chore-update-database
abnegate Feb 17, 2025
bc3e917
Track latest database
abnegate Mar 6, 2025
acf5711
Merge branch 'main' into chore-update-database
abnegate Mar 6, 2025
a0d6421
Merge pull request #100 from utopia-php/chore-update-database
abnegate Mar 6, 2025
899feb6
Update lock
abnegate Aug 13, 2025
c5e2232
Merge pull request #102 from utopia-php/chore-update-db
abnegate Aug 13, 2025
cb8707a
Update DB
abnegate Sep 4, 2025
cd59156
Merge pull request #103 from utopia-php/chore-update-db
abnegate Sep 4, 2025
cb92c59
Fix PSR autoload
abnegate Oct 20, 2025
79ac753
Unpin DB
abnegate Oct 20, 2025
ae497a7
Merge remote-tracking branch 'origin/main' into chore-update-db
abnegate Oct 20, 2025
f517e66
Fix image refs
abnegate Oct 20, 2025
3ee9320
Remove adminer
abnegate Oct 20, 2025
611fa66
Merge pull request #104 from utopia-php/chore-update-db
abnegate Oct 20, 2025
947d16c
Implement AppwriteTablesDB adapter
Meldiron Nov 13, 2025
04dc655
Shorten code
Meldiron Nov 13, 2025
b1a2319
Update adapter namespace
Meldiron Nov 13, 2025
25e4c02
Update docs
Meldiron Nov 13, 2025
6520245
Fix linter
Meldiron Nov 13, 2025
b378c5b
Merge pull request #105 from utopia-php/feat-appwrite-adapter
eldadfux Nov 13, 2025
96b8499
Refactor: assertions
lohanidamodar Nov 26, 2025
36edca6
Merge pull request #106 from utopia-php/refactor-assertions
Meldiron Nov 26, 2025
8a72773
Initial plan
Copilot Jan 5, 2026
14ccf84
Implement reset() method for Abuse interface and adapters
Copilot Jan 5, 2026
2694a0a
Improve error messages in Database and TablesDB adapters
Copilot Jan 5, 2026
f9ed068
Fix docblock and improve string concatenation consistency
Copilot Jan 5, 2026
c5bfa4d
Remove limit check from set() methods and simplify TablesDB logic
Copilot Jan 5, 2026
b51d34f
Improve PR quality
Meldiron Jan 5, 2026
b9aa135
Merge branch 'copilot/add-reset-method-to-abuse-interface' of https:/…
Meldiron Jan 5, 2026
31e4225
Revert changes
Meldiron Jan 5, 2026
7bece2f
Upgrade Appwrite SDK
Meldiron Jan 5, 2026
3339d05
Merge pull request #107 from utopia-php/copilot/add-reset-method-to-a…
Meldiron Jan 5, 2026
33a24ad
Update deps
abnegate Jan 15, 2026
98c6d7b
Update authz
abnegate Jan 15, 2026
b7fc951
Lint
abnegate Jan 15, 2026
15cd5db
Merge pull request #108 from utopia-php/chore-update-database
abnegate Jan 15, 2026
251856e
chore: upgrade utopia-php/database to 5.*
premtsd-code Jan 30, 2026
20bee84
Merge pull request #109 from utopia-php/chore-upgrade-database-5
abnegate Feb 2, 2026
3ee854f
Remove deprecated curl_close calls
ChiragAgg5k Apr 29, 2026
d2c834f
Clean up curl_close removal formatting
ChiragAgg5k Apr 29, 2026
53f4274
Merge pull request #110 from utopia-php/codex/remove-curl-close-php85
loks0n Apr 29, 2026
2a1fb79
feat: bump appwrite/appwrite to 23.* (typed models, TablesDBIndexType)
premtsd-code May 8, 2026
cfd290a
fix: require PHP >=8.2, fix latent ColumnStatus filter
premtsd-code May 11, 2026
a7a9bc3
fix: drop stale Utopia\Exception import (phpstan level max)
premtsd-code May 11, 2026
c20ded6
ci: add PHP 8.4 to test matrix
premtsd-code May 11, 2026
5d7efbe
Merge pull request #111 from utopia-php/feat/sdk-23
abnegate May 11, 2026
5f1b5e2
feat: bump appwrite/appwrite to 24.*
premtsd-code May 20, 2026
20400c5
feat: initial setup of the nats.php client
levivannoort May 27, 2026
00e59c8
Merge pull request #113 from utopia-php/feat-bump-sdk-24
abnegate Jun 3, 2026
0ecd54f
Update PHP SDK to 26
ChiragAgg5k Jun 17, 2026
e340f2c
Merge pull request #114 from utopia-php/codex/update-php-sdk-26
loks0n Jun 17, 2026
8f5df02
chore(deps): require utopia-php/database ^6.0.0 (#116)
abnegate Jun 18, 2026
3b52718
Add no-op and Redis pool time limit adapters (#115)
premtsd-code Jun 19, 2026
19bd061
refactor: rename package to utopia-php/nats with Utopia\NATS namespace
loks0n Jun 23, 2026
4f3c6e1
Merge pull request #1 from utopia-php/move-namespace
loks0n Jun 23, 2026
f152fbf
Merge pull request #33 from utopia-php/feat/absorb-nats
loks0n Jun 23, 2026
b063039
Merge pull request #40 from utopia-php/feat/rector-hoist
loks0n Jun 26, 2026
15dad9d
Merge pull request #48 from utopia-php/refactor/mirror-workflow
loks0n Jul 6, 2026
efd0553
Merge pull request #69 from utopia-php/docs/vale-linter
loks0n Jul 14, 2026
f4f0320
chore(deps)!: upgrade to utopia-php/pools 2.x
loks0n Jul 31, 2026
03cc86e
chore(deps): bump utopia-php/database to 7.x and refresh lock
loks0n Jul 31, 2026
460d941
Merge pull request #118 from utopia-php/chore/pools-2
loks0n Jul 31, 2026
1c928b3
chore: drop unused PHP 8.2 and 8.3 test images
loks0n Jul 31, 2026
47d8270
Merge pull request #119 from utopia-php/chore/php-8.4
loks0n Jul 31, 2026
43c6979
feat(appwrite): create the abuse table schema inline
claudear Aug 11, 2026
6b358a2
fix(tests): read listed indexes as models, columns as raw payloads
claudear Aug 11, 2026
0f0d16d
Merge pull request #120 from utopia-php/feat/inline-tablesdb-schema
abnegate Aug 11, 2026
c3f4107
Merge pull request #122 from utopia-php/nats-client-tier1-hardening
levivannoort Aug 11, 2026
d275ce3
feat(sliding-window): add storage-agnostic SlidingWindow adapter cont…
ArnabChatterjee20k Aug 12, 2026
0dbc1f0
feat(sliding-window): add RedisBase with atomic Lua check-and-increment
ArnabChatterjee20k Aug 12, 2026
f281091
feat(sliding-window): add Redis adapter
ArnabChatterjee20k Aug 12, 2026
d68199f
feat(sliding-window): add RedisCluster adapter with hash-tagged keys
ArnabChatterjee20k Aug 12, 2026
2d15f05
feat(sliding-window): add RedisPool adapter
ArnabChatterjee20k Aug 12, 2026
5daae51
test(sliding-window): add shared adapter test base
ArnabChatterjee20k Aug 12, 2026
4d8c86b
test(sliding-window): add Redis, RedisCluster and RedisPool test cases
ArnabChatterjee20k Aug 12, 2026
47a5459
Merge branch 'main' into strategy/sliding-window
ArnabChatterjee20k Aug 12, 2026
5c016a8
refactor(sliding-window): reduce adapter seams to eval/get/delete pri…
ArnabChatterjee20k Aug 12, 2026
bf8336d
fix(sliding-window): validate window/ttl bounds and cache weighted es…
ArnabChatterjee20k Aug 12, 2026
25016c0
refactor(sliding-window): improve window computation and state manage…
ArnabChatterjee20k Aug 12, 2026
252a10d
feat(token-bucket): add agnostic adapter and Redis-family base
ArnabChatterjee20k Aug 13, 2026
1830b61
feat(token-bucket): add Redis, RedisCluster and RedisPool adapters
ArnabChatterjee20k Aug 13, 2026
84f4424
test(token-bucket): add Redis, RedisCluster and RedisPool test cases
ArnabChatterjee20k Aug 13, 2026
70e1e4c
fix(token-bucket): floor available balance before deriving consumed c…
ArnabChatterjee20k Aug 13, 2026
6c152d3
fix(token-bucket): always read a fresh refill estimate in count()
ArnabChatterjee20k Aug 13, 2026
6a011fe
fix(token-bucket): scan every cluster master fully before paginating …
ArnabChatterjee20k Aug 13, 2026
e5000d3
feat(sliding-window): add None adapter
ArnabChatterjee20k Aug 13, 2026
4afb6a2
feat(token-bucket): add None adapter
ArnabChatterjee20k Aug 13, 2026
1eecb5e
Merge branch 'strategy/sliding-window' into strategy/token-bucket
ArnabChatterjee20k Aug 13, 2026
fba428f
fix(sliding-window): guard None against zero-sized window
ArnabChatterjee20k Aug 13, 2026
49ac83a
fix(sliding-window): drop stale count cache so estimate keeps decaying
ArnabChatterjee20k Aug 13, 2026
d75bb51
Merge pull request #122 from utopia-php/strategy/token-bucket
ArnabChatterjee20k Aug 13, 2026
821a188
Merge pull request #121 from utopia-php/strategy/sliding-window
abnegate Aug 13, 2026
71e464f
Merge pull request #164 from utopia-php/feat/queue-nats-backoff-stora…
levivannoort Aug 26, 2026
49b5210
Merge pull request #173 from utopia-php/fix/nats-connection-death-det…
levivannoort Sep 2, 2026
0d9db5e
Merge pull request #189 from utopia-php/fix/nats-requestmany-stall-ti…
levivannoort Sep 2, 2026
2eed467
Merge pull request #192 from utopia-php/fix/nats-pull-request-and-nod…
levivannoort Sep 2, 2026
1a51a37
Merge pull request #194 from utopia-php/chore/no-package-lock-files
ChiragAgg5k Sep 2, 2026
0707574
Merge pull request #229 from utopia-php/fix/nats-publish-many
levivannoort Sep 9, 2026
b6e91ed
Emit realtime updates for surviving two-way relationship peers
HarshMN2345 Sep 15, 2026
87d37ab
Cover realtime parent updates when deleting one-to-many children
HarshMN2345 Sep 15, 2026
cf5f4c8
Cover realtime updates after oneToMany parent deletion
HarshMN2345 Sep 15, 2026
e5a949d
Cover realtime updates after oneToOne child deletion
HarshMN2345 Sep 15, 2026
12bcdde
Cover realtime updates after oneToOne parent deletion
HarshMN2345 Sep 15, 2026
89ac20d
Cover realtime updates after manyToOne child deletion
HarshMN2345 Sep 15, 2026
57b82a0
Cover realtime updates after manyToOne parent deletion
HarshMN2345 Sep 15, 2026
54dd9b2
Cover realtime updates after manyToMany child deletion
HarshMN2345 Sep 15, 2026
916bde1
Cover realtime updates after manyToMany parent deletion
HarshMN2345 Sep 15, 2026
dc94bf7
Cover relationship deletion through TablesDB and mirrored row events
HarshMN2345 Sep 15, 2026
a0ebf6c
Verify client deletion preserves parent realtime permissions
HarshMN2345 Sep 15, 2026
ddad96b
Cover deduplicated notifications for peers linked by multiple attributes
HarshMN2345 Sep 15, 2026
851b5d5
Keep restricted relationship deletions silent in realtime
HarshMN2345 Sep 15, 2026
b107946
Verify denied child deletions cannot publish parent updates
HarshMN2345 Sep 15, 2026
afddd12
Prevent realtime updates while relationship deletion is only staged
HarshMN2345 Sep 15, 2026
dd77935
Replace both collection context aliases for related realtime events
HarshMN2345 Sep 15, 2026
638ee18
Merge pull request #268 from utopia-php/fix/nats-replay-request-after…
ChiragAgg5k Sep 15, 2026
5e407b9
Select relationship fields explicitly for persistence assertions
HarshMN2345 Sep 15, 2026
80dd331
Merge branch 'main' into codex/fix-relationship-delete-realtime
HarshMN2345 Sep 15, 2026
88f0dba
Notify surviving relationship peers for every onDelete mode
HarshMN2345 Sep 15, 2026
551d940
Move relationship delete realtime tests into RealtimeCustomClientTest
HarshMN2345 Sep 15, 2026
4c27999
Merge branch 'main' into codex/fix-relationship-delete-realtime
HarshMN2345 Sep 21, 2026
016f48b
Merge pull request #315 from utopia-php/codex/nats-requests
loks0n Sep 21, 2026
833c4a6
Merge branch 'main' into codex/fix-relationship-delete-realtime
HarshMN2345 Sep 22, 2026
672419e
test: add failing E2E tests proving JWT session null bug
jaysomani Sep 24, 2026
9b55e3e
fix tests: correct MFA factor-count JWT setup and recency description
jaysomani Sep 25, 2026
0897f98
fix(teams): accept 81 character roles on create
HarshMN2345 Sep 28, 2026
c777935
feat(oauth2): add Webflow provider
HarshMN2345 Sep 28, 2026
165d9dc
feat(messaging): allow filtering messages by users and targets
HarshMN2345 Sep 28, 2026
619b3cd
fix(detector): report ssr for TanStack Start apps that prerender a su…
HarshMN2345 Sep 28, 2026
3bff394
fix(oauth2): register Webflow in the provider list model
HarshMN2345 Sep 28, 2026
1440296
Merge branch 'main' into fix/issue-8577
HarshMN2345 Sep 28, 2026
d0e4ff5
fix(queue): dead-letter a message whose every attempt died, without t…
levivannoort Sep 28, 2026
452890d
fix(queue): re-read the spare delivery before parking on it
levivannoort Sep 28, 2026
d35e92d
Add 'packages/abuse/' from commit '821a1884c8067736e1a27d9fc70bdb2cc8…
ChiragAgg5k Sep 28, 2026
972431b
chore(abuse): mirror plumbing
ChiragAgg5k Sep 28, 2026
7086f0b
refactor: load abuse from packages/
ChiragAgg5k Sep 28, 2026
596eb0d
Merge remote-tracking branch 'origin/main' into chore/absorb-abuse
ChiragAgg5k Sep 28, 2026
e179349
fix(queue): park on the cached spare when the re-read fails
levivannoort Sep 28, 2026
0738302
Merge branch 'main' into fix/issue-8577
HarshMN2345 Sep 28, 2026
ec55235
fix(functions): return 400 for malformed execution headers
AayushKrGupta Sep 28, 2026
f3edba3
Merge remote-tracking branch 'origin/main' into chore/absorb-abuse
ChiragAgg5k Sep 28, 2026
6131e11
Merge remote-tracking branch 'origin/main' into chore/absorb-abuse
ChiragAgg5k Sep 28, 2026
5776212
Merge remote-tracking branch 'origin/main' into chore/absorb-abuse
ChiragAgg5k Sep 28, 2026
5fd5f53
fix(detector): stop scoring bare package.json as a framework hit
cursoragent Sep 29, 2026
2b75d36
Merge branch 'main' into fix/execution-headers-validation
HarshMN2345 Sep 29, 2026
93d9aae
fix(functions): keep scalar execution header values valid
HarshMN2345 Sep 29, 2026
6ec15e7
Merge pull request #13963 from AayushKrGupta/fix/execution-headers-va…
HarshMN2345 Sep 29, 2026
00c64b4
chore: bump self-hosted console to 1.2.5
HarshMN2345 Sep 29, 2026
79b0df2
Merge branch 'main' into fix/issue-8577
HarshMN2345 Sep 29, 2026
e1dcdc2
Merge pull request #13967 from appwrite/chore/bump-console-1-2-5
HarshMN2345 Sep 29, 2026
3b6eb03
refactor(teams): name the role length limit once
HarshMN2345 Sep 29, 2026
1f88932
Merge pull request #13954 from appwrite/chore/absorb-abuse
ChiragAgg5k Sep 29, 2026
9c4cf8c
Add 'packages/nats/' from commit '016f48bd25f7bf7a06e5e12a9c768036945…
ChiragAgg5k Sep 29, 2026
f91416e
chore(nats): mirror plumbing
ChiragAgg5k Sep 29, 2026
8d5066a
refactor: load nats from packages/
ChiragAgg5k Sep 29, 2026
7fbec2b
docs(rfc): keep nats in packages/ and list its baseline for phase 8
ChiragAgg5k Sep 29, 2026
597b8dd
Merge branch 'main' into fix/issue-8577
HarshMN2345 Sep 29, 2026
1f72af4
(fix): default Google OAuth2 user and tokens to empty array on non-JS…
ChiragAgg5k Sep 29, 2026
bb74889
Merge branch 'main' into fix/issue-13912
HarshMN2345 Sep 29, 2026
7d6fd78
fix(detector): detect TanStack Start SSR from the Nitro plugin
HarshMN2345 Sep 29, 2026
ecc5382
(fix): fail Google token exchange and refresh on invalid responses
ChiragAgg5k Sep 29, 2026
bc27df0
Merge branch 'main' into fix/jwt-session-null-mfa-current-resolution
Meldiron Sep 29, 2026
f1a2590
docs(oauth2): use masked real Webflow credentials as examples
HarshMN2345 Sep 29, 2026
22a375f
fix(nats): require PHP 8.3 for the typed class constants
ChiragAgg5k Sep 29, 2026
bc0c817
Merge pull request #13968 from appwrite/fix/google-oauth2-null-user
ChiragAgg5k Sep 29, 2026
91eb13f
fix(detector): match the Nitro call outside comments, without regex
HarshMN2345 Sep 29, 2026
b11d4f4
fix(detector): accept enabled: false anywhere in the prerender block
HarshMN2345 Sep 29, 2026
cbeda18
chore(nats): keep Rector's PHP 8.2/8.3 rules off the imported code
ChiragAgg5k Sep 29, 2026
e8f9f57
Merge branch 'main' into fix/issue-13912
HarshMN2345 Sep 29, 2026
fd75672
fix(oauth2): reject invalid Webflow credentials when enabling
HarshMN2345 Sep 29, 2026
8552460
Merge pull request #13969 from appwrite/chore/absorb-nats
ChiragAgg5k Sep 29, 2026
b438f02
Merge branch 'main' into fix/issue-8577
HarshMN2345 Sep 29, 2026
2230605
Merge branch 'main' into fix/issue-13912
HarshMN2345 Sep 29, 2026
d1a8396
fix(detector): read the whole prerender block when it holds nested br…
HarshMN2345 Sep 29, 2026
ed48848
Merge pull request #13946 from appwrite/fix/issue-8577
HarshMN2345 Sep 29, 2026
73bc950
fix(detector): follow the Nitro import's local name, ignore strings
HarshMN2345 Sep 29, 2026
07a9fb8
fix(detector): count namespace calls such as nitroPlugin.nitro()
HarshMN2345 Sep 29, 2026
22b4441
Merge branch 'main' into fix/issue-13912
HarshMN2345 Sep 29, 2026
26cdf36
fix(account): redirect native OAuth2 logins straight to the app callback
HarshMN2345 Sep 29, 2026
a0390f5
fix(detector): tie Nitro calls to the imported name
HarshMN2345 Sep 29, 2026
3710156
test(account): cover the default OAuth2 success redirect into the app
HarshMN2345 Sep 29, 2026
030047a
fix(detector): read spaced Nitro calls, every import and quoted braces
HarshMN2345 Sep 29, 2026
db77881
test: cover session resolution under JWT auth
Meldiron Sep 29, 2026
27a1043
test(account): assert the default OAuth2 handoff opens a working session
HarshMN2345 Sep 29, 2026
55f3a49
test: cover push target rotation under JWT auth
Meldiron Sep 29, 2026
2c24de9
fix(detector): read Nitro only from import sources and real calls
HarshMN2345 Sep 29, 2026
f03ea7d
Merge pull request #13970 from appwrite/fix/oauth-native-callback-red…
HarshMN2345 Sep 29, 2026
962f39c
refactor(detector): detect Nitro by its package, like SvelteKit
HarshMN2345 Sep 29, 2026
38b2bb3
Merge branch 'main' into fix/issue-13912
HarshMN2345 Sep 29, 2026
bd04773
docs(flutter): explain that native OAuth2 must use the default redirect
HarshMN2345 Sep 29, 2026
ef032e0
test: harden JWT TOTP setup and push rotation assertions
Meldiron Sep 29, 2026
484b453
fix: resolve the current session under JWT auth
Meldiron Sep 29, 2026
c86108b
Merge pull request #13960 from appwrite/fix/issue-13912
HarshMN2345 Sep 29, 2026
861b73f
fix(locale): correct misspelled link word in Arabic recovery email
HarshMN2345 Sep 29, 2026
8112413
fix(vcs): skip deployments for repositories the resource no longer li…
HarshMN2345 Sep 29, 2026
54cb9e3
Merge pull request #13972 from appwrite/fix/jwt-session-resolution
Meldiron Sep 29, 2026
b54b378
fix(locale): add missing space in Arabic recovery email footer
HarshMN2345 Sep 29, 2026
79ac7f7
Merge branch 'main' into codex/fix-relationship-delete-realtime
HarshMN2345 Sep 29, 2026
c60c1cf
fix(storage): clean up uploads when the antivirus scan cannot run
HarshMN2345 Sep 29, 2026
ab44710
Merge pull request #13974 from appwrite/fix/vcs-skip-disconnected-rep…
HarshMN2345 Sep 29, 2026
fadd0a8
chore(deps): require utopia-php/database ^7.4.0
HarshMN2345 Sep 29, 2026
448f7d2
refactor(databases): publish related updates from the database's dele…
HarshMN2345 Sep 29, 2026
97ce8ba
fix(storage): let local abort succeed once chunks are joined
HarshMN2345 Sep 29, 2026
7b9b031
fix(databases): keep a completed delete successful when related updat…
HarshMN2345 Sep 29, 2026
dc3a88e
test(realtime): assert the surviving document's update payload
HarshMN2345 Sep 29, 2026
2895ed2
chore(deps): bump utopia-php/migration to 2.0.8
HarshMN2345 Sep 29, 2026
3cd309a
Merge branch 'main' into codex/fix-relationship-delete-realtime
HarshMN2345 Sep 29, 2026
dd3e5a9
fix: mark the JWT's deleted session as current in the delete event
Meldiron Sep 29, 2026
33ac15b
Merge pull request #13978 from appwrite/chore/bump-migration-2-0-8
HarshMN2345 Sep 29, 2026
7c97d73
Merge pull request #13965 from appwrite/fix/detector-package-json-fal…
ChiragAgg5k Sep 29, 2026
b50fe33
fix(storage): retry S3 internal errors and replayable transport failures
ChiragAgg5k Sep 29, 2026
9198dc7
Merge branch 'main' into docs/flutter-oauth-redirects
HarshMN2345 Sep 29, 2026
c9bcea8
docs(flutter): move the OAuth redirect note above the platform sections
HarshMN2345 Sep 29, 2026
9ca321f
docs(storage): trim retry strategy comments
ChiragAgg5k Sep 29, 2026
62e3a78
Merge remote-tracking branch 'origin/docs/flutter-oauth-redirects' in…
HarshMN2345 Sep 29, 2026
6e774ab
Merge branch 'main' into codex/fix-relationship-delete-realtime
HarshMN2345 Sep 29, 2026
03bf54e
Merge pull request #13976 from appwrite/docs/flutter-oauth-redirects
HarshMN2345 Sep 29, 2026
6ff0cb0
Merge branch 'main' into fix/storage-antivirus-unreachable
HarshMN2345 Sep 29, 2026
c1ffd8b
fix(storage): only drop an upload's record once its file is gone
HarshMN2345 Sep 29, 2026
729c2b4
fix(storage): only replay S3 requests that cannot apply twice
ChiragAgg5k Sep 29, 2026
915af23
Merge pull request #13690 from appwrite/codex/fix-relationship-delete…
HarshMN2345 Sep 29, 2026
c635dd2
fix(storage): accept a replayed completion in large server-side copies
ChiragAgg5k Sep 29, 2026
a1aff98
fix(storage): prove a recovered copy is ours and never replay batch d…
ChiragAgg5k Sep 29, 2026
3188119
Merge pull request #13900 from jaysomani/fix/jwt-session-null-mfa-cur…
Meldiron Sep 29, 2026
a92e52a
feat(messaging): include messageId in the Appwrite push payload
ArnabChatterjee20k Sep 29, 2026
fc66e82
fix(storage): recognise a landed copy by the target changing, not by …
ChiragAgg5k Sep 29, 2026
4466fc1
test(mqtt): assert the live subscriber receives the campaign messageId
ArnabChatterjee20k Sep 29, 2026
8f5200c
Merge pull request #13977 from appwrite/fix/storage-antivirus-unreach…
HarshMN2345 Sep 29, 2026
7141b34
Merge branch 'main' into fix/ar-recovery-link-typo
HarshMN2345 Sep 29, 2026
607dd21
Merge pull request #13975 from appwrite/fix/ar-recovery-link-typo
HarshMN2345 Sep 29, 2026
46a9fb7
fix(storage): report an unproven multipart copy as failed
ChiragAgg5k Sep 29, 2026
aff68db
Merge branch 'main' into fix/issue-8974
HarshMN2345 Sep 29, 2026
8bdd89f
Merge pull request #13981 from appwrite/add-message-id-to-push-payload
ArnabChatterjee20k Sep 29, 2026
4b24f75
test(messaging): drop allowlist-mirroring validator test
HarshMN2345 Sep 29, 2026
dc55ba7
Merge branch 'main' into fix/issue-8974
HarshMN2345 Sep 29, 2026
004fdbc
Merge branch 'main' into fix/issue-8051
HarshMN2345 Sep 29, 2026
51fdce9
Merge pull request #13944 from appwrite/fix/issue-8051
HarshMN2345 Sep 29, 2026
fa9b0ae
Merge branch 'main' into fix/issue-8974
HarshMN2345 Sep 29, 2026
5d13980
Merge pull request #13958 from appwrite/fix/issue-8974
HarshMN2345 Sep 29, 2026
2e69bab
Merge pull request #13947 from appwrite/fix/queue-dead-letter-without…
levivannoort Sep 29, 2026
5c1bf95
Merge pull request #13979 from appwrite/fix/storage-s3-retry-transient
ChiragAgg5k Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/config/locale/translations/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"emails.magicSession.signature": "فريق {{project}}",
"emails.recovery.subject": "تغيير كلمة السر",
"emails.recovery.hello": "أهلا {{user}}،",
"emails.recovery.body": "برجاء اتباع الراط التالي لتغيير كلمة السر الخاصة بـ{{project}}",
"emails.recovery.footer": "لولم تطلب تغيير كلمة السر، يمكنك تجاهل هذه الرسالة",
"emails.recovery.body": "برجاء اتباع الرابط التالي لتغيير كلمة السر الخاصة بـ{{project}}",
"emails.recovery.footer": "لو لم تطلب تغيير كلمة السر، يمكنك تجاهل هذه الرسالة",
"emails.recovery.thanks": "شكرا،",
"emails.recovery.buttonText": "إعادة تعيين كلمة المرور",
"emails.recovery.signature": "فريق {{project}}",
Expand Down
11 changes: 11 additions & 0 deletions app/config/oAuthProviders.php
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,17 @@
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Twitch',
],
'webflow' => [
'name' => 'Webflow',
'developers' => 'https://developers.webflow.com/data/reference/oauth-app',
'icon' => 'icon-webflow',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Webflow',
],
'wordpress' => [
'name' => 'WordPress',
'developers' => 'https://developer.wordpress.com/docs/oauth2/',
Expand Down
69 changes: 32 additions & 37 deletions app/controllers/api/account.php
Original file line number Diff line number Diff line change
Expand Up @@ -600,22 +600,21 @@
->inject('response')
->inject('targetUser')
->inject('locale')
->inject('store')
->inject('proofForToken')
->action(function (Response $response, User $targetUser, Locale $locale, Store $store, ProofsToken $proofForToken) {
->inject('session')
->action(function (Response $response, User $targetUser, Locale $locale, ?Document $current) {


$sessions = $targetUser->getAttribute('sessions', []);
// While impersonating, the request runs on the impersonator's session, so none of
// the target's sessions is marked current.
$current = $targetUser->sessionVerify($store->getProperty('secret', ''), $proofForToken);
$currentId = $current?->getId();

foreach ($sessions as $key => $session) {
/** @var Document $session */
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));

$session->setAttribute('countryName', $countryName);
$session->setAttribute('current', ($current == $session->getId()) ? true : false);
$session->setAttribute('current', $currentId === $session->getId());
$session->setAttribute('secret', $session->getAttribute('secret', ''));

$sessions[$key] = $session;
Expand Down Expand Up @@ -734,16 +733,15 @@
->inject('response')
->inject('targetUser')
->inject('locale')
->inject('store')
->inject('proofForToken')
->action(function (?string $sessionId, Response $response, User $targetUser, Locale $locale, Store $store, ProofsToken $proofForToken) {
->inject('session')
->action(function (?string $sessionId, Response $response, User $targetUser, Locale $locale, ?Document $current) {

$sessions = $targetUser->getAttribute('sessions', []);
// While impersonating, the request runs on the impersonator's session, so 'current'
// resolves against none of the target's sessions and this throws. That matches the
// sessions list, which marks none of them current for the same reason.
$sessionId = ($sessionId === 'current')
? $targetUser->sessionVerify($store->getProperty('secret', ''), $proofForToken)
? $current?->getId()
: $sessionId;

foreach ($sessions as $session) {
Expand All @@ -752,7 +750,7 @@
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));

$session
->setAttribute('current', ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))))
->setAttribute('current', $session->getId() === $current?->getId())
->setAttribute('countryName', $countryName)
->setAttribute('secret', $session->getAttribute('secret', ''))
;
Expand Down Expand Up @@ -801,11 +799,12 @@
->inject('proofForToken')
->inject('domainVerification')
->inject('cookieDomain')
->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, DeletePublisher $publisherForDeletes, Store $store, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain) {
->inject('session')
->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, DeletePublisher $publisherForDeletes, Store $store, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain, ?Document $current) {

$protocol = $request->getProtocol();
$sessionId = ($sessionId === 'current')
? $user->sessionVerify($store->getProperty('secret', ''), $proofForToken)
? $current?->getId()
: $sessionId;

$sessions = $user->getAttribute('sessions', []);
Expand All @@ -820,13 +819,13 @@

unset($sessions[$key]);

$session->setAttribute('current', false);
$session->setAttribute('current', $session->getId() === $current?->getId());

if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // If current session delete the cookies too
$session
->setAttribute('current', true)
->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')));
if ($session->getAttribute('current')) {
$session->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')));
}

if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // If current session delete the cookies too
if (!$domainVerification) {
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
}
Expand Down Expand Up @@ -885,12 +884,11 @@
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
->inject('store')
->inject('proofForToken')
->action(function (?string $sessionId, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, Store $store, ProofsToken $proofForToken) {
->inject('session')
->action(function (?string $sessionId, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, ?Document $current) {

$sessionId = ($sessionId === 'current')
? $user->sessionVerify($store->getProperty('secret', ''), $proofForToken)
? $current?->getId()
: $sessionId;
$sessions = $user->getAttribute('sessions', []);

Expand Down Expand Up @@ -1627,15 +1625,17 @@
if (!empty($state['failure']) && !$redirectValidator->isValid($state['failure'])) {
throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL);
}
// The default relays live on the console host; the same path on any other allowed host is a customer page
// The default relays live on the console host; the same path on any other allowed host is a customer page.
// Native apps skip the relay: its JavaScript redirect into the app is late or dropped on slow in-app browsers.
$consoleHostname = \parse_url($platform['consoleUrl'] ?? '', PHP_URL_HOST);
$nativeCallback = ['scheme' => 'appwrite-callback-' . $project->getId()];

$failure = [];
if (!empty($state['failure'])) {
$failure = URLParser::parse($state['failure']);
}

$failureRedirect = (function (string $type, ?string $message = null, ?int $code = null, ?\Throwable $previous = null, array $params = []) use ($failure, $response, $project, $oauthDefaultFailure, $consoleHostname) {
$failureRedirect = (function (string $type, ?string $message = null, ?int $code = null, ?\Throwable $previous = null, array $params = []) use ($failure, $response, $project, $oauthDefaultFailure, $consoleHostname, $nativeCallback) {
$exception = new Exception($type, $message, $code, $previous, params: $params);
if (!empty($failure)) {
$query = URLParser::parseQuery($failure['query']);
Expand All @@ -1644,10 +1644,9 @@
'type' => $exception->getType(),
'code' => !\is_null($code) ? $code : $exception->getCode(),
]);
// Mirror success path: default OAuth failure relay needs project to deep-link
// back into the native app via appwrite-callback-{project}://
if ($failure['host'] === $consoleHostname && $failure['path'] === $oauthDefaultFailure) {
$query['project'] = $project->getId();
$failure = $nativeCallback;
}
$failure['query'] = URLParser::unparseQuery($query);
$response->redirect(URLParser::unparse($failure), 301);
Expand Down Expand Up @@ -2234,6 +2233,7 @@
$query['domain'] = $cookieDomain;
$query['key'] = $store->getKey();
$query['secret'] = $encoded;
$state['success'] = $nativeCallback;
}

$response
Expand Down Expand Up @@ -3508,11 +3508,10 @@
->inject('dbForProject')
->inject('queueForEvents')
->inject('hooks')
->inject('store')
->inject('proofForPassword')
->inject('proofForToken')
->inject('pwnedPasswords')
->action(function (string $password, string $oldPassword, Response $response, User $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, PasswordPwned $pwnedPasswords) {
->inject('session')
->action(function (string $password, string $oldPassword, Response $response, User $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, PasswordPwned $pwnedPasswords, ?Document $current) {
$userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions'));
// Check old password only if its an existing user.
if (!empty($user->getAttribute('passwordUpdate')) && !$userProofForPassword->verify($oldPassword, $user->getAttribute('password'))) { // Double check user password
Expand Down Expand Up @@ -3562,13 +3561,11 @@

$sessions = $user->getAttribute('sessions', []);

$current = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken);

$invalidate = $project->getAttribute('auths', default: [])['invalidateSessions'] ?? false;
if ($invalidate && !empty($current)) {
if ($invalidate && $current !== null) {
foreach ($sessions as $session) {
/** @var Document $session */
if ($session->getId() !== $current) {
if ($session->getId() !== $current->getId()) {
$dbForProject->deleteDocument('sessions', $session->getId());
}
}
Expand Down Expand Up @@ -5263,10 +5260,9 @@
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('store')
->inject('proofForToken')
->inject('authorization')
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken, Authorization $authorization) {
->inject('session')
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization, ?Document $current) {
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId;

$provider = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
Expand All @@ -5282,8 +5278,7 @@

$device = $detector->getDevice();

$sessionId = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken);
$session = $dbForProject->getDocument('sessions', $sessionId);
$session = $dbForProject->getDocument('sessions', $current?->getId() ?? '');
$name = "{$device['deviceBrand']} {$device['deviceModel']}";

// A session is one device install holding one push token per provider. Re-registering a rotated
Expand Down
1 change: 1 addition & 0 deletions app/init/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
const APP_LIMIT_ARRAY_LABELS_SIZE = 1000; // Default maximum of how many labels elements can there be in API parameter that expects array value
const APP_LIMIT_ARRAY_SCOPES_SIZE = 200; // Default maximum of how many scope elements can there be in API parameter that expects array value
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
const APP_LIMIT_ROLE_LENGTH = 81; // Maximum length of a team role: `project-<projectId>-<role>` is 9 template characters around two 36-character IDs
const APP_LIMIT_SUBQUERY = 1000;
const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 25;

Expand Down
2 changes: 2 additions & 0 deletions app/init/models.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
use Appwrite\Utopia\Response\Model\OAuth2TikTok;
use Appwrite\Utopia\Response\Model\OAuth2Tradeshift;
use Appwrite\Utopia\Response\Model\OAuth2Twitch;
use Appwrite\Utopia\Response\Model\OAuth2Webflow;
use Appwrite\Utopia\Response\Model\OAuth2WordPress;
use Appwrite\Utopia\Response\Model\OAuth2X;
use Appwrite\Utopia\Response\Model\OAuth2Yahoo;
Expand Down Expand Up @@ -435,6 +436,7 @@
Response::setModel(new OAuth2Yandex());
Response::setModel(new OAuth2X());
Response::setModel(new OAuth2WordPress());
Response::setModel(new OAuth2Webflow());
Response::setModel(new OAuth2Twitch());
Response::setModel(new OAuth2Stripe());
Response::setModel(new OAuth2Spotify());
Expand Down
19 changes: 17 additions & 2 deletions app/init/resources/request.php
Original file line number Diff line number Diff line change
Expand Up @@ -647,14 +647,29 @@
return $project;
}, ['dbForPlatform', 'request', 'console', 'authorization', 'utopia', 'projectIdFromPath']);

$context->set('session', function (User $user, Store $store, Token $proofForToken) {
$context->set('session', function (User $user, Store $store, Token $proofForToken, Request $request) {
if ($user->isEmpty()) {
return;
}

$sessions = $user->getAttribute('sessions', []);
$sessionId = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken);

$authJWT = $request->getHeaderLine('x-appwrite-jwt', '');
if (! $sessionId && ! empty($authJWT)) {
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
try {
$payload = $jwt->decode($authJWT);
} catch (JWTException) {
return;
}

$jwtSessionId = $payload['sessionId'] ?? '';
if (($payload['userId'] ?? '') === $user->getId() && ! empty($jwtSessionId) && $user->sessionActive($jwtSessionId)) {
$sessionId = $jwtSessionId;
}
}

if (! $sessionId) {
return;
}
Expand All @@ -666,7 +681,7 @@
}

return;
}, ['user', 'store', 'proofForToken']);
}, ['user', 'store', 'proofForToken', 'request']);

$context->set('pwnedPasswords', function (Cache $cache) {
// Nothing is asked until an operator points this at a service
Expand Down
12 changes: 9 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"psr-4": {
"Appwrite\\": "src/Appwrite",
"Executor\\": "src/Executor",
"Utopia\\Abuse\\": "packages/abuse/src",
"Utopia\\Agents\\": "packages/agents/src",
"Utopia\\Audit\\": "packages/audit/src",
"Utopia\\Auth\\": "packages/auth/src",
Expand All @@ -49,6 +50,7 @@
"Utopia\\Lock\\": "packages/lock/src",
"Utopia\\Messaging\\": "packages/messaging/src",
"Utopia\\Mqtt\\": "packages/mqtt/src",
"Utopia\\NATS\\": "packages/nats/src",
"Utopia\\OpenAPI\\": "packages/openapi/src",
"Utopia\\Platform\\": "packages/platform/src",
"Utopia\\Pools\\": "packages/pools/src",
Expand Down Expand Up @@ -78,6 +80,7 @@
"Tests\\E2E\\": "tests/e2e",
"Tests\\Unit\\": "tests/unit",
"Appwrite\\Tests\\": "tests/extensions",
"Utopia\\Abuse\\Tests\\": "packages/abuse/tests",
"Utopia\\Agents\\Tests\\": "packages/agents/tests",
"Utopia\\Audit\\Tests\\": "packages/audit/tests",
"Utopia\\Auth\\Tests\\": "packages/auth/tests",
Expand All @@ -102,6 +105,7 @@
"Utopia\\Lock\\Tests\\": "packages/lock/tests",
"Utopia\\Messaging\\Tests\\": "packages/messaging/tests",
"Utopia\\Mqtt\\Tests\\": "packages/mqtt/tests",
"Utopia\\NATS\\Tests\\": "packages/nats/tests",
"Utopia\\OpenAPI\\Tests\\": "packages/openapi/tests",
"Utopia\\Platform\\Tests\\": "packages/platform/tests",
"Utopia\\Pools\\Tests\\": "packages/pools/tests",
Expand Down Expand Up @@ -144,9 +148,8 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.20.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "2.0.*",
"utopia-php/config": "1.*",
"utopia-php/database": "^7.3.11",
"utopia-php/database": "^7.4.0",
"utopia-php/migration": "^2.0.0",
"mustangostang/spyc": "0.6.*",
"dragonmantank/cron-expression": "3.4.*",
Expand All @@ -164,7 +167,8 @@
"psr/http-factory": "^1.0",
"psr/http-client": "^1.0",
"psr/http-message": "^2.0",
"psr/container": "^2.0"
"psr/container": "^2.0",
"appwrite/appwrite": "^27.1"
},
"require-dev": {
"ext-fileinfo": "*",
Expand Down Expand Up @@ -193,6 +197,7 @@
}
},
"replace": {
"utopia-php/abuse": "*",
"utopia-php/agents": "*",
"utopia-php/audit": "*",
"utopia-php/auth": "*",
Expand All @@ -217,6 +222,7 @@
"utopia-php/lock": "*",
"utopia-php/messaging": "*",
"utopia-php/mqtt": "*",
"utopia-php/nats": "*",
"utopia-php/openapi": "*",
"utopia-php/platform": "*",
"utopia-php/pools": "*",
Expand Down
Loading
Loading