Skip to content

SDK bindings for recovery credentials and V2 password reset - #1435

Draft
w3irdrobot wants to merge 3 commits into
masterfrom
sdk/recovery-credentials
Draft

w3irdrobot wants to merge 3 commits into
masterfrom
sdk/recovery-credentials

Conversation

@w3irdrobot

Copy link
Copy Markdown
Contributor

Depends on #904. This adds the client side of the recovery-credential endpoints that #904 introduces. Until #904 merges, the new integration tests fail against master's backend, because the endpoints don't exist there yet. Keep this in draft until then.

Summary

Exposes #904's recovery-code management and seed-preserving password reset in both SDKs. Both already send everything over Transport V2, so the new V2-only routes need no transport changes.

Endpoint TypeScript (api.ts / context) Rust (OpenSecretClient)
GET /protected/recovery-code fetchRecoveryCodeStatus / getRecoveryCodeStatus get_recovery_code_status
POST /protected/recovery-code/enroll enrollRecoveryCode enroll_recovery_code
POST /protected/recovery-code/rotate rotateRecoveryCode rotate_recovery_code
DELETE /protected/recovery-code disableRecoveryCode disable_recovery_code
POST /password-reset/v2/options fetchPasswordResetOptions / getPasswordResetOptions get_password_reset_options
POST /password-reset/v2/complete completePasswordReset complete_password_reset
  • Reset completion modes. A completion either preserves data with the recovery code or acknowledges data loss. In TS that's a union ({mode: "preserve", recoveryCode} or {mode: "destructive", acknowledgeDataLoss: true}); in Rust it's PasswordResetCompletion. Both serialize to the server's externally tagged mode.
  • Sign-in after reset. A successful completion installs the returned token pair the same way login does. The React context then refreshes the user.
  • Account-switch guard. The context passes the signed-in user id to recovery management calls, the same way account deletion does, so a mid-request account switch can't return a recovery code for the wrong account.
  • Server owns validation. The SDK doesn't parse the recovery code, and every rejection keeps the server's generic error and status.
  • Debug redaction. Rust Debug output redacts recovery codes, passwords and reset secrets.
  • Legacy endpoint. confirmPasswordReset / confirm_password_reset behave the same as before; their docs now say they are destructive and point to the V2 flow.
  • Versions. Bumped to @mapleai/sdk 4.3.0 and maple-sdk 4.2.0, with the local-path consumer lockfiles (Agent, Research, proxy) updated to match. Published npm pins in the apps are unchanged, and nothing is published.

Verification

Run locally in the SDK Nix shell:

  • TS: format:check, build, and the credential-free unit suite, including the new recoveryCredentials.test.ts (wire shapes, token install on completion, DELETE with a body, error statuses, account-switch guard).
  • Rust: cargo fmt --check, clippy --all-targets --all-features -D warnings, --lib tests (126 passed, including the new wire-shape and redaction tests), cargo doc.
  • scripts/ci/verify-local-rust-deps.sh: Maple resolves maple-sdk 4.2.0 from local sdk/rust.
  • Focused e2e against Recovery credentials for preserving password reset #904's in-tree backend, with a private temporary Postgres and random loopback ports:
    • api.test.ts: 16 passed.
    • account_management: 7 passed, 3 ignored (the existing email-only tests).
    • The new tests use fresh accounts and cover enroll/rotate/disable with the wrong-password 401 and duplicate-enroll 409, guests being rejected, and invalid reset proofs being rejected with no credentials installed.
    • The backend log contained no recovery codes.

Not verified:

  • A full seed-preserving reset through the SDK. It needs the emailed reset code, so Recovery credentials for preserving password reset #904's test-recovery-flow.sh covers that.
  • The full os-sdk-e2e lane locally.
  • App or proxy builds against the bumped lockfiles.
  • Any published package.

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

w3irdrobot and others added 2 commits November 2, 2026 20:23
Add TypeScript and Rust bindings for the recovery-code endpoints and the
seed-preserving password reset from #904:

- recovery code status, enroll, rotate and disable, each with the
  current-password step-up; the React context guards them against an
  account switch mid-request
- V2 reset options and completion, which preserve data with a recovery
  code or require an explicit data-loss acknowledgment, then install the
  returned token pair
- Rust Debug output redacts recovery codes, passwords and reset secrets

The legacy confirm endpoint is unchanged; its docs now note it is
destructive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Update the local-path consumer lockfiles to the new crate version.
Published npm pins in the apps are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@w3irdrobot

Copy link
Copy Markdown
Contributor Author

Kept in draft until #904 merges

complete_password_reset installed the returned tokens unconditionally
after awaiting the response, so a sign-in or sign-out while the reset
was in flight could be overwritten by the reset account. Capture the
token generation before sending and install only if it is unchanged;
otherwise zeroize the tokens and return an authentication error,
matching the TypeScript SDK's snapshot guard. Add delayed-response
regression tests for both cases.

CompletePasswordResetResponse now redacts its tokens in Debug output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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