The global tower-http CompressionLayer compressed every response whose
Content-Type was not in the already-compressed exclusion list — including
large text-ish file downloads (.csv/.log/.sql/.json/source). That (a) burned
CPU re-encoding multi-GB bodies on the request path with no cached result,
and (b) made tower-http strip Content-Length and Accept-Ranges, breaking
byte-range seek and download resume.
Add a NotForDownloads predicate that skips compression for any response
carrying Content-Disposition (every download surface: REST file, share,
folder/zip, batch-zip, inline previews). API JSON and static assets never
set Content-Disposition, so they stay compressed.
Verified with the real tower-http layer + this exact predicate (64 MiB
text/plain download):
- Full download regains Content-Length + Accept-Ranges (were stripped);
/api/data stays brotli-compressed (fix is surgical).
- CPU: 2.7-3.6x less per download sequential; 6-7x less under 8-way concurrency.
- TTFB: 44ms->1ms (gzip), 110ms->1ms (brotli).
- Delivered content throughput: 2.4-2.8x higher.
Tradeoff: genuinely-compressible downloads now send more bytes on the wire;
reclaim via compress-at-rest if it ever matters.
https://claude.ai/code/session_01DCszkkU11LYxMEUWr4setK
The /api router added its own predicate-less CompressionLayer (routes.rs),
nested inside the global predicate-aware one in main.rs. As the inner layer it
compressed responses first, so the global predicate that skips already-
compressed media was bypassed for every /api response: video/audio/image/zip
downloads got Brotli-compressed (CPU + first-byte latency for ~0 bytes saved)
and lost their Content-Length (forced to chunked -> no client progress bar).
- Remove the redundant /api CompressionLayer; /api now flows through the
single global layer in main.rs.
- Make that predicate smarter: compress by default so nothing shrinkable is
missed, and skip ONLY already-compressed types. It no longer blanket-excludes
image/*, so image/svg+xml (text, ~70% shrink) now compresses; raster formats
are listed individually. Added the previously-missed already-compressed
types: Office (docx/xlsx/pptx), ODF, epub, jar, apk, 7z/rar/bzip2/zstd/xz,
woff/woff2 fonts, icons.
Net: media downloads keep Content-Length and skip pointless compression, while
text/JSON/JS/CSS/SVG/XML/ttf/otf/wasm still compress. fmt + clippy clean.
https://claude.ai/code/session_01UtfkS3nZF1vrF5jNAps6wV
ensure files does not exeed OXICLOUD_MAX_UPLOAD_SIZE, prefer to deny from header rather consuming bandwidth
add OXICLOUD_DIRECT_PUT_MAX_BYTES for direct PUT (non chunked), admins can fine tune their prefered values
initially server was accepting multiple instances on same port (Linux and MacOS only)
this can create issues during development (if a dev forget another running instance...)
Add OXICLOUD_REUSE_PORT variable to activate it, so only admins knowing this feature can activate itt
(permits multiple instance + let the OS schedduler to decide which process will handle a request)
if not enabled, other instance will receive exit with a:
`Error: Os { code: 48, kind: AddrInUse, message: "Address already in use" }`
- 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
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
The WOPI integration was not loading documents because the code only read
OXICLOUD_WOPI_BASE_URL, but the documentation and docker-compose examples
used OXICLOUD_WOPI_PUBLIC_BASE_URL. When only WOPI_PUBLIC_BASE_URL was set,
the wopi_base_url defaulted to config.base_url() which resolved to the
internal Docker hostname instead of the public URL.
Fixes#230.
Fix two issues causing login loop after successful admin setup:
1. CSP blocking inline styles: The frontend JavaScript dynamically sets
inline styles (e.g., element.style.display = 'none') for UI state
management. The CSP header only allowed 'self' for style-src, blocking
these dynamic styles. Added 'unsafe-inline' to style-src directive.
2. Session refresh 401 errors: The cookie Secure flag defaulted to true
when OXICLOUD_BASE_URL was not set, causing cookies to not be sent
over HTTP in Docker deployments. Changed the default to false when
the base URL is not explicitly set to HTTPS, with clear logging to
guide users to set OXICLOUD_COOKIE_SECURE=true for production.
Fixes#203
The constant 10 * 1024 * 1024 * 1024 (10 GB) overflows on 32-bit systems
where usize is 32-bit (max ~4GB). This caused compilation failures on
ARMv7 architecture.
Fix by using architecture-appropriate limits:
- 64-bit: 10 GB (unchanged)
- 32-bit: 1 GB (safe maximum for 32-bit usize)
Fixes#206
- frame-src: add 'blob:' (wildcard * only matches network schemes)
- media-src: new directive with 'self' blob: (was falling back to default-src)
- Fixes: PDFs blocked in iframe, videos blocked by media-src fallback
The Nextcloud integration added duplicate /api/auth/app-passwords
handlers that only accepted Bearer tokens, breaking cookie-authenticated
browser sessions (profile page). Remove the duplicates and mount the
original app_password_handler routes which use CurrentUser from the auth
middleware, supporting all auth methods (cookie, Bearer, Basic).
- JWT secret auto-generates and persists to <STORAGE_PATH>/.jwt_secret
- Remove setup token: first admin setup is open until system initialized
- Fix schema.sql: move CREATE EXTENSION pg_trgm/ltree to top
- Update login UI and auth.js to remove setup token fields
- Allow async_fn_in_trait lint crate-wide (internal project, 413 warnings)
- Add integration_tests feature to Cargo.toml to fix unexpected cfg warnings
- Collapse nested if statements into single conditions (13 locations)
- Add type_complexity allows on pg repository functions (12 locations)
- Fix dead code warnings in test modules with allow attributes
- Fix E0599 by gating new_stub() for integration_tests feature
- Add result_unit_err and result_large_err allows where appropriate
- Apply rustfmt formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move pg_trgm extension creation before CalDAV indexes that depend on it
- Remove duplicate app-password route registration that caused panic
- Fix missing comma in language selector array (Dutch entry)
- Await async canEdit() in file click handler (Promise was always truthy)
- Detect images by extension fallback when mime_type is octet-stream
(files uploaded via Nextcloud WebDAV API lack correct mime types)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement a complete Nextcloud client compatibility layer so that
Nextcloud desktop/mobile sync clients can connect to OxiCloud.
Key additions:
- Login Flow v2 (device auth) with OIDC bridge support
- WebDAV handler compatible with Nextcloud clients (PROPFIND, GET,
PUT, DELETE, MKCOL, MOVE, COPY, HEAD, PROPPATCH)
- OCS API endpoints (user info, capabilities, notifications stubs,
sharees, unified search)
- Basic Auth middleware with app password verification, account
lockout integration, and blake3-keyed auth cache
- App password management: create, list, revoke via both native
API (JWT-authenticated profile page) and Nextcloud OCS endpoints
- Nextcloud file ID mapping (oc:fileid) with persistent DB storage
- Chunked upload support (Nextcloud v2 chunking protocol)
- Trashbin WebDAV interface
- Avatar (SVG placeholder) and preview (redirect) handlers
- User profile page with app password management UI
- URL user validation on all DAV routes (403 on mismatch)
- Database schema for app_passwords and nextcloud_object_ids tables
All services are behind a `nextcloud.enabled` config flag and
cleanly separated under src/interfaces/nextcloud/.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The embed-assets commit made locales_path optional, and main.rs was
updated to pass None. Removing that commit restored the PathBuf
signature but left main.rs passing None. Restore the original
locales_path initialization.
The protected auth routes (/me, /change-password, /logout) were merged
with public routes in auth_handler.rs but never had auth middleware
applied in main.rs — so the CurrentUserId extractor always failed with
401. Split auth_routes() into auth_public_routes() and
auth_protected_routes(), applying auth + CSRF middleware to the latter.
Also added credentials: 'same-origin' to all 13 fetch calls in admin.js
so the browser sends HttpOnly auth cookies with requests.
- Rate limit login (5/min), register (3/hr), refresh (10/min) per IP
- Account lockout after 5 consecutive failed logins (15 min cooldown)
- Fix stored XSS in admin panel (escapeHtml on all user-controlled data)
- All limits configurable via OXICLOUD_RATE_LIMIT_* / OXICLOUD_LOCKOUT_* env vars
- Zero new dependencies (uses existing moka crate for in-memory caches)
- Includes unit tests for lockout service
The redirect middleware was a leftover from the custom HTTP server → Axum
migration. It executed on every single request (including static files),
allocating a String from the URI path and performing 3 starts_with() checks,
but never actually redirected anything — just debug logging.
- Remove middleware application from main.rs
- Remove pub mod redirect from middleware/mod.rs
- Delete redirect.rs (121 lines of dead code)
Saves ~80-170ns of overhead per request (String alloc + comparisons +
Tower layer dispatch).
Root cause: XML namespace prefixes (e.g. "D", "C") were not being
resolved to their actual namespace URIs (e.g. "DAV:",
"urn:ietf:params:xml:ns:caldav") during PROPFIND parsing. This caused
all property match arms to fall through to the catch-all, producing
empty XML elements.
Changes:
- Add namespace-aware XML parsing (collect_ns_decls + resolve_name) to
WebDavAdapter, used by all DAV protocol parsers (WebDAV, CalDAV,
CardDAV)
- Add /.well-known/caldav -> /caldav/ redirect (RFC 6764)
- Add root /caldav/ PROPFIND response with current-user-principal and
calendar-home-set discovery properties
- Add /caldav/principals/{username}/ PROPFIND handler (was 500 error)
- Add /caldav/{username}/ user calendar home handler (calendar-home-set
target)
- Respect Depth header at root: depth 0 returns only root entry, depth
1+ includes calendar children
- Fix pre-existing TRANSCODE_POOL_THREADS test compilation error
- Add 8 new tests covering namespace resolution, discovery properties,
and principal responses
https://claude.ai/code/session_01T49VBJSimgo28APxbucHzq
- Replace basic TcpListener::bind with socket2 tuned socket
- TCP_NODELAY: disable Nagle's algorithm (-5 to 40ms latency on small responses)
- SO_REUSEADDR: port available immediately after server restart
- SO_REUSEPORT: ready for multi-worker scaling (Linux)
- TCP_KEEPALIVE: detect dead connections within 60s/10s interval
- listen(2048): high backlog for WebDAV connection bursts
- Eliminate redundant create_dir_all calls from upload hot path
- Replace SHA-256 with BLAKE3 (~5x faster) for content-addressable hashing
in dedup_service, file_handler, file_upload_service, chunked_upload_service
- Add mimalloc as global allocator for 10-30% throughput improvement
- sha2 crate retained only for PKCE (OAuth2 standard requirement)
- BLAKE3 produces 64-char hex hashes (same format), no DB schema changes needed