Recovery credential architecture (transplanted from opensecret#286) - #904
Draft
w3irdrobot wants to merge 22 commits into
Draft
Recovery credential architecture (transplanted from opensecret#286)#904w3irdrobot wants to merge 22 commits into
w3irdrobot wants to merge 22 commits into
Conversation
…ers to seed_wrapping.rs The Phase 2 commit (7af07a2) accidentally reverted the Recovery variant from the CredentialKind enum after Phase 1 added it. It also never included the recovery wrapping helpers (compute_recovery_auth_binding, recovery_credential_lookup_hash, recovery_wrap_key, recovery_wrap_aad, new_recovery_seed_wrapping, verify_recovery_seed_wrapping) that are required for subsequent phases. This commit restores Recovery to the enum and adds the missing helpers plus 22 unit tests covering round-trip, wrong code/key/user/project, tamper-proof rejection, domain separation, and the new_seed_wrapping / verify_recovery_seed_wrapping boundary.
- Add reusable require_transport_v2 middleware (encryption_middleware) - Mount dedicated recovery sub-router: status/enroll/rotate/disable - Gate routes: v2 transport (outermost), user JWT, email-password user, password step-up, and seed opening through the signed AuthContext - Enroll/rotate seal+verify wraps before any DB write; CAS from Phase 3 adjudicates concurrency; disablement is idempotent - Keep recovery credentials out of logs; sanitized generic error bodies - Add 7 DB-backed route integration tests and security-invariant structure tests; extend sensitive log scanner identifiers - Mark Phase 5/6-consumed recovery crypto helpers for CI clippy parity
…tion Match the existing web mod pattern: re-export the sub-router function from src/web/mod.rs and import it bare in application_routes, instead of the namespace-qualified protected_routes::recovery_router path.
Remove recovery_wrap_key, recovery_wrap_aad, their RECOVERY_WRAP_* domain constants, RecoveryCodeError, and RecoveryCode::parse with its crockford decoder. None had a production consumer (parse arrives with Phase 6 reset completion) and none will be reintroduced without one. Recovery wraps keep opening through decrypt_seed_v1 with a recovery AuthBinding; the recovery_code display shape unit test now covers the canonical grouped format on its own.
This reverts commit 626fa3bad1dd4d0b10cf420e6d5e2db77b35d164.
Track the four open items opened by the Phase 4 work in the committed plan document instead of the disposable task scratchpad: management status-code contract, OAuth-only status eligibility, the plan-vs-implementation recovery-wrap-envelope reconciliation, and validation-gate evidence scope. Each carries the phase or event where the decision can no longer be deferred.
w3irdrobot
force-pushed
the
docs/recovery-credential-architecture
branch
from
September 10, 2026 00:41
168f4e3 to
f160804
Compare
- POST /password-reset/v2/complete behind the outermost v2-transport gate with Preserve/Destructive modes, shared proof reverification, and transport-aware token issuance on success. - Preserving completion opens the enrolled recovery wrap through decrypt_seed_v1 with a recovery auth binding, validates the opened seed, and replaces only the password credential under a user-row lock with a guarded reset consume and an unchanged-recovery-wrap CAS; a well-formed wrong code consumes exactly the selected request. - Destructive completion requires an explicit data-loss acknowledgment and reuses the legacy destructive reset path unchanged, creating no recovery. - Resolve the pending Phase 6 decision: delete the unused recovery_wrap_key/recovery_wrap_aad helpers, un-annotate the now-consumed RecoveryCode::parse, and reconcile the plan doc section. - 10 DB-backed route tests covering the P6.T matrix plus a completion-route security invariant test.
…yloads - PasswordResetConfirmPayload carries a presence-aware recovery_code Option<Option<String>> with a custom deserializer, so any present form of the field — a value, an empty string, or an explicit null — stays observable where a plain Option<String> would treat null as absent. - password_reset_confirm rejects any recovery_code presence with a generic 400 as its first statement, before project, user, reset-request, or recovery lookup; the unchanged one-shot destructive flow follows for old clients that omit the field. - 2 DB-backed route tests: the old-client destructive contract is retained exactly (response, request consumption, credentials, seed and data deletion, no recovery created), and every present recovery_code value is rejected before mutation with the reset request left active. - Security invariant pin for the presence-aware field shape, guard-before-lookup order, and absence of recovery material in the legacy handler. - Reconcile the plan doc's legacy-guard sketch with the implemented stricter shape.
The helper exported only DATABASE_URL/AEAD_TAMPER_TEST_DATABASE_URL and filtered to the AEAD-tamper and OAuth subsets, so the 31 DB-backed recovery tests silently never ran while the summary reported no skips. Export RECOVERY_TEST_DATABASE_URL and run the recovery subset serially with the same count and skip-marker assertions as the existing subsets.
…cisions Phases 0-8 are complete; fold the Phase 8 validation evidence, the implemented-surface file map, and the silent-rollout strategy into the plan doc. Confirm the client-facing management status codes and keep recovery_status reachable for OAuth-only users; close the deferred encrypted-carrier smoke and runtime log-capture rows, moving both to the client-rollout gate. The untracked tasks scratchpad and patterns reference are deleted now that the durable content lives here.
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.
Summary
Ports the recovery credential architecture work from
OpenSecretCloud/opensecret#286into the monorepo (
services/opensecret/). The feature is not complete — thisis a draft PR to continue development in the right codebase.
What is here
services/opensecret/docs/recovery-credential-plan.md, including the pending v1 decisions recorded with binding milestonesCredentialKindwith a database constraint (migration)RecoveryCodecrypto primitives and wrapping helpers (src/recovery_code.rs,src/seed_wrapping.rs)src/db.rs,src/recovery_db_tests.rs)src/web/protected_routes.rs,src/recovery_route_tests.rs)src/security_invariants.rs,src/crypto_property_tests.rs)Provenance
OpenSecretCloud/opensecret@9b5cdc8..docs/recovery-credential-architecture(each commit preserved with original authorship: Phase 1 → Phase 4).
services/opensecret/, minus the isolated toolchain-component commit whichwas intentionally dropped to keep the Nix-pinned toolchain untouched.
services/opensecret/rust-toolchain.tomlmatches upstream.Notes
docs/recovery-existing-patterns.md,docs/recovery-implementation-tasks.md) exist only in the working tree and are deliberately not committed.rust-analyzer,rust-src) were dropped from this PR and can land separately if wanted.Verification
$validate-opensecret/ componentjustrecipes from repo root)cargo testfor recovery modules (recovery_code,recovery_db_tests,recovery_route_tests,crypto_property_tests, security invariants)