Skip to content
Draft
5 changes: 5 additions & 0 deletions apps/chat/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ CALCOM_WEBHOOK_SECRET=your-calcom-webhook-secret
# Cal.com app URL for links
CALCOM_APP_URL=https://app.cal.com

# Shared secret for the push-notification delivery endpoint (POST /api/notifications/deliver).
# Must match CALCOM_CHAT_DELIVERY_SECRET on the /cal backend.
# Generate with: openssl rand -hex 32
CALCOM_DELIVERY_SECRET=your-delivery-secret

# ─── AI / LLM (Vercel AI Gateway) ────────────────────────────────────────────
# Single API key for all AI models. Get yours at vercel.com/ai-gateway
AI_GATEWAY_API_KEY=your-ai-gateway-api-key
Expand Down
111 changes: 111 additions & 0 deletions apps/chat/app/api/notifications/deliver/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import crypto from "node:crypto";
import { getLogger } from "@/lib/logger";
import { type DeliverRequest, deliverNotifications } from "@/lib/push-notifications/service";

const logger = getLogger("deliver-route");

function verifyDeliverySecret(header: string | null): boolean {
const secret = process.env.CALCOM_DELIVERY_SECRET;
if (!secret || !header) return false;
try {
const secretHmac = crypto.createHmac("sha256", "delivery-verify").update(secret).digest();
const headerHmac = crypto.createHmac("sha256", "delivery-verify").update(header).digest();
return crypto.timingSafeEqual(headerHmac, secretHmac);
} catch {
return false;
}
}

function isValidTimeZone(tz: string): boolean {
try {
Intl.DateTimeFormat("en-US", { timeZone: tz });
return true;
} catch {
return false;
}
}

function parseDeliverRequest(body: unknown): DeliverRequest | null {
if (typeof body !== "object" || body === null) return null;
const b = body as Record<string, unknown>;
if (b.platform !== "SLACK" && b.platform !== "TELEGRAM") return null;
if (
!Array.isArray(b.subscriptions) ||
b.subscriptions.length === 0 ||
b.subscriptions.length > 500
)
return null;
if (typeof b.payload !== "object" || b.payload === null) return null;

const p = b.payload as Record<string, unknown>;
if (typeof p.title !== "string" || typeof p.timeZone !== "string") return null;
if (typeof p.start !== "string" || typeof p.end !== "string") return null;
if (typeof p.notificationType !== "string" || p.notificationType === "") return null;
if (!Array.isArray(p.hosts) || !Array.isArray(p.attendees)) return null;
if (!isValidTimeZone(p.timeZone)) return null;
if (isNaN(Date.parse(p.start)) || isNaN(Date.parse(p.end))) return null;

if (b.platform === "SLACK") {
const valid = b.subscriptions.every(
(s: unknown) =>
typeof s === "object" &&
s !== null &&
typeof (s as Record<string, unknown>).identifier === "string" &&
typeof (s as Record<string, unknown>).teamId === "string"
);
if (!valid) return null;
} else {
const valid = b.subscriptions.every(
(s: unknown) =>
typeof s === "object" &&
s !== null &&
typeof (s as Record<string, unknown>).identifier === "string"
);
if (!valid) return null;
}
Comment thread
dhairyashiil marked this conversation as resolved.

return body as DeliverRequest;
}

export async function POST(request: Request) {
const secret = request.headers.get("x-cal-delivery-secret");
if (!verifyDeliverySecret(secret)) {
logger.warn("Delivery auth rejected");
return new Response(null, { status: 401 });
}

let body: unknown;
try {
body = await request.json();
} catch {
logger.warn("Failed to parse delivery request body");
return new Response(null, { status: 400 });
}

const parsed = parseDeliverRequest(body);
if (!parsed) {
logger.warn("Invalid delivery request body", {
platform: (body as Record<string, unknown>)?.platform,
});
return new Response(null, { status: 400 });
}

let results;
try {
results = await deliverNotifications(parsed);
} catch (err) {
logger.error("deliverNotifications threw", { error: String(err) });
return new Response(null, { status: 422 });
}

const failed = results.filter((r) => !r.success).length;
const invalid = results.filter((r) => r.invalidIdentifier).length;
logger.info("Delivery complete", {
platform: parsed.platform,
total: results.length,
failed,
invalidIdentifiers: invalid,
});

return Response.json({ results });
}
Loading
Loading