diff --git a/dev-api.py b/dev-api.py index 4b1c4b3..2bca90c 100644 --- a/dev-api.py +++ b/dev-api.py @@ -1293,9 +1293,9 @@ def get_email_config(): def save_email_config(payload: dict): try: EMAIL_PATH.parent.mkdir(parents=True, exist_ok=True) - # Extract password before writing yaml - password = payload.pop("password", None) or payload.pop("password_set", None) - # Only store if it's a real new value (not the sentinel True/False) + # Extract password before writing yaml; discard the sentinel boolean regardless + password = payload.pop("password", None) + payload.pop("password_set", None) # always discard — boolean sentinel, not a secret if password and isinstance(password, str): set_credential(EMAIL_CRED_SERVICE, EMAIL_CRED_KEY, password) # Write non-secret fields to yaml (chmod 600 still, contains username) @@ -1311,8 +1311,8 @@ def save_email_config(payload: dict): @app.post("/api/settings/system/email/test") def test_email(payload: dict): try: - # Allow test to pass in a password override, or use stored credential - password = payload.get("password") or get_credential(EMAIL_CRED_SERVICE, EMAIL_CRED_KEY) + # Always use the stored credential — never accept a password in the test request body + password = get_credential(EMAIL_CRED_SERVICE, EMAIL_CRED_KEY) host = payload.get("host", "") port = int(payload.get("port", 993)) use_ssl = payload.get("ssl", True) diff --git a/scripts/credential_store.py b/scripts/credential_store.py index f4cafaf..3a1c307 100644 --- a/scripts/credential_store.py +++ b/scripts/credential_store.py @@ -23,8 +23,9 @@ logger = logging.getLogger(__name__) _ENV_REF = re.compile(r'^\$\{([A-Z_][A-Z0-9_]*)\}$') -CRED_DIR = Path("config/credentials") -KEY_PATH = Path("config/.credential_key") +_PROJECT_ROOT = Path(__file__).parent.parent +CRED_DIR = _PROJECT_ROOT / "config" / "credentials" +KEY_PATH = _PROJECT_ROOT / "config" / ".credential_key" def _resolve_env_ref(value: str) -> Optional[str]: diff --git a/web/src/stores/settings/system.ts b/web/src/stores/settings/system.ts index bb4ea39..7160451 100644 --- a/web/src/stores/settings/system.ts +++ b/web/src/stores/settings/system.ts @@ -30,6 +30,10 @@ export const useSystemStore = defineStore('settings/system', () => { const deployConfig = ref>({}) const filePathsSaving = ref(false) const deploySaving = ref(false) + const filePathsError = ref(null) + const deployError = ref(null) + // Integration test/connect results — keyed by integration id + const integrationResults = ref>({}) async function loadLlm() { loadError.value = null @@ -158,11 +162,12 @@ export const useSystemStore = defineStore('settings/system', () => { `/api/settings/system/integrations/${id}/connect`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) } ) - if (error || !data?.ok) { - return { ok: false, error: data?.error ?? 'Connection failed' } - } - await loadIntegrations() - return { ok: true } + const result = error || !data?.ok + ? { ok: false, error: data?.error ?? 'Connection failed' } + : { ok: true } + integrationResults.value = { ...integrationResults.value, [id]: result } + if (result.ok) await loadIntegrations() + return result } async function testIntegration(id: string, credentials: Record) { @@ -170,7 +175,9 @@ export const useSystemStore = defineStore('settings/system', () => { `/api/settings/system/integrations/${id}/test`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) } ) - return { ok: data?.ok ?? false, error: data?.error ?? (error ? 'Test failed' : undefined) } + const result = { ok: data?.ok ?? false, error: data?.error ?? (error ? 'Test failed' : undefined) } + integrationResults.value = { ...integrationResults.value, [id]: result } + return result } async function disconnectIntegration(id: string) { @@ -206,7 +213,7 @@ export const useSystemStore = defineStore('settings/system', () => { body: JSON.stringify(filePaths.value), }) filePathsSaving.value = false - if (error) saveError.value = 'Failed to save file paths.' + filePathsError.value = error ? 'Failed to save file paths.' : null } async function loadDeployConfig() { @@ -222,14 +229,14 @@ export const useSystemStore = defineStore('settings/system', () => { body: JSON.stringify(deployConfig.value), }) deploySaving.value = false - if (error) saveError.value = 'Failed to save deployment config.' + deployError.value = error ? 'Failed to save deployment config.' : null } return { backends, byokAcknowledged, byokPending, saving, saveError, loadError, loadLlm, trySave, confirmByok, cancelByok, - services, emailConfig, integrations, serviceErrors, emailSaving, emailError, - filePaths, deployConfig, filePathsSaving, deploySaving, + services, emailConfig, integrations, integrationResults, serviceErrors, emailSaving, emailError, + filePaths, deployConfig, filePathsSaving, deploySaving, filePathsError, deployError, loadServices, startService, stopService, loadEmail, saveEmail, testEmail, saveEmailWithPassword, loadIntegrations, connectIntegration, testIntegration, disconnectIntegration, diff --git a/web/src/views/settings/SystemSettingsView.vue b/web/src/views/settings/SystemSettingsView.vue index 1a15bee..a6dac2f 100644 --- a/web/src/views/settings/SystemSettingsView.vue +++ b/web/src/views/settings/SystemSettingsView.vue @@ -144,8 +144,8 @@
- - {{ integrationResults[integration.id].ok ? '✓ OK' : '✗ ' + integrationResults[integration.id].error }} + + {{ store.integrationResults[integration.id].ok ? '✓ OK' : '✗ ' + store.integrationResults[integration.id].error }}
@@ -176,6 +176,7 @@ {{ store.filePathsSaving ? 'Saving…' : 'Save Paths' }} +

{{ store.filePathsError }}

@@ -199,6 +200,7 @@ {{ store.deploySaving ? 'Saving…' : 'Save (requires restart)' }} +

{{ store.deployError }}

@@ -288,8 +290,6 @@ async function handleConfirmByok() { const emailTestResult = ref(null) const emailPasswordInput = ref('') const integrationInputs = ref>({}) -const integrationResults = ref>({}) - async function handleTestEmail() { const result = await store.testEmail() emailTestResult.value = result?.ok ?? false @@ -307,8 +307,7 @@ async function handleConnect(id: string) { for (const field of integration.fields) { credentials[field.key] = integrationInputs.value[`${id}:${field.key}`] ?? '' } - const result = await store.connectIntegration(id, credentials) - integrationResults.value = { ...integrationResults.value, [id]: result } + await store.connectIntegration(id, credentials) } async function handleTest(id: string) { @@ -318,8 +317,7 @@ async function handleTest(id: string) { for (const field of integration.fields) { credentials[field.key] = integrationInputs.value[`${id}:${field.key}`] ?? '' } - const result = await store.testIntegration(id, credentials) - integrationResults.value = { ...integrationResults.value, [id]: result } + await store.testIntegration(id, credentials) } onMounted(async () => {