Skip to content

/auth/verify never returned the stored DID, so every login minted a throwaway one - #219

Merged
brianorwhatever merged 1 commit into
mainfrom
fix/auth-verify-returns-did
Jul 31, 2026
Merged

/auth/verify never returned the stored DID, so every login minted a throwaway one#219
brianorwhatever merged 1 commit into
mainfrom
fix/auth-verify-returns-did

Conversation

@brianorwhatever

@brianorwhatever brianorwhatever commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

This is why the re-mint still didn't run after #218.

The bug

/auth/verify hardcoded did: null in its response and never read the DID it already held. So every login looked like a first login:

  1. Client sees did: null → falls back to did:temp:{subOrgId}
  2. Client mints a brand-new did:webvh and POSTs /api/user/updateDID
  3. upsertUser refuses to overwrite an existing did:webvh (auth.ts:41) → silent no-op, while the endpoint still logs "DID updated successfully"
  4. Client keeps the discarded DID as its in-memory identity

Two consequences. A fresh keypair was minted and thrown away on every single login. And the client's DID silently disagreed with the database.

That second one is what defeated the re-mint: by the time the hook ran, the client believed it was already on boop.ad, so isStaleDidDomain returned false and it skipped — while users.did was still the trypoo.app DID with every row attached to it.

Confirmed on prod

After a sign-in, logs showed:

[userHttp] Updating DID for brian@aviary.tech to did:webvh:QmZudwPv…:boop.ad:user-20ed9d43-2d31-44
[userHttp] DID updated successfully for brian@aviary.tech

while findUserByEmail still returned the trypoo.app DID and previewRemint still counted every row against it — 344 items, 18 lists, 7 sites, 7 publications, 2 categories, 1 bookmark.

Fix

Return the stored did (and displayName). An existing user is recognised and the client stops minting. A genuinely new account still has no DID and takes the mint path unchanged.

Loop guard

With the DID now reported correctly, the hook fires — which exposes a second problem. Persisted auth state is restored on reload without contacting the server, so a cached pre-migration DID would re-trigger the hook every load and walk the account to a new DID each time.

Two independent defences:

  • Server: the endpoint refuses to migrate an identity already on WEBVH_DOMAIN. This holds regardless of client state.
  • Client: the hook refreshes cached auth state before reloading, so it doesn't burn a keypair per reload.

116 pass / 0 fail; both typechecks clean; lint clean on touched files.

After merge

Wait for Railway, not just Deploy Convex. Then sign in once per account. users.did should move and previewRemint against the old DID should drop to {}.

🤖 Generated with Claude Code

Note

Fix /auth/verify to return the stored DID instead of always minting a new one

  • convex/http.ts: After verifying a session, the handler now fetches the user by turnkeySubOrgId and returns the stored did and displayName instead of always returning null and the email-derived display name.
  • convex/userHttp.ts: POST /api/user/remintDid skips reminting when the user's existing did:webvh domain already matches WEBVH_DOMAIN, returning the current DID with a skipped reason.
  • src/hooks/useDidDomainRemint.ts: After a successful remint, the hook updates the persisted auth state in storage with the new DID before reloading the page.

Macroscope summarized 78f3d1a.

/auth/verify hardcoded `did: null` in its response and never read the DID it
already held. Every login therefore looked like a first login:

  1. client sees did:null, falls back to `did:temp:{subOrgId}`
  2. client mints a BRAND NEW did:webvh and posts /api/user/updateDID
  3. upsertUser refuses to overwrite an existing did:webvh (auth.ts:41), so the
     write is a silent no-op — the endpoint still logs "updated successfully"
  4. client keeps the discarded DID as its in-memory identity

So a fresh keypair was minted and thrown away on every single login, and the
client's DID disagreed with the database. That is why the stale-domain re-mint
never fired: by the time the hook ran, the client believed it was already on
boop.ad while users.did was still the trypoo.app DID, with all ~375 rows on it.

Confirmed on prod: after a sign-in, logs showed updateDID being called with a
new boop.ad DID, while users.did was unchanged and previewRemint still counted
every row against the old one.

Now returns the stored did (and displayName), so an existing user is recognised
and the client stops minting. A genuinely new account still has no DID and takes
the mint path unchanged.

Also guards the re-mint against a loop. Persisted auth state is restored on
reload without contacting the server, so a cached pre-migration DID would
re-trigger the hook on every load and walk the account to a new DID each time.
Two defences: the endpoint refuses to migrate an identity already on
WEBVH_DOMAIN, and the hook refreshes the cached auth state before reloading.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@brianorwhatever
brianorwhatever merged commit 34964c7 into main Jul 31, 2026
7 checks 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