fix(whatsapp): keep the stored verify_token when config is saved without one - #554
Open
Guru35 wants to merge 2 commits into
Open
fix(whatsapp): keep the stored verify_token when config is saved without one#554Guru35 wants to merge 2 commits into
Guru35 wants to merge 2 commits into
Conversation
…e stored one Regression cover for the settings page silently dropping the webhook verify token: the form never shows the stored (encrypted) token, so a save of any other field posted it empty and the route wrote NULL over it. Measured live 2026-09-05: whatsapp_config.verify_token was NULL and every Meta GET handshake got 403 — indistinguishable from a wrong token. resolveVerifyTokenForSave is a skeleton mirroring the current route behaviour so the test goes red on the assertion, not on the import: Tests 5 failed | 2 passed (7) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The settings form never shows the stored webhook verify token (encrypted at rest, not returned by GET), so the field arrived empty on every save that touched any other setting — and the route wrote NULL over the stored value (verify_token ? encrypt(verify_token) : null). Measured live on 2026-09-05: verify_token was NULL and every Meta GET handshake got 403, indistinguishable from a wrong token from the outside. Server: resolveVerifyTokenForSave() — a non-empty incoming token is trimmed and re-encrypted; empty/undefined keeps the stored ciphertext. The pre-existing row lookup now also selects verify_token. Clearing the token on its own stays unsupported; Reset Configuration wipes the row. Client: mirror the access-token treatment — show a mask when a token is stored, clear it on focus, and omit verify_token from the payload unless the user actually typed one (undefined is dropped from JSON, so the server keeps what it has). Hint text explains the mask (en + ko). Tests 829 -> 836 (7 new), typecheck clean, lint 0 errors (37 pre-existing warnings untouched). Red-first commit: efec1df. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Saving the WhatsApp settings form clears
whatsapp_config.verify_tokenunlessthe token field happens to be filled in — which it never is, because the form
does not render the stored value. Every save of any other field (phone
number id, WABA id, access token…) silently nulls the verify token, and Meta's
webhook GET handshake then returns 403. From the outside this is
indistinguishable from "you typed the wrong token".
Root cause is one expression in the save path:
An empty field means "keep what is stored", not "clear it" — the settings
GET endpoint deliberately never returns the token (it is encrypted at rest),
so empty is the normal state of that input.
Measured on a live self-hosted instance (2026-09-05): the row held
verify_token = NULL, Meta's verification GET returned 403, inbound messagedelivery stopped. Restoring the token fixed it; saving any other field broke
it again.
What changed
src/lib/whatsapp/verify-token.ts— newresolveVerifyTokenForSave(incoming, existingEncrypted):a non-empty value is encrypted and stored, an empty/whitespace value keeps
the existing ciphertext. Clearing a token on its own is not a supported
operation; Reset Configuration already wipes the whole row.
src/app/api/whatsapp/config/route.ts— save path reads the existing rowand delegates to the helper instead of inlining the ternary.
src/components/settings/whatsapp-config.tsx— the field now says the tokenis kept when left blank, so the behaviour is discoverable rather than
implied.
messages/en.json,messages/ko.json— that one hint string.Behaviour is unchanged when a token is typed.
Test plan
src/lib/whatsapp/verify-token.test.ts— 7 passed, writtenred-first (the test commit precedes the fix commit in this PR).
return existingEncrypted ?? nullwithreturn null(the old behaviour) turns 4 of the 7 red; reverting makesthem green again. The suite genuinely pins this behaviour rather than
passing vacuously.
npm run typecheck— clean.npx eslinton the four touched files — 0 errors, 1 warning, andthat warning (
react-hooks/exhaustive-depsinwhatsapp-config.tsx) ispre-existing: it reports on
upstream/maintoo, at line 204 vs 210 here— the shift is just the added lines.
npm run build— not run locally; left to CI.Related
Found while self-hosting; no existing issue covers it (searched open + closed
for
verify_token). Happy to split the i18n hint into its own commit or dropit if you would rather keep the fix minimal.