mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-08 01:43:55 +00:00
7d4e61625f
* fix(notifications): harden alert dispatch crash-safety and redact webhook secrets in logs Make NotificationService.dispatchAlert never reject so the many fire-and-forget callers (monitors, event streams, policy and image-update paths) cannot trigger an unhandledRejection on an unhealthy database. The whole dispatch body now sits inside a guard covering node resolution, the history insert, channel-table reads, and the WebSocket broadcast; a failure logs and drops the notification instead of crashing the process. An inner guard still splits the write-success and write-failure metrics. Also: - Redact webhook URLs in diagnostic logs via a new maskWebhookUrl helper; Discord/Slack/custom webhook URLs embed their token in the path, so only the origin is safe to emit. - Add error logging to four notification-history route handlers that previously swallowed database errors silently before returning 500. - Snapshot the subscriber set before broadcasting so a close/error handler firing mid-send cannot mutate the set during iteration. - Sanitize the admin-supplied route name in dispatch log lines. Adds tests for dispatch crash-safety (write failure, post-write routing failure, broadcast send failure), the success-path write metric, webhook URL masking, and history-route error logging. * fix(notifications): sanitize route name and patterns in create logs Apply sanitizeForLog to the admin-supplied route name and stack patterns in the route-creation log lines, matching the dispatch-site sanitization and closing the remaining log-injection path on this feature. Also extend tests: userinfo-stripping in maskWebhookUrl, subscriber-set snapshot behavior under an unsubscribe-during-send, and error logging on the mark-read, delete-one, and clear-all notification-history handlers.
31 lines
1.3 KiB
TypeScript
31 lines
1.3 KiB
TypeScript
export const NOTIFICATION_CHANNEL_TYPES = ['discord', 'slack', 'webhook'] as const;
|
|
export type NotificationChannelType = typeof NOTIFICATION_CHANNEL_TYPES[number];
|
|
|
|
export const cleanStackPatterns = (patterns: string[]): string[] =>
|
|
[...new Set(patterns.map(p => p.trim()).filter(Boolean))];
|
|
|
|
export function validateHttpsUrl(value: unknown): string | null {
|
|
if (!value || typeof value !== 'string' || !value.startsWith('https://')) return 'must be a valid HTTPS URL';
|
|
try { new URL(value); } catch { return 'is not a valid URL'; }
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Mask a channel webhook URL for logging. Discord/Slack/custom webhook URLs
|
|
* embed their auth token in the path (and sometimes the query), so only the
|
|
* origin is safe to emit. Returns `https://host/<redacted>` or a generic
|
|
* placeholder if the value is not a parseable URL.
|
|
*/
|
|
export function maskWebhookUrl(value: unknown): string {
|
|
if (typeof value !== 'string' || value.length === 0) return '<no url>';
|
|
try {
|
|
const { origin, pathname, search } = new URL(value);
|
|
// pathname is normalized to '/' for an origin-only URL, so anything else
|
|
// (or any query string) is a token-bearing segment we must not log.
|
|
const hasSecret = pathname !== '/' || search !== '';
|
|
return hasSecret ? `${origin}/<redacted>` : origin;
|
|
} catch {
|
|
return '<invalid url>';
|
|
}
|
|
}
|