- 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.
Decode XML-escaped WOPI discovery action attributes before storing them
so generated editor URLs use real query separators instead of literal
& entities. Add regression coverage for parsed actions and generated
edit URLs.
When OIDC providers (e.g. Keycloak) use email addresses as usernames or
when claims.sub contains @ or other invalid characters, the username
padding and collision-suffix logic could introduce invalid characters.
The fix filters claims.sub through the same allowed-character filter
before using it in username construction.
FixesDioCrafts/OxiCloud#259
Share links now point to /s/{token} (was /api/s/{token}) and render a
proper HTML page instead of raw JSON.
Changes:
- static/share.html: standalone public share page
- static/css/views/share-public.css: share page styles
- static/js/views/public/publicShare.js: client-side logic that fetches
share metadata via /api/s/{token}, handles password-protected shares,
and renders file download / folder info
- build.rs: include share.html in the HTML embed pipeline
- web/mod.rs: serve /s/{token} route (unauthenticated)
- share_dto.rs: generate URLs as /s/{token} instead of /api/s/{token}
- share_handler.rs: new download_shared_file() handler that validates the
share token and streams file content without requiring authentication
- routes.rs: mount GET /api/s/{token}/download (public, uses AppState)
Three changes to fix the immediate-logout issue reported by multiple
Docker users:
1. Add explicit `credentials: 'same-origin'` to the login fetch call.
This was the only fetch in the entire codebase missing it. While
modern browsers default to 'same-origin', some privacy configs or
older engines may default to 'omit', silently dropping Set-Cookie
headers from the login response.
2. Post-login cookie verification: after a successful login, the
frontend now checks that the CSRF cookie (non-HttpOnly, readable
by JS) was actually stored before redirecting. If the browser
rejected the cookies, a clear error message is shown explaining
the OXICLOUD_COOKIE_SECURE / HTTP mismatch.
3. Server-side diagnostic: the login handler now warns in logs when
Secure cookies are set on a request that didn't arrive via HTTPS
(no X-Forwarded-Proto: https header), pointing admins to the
OXICLOUD_COOKIE_SECURE=false fix.
Root cause: users who set OXICLOUD_BASE_URL=https://... (or have
OXICLOUD_COOKIE_SECURE=true) but access via plain HTTP get cookies
with the Secure flag, which browsers silently reject over HTTP.