- ensure not loading twice the same variables, namespace to ensure it in the future
- end to end tests where successfull, need to check it is not reusing a previous release (in cache from build CI)
- fix: #377#378
- GET /api/s/{token}/contents — list root folder of a shared folder
- GET /api/s/{token}/contents/{folder_id} — list subfolder
- GET /api/s/{token}/file/{file_id} — download a file within a shared folder
- GET /api/s/{token}/zip — download root as ZIP
- GET /api/s/{token}/zip/{folder_id} — download subfolder as ZIP
(added from @abnvle)
Bug 1 & 2 (webdav_handler.rs handle_put() update branch):
- After a successful file update via WebDAV PUT, if the content type is a supported image:
a. delete_thumbnails(file_id) — evicts the stale moka cache entry
b. Spawns a background task to read the new blob bytes and call generate_all_sizes_background_from_bytes
Bug 3 & 4 (dedup_service.rs):
- Added thumbnail_service: Option<Arc<ThumbnailService>> field with a with_thumbnail_service() builder
- In remove_legacy_reference(): calls delete_blob_thumbnails(hash) when ref_count hits 0
- In remove_manifest_reference(): calls delete_blob_thumbnails(file_hash) when manifest's last ref is dropped
- Wired in di.rs — the thumbnail service is created before dedup service so the ordering works cleanly
- protect file_management_service::rename_file with validate_storage_name
- remove specific rename modal and use the generic modal class (less duplicate)
- handle errors on modal action: do not close the modal on error and display this error
- hide "Go to parent folder" contextMenu if section is files and folder is the same as current one
- create common function `validate_storage_name()` to check files & folder name
- replace previous duplicate check with this one
- better check on create_folder rename_folder (was only checking non empty)
- use correct error class to ensure 400 on API (was 500)
fix: #345
note: no unit test here, but plan e2e test for that
next: improve UI to display error
Security: session hardening
Refresh token rotation with theft detection (family_id)
- Added family_id column to auth.sessions (migration 20260507000000_session_family.sql) grouping all tokens issued from the same login into a family
- On refresh, the new session inherits the parent's family_id
- If a revoked token is replayed (indicates the token was stolen after rotation), the entire family is immediately invalidated and a warning is logged — forcing re-authentication on all devices
SameSite=Strict on refresh cookie
- Access cookie stays SameSite=Lax (needed for top-level navigation)
- Refresh cookie upgraded to SameSite=Strict — it is only ever used for explicit POST to /api/auth/refresh, never via cross-site navigation
Refresh token TTL: 30 days → 7 days
- With rotation, active sessions auto-renew and effectively never expire
- Inactive sessions expire after 7 days instead of 30, reducing the theft window
Five new public endpoints under /api/s/{token}/...:
GET /contents
GET /contents/{folder_id}
GET /file/{file_id}
GET /zip
GET /zip/{folder_id}
All honour the unlock cookie from /verify, so password-protected
folder shares work end-to-end.
Folder/file IDs are validated against the share subtree via a single
ltree containment query (O(log N) on the existing GiST index).
Out-of-scope IDs return 404.
download_shared_file refactored to a Range/304/206/416-aware
serve_share_file helper, shared with the new /file/{file_id}
endpoint. content_disposition extracted from FileHandler so RFC 5987
formatting is identical across auth and share download paths.
After successful POST /api/s/{token}/verify, the server issues a
short-lived signed JWT cookie (oxi_share_unlock_<token>; HttpOnly;
SameSite=Lax; 1h TTL). Subsequent /api/s/{token} and
/api/s/{token}/download requests honour the cookie to bypass the
password gate, so password-protected file downloads work end-to-end.
- New module src/infrastructure/services/share_unlock_cookie.rs:
issue_jwt, verify_jwt, extract_from_cookie_header, build_set_cookie
+ 10 unit tests.
- New ShareService methods issue_unlock_jwt and
get_shared_link_with_unlock; trait method get_shared_link_by_token
delegates to a private fetch_share_resolved with an
allow_password_protected flag.
- access_shared_item, verify_shared_item_password, and
download_shared_file honour the unlock cookie.
Reuses OXICLOUD_JWT_SECRET, no new env var. Auth-token JWTs and
unlock-cookie JWTs cannot be confused: auth requires
username/email/role/jti claims, unlock has only sub/exp/iat.
Cross-share replay rejected via claims.sub == requested_token check.
a drop outside of the browser will:
- upload the file if only 1 file selected
- upload a .zip of the directory or multiple selection (browsers do not permit multiple upload yet)
note: I had to create a new handler because a post request to /api/batch/download is possible via JS but it will create a memory blob in the browser, during the drag action.
This may exhaust the browser's memory if heavy files
This will initiate zip creation from the server even if drop is canceled
The best approach is to add a handler supporting GET calls, this call will be triggered by the browser on drop action outside of it's window
a drop outside of the browser will:
- upload the file if only 1 file selected
- upload a .zip of the directory or multiple selection (browsers do not permit multiple upload yet)
note: I had to create a new handler because a post request to /api/batch/download is possible via JS but it will create a memory blob in the browser, during the drag action.
This may exhaust the browser's memory if heavy files
This will initiate zip creation from the server even if drop is canceled
The best approach is to add a handler supporting GET calls, this call will be triggered by the browser on drop action outside of it's window
The PROPFIND handlers (added in #153) handle the /caldav/{username}/ and
/carddav/{username}/ prefixes returned by discovery, but do so via an
expensive database call to distinguish usernames from UUIDs. The remaining
handlers (PUT, GET, DELETE, REPORT, PROPPATCH) don't handle the prefix
at all, causing 500 errors when standard clients (DAVx5, Apple Calendar,
Thunderbird) follow the discovery URLs.
This commit:
1. Adds a strip_username_prefix() helper that uses a UUID parse check
to distinguish collection IDs from usernames (no DB call needed).
2. Applies it to PUT, GET, DELETE, REPORT, and PROPPATCH handlers in
both caldav_handler.rs and carddav_handler.rs.
3. Refactors PROPFIND in both files to use the same UUID heuristic for
consistency, avoiding unnecessary database queries.
Includes 7 unit tests for the helper function.