Repository navigation
feat(server): wallet-authenticated SEP-10 sessions - #147
Merged
Merged
Conversation
SEP-10 sign-in needs a client challenge, a server-only challenge row, and one auth user per wallet before verification can land. Co-authored-by: Cursor <cursoragent@cursor.com>
Production must not serve challenges until the SEP-10 signing key and auth domains are set, and that secret must never come from the environment or an error message. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Deploying puls3 with
|
| Latest commit: |
6443b0a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://dd88311d.puls3-4lw.pages.dev |
| Branch Preview URL: | https://feat-136-wallet-sessions-01.puls3-4lw.pages.dev |
moises-cisneros
approved these changes
Oct 9, 2026
Contributor
There was a problem hiding this comment.
Summary of Review
Reviewed and validated against PR #77 (docs/architecture/api.md) and OpenSpec Change #136 (specs/wallet-auth/spec.md, design.md, tasks.md).
Verification & Test Execution
- Static Analysis:
dart analyze --fatal-infospasses cleanly with 0 issues acrosspuls3_server,puls3_client, andpuls3_domain. - Formatting:
dart format --set-exit-if-changed .clean (0 files changed). - Test Suite: Executed the full test suite against PostgreSQL and Redis test instances (756 tests passed, 0 failures). Integration suites
wallet_auth_endpoint_test,wallet_idp_test,wallet_session_hire_test, andchallenge_store_testall run clean. - Scenarios: Formal scenarios W1 through W31 are fully verified and covered by tests.
Architecture & Design Alignment
- Rate Limiting & Challenge Lifecycle:
createChallengerate limiting (5 attempts/min per target wallet) strictly follows OpenSpec D14 / Scenario W5.- Deferring cleanup of unconsumed/expired challenges was deliberately scoped out in
design.md:157andtasks.md:30. Recommend a non-blocking follow-up issue for a background cleanup job and evaluating IP-level rate limiting at the reverse proxy layer.
- Signature Verification & Atomicity:
Sep10Verifiercorrectly enforces that all client signatures belong to authorized signers on chain (Scenario W11 / rule 5).- Atomic consumption in
ServerpodChallengeStore.consumechecksexpiresAt > nowinside the database transaction, preventing race conditions and replay attacks.
- Session & Endpoint Integration:
- Clean transition to
WalletSessionWalletresolving the bound wallet fromwallet_account. Replaces the template email IDP cleanly.
- Clean transition to
Minor Non-blocking Follow-ups
puls3_server/README.md: Contains minor stale documentation referencingFailClosedSessionWalletas active in production andemailSecretHashPepper(lines 77-80, 407). Can be updated in a follow-up doc polish.WalletAuthEndpoint._service: RebuildsTokenManagerper request; consider reusing a shared instance in a future optimization pass.
This was referenced Oct 10, 2026
2 of 4 tasks
moises-cisneros
added a commit
that referenced
this pull request
Oct 10, 2026
…e with challenge checks, session on the Serverpod client) (#149) * feat(flutter): sign in with the wallet (SEP-10) before a real hire Since #147 the server only accepts hire calls from a wallet-bound session. The app only read the Freighter address, so with HIRE=server every hire ended in "Sign-in with your wallet is not available yet". It also could not sign the challenge: FreighterWallet.signTransaction requires every source to be the connected account (#69), but a SEP-10 challenge's source is the server. Wallet: - WalletPort.signChallenge(SignInChallenge), with assertSep10Challenge: sequence 0 (never submittable), only manage_data operations, the first one "<home domain> auth" from the connected account, every other one from the server (the transaction source, never the account), time bounds that include now, and the server key and home domain when the app knows them. After signing, the body must be unchanged and carry a new signature from the connected account (shared with signTransaction). - MockWallet and WalletController implement it. Session: - WalletSession: createChallenge, wallet signature, verifyChallenge, then the AuthSuccess is stored by the client's FlutterAuthSessionManager, which sends it on every call. A session is reused only for the wallet that opened it; a challenge issued for another wallet is never signed. - main.dart sets client.authSessionManager and reads serverSigningKey and homeDomain from config.json auth when the server publishes them. Hire flow: - HireGateway.ensureSignedIn / forgetSession (no-op in the demo). The flow signs in after connecting and before createHire (new step signingIn). A session the server refuses (NotAuthenticated, AuthenticationUnavailable) or a refused sign-in (ChallengeExpired, ChallengeConsumed, ChallengeNotFound, InvalidWalletSignature: HireSignInFailed) is dropped, so Try again signs in afresh. ChallengeRateLimited maps to "too many sign-in attempts". Tests: SEP-10 challenges built like the server's codec, signed by a real key through the fake Freighter bridge (valid, and every refusal); session reuse, wallet switch, wrong-wallet challenge, declined and refused sign-ins; the sheet signs in before createHire and recovers after a refusal; readAuthConfig. 267 app tests pass, web release build succeeds, and breaking each of the 10 guards fails a test. Refs #136 * feat(flutter): hire detail screen (S06) that polls getHire and shows the agent's result (#150) - HireGateway.getHire returns HireProgress (from HireDetail): escrow status, run status, failure reason, result and the fund tx. HireStage derives Funded/Queued/Running/Result ready/Run failed/Submitted/ Completed/Rejected (Cancelled from open)/Expired; a succeeded run that is still `running` with a result reads as Result ready. - ServerHireGateway maps HireNotFound/HireNotOwned/InvalidHireId to HireNotFound and takes the payment link from the fund submission only. - /hires/:id (HireDetailScreen + HireDetailView): polls every 4 s until the hire is final, keeps the last status with a Retry banner when a refresh fails, asks to connect or sign in (SEP-10) when needed, copies the result, opens the payment on StellarExpert. - The hire sheet's confirmed state gets "View hire". - Pages build: PULS3_HIRE sets the HIRE dart-define (server or demo). - Approve and Reject are not offered: they need the server-signed submit (#97). Refs #28 * fix(env): document PULS3_HIRE read by the Pages build script --------- Co-authored-by: Moises Cisneros <110249336+moises-cisneros@users.noreply.github.com> Co-authored-by: moises-cisneros <moisescisnerosdl@gmail.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.
Refs #136
size:exception
Server half of wallet-authenticated sessions. Flutter and the Freighter check stay in a stacked PR. This is over the 400-line review budget on purpose: ten work-unit commits, one behavior each.
Summary
SEP-10 challenges sign a wallet in and Serverpod issues the session. Hire methods require that session and resolve the wallet from
wallet_account. The template email sign-in is gone.config.jsonpublishes the public auth values (G…signing address, domains, network), never theS…secret.Work units
authorityOfand the account-entry RPC read.WalletIdpfind-or-create.WalletSessionWallet,requireLogin, and the EmailIdp removal.api.mdandAppConfigRoute.Acceptance criteria
HireEndpoint.createHireworks for it (integration test).flutter analyze/flutter testpass. (PR 2)Verification evidence
Notes for reviewers
WalletAuthEndpoint(client.walletAuth). A generatedClient.authwould shadow the auth extension.signedChallengeXdr), notsignMessage.noClientSignature.FailClosedSessionWalletremains a test double. Production usesWalletSessionWallet.