@mulmobridge/webhook-runtime 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +28 -0
- package/package.json +17 -1
package/README.md
CHANGED
|
@@ -15,3 +15,11 @@ Messenger, Google Chat).
|
|
|
15
15
|
|
|
16
16
|
These are security-relevant and hardened through Codex reviews (#1326);
|
|
17
17
|
keeping one copy means a fix lands once, not once per bridge.
|
|
18
|
+
|
|
19
|
+
## Related projects
|
|
20
|
+
|
|
21
|
+
Published from the MulmoClaude monorepo by [Receptron](https://github.com/receptron).
|
|
22
|
+
|
|
23
|
+
- **[MulmoClaude](https://github.com/receptron/mulmoclaude)** — an open-source AI assistant platform that runs on your own computer. Claude Code as the engine, a personal wiki for long-term memory, schema-driven collections for your data, and chat that summons the right GUI (markdown, charts, forms, spreadsheets, wikis) for each task.
|
|
24
|
+
- **[MulmoTerminal](https://github.com/receptron/mulmoterminal)** — a terminal-first cockpit for running many AI coding agents in parallel. One roster showing every session's summary and PR status, tmux-backed session persistence, git-worktree isolation, one-click PRs, and mobile push with remote reply.
|
|
25
|
+
- **[MulmoTerminal manual](https://receptron.github.io/mulmoterminal/)** — setup, workflows, feature reference, configuration, mobile notifications, and alternative / local model providers. Available in English and Japanese.
|
package/dist/index.d.ts
CHANGED
|
@@ -12,3 +12,17 @@ export declare const SAFE_CHALLENGE_RE: RegExp;
|
|
|
12
12
|
* `null`. Non-string query forms (`?hub.challenge[]=…`) return `null`
|
|
13
13
|
* rather than coercing to "", so callers can't compare a coerced empty. */
|
|
14
14
|
export declare function narrowChallenge(raw: unknown): string | null;
|
|
15
|
+
export interface MetaVerificationResult {
|
|
16
|
+
status: 200 | 403;
|
|
17
|
+
body: string;
|
|
18
|
+
verified: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function metaVerificationResult(query: Record<string, unknown>, verifyToken: string): MetaVerificationResult;
|
|
21
|
+
export interface MetaWebhookVerificationOptions {
|
|
22
|
+
rateLimit: RateLimitRequestHandler;
|
|
23
|
+
verifyToken: string;
|
|
24
|
+
/** Log prefix, e.g. "messenger" / "whatsapp". */
|
|
25
|
+
label: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function registerMetaWebhookVerification(app: Express, opts: MetaWebhookVerificationOptions): void;
|
|
28
|
+
export declare function verifyMetaHmacSignature(rawBody: string, signature: string, appSecret: string): boolean;
|
package/dist/index.js
CHANGED
|
@@ -101,3 +101,31 @@ export function narrowChallenge(raw) {
|
|
|
101
101
|
return null;
|
|
102
102
|
return raw;
|
|
103
103
|
}
|
|
104
|
+
// Pure decision for Meta's GET handshake: echo `hub.challenge` only when
|
|
105
|
+
// `hub.mode=subscribe`, `hub.verify_token` matches, and the challenge clears
|
|
106
|
+
// `narrowChallenge`. Split out from the Express handler so it can be tested
|
|
107
|
+
// without a request object. Messenger and WhatsApp share this byte-for-byte.
|
|
108
|
+
export function metaVerificationResult(query, verifyToken) {
|
|
109
|
+
const challenge = narrowChallenge(query["hub.challenge"]);
|
|
110
|
+
if (query["hub.mode"] === "subscribe" && query["hub.verify_token"] === verifyToken && challenge !== null) {
|
|
111
|
+
return { status: 200, body: challenge, verified: true };
|
|
112
|
+
}
|
|
113
|
+
return { status: 403, body: "Forbidden", verified: false };
|
|
114
|
+
}
|
|
115
|
+
// Register the shared Meta webhook-verification GET handler (Messenger,
|
|
116
|
+
// WhatsApp). Always `text/plain` — the CodeQL `js/reflected-xss` defence for
|
|
117
|
+
// the echoed challenge (see narrowChallenge + SAFE_CHALLENGE_RE above).
|
|
118
|
+
export function registerMetaWebhookVerification(app, opts) {
|
|
119
|
+
app.get("/webhook", opts.rateLimit, (req, res) => {
|
|
120
|
+
const result = metaVerificationResult(req.query, opts.verifyToken);
|
|
121
|
+
if (result.verified)
|
|
122
|
+
console.log(`[${opts.label}] webhook verified`);
|
|
123
|
+
res.status(result.status).type("text/plain").send(result.body);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Meta prefixes the x-hub-signature-256 hex digest with `sha256=`; strip it
|
|
127
|
+
// before the timing-safe compare. Wraps the generic hex/SHA-256 HMAC check so
|
|
128
|
+
// the Messenger and WhatsApp bridges stop carrying identical copies.
|
|
129
|
+
export function verifyMetaHmacSignature(rawBody, signature, appSecret) {
|
|
130
|
+
return verifyHmacSignature(rawBody, signature.replace("sha256=", ""), appSecret, "sha256", "hex");
|
|
131
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mulmobridge/webhook-runtime",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Shared HTTP-webhook plumbing (Express app, trust-proxy, rate limit, HMAC verify) for the MulmoClaude messaging bridges",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,5 +34,21 @@
|
|
|
34
34
|
"@types/express": "^5.0.0",
|
|
35
35
|
"tsx": "^4.23.1",
|
|
36
36
|
"typescript": "^6.0.3"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/receptron/mulmoclaude/tree/main/packages/webhook-runtime#readme",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/receptron/mulmoclaude.git",
|
|
42
|
+
"directory": "packages/webhook-runtime"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"mulmobridge",
|
|
46
|
+
"webhook",
|
|
47
|
+
"express",
|
|
48
|
+
"hmac",
|
|
49
|
+
"rate-limit"
|
|
50
|
+
],
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/receptron/mulmoclaude/issues"
|
|
37
53
|
}
|
|
38
54
|
}
|