fix(calendar): SSRF exception for Exchange + error messages + domain in edit

- SSRF check skipped for Exchange type (internal EWS servers are common)
- testConnectionFromConfig catches SSRF/validation errors, returns {success:false,error} instead of throwing 403
- updateSource reads existing.type to determine effective type for SSRF check
- Panel shows saveError/editSaveError on failed add/update
- Edit form initialValues now includes domain field
- i18n: calendar.saveError key added (de+en)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 13:41:32 +02:00
parent b719291bdc
commit 51d8c2f14e
4 changed files with 29 additions and 8 deletions
@@ -37,6 +37,8 @@ export function CalendarSettingsPanel() {
const [editingId, setEditingId] = useState<string | null>(null);
const [deletingId, setDeletingId] = useState<string | null>(null);
const [isSaving, setIsSaving] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null);
const [editSaveError, setEditSaveError] = useState<string | null>(null);
const [testResults, setTestResults] = useState<Record<string, 'success' | 'error' | 'testing'>>({});
// Load sources on mount
@@ -76,6 +78,7 @@ export function CalendarSettingsPanel() {
// Add new source
const handleAddSource = useCallback(async (payload: CreateSourcePayload) => {
setIsSaving(true);
setSaveError(null);
try {
const created = await addSource(payload);
setSources((prev) => [...prev, created]);
@@ -93,11 +96,11 @@ export function CalendarSettingsPanel() {
setTestResults((prev) => ({ ...prev, [created.id]: 'error' }));
}
} catch {
// Error handled silently
setSaveError(t('calendar.saveError') || 'Fehler beim Speichern');
} finally {
setIsSaving(false);
}
}, []);
}, [t]);
// Delete source
const handleDeleteSource = useCallback(async (id: string) => {
@@ -282,6 +285,9 @@ export function CalendarSettingsPanel() {
{/* Edit form (below the source being edited) */}
{editingId && (
<div className="rounded-md border border-border bg-card p-4">
{editSaveError && (
<p className="mb-3 text-sm text-destructive">{editSaveError}</p>
)}
<h3 className="mb-3 text-sm font-semibold text-foreground">
Edit Source
</h3>
@@ -296,12 +302,14 @@ export function CalendarSettingsPanel() {
url: s.url,
username: s.username ?? '',
exchangeMode: s.exchangeMode ?? undefined,
domain: s.domain ?? undefined,
color: s.color ?? undefined,
}
: undefined;
})()}
onSave={async (payload) => {
setIsSaving(true);
setEditSaveError(null);
try {
const updated = await updateSource(editingId, payload);
setSources((prev) =>
@@ -309,7 +317,7 @@ export function CalendarSettingsPanel() {
);
setEditingId(null);
} catch {
// Error handled silently
setEditSaveError(t('calendar.saveError') || 'Fehler beim Speichern');
} finally {
setIsSaving(false);
}
@@ -337,6 +345,9 @@ export function CalendarSettingsPanel() {
<h3 className="mb-3 text-sm font-semibold text-foreground">
{t('calendar.addSource')}
</h3>
{saveError && (
<p className="mb-3 text-sm text-destructive">{saveError}</p>
)}
<CalendarSourceForm
onSave={handleAddSource}
onCancel={() => setShowAddForm(false)}
+1
View File
@@ -208,6 +208,7 @@
"formTesting": "Teste...",
"formTestSuccess": "Verbindung erfolgreich",
"formTestFailed": "Verbindung fehlgeschlagen",
"saveError": "Speichern fehlgeschlagen. Bitte Eingaben prüfen.",
"formSave": "Speichern",
"formSaving": "Wird gespeichert...",
"formCancel": "Abbrechen",
+1
View File
@@ -208,6 +208,7 @@
"formTesting": "Testing...",
"formTestSuccess": "Connection successful",
"formTestFailed": "Connection failed",
"saveError": "Save failed. Please check your input.",
"formSave": "Save",
"formSaving": "Saving...",
"formCancel": "Cancel",