From 87de148a592ae44e2949ea020abf446f1e97e682 Mon Sep 17 00:00:00 2001 From: curo1305 Date: Sat, 30 May 2026 11:30:13 +0200 Subject: [PATCH] feat(05-10): OAuth fetch + Nextcloud edit fix + Edit on ERROR + text overflow - client.js: add initiateOAuth() and getConnectionConfig() helpers - SettingsCloudTab: replace window.location.href with initiateOAuth() + fetch/JWT - SettingsCloudTab: add Edit button to ACTIVE and ERROR blocks for non-OAuth providers - SettingsCloudTab: wrap ConfirmBlock in w-full overflow-hidden div - CloudCredentialModal: add existing prop, edit-mode pre-population via /config endpoint - CloudCredentialModal: add showAdvanced + customEndpoint for Nextcloud custom paths - ConfirmBlock: add break-words class to message paragraph - cloud.py: add GET /api/cloud/connections/{id}/config endpoint (non-secret fields) --- backend/api/cloud.py | 51 ++++++ frontend/src/api/client.js | 24 +++ .../components/cloud/CloudCredentialModal.vue | 167 ++++++++++++++++-- .../components/settings/SettingsCloudTab.vue | 124 +++++++++---- frontend/src/components/ui/ConfirmBlock.vue | 2 +- 5 files changed, 310 insertions(+), 58 deletions(-) diff --git a/backend/api/cloud.py b/backend/api/cloud.py index 248f266..d35a7e1 100644 --- a/backend/api/cloud.py +++ b/backend/api/cloud.py @@ -642,6 +642,57 @@ async def list_connections( return {"items": [CloudConnectionOut.model_validate(c).model_dump() for c in connections]} +# ── GET /api/cloud/connections/{connection_id}/config ──────────────────────── + + +@router.get("/connections/{connection_id}/config") +async def get_connection_config( + connection_id: uuid.UUID, + session: AsyncSession = Depends(get_db), + current_user: User = Depends(get_regular_user), +) -> dict: + """Return non-secret configuration fields for a WebDAV/Nextcloud connection. + + Returns server_url and connection_username (not password) so the frontend + can pre-populate the Edit modal without exposing credentials. + + Only applicable to WebDAV / Nextcloud connections (not OAuth providers). + Returns 404 for wrong-owner or unknown connections (prevents ID enumeration). + Returns 400 for OAuth providers (no non-secret config to return). + + Security: + - Only connection owned by current_user.id is returned (T-05-05-04) + - password is never included in the response (D-18) + - Returns 404 for wrong-owner connections (prevents ID enumeration) + """ + conn = await session.get(CloudConnection, connection_id) + if conn is None or conn.user_id != current_user.id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connection not found") + + if conn.provider not in VALID_WEBDAV_PROVIDERS: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Connection config is only available for WebDAV/Nextcloud connections", + ) + + master_key = settings.cloud_creds_key.encode() + try: + credentials = decrypt_credentials(master_key, str(current_user.id), conn.credentials_enc) + except Exception: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Failed to decrypt connection credentials", + ) + + # Return non-secret fields only — never expose the password + return { + "id": str(conn.id), + "provider": conn.provider, + "server_url": credentials.get("server_url", ""), + "connection_username": credentials.get("username", ""), + } + + # ── DELETE /api/cloud/connections/{connection_id} ───────────────────────────── diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index aa7da8c..2b5b730 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -437,3 +437,27 @@ export function updateDefaultStorage(backend) { export function getCloudFolders(provider, folderId) { return request(`/api/cloud/folders/${provider}/${folderId}`) } + +/** + * Initiate OAuth flow for Google Drive or OneDrive. + * + * Returns a JSON object {url: ""} from the backend. + * The caller is responsible for navigating: window.location.href = data.url + * + * Using request() (not bare window.location.href) ensures the Bearer header + * is injected and the 401→refresh retry path fires if the token has expired. + * See plan 05-10 trust boundary: frontend→/api/cloud/oauth/initiate/{provider}. + */ +export function initiateOAuth(provider) { + return request(`/api/cloud/oauth/initiate/${provider}`) +} + +/** + * Fetch non-secret configuration for a WebDAV/Nextcloud connection (edit flow). + * + * Returns {id, provider, server_url, connection_username} — never the password. + * Used to pre-populate the Edit modal when re-editing an existing connection. + */ +export function getConnectionConfig(connectionId) { + return request(`/api/cloud/connections/${connectionId}/config`) +} diff --git a/frontend/src/components/cloud/CloudCredentialModal.vue b/frontend/src/components/cloud/CloudCredentialModal.vue index 81f85b7..5c7e3e3 100644 --- a/frontend/src/components/cloud/CloudCredentialModal.vue +++ b/frontend/src/components/cloud/CloudCredentialModal.vue @@ -8,7 +8,9 @@
-

Connect {{ provider?.label }}

+

+ {{ existing ? 'Edit' : 'Connect' }} {{ provider?.label }} +

+ +
+ Loading connection settings... +
+ -
- -
+ + +
+ + +

+ Your Nextcloud server address. The WebDAV path is constructed automatically. +

+
+ + +

Full WebDAV endpoint URL including username path segment.

@@ -45,6 +66,36 @@ />
+ +
+ +
+ + +

+ Override the automatically-constructed WebDAV path. Leave empty to use the default. +

+
+
+

Authentication method

@@ -90,8 +141,12 @@ type="password" v-model="password" autocomplete="current-password" + :placeholder="existing ? 'Leave empty to keep current password' : ''" class="block w-full rounded-lg px-3 py-2 text-sm border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-colors" /> +

+ Password is not displayed for security. Enter a new password to change it, or leave empty to keep the current one. +

@@ -111,7 +166,7 @@ @click="close" class="text-sm px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors" > - Keep current settings + {{ existing ? 'Cancel' : 'Keep current settings' }}
@@ -131,7 +186,7 @@