fix(settings): remember browser credentials by default
- Default fresh browser sessions to encrypted credential persistence with an explicit session-only opt-out - Keep API-key links left-aligned, legible, and limited to their text click target - Update credential guidance and browser coverage
This commit is contained in:
parent
858e6451de
commit
a7a44e90b2
|
|
@ -54,7 +54,7 @@ Property-panel anatomy in `packages/vue/src/primitives/PropertySection/`, `Segme
|
|||
|
||||
Credential persistence lives under `src/app/settings/credentials/`. Settings components receive `CredentialManager` and may inspect status, replace, or clear credentials; runtime adapters receive `CredentialResolver`. Components must not read saved secrets or keep them in long-lived reactive refs. Non-secret provider preferences remain in normal settings storage.
|
||||
|
||||
Tauri stores secrets in the native system credential store through `desktop/src/credentials.rs`; browser sessions default to memory and may explicitly opt into WebCrypto-encrypted IndexedDB storage. Native failures must never silently fall back to browser or plaintext storage. New integration credentials use stable `CredentialRef` values and join the unified Settings surface rather than adding feature-local key forms.
|
||||
Tauri stores secrets in the native system credential store through `desktop/src/credentials.rs`; browsers default to WebCrypto-encrypted IndexedDB storage and may explicitly opt out to session-only memory. Native failures must never silently fall back to browser or plaintext storage. New integration credentials use stable `CredentialRef` values and join the unified Settings surface rather than adding feature-local key forms.
|
||||
|
||||
Storage-provider schemas and runtime adapters live under `src/app/integrations/storage/`; non-secret preferences and credential references stay separate, and adapters resolve secrets at operation time. Local-first document caching and outbox synchronization live under `src/app/storage/`. A remote storage binding augments document source state and must not replace local file identity.
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
- Drag with the Text tool to create a fixed-size text box, or click to create auto-width text.
|
||||
- Target a specific open document and page from live CLI and MCP automation, including sessions with multiple documents.
|
||||
- Test OpenAI-compatible provider connections from AI settings with clearer setup errors.
|
||||
- Manage AI, agent, and media credentials from unified Settings, using the system credential store on desktop and optional encrypted storage in the browser.
|
||||
- Manage AI, agent, and media credentials from unified Settings, using the system credential store on desktop and encrypted browser storage by default with a session-only option.
|
||||
- Assign separate Design, Review, Fast, and Vision models, providers, endpoints, and credentials from AI settings.
|
||||
- Connect an S3-compatible storage workspace with local-first saves, background synchronization, and centrally managed credentials.
|
||||
- Convert image layers into editable vector layers with Recraft or fal.ai from the canvas context menu. (#322)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ export function hasLegacyCredentialStorage(): boolean {
|
|||
}
|
||||
|
||||
export function browserRemembersCredentials(): boolean {
|
||||
return browserCredentialStorage()?.getItem(BROWSER_PERSISTENCE_KEY) === 'remembered'
|
||||
const storage = browserCredentialStorage()
|
||||
return storage ? storage.getItem(BROWSER_PERSISTENCE_KEY) !== 'session' : false
|
||||
}
|
||||
|
||||
export function setBrowserRemembersCredentials(remembered: boolean): void {
|
||||
const storage = browserCredentialStorage()
|
||||
if (!storage) return
|
||||
if (remembered) storage.setItem(BROWSER_PERSISTENCE_KEY, 'remembered')
|
||||
else storage.removeItem(BROWSER_PERSISTENCE_KEY)
|
||||
storage.setItem(BROWSER_PERSISTENCE_KEY, remembered ? 'remembered' : 'session')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ const { href } = defineProps<{ href: string }>()
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<AppTextButton size="xs" underline @click="openExternalLink(href)">
|
||||
<AppTextButton
|
||||
size="sm"
|
||||
underline
|
||||
:ui="{ base: 'self-start text-left text-[11px]' }"
|
||||
@click="openExternalLink(href)"
|
||||
>
|
||||
<slot />
|
||||
</AppTextButton>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -150,9 +150,11 @@ test('saving API key in unified settings shows chat interface', async () => {
|
|||
await expect(page.getByTestId('app-settings-dialog')).toBeVisible()
|
||||
await expect(page.getByTestId('settings-remember-credentials')).toHaveAttribute(
|
||||
'data-state',
|
||||
'unchecked'
|
||||
'checked'
|
||||
)
|
||||
await expect(page.getByTestId('settings-credential-backend')).toContainText(
|
||||
'encrypted browser storage'
|
||||
)
|
||||
await expect(page.getByTestId('settings-credential-backend')).toContainText('this session only')
|
||||
await page.locator('[data-model-id]').first().click()
|
||||
await page.getByTestId('settings-model-provider').click()
|
||||
await page.getByRole('option', { name: 'OpenRouter' }).click()
|
||||
|
|
@ -270,10 +272,12 @@ test('transport errors show an actionable toast', async () => {
|
|||
})
|
||||
|
||||
test('"Get API key" link opens external URL via window.open', async () => {
|
||||
await page.evaluate("localStorage.removeItem('open-pencil:ai-key:openrouter')")
|
||||
await page.reload()
|
||||
await canvas.waitForInit()
|
||||
await chatTab().click()
|
||||
await page.getByTestId('provider-settings-trigger').click()
|
||||
await page.locator('[data-model-id]').first().click()
|
||||
await page.getByTestId('provider-settings-clear-key').click()
|
||||
await page.getByRole('button', { name: 'Back' }).click()
|
||||
await page.getByTestId('app-settings-done').click()
|
||||
await expect(page.getByTestId('provider-setup-open-settings')).toBeVisible()
|
||||
await page.getByTestId('provider-setup-open-settings').click()
|
||||
await page.locator('[data-model-id]').first().click()
|
||||
await page.getByTestId('settings-model-provider').click()
|
||||
|
|
|
|||
|
|
@ -80,8 +80,6 @@ test('remembered browser credentials survive reload and clear centrally', async
|
|||
await page.getByTestId('provider-setup-open-settings').click()
|
||||
|
||||
const remember = page.getByTestId('settings-remember-credentials')
|
||||
await expect(remember).toHaveAttribute('data-state', 'unchecked')
|
||||
await remember.click()
|
||||
await expect(remember).toHaveAttribute('data-state', 'checked')
|
||||
await expect(page.getByTestId('settings-credential-backend')).toContainText(
|
||||
'encrypted browser storage'
|
||||
|
|
|
|||
Loading…
Reference in a new issue