mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
Merge pull request #65 from ArnasDon/fix/webhook-fail-closed
fix(webhook): require META_APP_SECRET, fail closed
This commit is contained in:
commit
8309b2a95b
16
.env.local.example
Normal file
16
.env.local.example
Normal file
@ -0,0 +1,16 @@
|
||||
# Supabase
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
|
||||
# Supabase Service Role (for server-side operations like webhook handling)
|
||||
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
||||
|
||||
# WhatsApp Token Encryption (64 hex chars = 32 bytes for AES-256-CBC)
|
||||
# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
||||
ENCRYPTION_KEY=your-64-char-hex-key-here
|
||||
|
||||
# Meta App Secret — REQUIRED. Used to verify the HMAC-SHA256 signature
|
||||
# Meta attaches to inbound webhook POSTs. Without it, the webhook
|
||||
# rejects every request. Find it at:
|
||||
# Meta for Developers → your app → App Settings → Basic → App Secret
|
||||
META_APP_SECRET=your-meta-app-secret
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -32,6 +32,9 @@ yarn-error.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
# keep example files so forkers have a template to copy
|
||||
!.env.local.example
|
||||
!.env.example
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
@ -12,12 +12,12 @@ minimal template; the table below is the full reference.
|
||||
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Supabase → Project Settings → API → **anon / public** key | Public. Relies on RLS for safety. |
|
||||
| `SUPABASE_SERVICE_ROLE_KEY` | Supabase → Project Settings → API → **service_role** key | **Secret.** Bypasses RLS. Used by webhook + admin routes only. |
|
||||
| `ENCRYPTION_KEY` | Generate: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"` | 64 hex chars (32 bytes, AES-256-CBC). **Rotating breaks existing tokens.** |
|
||||
| `META_APP_SECRET` | Meta → App Settings → Basic → **App Secret** | Verifies the `X-Hub-Signature-256` HMAC on every inbound webhook. **Without it the webhook rejects every request** — a public deploy cannot receive messages until this is set. |
|
||||
|
||||
## Recommended
|
||||
|
||||
| Variable | Purpose |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `META_APP_SECRET` | Meta → App Settings → Basic → **App Secret**. Enables `X-Hub-Signature-256` verification on incoming webhooks. |
|
||||
| `NEXT_PUBLIC_SITE_URL` | Canonical public URL (e.g., `https://crm.example.com`). Used for absolute URLs, sitemap, OG images. |
|
||||
|
||||
## Optional
|
||||
@ -34,12 +34,14 @@ NEXT_PUBLIC_SUPABASE_URL=https://abcd1234.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOi...
|
||||
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOi...
|
||||
|
||||
# Meta App Secret — required for webhook signature verification
|
||||
META_APP_SECRET=abcdef0123456789...
|
||||
|
||||
# Encryption — DO NOT change after first deploy
|
||||
ENCRYPTION_KEY=3f9c0a7e4d8b2f1a6c5e8d4b9f0a2c6e8d4b9f0a2c6e8d4b9f0a2c6e8d4b9f0a
|
||||
|
||||
# Public URL + Meta webhook signing
|
||||
# Public URL
|
||||
NEXT_PUBLIC_SITE_URL=https://crm.example.com
|
||||
META_APP_SECRET=abcdef0123456789...
|
||||
|
||||
# Automation cron
|
||||
AUTOMATION_CRON_SECRET=generate-a-long-random-string
|
||||
|
||||
@ -50,9 +50,11 @@ Set `NEXT_PUBLIC_SITE_URL` **and** add the same origin to Supabase →
|
||||
- The callback URL must be reachable without auth. Test it yourself:
|
||||
`curl 'https://crm.example.com/api/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=<token>&hub.challenge=test'`
|
||||
should return `test`.
|
||||
- If you set `META_APP_SECRET`, check the app secret matches — a bad
|
||||
secret causes 401s on inbound webhooks, but verification itself does
|
||||
not use it.
|
||||
- `META_APP_SECRET` must be set on the deployment (it's required —
|
||||
without it, every inbound POST gets a 401). If it's set but wrong,
|
||||
you'll also see 401s for every POST while the initial subscribe
|
||||
handshake (which doesn't use it) works. Double-check the value
|
||||
matches Meta → App Settings → Basic → App Secret exactly.
|
||||
|
||||
### Messages go out but nothing comes in
|
||||
|
||||
|
||||
@ -83,11 +83,14 @@ Under the webhook config, **Subscribe to** at minimum:
|
||||
`message_template_status_update` keeps Meta-approved template statuses in
|
||||
sync on the broadcast UI.
|
||||
|
||||
## 6. Signature verification (recommended)
|
||||
## 6. Signature verification (required)
|
||||
|
||||
Set `META_APP_SECRET` to your app's **App Secret** (Meta for Developers →
|
||||
App Settings → Basic). When present, WaCRM validates the `X-Hub-Signature-256`
|
||||
header on every inbound webhook and drops unsigned or forged calls.
|
||||
App Settings → Basic). WaCRM validates the `X-Hub-Signature-256` HMAC
|
||||
on every inbound webhook and **rejects every request if the env var is
|
||||
not set** — anyone who knew the webhook URL could otherwise spoof
|
||||
messages and status updates. Configure it before pointing the webhook
|
||||
at a deployed instance.
|
||||
|
||||
## 7. Send a test message
|
||||
|
||||
|
||||
@ -11,11 +11,12 @@ import crypto from 'node:crypto'
|
||||
* Reference:
|
||||
* https://developers.facebook.com/docs/graph-api/webhooks/getting-started#verify-payloads
|
||||
*
|
||||
* Rollout behavior:
|
||||
* If `META_APP_SECRET` is unset, we **fail open** with a loud warning
|
||||
* log. This avoids breaking existing deployments at the moment this
|
||||
* code ships — operators can set the env var on their next deploy.
|
||||
* Once the env var is set, verification is strict.
|
||||
* Contract:
|
||||
* `META_APP_SECRET` is **required**. If it's missing we fail closed —
|
||||
* every request is rejected until the operator configures the
|
||||
* secret. A previous version fell open with a warning log, which is
|
||||
* unsafe for a public template: anyone who forgets the env var would
|
||||
* be running a fully spoofable webhook.
|
||||
*/
|
||||
export function verifyMetaWebhookSignature(
|
||||
rawBody: string,
|
||||
@ -23,11 +24,12 @@ export function verifyMetaWebhookSignature(
|
||||
): boolean {
|
||||
const secret = process.env.META_APP_SECRET
|
||||
if (!secret) {
|
||||
console.warn(
|
||||
'[webhook] META_APP_SECRET not set — skipping signature verification. ' +
|
||||
'Set the env var to harden against spoofed webhook POSTs.',
|
||||
console.error(
|
||||
'[webhook] META_APP_SECRET is not set — rejecting request. ' +
|
||||
'Configure the env var (Meta → App Settings → Basic → App Secret) ' +
|
||||
'to enable signature verification.',
|
||||
)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
if (!signatureHeader) return false
|
||||
|
||||
Loading…
Reference in New Issue
Block a user