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
+13 -5
View File
@@ -141,7 +141,8 @@ export class CalendarService {
* T-05-11: Validates URL against private IP ranges (SSRF).
*/
async addSource(userId: string, tenantId: string, dto: CreateCalendarSourceDto) {
await this.validateUrlNotPrivate(dto.url);
// Exchange EWS servers are commonly on internal networks — SSRF check skipped for exchange type
if (dto.type !== 'exchange') await this.validateUrlNotPrivate(dto.url);
const data: Record<string, unknown> = {
userId,
@@ -174,7 +175,7 @@ export class CalendarService {
async updateSource(id: string, userId: string, dto: UpdateCalendarSourceDto) {
const existing = await this.prisma.calendarSource.findUnique({
where: { id },
select: { userId: true },
select: { userId: true, type: true },
});
if (!existing) {
@@ -184,7 +185,9 @@ export class CalendarService {
throw new ForbiddenException('Not your calendar source');
}
if (dto.url) {
const effectiveType = dto.type ?? existing.type;
// Exchange EWS servers are commonly on internal networks — SSRF check skipped for exchange type
if (dto.url && effectiveType !== 'exchange') {
await this.validateUrlNotPrivate(dto.url);
}
@@ -287,7 +290,12 @@ export class CalendarService {
async testConnectionFromConfig(
dto: TestCalendarSourceConfigDto,
): Promise<{ success: boolean; error?: string }> {
await this.validateUrlNotPrivate(dto.url);
try {
// Exchange EWS servers are commonly on internal networks — SSRF check skipped for exchange type
if (dto.type !== 'exchange') await this.validateUrlNotPrivate(dto.url);
} catch (e: any) {
return { success: false, error: e?.message ?? 'URL not allowed' };
}
const provider = this.getProvider(dto.type);
const tempSource = {
@@ -302,7 +310,7 @@ export class CalendarService {
try {
const success = await provider.testConnection(tempSource);
return { success };
} catch {
} catch (e: any) {
return { success: false, error: 'Connection failed' };
}
}