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:
@@ -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' };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user