Conversation
Adds time-delayed social recovery for a locked-out account owner. A user nominates trusted guardians (platform users or external contacts) who must explicitly accept; a claimant can reopen the account only when a quorum of guardians independently approves AND a mandatory cooling-off delay elapses uncontested. No single party — including the platform — can unilaterally take over an account. Structural defences, each enforced in more than one layer: - Quorum is never 1-of-N: Zod schema, service check, and a DB CHECK (>= 2). - Mandatory 24-168h delay (default 48h), stamped ONCE onto the request at quorum from the policy in force then, so editing the policy mid-flight cannot pull a live deadline forward. - Loud multi-channel alerting on initiation to the account owner's own registered contacts plus every guardian; socket-only events so a recovery alert can never be suppressed by a misconfigured webhook. - The owner can cancel unilaterally at any point before execution. - Execution has no HTTP endpoint: a scheduled sweep is the only caller, revoking every live session with reason `account_recovery`. Anti-enumeration: the public, unauthenticated POST /recovery/initiate returns an identical 202 for every valid body and relies on a partial unique index (one live request per account) rather than a racy read-then-write. Includes: RecoveryGuardian/RecoveryPolicy/RecoveryRequest/RecoveryApproval models + migration with CHECK constraints and partial unique index (and rollback); service, routes, notifications, and the sweep job; env config and a dedicated recovery rate limiter; docs/ACCOUNT_RECOVERY.md threat model and docs/openapi.yaml; 69 unit tests across service, notifications, routes, sweep, and migration-structure suites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@CHKM001 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Contributor
|
Fix conflict |
…ial-recovery # Conflicts: # prisma/schema.prisma # src/events/types.ts # src/index.ts # src/services/refresh-token.service.ts
Contributor
Author
|
Pls review! |
Contributor
|
Fix conflict |
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.
PR #535 Guardian-Based Social Recovery for Account Access
Summary
Adds time-delayed social recovery for a locked-out account owner. A user nominates trusted guardians (other platform users or external email/phone contacts) who must explicitly accept. Later, a claimant who has lost their authentication method can reopen the account only when a quorum of those guardians independently approves and a mandatory cooling-off delay elapses uncontested. No single party — including the platform — can unilaterally take over an account.
This recovers account access (the owner's sessions and ability to authenticate), not the custodial wallet's key material, which the platform already holds and which is a separate problem (
src/keys/registry.ts). Friction is the point: the design optimises for making unauthorized recovery hard, not for making legitimate recovery frictionless.What's Included
Data Model (
prisma/schema.prisma+ migration)RecoveryGuardian— a nominated guardian (platform user or external email/phone), with explicit-accept status and a SHA-256 invite-token digest.RecoveryPolicy— one per account:requiredApprovals,recoveryDelayHours,maxGuardians.RecoveryRequest— a recovery attempt; carries theexecuteAfterdeadline stamped once at quorum.RecoveryApproval— one row per guardian decision; unique per(requestId, guardianId).20260930140000_add_guardian_recovery(+rollback.sql) encodes the invariants Prisma cannot express: aCHECKthat quorum is>= 2, aCHECKthat the delay is>= 24h, and a partial unique index allowing at most one live recovery request per account.Service (
src/guardians/service.ts,src/guardians/notifications.ts)Routes (
src/routes/recovery.ts)/api/v1/recovery/policy/api/v1/recovery/guardians/api/v1/recovery/guardians/{guardianId}/api/v1/recovery/guardians/{guardianId}/accept/api/v1/recovery/invitations/respond/api/v1/recovery/initiate/api/v1/recovery/requests/{requestId}/api/v1/recovery/requests/{requestId}/cancel/api/v1/recovery/guardian/requests/api/v1/recovery/guardian/requests/{id}/decide/api/v1/recovery/guardian/decideExecution (
src/jobs/guardianRecoverySweep.ts)executeRecovery— there is deliberately no HTTP execute endpoint (a caller would revoke its own session mid-request).executeAfterhas passed (batch-capped).account_recovery. No password reset, no wallet-key touch — the owner recovers by signing in again.Wiring
src/index.ts; sweep scheduled on boot and cleared on graceful shutdown.recoveryRateLimiter(src/config/env.ts,src/middleware/rateLimiter.ts).src/events/types.ts) and payload allowlists (src/utils/api-formatters.ts).RevocationReasonextended withaccount_recovery(src/services/refresh-token.service.ts).Docs
docs/ACCOUNT_RECOVERY.md— full threat model, controls table, and stated limits.docs/openapi.yaml—Account Recoverytag, schemas, and all eleven endpoints.Security & Threat Model
CHECK).POST /recovery/initiateis unauthenticated and returns an identical202for every valid body (account exists or not, guardians or not, live request or not). A partial unique index, not a racy read-then-write, guarantees one live request per account; aP2002maps to the same generic response.Known Limitations (documented, not bugs)
Testing
guardians/service,guardians/notifications,guardians/structural(asserts the migration's CHECK constraints and partial index survive),jobs/guardianRecoverySweep, androutes/recovery.tsc --noEmitintroduces no new type errors from this feature.Base & Scope Notes
main; contains only Guardian-Based Social Recovery for Account Access #535. Unrelated in-flight work (Borrow Against Deposited Collateral (Non-Liquidating Credit Line) #532 collateral-loan edits and assorted untagged refactors) that was interleaved in the working tree was deliberately excluded.main(e.g.alertRulesdelivery-channel union,admin-impersonationevent topic, missingnotificationPreferencemodel). None originate from Guardian-Based Social Recovery for Account Access #535; they are resolved by other feature branches not yet merged tomain.Closes #535