feat(noter): server-issued challenge nonce for wallet auth replay protection#53
Open
Ashwin-3cS wants to merge 3 commits intoMystenLabs:devfrom
Open
feat(noter): server-issued challenge nonce for wallet auth replay protection#53Ashwin-3cS wants to merge 3 commits intoMystenLabs:devfrom
Ashwin-3cS wants to merge 3 commits intoMystenLabs:devfrom
Conversation
Collaborator
|
Hi @Ashwin-3cS LGTM overall. Please fix before merge:
|
… protection Replace client-generated auth messages with a server-issued one-time challenge nonce. The server generates a random nonce stored in a new walletChallenges table with a 5-minute TTL. The client signs the nonce, and the server deletes it on use — replaying a captured signature fails because the challenge no longer exists.
- Use DELETE...RETURNING in connectWallet to eliminate TOCTOU race - Clean up expired challenges in getChallenge to prevent table bloat - Add Drizzle migration for walletChallenges table
aa19364 to
23b2a05
Compare
Contributor
Author
Hi @ducnmm; thanks for the detailed review! Addressed all three points in the follow-up commit:
|
Contributor
Author
|
The diff shows ~3k lines due to the fork base being out of sync at push time; actual change is ~1,270 lines, mostly the migration snapshot. |
Contributor
Author
|
Resolved the pnpm-lock.yaml conflict by merging latest dev into the branch and regenerating the lockfile via pnpm install. The diff is clean; no manual edits to the lockfile. @ducnmm let me know if there are any blockers or further changes needed for this PR. |
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.
Description
This change builds on the previous PR #52 , which introduced server-side signature verification but did not cover replay protection as it was out of scope. This PR introduces a server-issued challenge flow to prevent replay attacks. Given the additional scope, this is raised as a separate PR with the necessary changes and inclusions.
What changed
New
getChallengeendpointThe server generates a random 32-byte nonce, stores it in a new
wallet_challengestable with a 5-minute TTL, and returns it to the client.connectWalletnow verifies against a server-issued nonceThe handler looks up the challenge by ID, deletes it atomically (consuming it), and verifies that the signature was created over that nonce. A replayed request fails because the challenge no longer exists.
Why a new table instead of reusing
walletSessions?The zkLogin flow uses a two-step pattern (create session stub without userId, then complete after verification). A similar approach was considered here, but
walletSessionscontains severalNOT NULLcolumns (walletAddress,signedMessage,signature,signedAt,walletType) that have no meaningful values at challenge time. Making them nullable would weaken the schema contract for completed sessions. A dedicated table with only the required fields keeps the design cleaner.Files changed
schema.ts— addwalletChallengestableroute.ts— addgetChallenge, updateconnectWallethandlerinput.ts— replacemessagewithchallengeIdinconnectWalletInputauth-button-group.tsx/wallet-button.tsx— fetch challenge before signinguse-auth.ts— update params typewallet-client.ts— removegenerateAuthMessage()constant.ts— rename session storage key fromzklogin:session:idtoauth:session:idTest plan
Tested locally :
connectWalletrequest with consumedchallengeId→ 404 "Challenge not found or already used"