Skip to content

fix: authenticated free users are no longer treated as anonymous#7

Merged
mastermanas805 merged 1 commit intomasterfrom
fix/authed-free-user-provisioning
Apr 23, 2026
Merged

fix: authenticated free users are no longer treated as anonymous#7
mastermanas805 merged 1 commit intomasterfrom
fix/authed-free-user-provisioning

Conversation

@mastermanas805
Copy link
Copy Markdown
Member

Reproduced

Passing a valid Bearer JWT to `/db/new`:

```bash
curl -X POST https://api.instanode.dev/db/new \
-H "Authorization: Bearer " \
-H 'Content-Type: application/json' \
-d '{"name":"my-project"}'
```

returned the same anonymous resource on every call, with
`"tier":"anonymous"` and
`"note":"Returning your existing database. Keep it forever: ..."`
— even though the caller is signed in. The handler was treating the
authenticated request as unauthenticated.

Root cause

`handlers.go` gated the fingerprint anti-abuse dedup on `if !isPaid`,
where `isPaid = (authed && PlanTier == "paid")`. An authenticated
FREE user has a valid JWT but `PlanTier != "paid"`, so they fell into
the anonymous branch. The fingerprint dedup then matched any anonymous
resource on their /24 subnet and returned it — silently ignoring auth.

Fix

Switch the gate to `if !isAuthed`. Fingerprint dedup is anti-abuse for
unauthenticated traffic; once someone has signed in, we know them and
can attribute resources directly. Three ownership shapes:

State Tier TTL Owner (migrated_to_user_id) Fingerprint dedup
Not authed anonymous 24h NULL yes
Authed free anonymous 24h user.ID no
Authed paid paid none user.ID no

Same fix in both `handleNewDB` and `handleNewWebhook`. Note message
on the authed-free path now surfaces the upgrade path
(`/pricing.html`) instead of the claim path (`/start?token=...`) —
they've already signed in, nothing to claim.

While in the neighbourhood, corrected a handful of `note` strings
that still pointed at `s.baseURL` (the API host) for /start and
/pricing links — those belong on `s.marketingURL`.

Verification

  • `go build ./...`, `go vet ./...`, `go test ./...` all pass
  • Post-deploy: repeat the failing curl with a valid Bearer token,
    confirm each call returns a new token and connection_url,
    `migrated_to_user_id` is populated, and `tier == "anonymous"`
    for free users / `"paid"` for paid.

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).
@mastermanas805 mastermanas805 merged commit 21c946b into master Apr 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant