Edouard Vanbelle
a9e05ec47c
test: full caldav + carddav test suite
2026-07-14 22:01:31 +02:00
Edouard Vanbelle
7966c7178a
fix(528): pass3: PUT with RECURRENCE-ID
2026-07-14 20:42:18 +02:00
Edouard Vanbelle
02f67f7a5c
fix(528): pass2 add recurrence_id field
2026-07-14 20:42:18 +02:00
Edouard Vanbelle
184b9dfab6
fix(528): ical and recurrence
...
import use if ical and use ical::IcalParser
prepare unit test
2026-07-14 20:42:18 +02:00
Dionisio Pozo
7557084ad3
Merge pull request #586 from EdouardVanbelle/fix/caldav+carddav-error-handling
...
fix(caldav+carddav) error handling
2026-07-14 15:59:34 +02:00
Edouard Vanbelle
a7a45b3383
fix(caldav+carddav): raise 400 error on param issue
...
rather than a 500
2026-07-14 14:52:58 +02:00
Dionisio Pozo
4b98f8f0e7
Merge pull request #584 from EdouardVanbelle/doc/update
...
doc: update doc to reflect recent changes
2026-07-14 14:30:49 +02:00
Edouard Vanbelle
54b5b3bf4f
feat(caldav+carddav): auto create default cal & card
...
automatically create default Calendar and default addressbook per user
(no creation if user already have a such resource)
default name are "Personal"
this is using the user's life cycle like does the drives
answers to issue #545
2026-07-14 14:30:05 +02:00
Edouard Vanbelle
5e95d6dccf
doc: update doc to reflect recent changes
...
- grants: permission moved to roles
- new resources (Drive, Caldav, Carddav, Playlist) now using ReBAC
- expired shared now cleaned up
- drive visible in Webdav
- new login/registration options (domain allow list, policies, etc)
- upgrade of external user into internal user
2026-07-14 13:31:48 +02:00
Dionisio Pozo
970f97b91a
Merge pull request #570 from swissiety/rfc-4331-quota-properties
...
feat(webdav): RFC 4331 quota-available-bytes/quota-used-bytes
2026-07-14 12:58:22 +02:00
Dionisio Pozo
3ecfddd6b8
Merge pull request #583 from EdouardVanbelle/doc/app-password
...
doc(app password): correct doc: webdav login via app password
2026-07-14 12:58:06 +02:00
Dionisio Pozo
0832f48791
Merge pull request #582 from EdouardVanbelle/security
...
security(auth): specify to agents that OIDC should never be bypassed
2026-07-14 12:23:33 +02:00
Edouard Vanbelle
ebb11f19c2
doc(webdav): login is via app password
2026-07-14 12:21:51 +02:00
Dionisio Pozo
2f1bea165c
Merge pull request #581 from EdouardVanbelle/vitepress
...
chore(vitepress): convert ../ references to DioCrafts/OxiCloud links
2026-07-14 12:16:32 +02:00
Dionisio Pozo
bfc7a79423
Merge pull request #580 from EdouardVanbelle/feat/user-upgrade
...
feat(user upgrade): permit upgrade of in invited account into internal account
2026-07-14 12:11:11 +02:00
Edouard Vanbelle
f738e3f442
security(auth): specify to agents that OIDC should never be bypassed
2026-07-14 12:07:30 +02:00
Edouard Vanbelle
33d0c460fd
chore(vitepress): convert ../ references to github/DioCrafts/OxiCloud links
...
unblock site generation and link code reference to github DioCrafts/OxiCloud project
2026-07-14 12:02:12 +02:00
Edouard Vanbelle
1fa1966fbe
feat(upgrate): add i18n for account upgade
2026-07-14 11:40:16 +02:00
Edouard Vanbelle
f331dbf0ee
feat(account): upgrade external to internal
2026-07-14 11:10:23 +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
Dionisio Pozo
17bfb57af7
Merge pull request #578 from EdouardVanbelle/feat/user-regisration-with-allow-list
...
feat(registration): add a domain allow list
2026-07-14 09:06:34 +02:00
Edouard Vanbelle
341e354162
test(frontend): correct test to fit sign up/in
2026-07-14 03:27:28 +02:00
Edouard Vanbelle
691d01a458
clippy
2026-07-14 03:18:04 +02:00
Edouard Vanbelle
3350e77080
fix(env): ensure .env is not taken when using --config
2026-07-14 03:16:27 +02:00
Edouard Vanbelle
0ad283e590
fix(magic-link): correct url to fit sveltekit
2026-07-14 03:16:27 +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
01da450cf6
feat(registration): add a domain allow list
...
add:
- OXICLOUD_REGISTRATION_ALLOWED_EMAIL_DOMAINS to specify list of domains allowing a self registration
- OXICLOUD_REQUIRE_VERIFIED_EMAIL=true|false
- OXICLOUD_AUTH_METHODS=password,magic_link (login methods, OIDC is on top of this)
- OXICLOUD_AUTH_POLICIES=permit_magic_link_for_password_users (OIDC is on top)
2026-07-14 02:43:39 +02: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
Edouard Vanbelle
b9d6fa39c0
feat(user-pref): revert view mode as user-prefs serverside
...
previous change is breaking playwright tests, need to check later changes
2026-07-13 23:16:09 +02:00
M.Schmidt
c62f97d8f2
readd removed utility, cleanup/shorten enum usage
2026-07-13 22:22:12 +02:00
Edouard Vanbelle
b18f0dc74a
test(front): isolate dotfile e2e fixtures under scratch parents
2026-07-13 22:00:53 +02:00
Dionisio Pozo
899a08bebe
Merge pull request #575 from EdouardVanbelle/doc/drive
...
docs(drive): add drive doc
2026-07-13 21:36:22 +02:00
Dionisio Pozo
2d49c3c8de
Merge pull request #574 from EdouardVanbelle/fix/nextcloud-with-oidc-and-drive-picker
...
fix(nc): login OIDC + drive picker
2026-07-13 21:36:08 +02:00
Edouard Vanbelle
063382ad60
test(front): test dotfile view/hidden
2026-07-13 21:21:26 +02:00
Edouard Vanbelle
0296d157a3
chore: remove deprecated playwright wrapped
2026-07-13 21:21:26 +02:00
M.Schmidt
6701ddfc17
Merge branch 'main' into rfc-4331-quota-properties
...
# Conflicts:
# src/interfaces/nextcloud/report_handler.rs
# src/interfaces/nextcloud/webdav_handler.rs
# tests/api/run.sh
2026-07-13 20:32:01 +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
M.Schmidt
e5c8d89da9
fix(webdav): bump storage usage on PUT, not just REST multipart upload
...
update_file_streaming_with_perms (the method behind every WebDAV/
NextCloud/WOPI PUT) never called the storage-usage-delta hook, so
drives.used_bytes and the RFC 4331 quota-used-bytes property never
reflected content written via WebDAV — only the REST multipart
upload path bumped usage. Extract apply_storage_usage_delta() from
maybe_update_storage_usage() and wire it into both branches: the
overwrite path applies new_size - old_size, the create path applies
the full size.
Also fixes the two RFC 4331 hurl tests that caught this:
nc_webdav_quota_properties.hurl had a Hurl parse error ([BasicAuth]
section keys can't mix literal+template, so the {user}~{folder}
composite marker is now pre-resolved via [Options] variable: before
being referenced as a single template), and both quota-properties
tests now retry the post-upload PROPFIND (matching the existing
drive_quota.hurl/user_envelope_quota.hurl pattern) since the delta
is applied fire-and-forget on a background task.
2026-07-13 20:00:01 +02:00
M.Schmidt
8a405af5e1
refactor(drive): match DriveKind directly instead of Drive::is_personal()
...
Drops the boolean is_personal() wrapper in favor of matching
DriveKind::Personal/Shared at the two call sites, matching the
exhaustive-match convention already used for DriveKind elsewhere
(as_str, parse, DriveKindDto::from).
2026-07-13 19:51:40 +02:00
Edouard Vanbelle
fe66a379b7
docs(drive): add drive doc
...
documentation for users
2026-07-13 19:16:23 +02:00
Edouard Vanbelle
05ef55a8e0
fix(nc): login OIDC + drive picker
...
ensure OIDC is supported during nextcloud login
flow is:
1. nextcloud
2. oxicloud login ( direct pass or OIDC according config)
3. drive picker (if user has multiple drive)
4. success page + backchannel login to nextcloud
2026-07-13 18:30:20 +02:00
Dionisio Pozo
06da428493
Merge pull request #572 from EdouardVanbelle/feat/nextcloud-chrooted-drive
...
feat/nextcloud chrooted drive
2026-07-13 09:37:10 +02:00
Dionisio Pozo
e71ef59a04
Merge pull request #571 from EdouardVanbelle/fix/cached-elements
...
fix(front): unregister cache prio to 0.8.0
2026-07-13 09:36:51 +02:00
Dionisio Pozo
22e09c09be
Merge pull request #538 from swissiety/webdav-litmus-compliance
...
implement dead properties for nextcloud handler and fixup frontend migration leftover
2026-07-13 09:36:43 +02:00
Dionisio Pozo
8301e0c2cb
Merge pull request #569 from EdouardVanbelle/feat/grants-cleanup
...
feat(grant): clean up expired grants
2026-07-13 09:36:29 +02:00
M.Schmidt
c07aeabd85
feat(webdav): drive-aware RFC 4331 quota properties
...
resolve_quota only ever reported the caller's personal envelope,
ignoring the drive_id already resolved at every PROPFIND call site —
shared drives with their own quota showed the wrong numbers. Adds
AppState::resolve_webdav_quota, shared by both WebDAV surfaces:
nil drive_id or personal drive -> account envelope, shared drive ->
its own storage.drives quota/used_bytes.
Also adds quota-used-bytes/quota-available-bytes to the NextCloud-
compatible surface, which previously had no RFC 4331 support at all.
Registers webdav_quota_properties.hurl and the new
nc_webdav_quota_properties.hurl in tests/api/run.sh — neither was
wired into the suite before this change.
2026-07-13 00:34:17 +02:00
M.Schmidt
fdef73380f
fix(thumbnail): remove redundant reference in format! arg
...
clippy::useless_borrows_in_formatting
2026-07-12 22:30:45 +02:00
M.Schmidt
7011fdff5a
Merge origin/main into webdav-litmus-compliance
2026-07-12 22:25:12 +02:00
Edouard Vanbelle
52814b4d7c
fix(nextcloud): fix chroot + synchronisation
...
- add better hurl coverage on nextcloud chrooted login
- fix issue with nextcloud using /{drive name}/~{drive id}/
- fix trashbin handler
confusion username vs {username}~{folder id}
2026-07-12 22:19:45 +02:00
Edouard Vanbelle
230927a80e
fix(templates): ensure template use frontend css
...
this fix the nextcloud login + drive selector (chroot)
fix also invitation / magic link
also correct the UX: once user has logged in nextcloud, show an explicita page
2026-07-12 22:19:42 +02:00