FRE-4520: Fix security vulnerabilities in notification template system
- Fix HTML injection vulnerability with proper entity encoding - Fix rate limit cleanup bug (count vs timestamp confusion) - Add URL validation to prevent open redirect attacks - Add expiration to in-memory deduplication entries - Use Zod schema for config validation - Add email format validation All 29 tests passing. Ready for Code Reviewer final review. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -21,6 +21,11 @@ export interface RateLimitResult {
|
||||
resetInSeconds: number;
|
||||
}
|
||||
|
||||
interface DeduplicationEntry {
|
||||
externalIds: Set<string>;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
export class NotificationService {
|
||||
private static instance: NotificationService;
|
||||
private emailService: EmailService;
|
||||
@@ -28,7 +33,7 @@ export class NotificationService {
|
||||
private pushService: PushService;
|
||||
private redisService: RedisService;
|
||||
private config: ReturnType<typeof loadNotificationConfig>;
|
||||
private pendingDeduplication = new Map<string, Set<string>>();
|
||||
private pendingDeduplication = new Map<string, DeduplicationEntry>();
|
||||
private preferenceCache = new Map<string, NotificationPreference>();
|
||||
|
||||
private constructor() {
|
||||
@@ -64,9 +69,10 @@ export class NotificationService {
|
||||
dedupKey: DeduplicationKey
|
||||
): Promise<NotificationResult> {
|
||||
const dedupId = `${dedupKey.userId}:${dedupKey.templateId}:${dedupKey.key}`;
|
||||
const windowSet = this.pendingDeduplication.get(dedupId);
|
||||
const windowSeconds = dedupKey.windowSeconds || this.config.redis.dedupWindowSeconds;
|
||||
const entry = this.pendingDeduplication.get(dedupId);
|
||||
|
||||
if (windowSet && windowSet.size > 0) {
|
||||
if (entry && Date.now() < entry.expiresAt && entry.externalIds.size > 0) {
|
||||
return {
|
||||
notificationId: `dedup-${Date.now()}`,
|
||||
channel: notification.channel,
|
||||
@@ -78,10 +84,11 @@ export class NotificationService {
|
||||
const result = await this.send(notification);
|
||||
|
||||
if (result.status === 'sent') {
|
||||
if (!windowSet) {
|
||||
this.pendingDeduplication.set(dedupId, new Set());
|
||||
}
|
||||
this.pendingDeduplication.get(dedupId)!.add(result.externalId!);
|
||||
const now = Date.now();
|
||||
this.pendingDeduplication.set(dedupId, {
|
||||
externalIds: new Set([result.externalId!]),
|
||||
expiresAt: now + windowSeconds * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user