docs(12-06): add plan 06 SUMMARY — UUID-only navigation, multi-account, credential-update

This commit is contained in:
curo1305
2026-06-21 22:30:51 +02:00
parent c85e4abd91
commit 4a910549ac
@@ -0,0 +1,104 @@
---
phase: "12"
plan: "06"
subsystem: cloud-navigation
tags: [cloud, navigation, multi-account, uuid, security]
dependency_graph:
requires: ["12-05"]
provides: [connection-id-native-lifecycle, uuid-only-navigation, multi-account-settings]
affects: [backend/api/cloud/connections.py, frontend/src/components/cloud, frontend/src/components/settings]
tech_stack:
added: []
patterns: [connection-ID-based-routing, owner-scoped-credential-update, always-insert-pattern]
key_files:
created:
- frontend/src/components/cloud/__tests__/CloudProviderTreeItem.test.js
- frontend/src/components/cloud/__tests__/CloudFolderTreeItem.test.js
- frontend/src/components/layout/__tests__/AppSidebar.test.js
modified:
- backend/api/cloud/connections.py
- backend/tests/test_cloud.py
- frontend/src/api/cloud.js
- frontend/src/api/utils.js
- frontend/src/components/cloud/CloudCredentialModal.vue
- frontend/src/components/cloud/CloudProviderTreeItem.vue
- frontend/src/components/cloud/CloudFolderTreeItem.vue
- frontend/src/components/layout/AppSidebar.vue
- frontend/src/components/settings/SettingsCloudTab.vue
- backend/main.py
- frontend/package.json
decisions:
- "Always INSERT new connection rows (never upsert by provider) to allow same-provider multi-account"
- "PUT /connections/{id}/credentials preserves existing password when field omitted"
- "CloudFolderTreeItem uses connectionId prop instead of provider slug everywhere"
- "Cloud Storage sidebar link active only on exact /cloud route"
metrics:
duration: "~30 minutes"
completed: "2026-06-21"
tasks_completed: 3
files_changed: 14
---
# Phase 12 Plan 06: Cloud Connection Navigation and Multi-Account Support Summary
**One-liner:** UUID-only cloud navigation with same-provider multi-account independence, PUT credential-update endpoint, and route-aware sidebar/breadcrumb selection.
## What Was Built
### Task 1: Connection-ID native lifecycle
**Backend:**
- Replaced `_upsert_cloud_connection` (upsert by provider) with `_insert_cloud_connection` (always INSERT new UUID row). Two POSTs for Nextcloud now create two independent rows with distinct UUIDs and independent encrypted credentials.
- Added `PUT /api/cloud/connections/{id}/credentials` endpoint: owner-scoped (404 on wrong owner), merges submitted fields with existing credentials (password omitted = preserve existing), re-runs `validate_cloud_url` and health-check before committing (T-12-06-04 SSRF gate), audit-logged, credential-free response.
- Backend tests: `test_same_provider_connections_are_independent` proves two distinct UUIDs; `test_credential_update_wrong_owner_returns_404` covers T-12-06-01 IDOR.
**Frontend:**
- `CloudCredentialModal.submit`: calls `updateWebDavCredentials(existing.id, ...)` on edit (targeting the specific UUID), `connectWebDav(...)` only for new connections.
- `SettingsCloudTab`: replaced `connectionFor(provider)` (first-match) with `connectionsFor(provider)` (all matches), renders each as an independent named row with its own Rename/Edit/Remove actions, plus an always-visible "Add account" / "Connect" button per provider.
- `utils.js request()`: FastAPI validation arrays (`detail: [{loc, msg}]`) now summarised into a concise `field: message` string instead of bare `HTTP 422`.
- New API export `updateWebDavCredentials` in `cloud.js`.
### Task 2: UUID-only navigation and sidebar repair
- `CloudProviderTreeItem`: uses `getCloudFoldersByConnectionId(connection.id, '')` (never `getCloudFolders(provider, ...)`), routes to `/cloud/{connection.id}/root`, exposes `isActive` computed from exact route prefix match.
- `CloudFolderTreeItem`: accepts `connectionId` prop (replaces `provider` slug), uses `getCloudFoldersByConnectionId(connectionId, folder.id)` for children, routes to `/cloud/{connectionId}/{folder.id}`, `isActive` = exact path match.
- `AppSidebar`: Cloud Storage nav-link active only on `$route.path === '/cloud'` (exact), not `startsWith` — prevents generic highlight while inside a connection.
- `CloudFolderView` was already connection-ID based (prior plan); breadcrumbs already use `connectionRoot` prop.
### Task 3: Full suites, build, version, docs
- All 516 backend tests pass (7 xfailed, 17 skipped — pre-existing).
- All 334 frontend tests across 42 files pass.
- Production build clean (610 ms).
- npm audit: 0 vulnerabilities.
- Version bumped 0.2.1 → 0.2.2 in `backend/main.py` and `frontend/package.json`.
## Verification Against UAT Failures
| UAT Symptom | Fixed by |
|---|---|
| POST Nextcloud 201 → sidebar browse → 422 (UUID endpoint received provider slug) | CloudProviderTreeItem now passes `connection.id` not `connection.provider` |
| Two same-provider connections not possible (upsert merged them) | `_insert_cloud_connection` always creates a new row |
| Settings showed only first Nextcloud account | `connectionsFor()` renders all accounts per provider |
| Editing one connection could affect another same-provider row | `PUT /connections/{id}/credentials` is owner + ID scoped |
| Bare HTTP 422 shown to user on validation failure | `utils.js` now formats FastAPI validation arrays into field messages |
## Deviations from Plan
None — plan executed exactly as written.
## Known Stubs
None — all cloud navigation uses real connection IDs from API responses.
## Threat Flags
None — new `PUT /connections/{id}/credentials` endpoint follows the same security pattern as existing endpoints: owner scope via `_get_owned_connection`, SSRF validation, health-check, credential-free response, audit log.
## Self-Check: PASSED
- `backend/api/cloud/connections.py` — modified, `_insert_cloud_connection` present
- `PUT /connections/{id}/credentials` endpoint registered in router
- `frontend/src/components/cloud/CloudProviderTreeItem.vue` — uses `getCloudFoldersByConnectionId`
- `frontend/src/components/cloud/CloudFolderTreeItem.vue` — uses `connectionId` prop
- All commits verified: 731b65e, 057b499, c85e4ab