Commit Graph

26 Commits

Author SHA1 Message Date
Edouard Vanbelle 5ebe2d3bae feat(oidc): add auto-redirect for OIDC
add `auto_redirect_if_standalone_oidc` in `OXICLOUD_AUTH_POLICIES`
let admin decide to redirect immediately to IdP if OIDC is the only auth method enabled
2026-08-03 01:01:00 +02:00
Claude aba89c4f5d perf: eliminate N+1 hot-path queries, cache immutable lookups, stop re-compressing compressed bytes
Every change is benchmark-verified (harness + before/after numbers in
benches/, measured on this branch; reproduction commands in each doc):

DAV / sync-client hot paths
- PROPFIND dead-properties: one = ANY($1) query per 500-child page instead
  of one sequential query per child, and indexable `=` predicates instead
  of IS NOT DISTINCT FROM (seq scans). 2,000-child folder: 1.07-4.54 s of
  DB chatter -> 4-6 ms (258-773x). Applied to native + NC PROPFIND and
  both NC REPORT handlers. [benches/DEAD-PROPS.md]
- Folder paging: keyset cursor (name > $last) + new partial index
  (folder_id, name) replaces LIMIT/OFFSET full-folder rescan per page.
  Full 20k-file walk: 1266 ms -> 77 ms (16.5x). New migration
  20260917000000. [benches/PROPFIND-PAGING.md]
- NC chroot / default-drive resolution: moka caches (30 s TTL, explicit
  invalidation on drive mutations) for find_default_for_user and the
  markerless chroot FolderDto. 2 uncached queries + 2 pool checkouts per
  NC/WebDAV/WOPI request -> sub-us moka hit (p50 0.7-3.6 ms -> ~1 us).
  [benches/CHROOT-CACHE.md]
- Quota: PROPFINDs whose prop list never names a quota prop skip the
  2-query resolution entirely (wants_quota()); the remaining lookups read
  2 columns instead of the full auth.users row with its <=512 KiB avatar
  (11-16x, p50 3.4 ms -> 0.29 ms). Same narrow read now gates every
  upload quota check. [benches/QUOTA-PATH.md]

CPU on the request path
- ZIP exports (folder download, share ZIP, batch download): entries whose
  MIME says already-compressed (JPEG/MP4/zip/pdf/...) are Stored instead
  of Deflate - deflate ran inline on the tokio writer task at ~41 MB/s
  for ~0% size gain. Mixed media corpus: 4.31x wall and CPU, archive size
  unchanged. Shared predicate in common::mime_detect. [benches/ZIP-MEDIA.md]
- Compression layers: tower-http's default maps to Brotli QUALITY 11
  (verified in brotli-8.0.2 source and empirically: 90 ms per 64 KiB JSON
  response, 1.3 s per 700 KiB bundle). Both layers pinned to Precise(4):
  99x less CPU for ~15% more bytes. SPA assets are now precompressed at
  build time (scripts/precompress.mjs, 77% smaller) and served via
  ServeDir::precompressed_br/gzip: 2016x less per-request work, and
  clients get the better q11 bytes. [benches/STATIC-PRECOMPRESSED.md]

Batched / cached backend paths [benches/NPLUS1-AND-CACHES.md]
- Content-search ReBAC re-verification: new
  AuthorizationEngine::check_files_read_batch (default = old loop;
  PgAclEngine override batches drive resolution + reuses role cache).
  200 sequential point SELECTs per search -> 1-2 queries.
- Batch-ZIP subtree downloads: drop per-file re-authz + per-file Recent
  recording (2 writes/file) for subtree entries already authorized at the
  root - mirrors the native folder-download path. ~6,000 statements
  removed from a 2,000-file archive.
- CDC chunk manifests: immutable by content address, now moka-cached
  (weight-bounded 32 MiB, 60 s TTL, positive-only, invalidated on delete)
  - removes one manifest query (p50 0.44-4.4 ms) from every stream,
  range and full blob read.
- People tab: grouped COUNT + batched cover lookup instead of dragging
  every face row with its 2 KiB embedding (10k faces: 30.4 ms & 21 MB ->
  3.8 ms & 1.3 KB, 8.1x); merge() is one set-based UPDATE.
  [benches/PEOPLE-LIST.md]
- Photos timeline cursor: raw timestamptz comparison instead of
  EXTRACT(EPOCH ...) wrapper + IS NULL OR disjunction - cursor is an
  index boundary again, deep scroll stops re-scanning skipped rows.
- Public share landing: one atomic UPDATE ... access_count + 1 (was
  SELECT + full-row write-back: racy, lost updates, clobbered concurrent
  owner edits) - 3 round-trips -> 2 per visit.
- move_to_trash: dead full-entity SELECT feeding a documented no-op
  removed from both branches; dead fields dropped from TrashService.
- NFC normalization: is_nfc_quick fast path skips the decompose/recompose
  state machine for the ~100% already-NFC case (every row loaded from PG).

Frontend
- Large folders paint after page one (~200 items) via fetchFolderListing's
  new onPage hook instead of waiting for every sequential page.
- Tested-and-reverted (kept for the record): cached Intl.Collator for name
  sorts - vitest showed it 2x SLOWER than V8's argument-less localeCompare
  fast path (5.6 ms vs 12.1 ms / 5k names). Sort order untouched.

New bench harnesses under examples/ (bench feature): zip_media,
dead_props, chroot_cache, quota_path, people_list, propfind_paging,
static_precompress.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBK1RdtzyP6759Muqe1K1w
2026-07-16 14:20:20 +00:00
Edouard Vanbelle 3fe6af25f1 fix(loading): fix issue with sveltekit and scripts
fix issues like:
```
Executing inline script violates the following Content Security Policy directive 'script-src 'self''.
Either the 'unsafe-inline' keyword, a hash ('sha256-Vv9My0PApDW3C+xGLu9cH98KLrOg/Qhc7hlT1lK5tyM='),
or a nonce ('nonce-...') is required to enable inline execution. The action has been blocked.
```
2026-07-14 02:26:36 +02:00
DioCrafts 54639d466a chore(frontend): remove the legacy vanilla-JS frontend and its tooling
The SvelteKit app under /frontend has fully superseded the legacy
vanilla-JS/CSS frontend in /static, which was only ever served by a
debug `cargo run` / `PROFILE=dev` and never shipped to production.
Remove it together with the whole subsystem that existed only to
support it (~54k lines).

Frontend & assets:
- Delete /static (js/, css/, *.html, sw.js, basemaps/, locales symlink).
- Relocate the brand/PWA assets (logo/, favicon.ico, manifest.webmanifest)
  to frontend/static/ so they ship with the SPA. This also fixes the
  favicon, which app.html referenced but was missing from the prod bundle.
- Migrate the Nextcloud login-flow redirects from /nextcloud-error.html
  to the SvelteKit /nextcloud/error route.

Web layer:
- Simplify resolve_static_path: drop the PROFILE=dev branch; always prefer
  the Vite static-dist/ build, fall back to the configured path.
- Resolve i18n locales from the served SPA dir with a frontend/static
  fallback so `just dev` works without a prior build.

Build:
- Prune build.rs from 1262 to ~70 lines (git metadata only); the Rust asset
  pipeline and the OXICLOUD_RUST_ASSETS rollback flag are gone.
- Drop the now-unused build-dependencies (oxc_*, lightningcss).
- Remove the COPY static lines from the Dockerfile (cacher + builder).

Tooling & docs:
- Delete biome.json, jsconfig.json, tools/check-*.py, identifier.sh.
- Remove the legacy front-* justfile recipes; repoint the design-system
  scripts (locales, dead-tokens, brand-drift, token-docs) at the frontend,
  and drop check-contrast/check-headings (coupled to the old token
  taxonomy / multi-page HTML).
- Repoint docs/DESIGN-SYSTEM.md links; remove 5 superseded docs/plan/*.

Backend dead code:
- Remove the dead `folder_repo` field from FileBlobWriteRepository.
- Remove the deprecated GET /api/folders/{id}/listing endpoint
  (superseded by /resources).

Verified: cargo clippy (all-features/all-targets) clean, cargo test
--workspace 448 passed, cargo fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:20:10 +02:00
DioCrafts 5812257071 fix(csp,upload): allow WASM in CSP + delta-worker liveness watchdog
Root cause of folder uploads "freezing at ~95%": the global Content-Security-
Policy `script-src` was `'self'` + inline-script hashes with NO
`'wasm-unsafe-eval'`. Chromium therefore blocked `WebAssembly.instantiate`
("Wasm code generation disallowed by embedder"), so the vendored BLAKE3/FastCDC
WASM threw on instantiation — both on the main thread (instant by-hash uploads
and the batch dedup check) and inside the delta-upload worker. Every file then
fell back to a plain byte upload, and the backend logs showed 0 check-batch /
0 negotiate calls. Large files (32 MB service logs) compounded it and the
session token expired mid-upload, so the last handful failed.

- web/mod.rs: add `'wasm-unsafe-eval'` to `script-src`. WASM-only, safe variant
  — does NOT enable `eval()`/`new Function()`. Restores instant uploads, delta
  (sub-file dedup), and the client hashing the idempotent re-upload relies on.
- deltaUpload.ts: liveness watchdog on the delta worker. A healthy worker posts
  progress sub-second; if it goes silent for 20 s it is wedged (WASM init or
  chunking hung without throwing) — disable delta for this file AND every later
  one so they fall straight through to a plain upload instead of each burning
  the full 120 s+ delta timeout. Defense-in-depth so a broken WASM path can
  never again freeze an upload for minutes.

cargo test: pass. npm run check: clean, 58 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 01:46:48 +02:00
DioCrafts 5ccceb463a fix(places): unblock MapLibre worker and fix false-positive basemap probe
Two issues kept the Photos → Places map blank once the SPA could boot:

- CSP `worker-src 'self'` blocked MapLibre GL, which spawns its web worker
  from a blob: URL, so the map never constructed. Allow `worker-src
  'self' blob:` ('self' still covers same-origin workers like delta-upload).

- `checkBasemap()` trusted `res.ok`, but the SPA fallback serves index.html
  (HTTP 200, text/html) for any missing path — so a missing basemap.pmtiles
  read as "present", and pmtiles.js then choked on HTML ("Wrong magic
  number for PMTiles archive"). Reject text/html responses so an absent
  basemap falls back cleanly to the themed blank style.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 21:04:56 +02:00
DioCrafts 824ca03ad4 fix(security): allow SvelteKit inline bootstrap via per-script CSP hashes
The global `script-src 'self'` CSP header blocked every inline <script>,
so the SvelteKit hydration bootstrap never ran and the SPA never mounted
(blank page behind the splash spinner). The anti-FOUC theme init in
app.html was blocked too.

Instead of weakening the policy with 'unsafe-inline', the backend now
builds `script-src` at startup from 'self' plus a SHA-256 hash of every
inline <script> in the served HTML shells. The policy stays strict (no
'unsafe-inline' for scripts) and the hashes are recomputed from the built
assets on each startup, so a frontend rebuild needs no header edit — even
though SvelteKit's bootstrap hash changes every build.

- web::content_security_policy builds the header; web::resolve_static_path
  is extracted so serving and hashing read the exact same bytes.
- byte-exact inline-script extraction (skips src= externals), unit-tested
  against a known SHA-256 vector and extraction edge cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:42:22 +02:00
Bradley Nelson daa3010458 init new frontend 2026-06-17 17:06:30 -06:00
Diocrafts edf1e66989 feat: add public share page with download support (#253)
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)
2026-04-12 01:51:30 +02:00
Edouard Vanbelle badf35f08f chore: remove all executable attributes on non bash files 2026-04-01 23:14:42 +02:00
Edouard Vanbelle d78b279ec9 style: apply rust format + fix clippy warning 2026-03-26 10:05:44 +01:00
Edouard Vanbelle 8194b01299 chore: static: if PROFILE=dev assets are directly /static without any cache 2026-03-25 16:33:04 +01:00
zjean 18518bedaf fix: resolve clippy warnings and rustfmt issues for CI compliance
Fix all clippy lints (collapsible if, clone on Copy, needless borrow,
redundant bindings, unused params) and apply rustfmt across the codebase.
Update test mocks to match Uuid-based trait signatures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 14:34:07 +01:00
Diocrafts f409a9edd7 perf(frontend): add build.rs asset pipeline with oxc + lightningcss
- Bundle 31 JS files → single app.{hash}.js (oxc minifier)
- Bundle 36 CSS files → single app.{hash}.css (lightningcss)
- Resolve CSS @import chains at build time
- Inline theme-init.js to eliminate render-blocking script
- Minify all individual JS/CSS/JSON assets in static-dist/
- Auto-update Service Worker cache manifest with bundle hashes
- FNV hash-based cache-busting filenames

Results: 88 → 12 requests, 640 kB → 96.5 kB transferred (-85%)

Build modes:
- Debug: copies HTML to OUT_DIR, serves original static/
- Release: generates static-dist/ with processed assets

Also:
- Update Dockerfile to include build.rs in cacher stage
- Serve static-dist/ in release Docker builds
- Remove host static/ bind mount from docker-compose
- Switch include_str!() to OUT_DIR for all HTML pages
2026-03-08 13:10:38 +01:00
Dionisio f2d35ca792 feat: auto-persist JWT secret, remove setup token requirement
- 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
2026-03-05 22:12:53 +01:00
Dionisio 48d853360e feat: implement OAuth 2.0 Device Authorization Grant (RFC 8628) for WebDAV/CalDAV/CardDAV
Adds full Device Authorization Grant flow so DAV clients (rclone, etc.)
can authenticate without browser-based OAuth redirects.

New files:
- Domain entity: DeviceCode with status lifecycle (pending/authorized/denied/expired)
- Port: DeviceCodeStoragePort trait (7 async methods)
- DTOs: request/response types for all device auth endpoints
- Repository: DeviceCodePgRepository (PostgreSQL implementation)
- Service: DeviceAuthService (initiate, verify, approve, deny, poll, cleanup)
- Handler: 6 HTTP endpoints (2 public + 4 protected)
- Static: device-verify.html verification page served at /device

Flow:
1. Client POST /api/auth/device/authorize → device_code + user_code
2. User opens /device?code=XXXX in browser, approves
3. Client polls POST /api/auth/device/token → receives JWT tokens
4. Client uses Bearer token with existing WebDAV/CalDAV/CardDAV middleware

Schema: auth.device_codes table + device_code_status enum added to schema.sql

Closes #152
2026-03-01 11:54:43 +01:00
Dionisio 71c2cb5edb perf: Arc<AppState>, streaming PROPFIND, spawn_blocking SHA-256
- Issue #4: Wrap AppState in Arc — eliminates 42 Arc::clone + 16 String::clone per request
- Issue #2: Reject Depth:infinity with 403 + streaming XML with paginated DB queries
- Issue #5: Move chunked upload assembly (SHA-256 hash-on-write) to spawn_blocking
- Remove ~270 lines dead code from di.rs (unused builders, Default impl, stubs)
- Clean up unused tokio imports in chunked_upload_service.rs
2026-02-24 15:11:56 +01:00
George Wu 6a9554d23f feat: add breadcrumb navigation and refactor SPA view management
- Add breadcrumb navigation with folder hierarchy display
- Create setCurrentSection() helper to centralize view state management
- Derive nav item section dynamically from DOM data-i18n attribute
- Remove remnant /shared page and consolidate to SPA sharedView
- Add search input to sharedView for client-side filtering
- Fix 'Go to Files' button to use switchToFilesView()
2026-02-22 20:20:00 -08:00
George Wu b28aa341b3 Enable brotli compression for API and static files
- Add compression-br feature to tower-http
- Apply CompressionLayer to API routes (JSON responses)
- Apply CompressionLayer to static files (CSS, JS, locales)
- File downloads remain uncompressed (avoid double compression)
2026-02-17 23:00:45 -08:00
Diocrafts f661282962 perf(web): add gzip compression + cache-control headers for static assets
- Add CompressionLayer to web router for gzip compression of JS/CSS/JSON/SVG
- Add Cache-Control header (7 days + stale-while-revalidate) via SetResponseHeaderLayer
- Add 'set-header' feature to tower-http dependency
- Result: 67-85% reduction in transfer size for all static assets
2026-02-17 19:29:42 +01:00
Dionisio 4c98c5a657 style: apply cargo fmt to entire codebase
Standardize code formatting across all 173 Rust source files
using rustfmt. No functional changes - purely cosmetic.

This establishes a consistent code style baseline for the
project going forward.
2026-02-14 01:29:34 +01:00
Dionisio 4560a8042c fix(routes): serve pages with clean URLs (no .html extension)
Add server-side routes for /profile, /admin, and /shared that
serve their respective HTML pages directly (same pattern already
used for /login). Updated all frontend references to use clean
URLs instead of .html extensions.

Fixes #99
2026-02-13 22:44:20 +01:00
DioCrafts e22c0ac855 fix trash and additional bugs 2025-03-26 18:33:22 +01:00
DioCrafts 38b0e9594b fix auth errors and add primigenial paper trash 2025-03-24 16:47:42 +01:00
DioCrafts cafad0fbfd adding user authentication 2025-03-20 09:22:31 +01:00
root fe19bc8505 Initial commit 2025-03-17 21:28:08 +01:00