dieumercimvemba commited on
Commit
16b462c
·
verified ·
1 Parent(s): 2d0fe47

Update src/server.js

Browse files
Files changed (1) hide show
  1. src/server.js +46 -58
src/server.js CHANGED
@@ -4,10 +4,11 @@
4
  * Secure HTTP Gateway + n8n Bridge
5
  * Scientific-grade operational constraints:
6
  * - Minimal public surface area
7
- * - API key authentication for outbound send requests
8
  * - Rate limiting
9
- * - Optional HMAC signature to n8n
10
  * - Strict input validation (Zod)
 
11
  * ============================================================
12
  */
13
 
@@ -15,9 +16,13 @@ const express = require("express");
15
  const helmet = require("helmet");
16
  const rateLimit = require("express-rate-limit");
17
  const axios = require("axios");
18
- const pino = require("pino-http");
19
  const { z } = require("zod");
20
 
 
 
 
 
21
  const { startWhatsApp } = require("./whatsapp");
22
  const { requireApiKey, signPayload } = require("./security");
23
 
@@ -25,7 +30,7 @@ const app = express();
25
  app.disable("x-powered-by");
26
  app.use(helmet());
27
  app.use(express.json({ limit: "256kb" }));
28
- app.use(pino());
29
 
30
  const limiter = rateLimit({
31
  windowMs: 60 * 1000,
@@ -42,71 +47,54 @@ const ALLOWED_TO_PREFIX = process.env.ALLOWED_TO_PREFIX || "";
42
 
43
  let sock = null;
44
 
 
 
 
 
 
 
 
45
  app.get("/health", (req, res) => {
46
  res.json({ ok: true, whatsappReady: Boolean(sock) });
47
  });
48
 
49
- const SendSchema = z.object({
50
- to: z.string().min(10).max(40),
51
- text: z.string().min(1).max(3000)
52
- });
 
 
 
 
53
 
54
- app.post("/v1/send", requireApiKey, async (req, res) => {
55
  try {
56
- if (!sock) return res.status(503).json({ error: "WhatsApp not ready" });
57
-
58
- const parsed = SendSchema.safeParse(req.body);
59
- if (!parsed.success) return res.status(400).json({ error: "Invalid payload" });
60
-
61
- const { to, text } = parsed.data;
62
-
63
- if (ALLOWED_TO_PREFIX) {
64
- // Basic safety: allow only JIDs matching your expected prefix
65
- // Example: 243xxxx@s.whatsapp.net
66
- if (!to.startsWith(ALLOWED_TO_PREFIX) && !to.startsWith(`${ALLOWED_TO_PREFIX}`)) {
67
- return res.status(403).json({ error: "Recipient not allowed" });
68
- }
69
- }
70
-
71
- await sock.sendMessage(to, { text });
72
- return res.json({ sent: true });
73
  } catch (e) {
74
- return res.status(500).json({ error: "Send failed" });
75
  }
76
- });
77
 
78
- async function postToN8n(event) {
79
- if (!N8N_WEBHOOK_INBOUND) return;
 
 
 
80
 
81
- const headers = {};
82
- if (N8N_HMAC_SECRET) {
83
- headers["x-steny-signature"] = signPayload(event, N8N_HMAC_SECRET);
84
- }
85
-
86
- await axios.post(N8N_WEBHOOK_INBOUND, event, { headers, timeout: 15000 });
87
- }
88
-
89
- async function main() {
90
- console.log("Steny Bridge booting...");
91
- sock = await startWhatsApp({
92
- onIncomingText: async ({ from, text }) => {
93
- // Conservative policy: only handle inbound user messages.
94
- const event = { from, text, timestamp: Date.now() };
95
-
96
- try {
97
- await postToN8n(event);
98
- } catch (e) {
99
- // Do not leak secrets or stack traces
100
- }
101
- }
102
  });
103
 
104
- app.listen(PORT, () => {
105
- // No console secrets; logs only operational signals
106
- console.log(`Steny Bridge listening on port ${PORT}`);
107
- });
108
- }
109
 
110
- main().catch(() => {
111
- process.exit(1);
 
112
  });
 
 
 
 
 
 
 
4
  * Secure HTTP Gateway + n8n Bridge
5
  * Scientific-grade operational constraints:
6
  * - Minimal public surface area
7
+ * - API key auth for outbound send requests
8
  * - Rate limiting
9
+ * - HMAC signature to n8n (optional but recommended)
10
  * - Strict input validation (Zod)
11
+ * - Runtime diagnostics endpoint (/diag) for network validation
12
  * ============================================================
13
  */
14
 
 
16
  const helmet = require("helmet");
17
  const rateLimit = require("express-rate-limit");
18
  const axios = require("axios");
19
+ const pinoHttp = require("pino-http");
20
  const { z } = require("zod");
21
 
22
+ // Diagnostics
23
+ const dns = require("dns").promises;
24
+ const https = require("https");
25
+
26
  const { startWhatsApp } = require("./whatsapp");
27
  const { requireApiKey, signPayload } = require("./security");
28
 
 
30
  app.disable("x-powered-by");
31
  app.use(helmet());
32
  app.use(express.json({ limit: "256kb" }));
33
+ app.use(pinoHttp());
34
 
35
  const limiter = rateLimit({
36
  windowMs: 60 * 1000,
 
47
 
48
  let sock = null;
49
 
50
+ /**
51
+ * Root endpoint (prevents HF "connection not allowed" confusion)
52
+ */
53
+ app.get("/", (req, res) => {
54
+ res.status(200).send("Steny Bridge is running.");
55
+ });
56
+
57
  app.get("/health", (req, res) => {
58
  res.json({ ok: true, whatsappReady: Boolean(sock) });
59
  });
60
 
61
+ /**
62
+ * Diagnostics endpoint
63
+ * Use it to confirm if the container can resolve and reach WhatsApp Web.
64
+ * - DNS check for web.whatsapp.com
65
+ * - HTTPS check to a neutral endpoint (google.com)
66
+ */
67
+ app.get("/diag", async (req, res) => {
68
+ const out = {};
69
 
 
70
  try {
71
+ out.dns_web_whatsapp = await dns.lookup("web.whatsapp.com");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  } catch (e) {
73
+ out.dns_web_whatsapp_error = e.message;
74
  }
 
75
 
76
+ out.https_google = await new Promise((resolve) => {
77
+ const r = https.get("https://www.google.com", (resp) => {
78
+ resolve({ status: resp.statusCode });
79
+ resp.resume();
80
+ });
81
 
82
+ r.on("error", (e) => resolve({ error: e.message }));
83
+ r.setTimeout(8000, () => {
84
+ r.destroy(new Error("timeout"));
85
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  });
87
 
88
+ res.json(out);
89
+ });
 
 
 
90
 
91
+ const SendSchema = z.object({
92
+ to: z.string().min(10).max(60),
93
+ text: z.string().min(1).max(3000)
94
  });
95
+
96
+ app.post("/v1/send", requireApiKey, async (req, res) => {
97
+ try {
98
+ if (!sock) return res.status(503).json({ error: "WhatsApp not ready" });
99
+
100
+ const parse