Skip to content

[pull] main from appwrite:main - #270

Merged
pull[bot] merged 36 commits into
djacidfx:mainfrom
appwrite:main
Sep 29, 2026
Merged

pull[bot] merged 36 commits into
djacidfx:mainfrom
appwrite:main

Conversation

@pull

@pull pull Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

imtia33 and others added 30 commits September 28, 2026 20:00
- Store the photo ID in users.avatar instead of the full path
- Rename avatars collection to photos and drop its path attribute
- Rename Avatar response model to Photo
- Lock uploads per upload ID instead of per user; drop the lock from delete
- Generate the chunked test payload instead of shipping a 17 MB fixture

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Lock per user around chunks, activation and deletion via the lock pool
- Validate Content-Range shape and actual chunk size against the range
- Accept only PNG, JPEG, GIF and WebP by content
- Delete storage before records so failed deletes stay retriable
- Retry activation when a completed upload was never made active
- Audit photo deletion against the session user
- Store photos under _photos/ so bucket deletion can't remove them
- Test the served image instead of upload metadata

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replace the users.avatar pointer with a users.photos subquery attribute
that returns the newest completed photo, cached with the user document.
Photo rows are the only state; Update and Delete purge the user cache
after changing them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rename users.photos to users.photo and return the live photo with
findOne instead of a one-item list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drop chunked uploads: a photo is one request of up to one upload chunk
(5MB), validated by content before it is stored. Every photos row is now
a finished photo, so the chunk counters, upload metadata and the per-user
lock go away; each upload removes only older photos, which lets
concurrent uploads settle on the newest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…fore aggregating

Execution reads resolve the latest version of each execution with argMax,
grouped by id. Only the resource filters ran before that grouping, so a
listing for one hour of a busy function still grouped the function's whole
history, document included, before discarding everything outside the hour.
A burst of such listings exhausted a regional ClickHouse's memory and failed
every other query on the node with it.

A $createdAt range now also selects, before aggregation, the executions that
have at least one version inside it. Versions themselves are not filtered by
it: executions queued through the API get a later createdAt on the versions
the functions worker writes, so filtering versions could leave an older one
as the latest and return a stale status or a deleted execution.
Replace the photos collection and its subquery with photoId and
photoSize attributes on users, drop the stored mime type, and accept
PNG and JPEG only. Files live in a per-user folder that user deletion
removes whole, which also clears anything a racing request left behind.
Update now returns the user.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Move photo file deletion out of the provider and into the endpoints and
the Deletes worker.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replace the provider's getPath() with APP_STORAGE_PHOTOS and build the
path where it is needed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The unit test matched the whole generated SQL with regular expressions, which
AGENTS.md rules out and which could not tell whether the returned executions
are right. Keep it to plain checks that the window adds the id pre-filter and
never filters versions by createdAt, and add an e2e test that lists a sync
and an async execution through windows around their createdAt values,
including the async execution's queued createdAt, which must never return
the stale queued version.
- Serialise photo updates and deletes per user, so the replaced photo is
  always the one whose file is removed
- Return MODEL_ACCOUNT instead of MODEL_USER, which exposed the password hash
- Accept WebP alongside PNG and JPEG
- Check that JPEG and WebP uploads are served

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…g windows

Sync executions also reach the store through the executions queue. The test read the sync execution's createdAt without checking it was stored yet, so under parallel CI load the covering window could be built from a missing value and see only the async execution.
Only remove the uploaded file when the user was not switched to it yet;
a failure after the switch is logged and the request succeeds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The lock added more code than the race it prevents is worth. A file left
behind by concurrent requests is removed with the user's photo folder
when the user is deleted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The async create response returns $createdAt in the database format
(2026-09-29 12:46:25.848) while reads return ISO 8601. Taking min() of the two
as strings sorts the space before the T, so the window started at the later
queued time and left out the sync execution. Normalise every timestamp to ISO
first, and drop the diagnostic message that found this.
…-pushdown

fix(executions): narrow listings to the requested createdAt window before aggregating
Adds optional bool param current (default true) to DELETE /account/sessions.
When false, all sessions except the calling session are deleted, allowing
sign-out everywhere while staying signed in on the current device.

Uses the session resource from #13900 so JWT auth works correctly for
both cookie and JWT authenticated callers.

X-Fallback-Cookies header is now only sent when current=true to avoid
wiping the caller's stored session cookie when signing out other sessions.

E2E tests cover cookie and JWT auth paths with three-session setup
per spec: other two sessions get 401, calling session stays 200,
listSessions returns exactly one session marked current.
Swoole drops a multipart request without a boundary before it reaches the app, so the test got a bare 400 instead of the missing-file error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Photo updates and deletes are guarded by the user's update timestamp, so a concurrent upload is never cleared by a delete and a replaced photo is never left behind.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat: custom user avatar upload and delete
…-param-fresh

feat: add current param to deleteSessions endpoint
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
User JWTs now carry a projectId claim, and the HTTP and realtime user
resources reject a JWT minted for another project. Tokens minted before
the claim existed are still accepted when they name a session, since the
session ID is server-generated and exists only in the minting project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…only

The X-Appwrite-Organization header belongs to the Organization API, which
is where the console SDK sends it. The console team resource resolved it
for every console route; it now does so on organization routes only, so
every other console route keeps naming its own team.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Organization module already declares its routes with the organization
group, and its init hook requires the team on that same group. Resolve the
header by the group rather than by path prefix, so the two sides of the
contract read the same declaration.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Bind user JWTs to the project that issued them
Meldiron and others added 6 commits September 29, 2026 19:19
fix(console): resolve the organization header on organization routes only
Move the public-domain and public-address checks for the image, favicon
and screenshots endpoints into a PublicURL param validator, reject
screenshot headers that target cloud metadata services, and give the
browser service its own network.

Co-authored-by: Cursor <cursoragent@cursor.com>
The chunked-upload branch of the function and site deployment endpoints
read the x-appwrite-id header straight into the on-disk deployment name
without validating it. A `..` value escapes the per-project storage root
(Device::getAbsolutePath collapses `..` lexically with no containment
check), letting any key with functions.write/sites.write write into other
tenants' storage and overwrite their deployment archives. The escaped
path is also persisted as the deployment's sourcePath, which the deletes
worker later trusts, turning a normal delete into a cross-project file
delete (CWE-22).

Storage file uploads already UID-validate the same header; the two
deployment endpoints did not. Apply the identical check right after the
content-range parse, rejecting invalid ids with STORAGE_INVALID_APPWRITE_ID.
…versal

fix(deployments): validate x-appwrite-id to prevent path traversal
The browser service can no longer resolve internal Docker service names,
so a user-supplied screenshot URL cannot target internal containers by
name even if it reaches the browser.

Co-authored-by: Cursor <cursoragent@cursor.com>
fix(avatars): validate remote URLs with a public URL param validator
@pull pull Bot locked and limited conversation to collaborators Sep 29, 2026
@pull pull Bot added the ⤵️ pull label Sep 29, 2026
@pull
pull Bot merged commit bce8ac2 into djacidfx:main Sep 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants