Commit Graph

42 Commits

Author SHA1 Message Date
Edouard Vanbelle 27ab392a35 i18n: fix uncontexted strings
For example jobs where translated into "employment" in other language
Remove english text in other languages
2026-08-13 00:30:55 +02:00
Edouard Vanbelle 2be2928ddc i18n: update all languages 2026-08-09 20:37:09 +02:00
Edouard Vanbelle a7df46f8f8 feat(sessions): show session origin in admin panel + test 2026-08-09 16:36:42 +02:00
Edouard Vanbelle 321f3ad733 i18n(session): add missing translations 2026-08-09 14:04:42 +02:00
Edouard Vanbelle bbef8afb7b fix(logout): reduce unwanted API call during logout
- stop trying to refresh session
- display "successfully signed out" rather "your session is expired"
2026-08-09 13:51:35 +02:00
Edouard Vanbelle 9d80bddb5f feat(oidc): apply i18n 2026-08-08 20:49:09 +02:00
Edouard Vanbelle 21607e3e7f feat(opaque): improve password change
- rebuild the opaque envoloppe
- revoke all other useer's sessions
- send a security email to user
2026-08-06 20:52:17 +02:00
Edouard Vanbelle 015f2da0f7 refactor(backend): normalize naming convention to backend rather storage
no ambiguity with the backend rather storage
2026-08-02 14:56:29 +02:00
Edouard Vanbelle e12d8bcb90 feat(job): show if a job is recverable 2026-08-02 02:40:33 +02:00
Edouard Vanbelle a58351b7ad feat(storage key rot): add admin panel 2026-08-02 02:40:33 +02:00
Edouard Vanbelle bbfb106a32 feat(maintenance): add a maintenance notification during backend migration 2026-08-01 18:46:17 +02:00
Edouard Vanbelle 48cbec8fae feat(admin ui): too many tabs, change display 2026-07-30 01:14:35 +02:00
Edouard Vanbelle 9fe47a53ea feat(jobs): add admin call to purge jubs result 2026-07-30 00:39:11 +02:00
Edouard Vanbelle 5527d09618 feat(recoverable-job): check if old blob (no cdc) still remains: notice only 2026-07-29 22:47:07 +02:00
Edouard Vanbelle 0f12399a48 feat(recoverable-job): fix files_consistency to check blob chunk consistency 2026-07-29 22:17:15 +02:00
Edouard Vanbelle 5881968f50 feat(recoverable-job): add progress view 2026-07-29 08:41:19 +02:00
Edouard Vanbelle 4336eca4d1 feat(recoverable-job): add admin page 2026-07-29 08:03:43 +02:00
Edouard Vanbelle 3b31b8911b feat(breadcrumb): build breadcrumb in 1 API call
add /api/folders/{id}/ancestors

    this API to iterate parent up to the drive root or the shared folder
    this will help UI to build the breadcrumb in 1 API call
    and to identify the root element (is it a drive users has access to or
    a shared folder ?)

    ui: now only 1 API call is now required to build the breadcrumb
2026-07-27 01:33:09 +02:00
Edouard Vanbelle 7718970e75 i18n(search): apply i18n on all filters 2026-07-26 18:18:15 +02:00
Edouard Vanbelle 0cc77f7a36 feat(ui): show a notification if user try to drop a file in another section than /files 2026-07-20 23:19:44 +02:00
Edouard Vanbelle c286eed3b2 feat(ui): add a dropzone when uploading files from system
- add a dropzone on the whole screen
 - correct z-index according design system
2026-07-20 23:19:12 +02:00
Edouard Vanbelle a520afcf7c feat(ui:items): uploading an item with a swimlane display
restore the legacy display with new element uploaded, when swimlane is in place
    as the sort is done by server side just add new element in a "new elements" swimlane.
    if user continue to scroll down in cursor/pages and item is found from server,
    UI remove it from the new element and restore the position

    other option: is user refresh it's page, server will restore the natural order
2026-07-20 22:11:00 +02:00
Edouard Vanbelle 5b8fb68b30 feat(items): clarify column names 2026-07-20 21:25:52 +02:00
Edouard Vanbelle 63589e595e refactor(ui): recent: add a quick button to remove item from recent 2026-07-20 12:26:45 +02:00
Edouard Vanbelle 75ae837f1a refator(ui): merge of ResourceList part1 2026-07-20 12:26:45 +02:00
Edouard Vanbelle 2a08fe83ae feat(user): admin can promote external user + security on deletion
promotion by admin of external user into internal possible
    deletion of a user request admin to enter it's email, this is to prevent any miss click
2026-07-19 16:26:11 +02:00
Edouard Vanbelle e003a8c55b feat(admin): display external users for security reasons 2026-07-19 16:26:11 +02:00
Edouard Vanbelle 5982efd783 i18n(drive): correct locales for drive sections 2026-07-19 16:26:07 +02:00
Edouard Vanbelle 05dfda9aa3 feat(drive): add quota update handler
per today: admin only can update quota
    shared drive can have quota updated (personal drive's quota belong to user's quota)
2026-07-19 16:25:41 +02:00
Edouard Vanbelle a6427fc028 feat(drive): add readonly policy
permmit admin to freeze a drive, trash janitor background job is also disabled for this drive
2026-07-16 01:02:15 +02:00
Edouard Vanbelle 1fa1966fbe feat(upgrate): add i18n for account upgade 2026-07-14 11:40:16 +02:00
Dionisio Pozo 5ae551a93d Merge pull request #577 from EdouardVanbelle/feat/users-perfs-and-filter-dotfiles
feat: users prefs server side + filter dotfiles + filter shares by resource type
2026-07-14 09:07:09 +02:00
Edouard Vanbelle e94063d96a test(login/register): via password or magic-link
Password login

┌─────┬────────────────────────────────────────────────────┬────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────┐
│  #  │                        Case                        │         Where          │                                          Assertion                                          │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L1  │ Login by username                                  │ auth_login.hurl Case 1 │ 200 + access_token, user.email match                                                        │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L2  │ Login by email (dispatch on @)                     │ auth_login.hurl Case 2 │ 200, same session shape as L1                                                               │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L3  │ Bad password on username path                      │ auth_login.hurl Case 3 │ 403 anti-enum                                                                               │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L4  │ Bad password on email path                         │ auth_login.hurl Case 4 │ 403 anti-enum (same shape as L3)                                                            │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L5  │ Unknown username                                   │ auth_login.hurl Case 5 │ 403 anti-enum (same shape as L3)                                                            │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L6  │ Unknown email                                      │ auth_login.hurl Case 6 │ 403 anti-enum (same shape as L3)                                                            │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L7  │ /api/auth/oidc/providers reports methods correctly │ auth_login.hurl Case 7 │ password_login_enabled: true, magic_link_login_enabled: true, require_verified_email: false │
└─────┴────────────────────────────────────────────────────┴────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────┘

Password registration

┌─────┬───────────────────────────────────────────────────┬──────────────────────────────┬─────────────────────────────────────────────────────────┐
│  #  │                       Case                        │            Where             │                        Assertion                        │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤
│ R1  │ Classic username + email + password → uniform 200 │ registration.hurl Step 2     │ anti-enum message contains "request received"           │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤
│ R2  │ Login after register works                        │ registration.hurl Step 2b    │ 200 + session for the new user                          │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤
│ R3  │ Email collision → uniform 200 (no rewrite)        │ registration.hurl Steps 8-10 │ attacker password doesn't work; original account intact │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤
│ R4  │ Username collision → uniform 200                  │ registration.hurl Step 11    │ same anti-enum shape                                    │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤
│ R5  │ Off-domain rejection                              │ registration.hurl Step 12    │ 403 RegistrationDomainNotAllowed                        │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤
│ R6  │ Case-insensitive domain match                     │ registration.hurl Step 12b   │ uniform 200 on charlie@EXAMPLE.COM                      │
└─────┴───────────────────────────────────────────────────┴──────────────────────────────┴────────────────────────────┘

Magic-link registration (email-only signup)

┌─────┬──────────────────────────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────┐
│  #  │                                               Case                                               │             Where             │                   Assertion                    │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR1 │ Email-only signup → welcome mail queued                                                          │ registration.hurl Step 3      │ uniform 200 + browser-binding cookie set       │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR2 │ Welcome mail contains magic-link URL                                                             │ registration.hurl Step 4      │ captured from mock SMTP                        │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR3 │ PR 22 cross-browser confirmation page                                                            │ registration.hurl Step 5a     │ 200 HTML "different browser"                   │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR4 │ Cookie-bound redemption lands on SPA                                                             │ registration.hurl Step 5b     │ 302 → /files (SvelteKit route, post-migration) │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR5 │ email_verified_at stamped after redemption                                                       │ registration.hurl Step 6      │ field present on /api/auth/me                  │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR6 │ Second magic-link post-signup                                                                    │ registration.hurl Step 7      │ uniform 200                                    │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR7 │ Profile PATCH — no-op, name set, empty-string rejected, username-taken 409, claim-once 409, etc. │ registration.hurl Steps 6a–6i │ full profile lifecycle                         │
└─────┴──────────────────────────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────┘

Magic-link login (existing account)

┌─────┬──────────────────────────────────────────────────────────┬──────────────────────────────────────┬───────────────────────────────────────┐
│  #  │                           Case                           │                Where                 │                             Assertion                              │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML1 │ Baseline password login still works                      │ auth_magic_link_login.hurl Steps 1-2 │ 200                                                                │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML2 │ magic-link/send with email identifier                    │ auth_magic_link_login.hurl Step 3    │ uniform 200 + cookie                                               │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML3 │ magic-link/send with username identifier (dispatch on @) │ auth_magic_link_login.hurl Step 4    │ uniform 200                                                        │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML4 │ Password-user policy: mail actually sent                 │ auth_magic_link_login.hurl Step 5    │ SMTP capture proves permit_magic_link_for_password_users in effect │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML5 │ Redemption creates a session                             │ auth_magic_link_login.hurl Steps 6-7 │ 302 → /files, /api/auth/me returns the same user                   │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML6 │ Anti-enum on unknown identifier                          │ auth_magic_link_login.hurl Step 8    │ same uniform 200 shape as ML3                                      │
└─────┴──────────────────────────────────────────────────────────┴──────────────────────────────────────┴───────────────────────────────────────┘

OIDC

┌─────┬────────────────────────────────────────────────────────────────────────┬───────────────────┬────────────────────────────────────────────────────────────────────────────────────────────┐
│  #  │                                  Case                                  │       Where       │                                                        Assertion                                                        │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O1  │ Setup local admin (bootstrap)                                          │ oidc.hurl Step 1  │ 201                                                                                                                     │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O2  │ Providers endpoint — OIDC visible                                      │ oidc.hurl Step 2  │ enabled: true, provider_name: MockSSO, password_login_enabled: true, magic_link_login_enabled: false (OIDC-master rule) │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O2b │ Magic-link/send refused (endpoint layer)                               │ oidc.hurl Step 2b │ 403 MagicLinkLoginDisabled — proves the policy gate fires, not a 503 SMTP-unwired                                       │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O3  │ Authorize redirect includes PKCE + state                               │ oidc.hurl Step 3  │ 307 to fake IdP                                                                                                         │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O4  │ IdP round-trip + JIT provisioning                                      │ oidc.hurl Step 4  │ Callback lands on /login?oidc_code=…                                                                                    │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O5  │ Code exchange → session cookies                                        │ oidc.hurl Step 5  │ 200 + all three cookies                                                                                                 │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O6  │ JIT profile mapping (name, given/family, picture, groups → admin role) │ oidc.hurl Step 6  │ every claim reflected on /api/auth/me                                                                                   │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O7  │ Refresh rotation on OIDC session                                       │ oidc.hurl Step 7  │ new access/refresh/CSRF cookies                                                                                         │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O8  │ Refreshed cookies authenticate                                         │ oidc.hurl Step 8  │ 200 on /api/auth/me                                                                                                     │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O9  │ Repeat login = same local user (no dup)                                │ oidc.hurl Step 9  │ user_id stable                                                                                                          │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O10 │ Anti-takeover: unverified email → refused                              │ oidc.hurl Step 10 │ 401/403                                                                                                                 │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O11 │ One-time code replay refused                                           │ oidc.hurl Step 11 │ second /exchange → 401                                                                                                  │
└─────┴────────────────────────────────────────────────────────────────────────┴───────────────────┴────────────────────────────────────────────────────────────────────────────────────────────┘

test
2026-07-14 03:16:25 +02:00
Edouard Vanbelle 5aaf49859e feat(user-perf): add ui user-perf + dotfile filter
- add resource kind filter (file, folder, drive) in shared section (localStorage stored)
- add user preferences serverside store
- add client side dotfile filter (show/hide dotfiles) (user perf stored, default: dotfiles are shown)
  for security trashed dotfile are always displayed

  protection added: if a folder has only hidden items, a notification invite user to display it
  if a user rename or create a hidden item, a notification tells it to user
2026-07-13 20:27:52 +02:00
Edouard Vanbelle 09339ea63f feat(drive): UI: show policiesto drive's members
and add tests
2026-07-01 22:35:08 +02:00
Edouard Vanbelle 26d3c692ba feat(drive): policies management from UI 2026-06-26 18:57:35 +02:00
DioCrafts db97a88956 feat(upload): skip unreadable files (FIFOs/sockets) + auto-reload on new deploy
Two robustness fixes behind the recurring "folder upload stuck at ~93%" reports.

1. Skip non-regular files up front. A copied s6/runit service tree contains
   FIFOs (e.g. supervise/control named pipes) that report a size but BLOCK
   FOREVER when the browser reads them — the deterministic ~8-files-short that
   no retry/watchdog tweak could fix. uploadBatch/uploadTree now probe each
   file's first chunk against a 3 s timeout (partitionReadable), upload only the
   readable ones, and report the rest: "N uploaded · M skipped (not regular
   files)". Progress runs over the uploadable count, so it reaches 100% instead
   of parking at 93% while a lane hangs on a pipe.

2. Auto-reload on a new deploy. svelte.config.js polls _app/version.json
   (60 s); the root layout reloads itself when the deployed build changes —
   unless an upload is in flight — so an open tab can't keep running stale code
   after a rebuild (the recurring "my fix isn't applied" trap).

npm run check: 0 errors, 58 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:08:29 +02:00
DioCrafts 6123843dd0 fix 2026-06-20 19:13:18 +02:00
DioCrafts 2ebc4e82b2 feat(upload): batch dedup-check + instant uploads + resilient parallel uploads
Backend:
- POST /api/dedup/check-batch — returns the subset of submitted whole-file
  BLAKE3 hashes the caller already owns, in one query (user-scoped,
  anti-enumeration via idx_files_blob_hash). Lets a client learn which of N
  files it can skip with a single round trip.
  (dedup_service::user_owned_blob_references, dedup_handler, routes) + tests.

Frontend — upload pipeline:
- Instant ("by-hash") upload for content the caller already owns: hash every
  in-band file, ONE /api/dedup/check-batch, create the owned ones with zero
  content bytes, upload only the rest. Covers all sizes below the 8 MB delta
  threshold (delta handles larger files). vendor/hashWasm computes the
  whole-file BLAKE3 on the main thread.
- Resilient parallel uploads: bounded concurrency (4) + a per-file deadline,
  so one stuck/slow/failing file no longer freezes the whole batch — it blocks
  only its own lane and times out / is skipped while the rest proceed. Quota
  exhaustion stops the run early; partial results are reported ("N uploaded,
  M failed").
- Folder uploads (uploadTree) show live bell progress + a final result and go
  through the same dedup + parallel pipeline.
- Storage bar ("Almacenamiento") refreshes after uploads/deletes
  (session.refresh) instead of showing the stale login value.

Frontend — i18n / UI fixes:
- Fix literal {{count}} and {{percentage}}/{{used}}/{{total}} (param-name
  mismatches) in the selection toolbar and storage line; add es strings.
- Remove the underline on user-menu link rows.

Benchmark (uploadStrategies.bench.test.ts) compares baseline / per-file / batch:
the batch collapses N per-file probes into one check (e.g. a WAN 1000-file run
drops from 1700 to 1001 round trips) while matching per-file's byte savings.

Also includes in-progress group virtual-description i18n work present in the
working tree (groups.ts, ResourceList, locale `groups` keys).

Verified: cargo clippy -D warnings (clean), backend 448 tests; frontend
npm run check (clean), 58 unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 17:03:30 +02:00
Claude 5494efea35 feat(frontend): port Places, People & photo tabs to the Svelte app
Bring the Photos/People/Places UI that main added (in the legacy vanilla
frontend) into the SvelteKit rewrite, wired to the now-merged backend
(/api/photos/geo, /api/people/*).

Photos page (routes/photos/+page.svelte):
- Moments | Places | People sub-tabs (the People tab appears only when the
  faces feature is enabled, via a /api/people capability probe), mirroring
  the vanilla photos sub-nav.
- Square ↔ justified layout toggle. Justified uses a Flickr-style
  row-packer over the width/height the photos list endpoint returns
  (PhotoItem), falling back to 1:1 when dimensions are missing.

New components:
- PhotoLightbox.svelte — the lightbox extracted from the photos page into a
  reusable component (items + bindable index, onDelete callback) so the
  grid, People and Places all share one implementation (no duplication).
- PlacesMap.svelte — MapLibre GL map with server-clustered markers; the
  vector basemap is optional (probed at /basemaps/basemap.pmtiles, themed
  fallback otherwise). Cluster click zooms in or opens the lightbox.
- PeopleView.svelte — identity-cluster grid → per-person photo grid, with
  rename via the in-app prompt dialog.

Supporting:
- api/endpoints/people.ts (+ peopleEnabled probe); photos.ts gains
  fetchPhotosGeo + GeoCluster + PhotoItem; fileThumbnailUrl takes a size.
- lib/vendor/maplibre.ts — minimal typings + lazy loader for the vendored
  MapLibre GL + pmtiles globals (kept any-free for ESLint).
- utils/media.ts — shared isVideo / photoTimestamp / minimalPhotoItem.
- Vendored maplibre-gl 5.24.0 + pmtiles 4.4.1 under static/vendors and an
  optional static/basemaps dir, matching the PR's vendored-asset pattern.
- New photos.tab_*/layout_*/map_* + people.* keys in en.json.

Verified: npm run check (svelte-check + eslint + stylelint + prettier),
npm run test:unit (36 pass), and npm run build all green.
2026-06-19 12:59:16 +00:00
Bradley Nelson 4ddadb5d0f update i18n 2026-06-17 22:52:06 -06:00
Bradley Nelson daa3010458 init new frontend 2026-06-17 17:06:30 -06:00