` component (the div at the current line starting `v-if="confirmRemoveId === connectionFor(provider.key)?.id"`). This div needs to break out of the flex row — it is already rendered outside the `flex items-center` row as a sibling block, so adding `w-full` and `overflow-hidden` is sufficient.
### 5. CloudCredentialModal.vue — fix Nextcloud edit custom endpoint pre-population
In the watch handler on `props.show`, after the existing logic that sets `serverBase.value` by extracting the hostname:
- Compute what the auto-constructed URL would be using the extracted hostname and `existing.connection_username`:
`const autoUrl = match ? \`${match[1]}/remote.php/dav/files/${encodeURIComponent(existing.connection_username ?? '')}/\` : ''`
- If `existing.server_url !== autoUrl` (the stored URL doesn't match the auto-constructed pattern) AND `existing.server_url` has a path beyond just the hostname, then:
- Set `showAdvanced.value = true`
- Set `customEndpoint.value = existing.server_url`
- Otherwise leave `showAdvanced.value = false` and `customEndpoint.value = ''` (existing behavior).
### 6. ConfirmBlock.vue — add break-words to message paragraph
Change `` to `
`.
### 7. SettingsCloudTab.test.js — add Vitest test for handleConnect OAuth flow
The file `frontend/src/components/settings/__tests__/SettingsCloudTab.test.js` already exists. Add a new test asserting the corrected OAuth connect flow:
- Mock `initiateOAuth` from `../../../../api/client.js` using `vi.mock`: `vi.mock('../../../../api/client.js', () => ({ initiateOAuth: vi.fn() }))`.
- In the test, set `initiateOAuth.mockResolvedValue({ url: 'https://accounts.google.com/o/oauth2/auth?state=xyz' })`.
- Assign `window.location = { href: '' }` (use `Object.defineProperty` or `vi.stubGlobal` to make `window.location.href` writable in the test environment).
- Mount `SettingsCloudTab` with a stubbed auth store that has a valid accessToken.
- Find a Connect button for a provider with `key === 'google_drive'` and trigger a click event simulating `handleConnect({ key: 'google_drive' })`.
- Assert: `initiateOAuth` was called with `'google_drive'`.
- Assert: `window.location.href` was set to `'https://accounts.google.com/o/oauth2/auth?state=xyz'` (the exact URL returned by the mock — confirming the component navigates to `data.url` and not directly to `/api/cloud/...`).
- Use `afterEach(() => vi.restoreAllMocks())`.
cd /Users/nik/Documents/Progamming/document_scanner/frontend && npm run test -- --reporter=verbose --run src/components/settings/__tests__/SettingsCloudTab.test.js 2>&1 | tail -20 && npm run build 2>&1 | tail -5
Vitest test for handleConnect OAuth flow passes. Frontend build passes with zero errors. All four UI changes are applied: OAuth uses fetch, ERROR rows have Edit button, Nextcloud watch handler preserves custom endpoint, ConfirmBlock message has break-words.
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| frontend→/api/cloud/oauth/initiate | Now goes through fetch() with Bearer header instead of bare browser navigation |
| OAuth URL returned to frontend | URL is generated by the backend OAuth library and stored state in Redis — frontend only receives the URL string |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-05-10-01 | Spoofing | oauth_initiate auth | mitigate | get_regular_user still enforced — only authenticated users receive the OAuth URL |
| T-05-10-02 | Information Disclosure | OAuth URL in JSON response | accept | URL is a standard OAuth authorization URL with CSRF state token — no credentials in the URL |
| T-05-10-03 | Tampering | OAuth state token | mitigate | State token generated server-side (secrets.token_urlsafe(32)), stored in Redis with TTL 1800, single-use (deleted in callback) — unchanged from original design |
| T-05-10-04 | Spoofing | Nextcloud custom endpoint re-edit | accept | Pre-populated values come from encrypted DB credentials decrypted server-side and returned as server_url field — not user-alterable before display |
| T-05-10-SC | Tampering | npm/pip installs | mitigate | No new packages installed in this plan |
After both tasks complete:
- `pytest backend/tests/test_cloud.py::test_oauth_initiate_returns_json_url backend/tests/test_cloud.py::test_oauth_initiate_requires_auth -v`
- `npm run test -- --run src/components/settings/__tests__/SettingsCloudTab.test.js` — Vitest test for handleConnect OAuth flow passes
- `npm run build` — zero errors
- Manual: click Connect on Google Drive — browser navigates to accounts.google.com (not localhost 401)
- Manual: edit Nextcloud connection with custom endpoint — Advanced section opens with endpoint pre-filled
- Manual: connection in ERROR state — Edit button is visible
- Manual: disconnect confirmation text wraps within its container
- oauth_initiate returns 200 JSON {url} (not 302 redirect)
- Two new pytest tests pass for OAuth initiate; mock uses explicit `mock_flow.authorization_url.return_value = ("https://accounts.google.com/test", "state123")` two-tuple
- handleConnect in SettingsCloudTab uses fetch() + initiateOAuth(); no window.location.href to /api path
- Vitest test asserts initiateOAuth called with provider key and window.location.href set to returned URL
- ERROR status template block has Edit button for non-OAuth providers
- CloudCredentialModal watch handler repopulates customEndpoint and showAdvanced when stored URL differs from auto-constructed pattern
- ConfirmBlock message paragraph has break-words class
- Full frontend build and pytest suite have zero new failures