fix: /db/new + /webhook/new are idempotent by name for authed users#8
Merged
mastermanas805 merged 4 commits intomasterfrom Apr 23, 2026
Merged
fix: /db/new + /webhook/new are idempotent by name for authed users#8mastermanas805 merged 4 commits intomasterfrom
mastermanas805 merged 4 commits intomasterfrom
Conversation
Reproduced: passing a valid Bearer JWT to /db/new or /webhook/new returned the *same anonymous resource repeatedly*, with "tier": "anonymous" "note": "Returning your existing database. Keep it forever: ..." despite the caller already being signed in. Root cause in handlers.go: the handler gated the fingerprint-based anti-abuse dedup on `if !isPaid`, where isPaid == (auth valid && PlanTier == "paid"). An authenticated FREE-tier user has a valid JWT but PlanTier != "paid", so they fell into the anonymous branch — fingerprint dedup kicked in, ignoring their auth, and returned whatever anon resource lived on their /24 subnet today. Fix: gate the fingerprint dedup on `if !isAuthed`. Anti-abuse is for unauthenticated callers; once a user has signed in, we know who they are and can attribute resources directly. Three shapes: - not authed → anonymous tier + fingerprint dedup + 24h TTL, no owner - authed free → anonymous tier (limits/TTL) + user_id set, no dedup - authed paid → paid tier + user_id set, no TTL, no dedup Both handleNewDB and handleNewWebhook updated. Note message on the authed-free path now points at /pricing.html (upgrade) instead of /start?token=... (claim) — they're already signed in, there's nothing to claim. While in the neighbourhood, corrected several notes to use s.marketingURL for /start and /pricing.html links (they were still referencing s.baseURL which is the API host).
Re-running a script that does
URL=$(curl /db/new -d '{"name":"my-project"}' | jq -r .connection_url)
echo DATABASE_URL=$URL >> .env
used to orphan the first resource and create a second one each run.
Real-world usage (CI re-runs, teammate re-checkouts, "rerun my
notebook tomorrow") racks up duplicate DBs, burns plan limits, and
leaks resources the user never intended to keep.
Fix: for authenticated callers, /db/new and /webhook/new now look
up (migrated_to_user_id, resource_type, name) before creating. If
an active match exists, return it with a "returning your existing"
note and the DELETE endpoint to use if they really wanted a fresh
one. If not, create new (existing behaviour).
Unauthed callers unchanged — fingerprint dedup there is an
anti-abuse cap, and name isn't a stable identity for a user we
don't know.
Scope / edge cases:
- name collisions across resource types are fine (postgres + webhook
named "foo" are independent)
- deleted resources aren't matched (status != 'active')
- no unique constraint on (user_id, type, name) — a tight race
window can still produce duplicates, but worst case is an
annoying dupe not a 5xx. Constraint + migration can come later.
go build / go vet / go test all pass.
This was referenced Apr 23, 2026
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.
Problem
Re-running a script like
```bash
URL=$(curl /db/new -d '{"name":"my-project"}' | jq -r .connection_url)
echo "DATABASE_URL=$URL" >> .env
```
orphaned the first database and created a second on every run.
Real-world workflows (CI re-runs, teammate re-checkouts, "rerun my
notebook tomorrow") quietly burned plan quota. The only system that
does this is one that hasn't thought about the actual use case —
Supabase, Neon, every peer treats project name as unique per account.
Fix
Added `lookupExistingNamed(ctx, userID, resourceType, name)`. Both
`handleNewDB` and `handleNewWebhook` now call it before any
provisioning work:
would have (id, token, connection_url / receive_url, tier,
expires_at) plus a `note` explaining how to delete + re-provision
if the user really wants a fresh one.
a duplicate, not a 5xx).
Unauthed callers unchanged — fingerprint dedup there is anti-abuse,
not ownership semantics.
Edge cases handled
resource_type)
Known gap
No DB unique constraint on `(migrated_to_user_id, resource_type, name)`,
so a tight race between two simultaneous requests could still insert
duplicates. Worst case is an annoying dupe, not a 5xx. Constraint +
backfill migration belongs in a follow-up.
Verification post-deploy
```bash
JWT=
first call creates
curl -X POST https://api.instanode.dev/db/new \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d '{"name":"idempotent-test"}' | jq .token
second call with same name: SAME token in response
curl -X POST https://api.instanode.dev/db/new \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d '{"name":"idempotent-test"}' | jq .token
different name: NEW token
curl -X POST https://api.instanode.dev/db/new \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d '{"name":"something-else"}' | jq .token
```