cadrumo.application.user_profile.recovery_custody module

Optional per-profile recovery: enrol, revoke, inspect, and reset the passphrase.

Recovery is a second wrapper over the same data-encryption key, opened by a minted recovery code instead of the operator’s passphrase. It is off by default. An operator who wants it enrols after the profile exists, proves the current passphrase to do so, and copies the code down once; the code is never persisted, never logged, and never enters a result envelope. The only thing the code is good for is reset_profile_passphrase_with_recovery(): it proves the enrolled wrapper against the capsule’s own sentinel and re-wraps the key under a new passphrase, so a forgotten passphrase is replaced rather than the records lost.

Every door here works on a COMMITTED capsule under its custody transaction lock. Nothing mints, rotates, or re-derives a key schedule; the DEK epoch is preserved throughout, which is what keeps an enrolled code valid across ordinary passphrase changes.

See also

rotate_profile_passphrase()

The passphrase-proved sibling of the reset door; both share one re-wrap primitive.

exception ProfileRecoveryError(message=None, *, context=None, translated_message=None, password_refusal=None)[source]

Bases: CadrumoError

Raised when a recovery door cannot be honoured as supplied.

Parameters:
Return type:

None

property password_refusal: ProspectiveProfilePasswordRefusal | None

Retain the typed refusal for trusted in-process consumers only.

code
class ProfileRecoveryEnrollment(envelope, recovery_key)[source]

Bases: object

One minted recovery wrapper and the secret that opens it.

The secret rides in its wipeable container rather than as a str: the operator holds it across an interactive confirmation lasting as long as it takes a human to copy a code down, and a string copy is unreachable by any wipe primitive for its whole lifetime. The enrolment door owns the wipe and takes it as soon as the handover returns.

Parameters:
envelope: ProfileCustodyRecoveryEnvelopePort
recovery_key: ProfileRecoveryKeyPort
class ProfileRecoveryStatus(**data)[source]

Bases: BaseModel

Whether one committed profile currently has recovery enrolled.

Parameters:

data (Any)

profile_id: ProfileId
enrolled: bool
class ProfileRecoveryEnrollmentOutcome(**data)[source]

Bases: BaseModel

Non-secret result of one completed enrolment or revocation.

Parameters:

data (Any)

profile_id: ProfileId
enrolled: bool
changed: bool
class ProfilePassphraseResetOutcome(**data)[source]

Bases: BaseModel

Non-secret result of one passphrase reset proved by the recovery code.

Parameters:

data (Any)

profile_id: ProfileId
password_generation: int
dek_epoch_preserved: bool
recovery_enrollment_retained: bool
profile_recovery_status(*, profile_id, root=None)[source]

Report whether profile_id has a recovery wrapper enrolled.

Reads only the committed capsule’s layout; no secret is required and no key material is touched.

Return type:

ProfileRecoveryStatus

Parameters:
enroll_profile_recovery(*, profile_id, current_passphrase, recovery_handover, root=None)[source]

Mint a recovery code for a committed profile and install its wrapper.

The current passphrase is the authorisation: a second door onto the records may only be opened by someone who already holds the first. The code is delivered through recovery_handover and nowhere else, and the callback must return the exact code it received as possession proof. The wrapper is installed only after that proof matches, so an operator who could not copy the code down is left exactly where they started rather than enrolled under a secret nobody holds.

Parameters:
  • profile_id (UUID) – The committed profile to enrol.

  • current_passphrase (str) – Proof of the existing credential. Never logged.

  • recovery_handover (Callable[[ProfileRecoveryEnrollment], str]) – The one channel the code reaches the operator through. It is invoked once, before anything is written, and the key is wiped by the time this returns. Raising from the callback aborts the enrolment.

  • root (Path | None) – Storage root override; the effective root when omitted.

Raises:

ProfileRecoveryError – When the profile is already enrolled, the passphrase does not open the committed envelope, or the returned proof differs from the minted code.

Return type:

ProfileRecoveryEnrollmentOutcome

revoke_profile_recovery(*, profile_id, current_passphrase, root=None)[source]

Remove the enrolled recovery wrapper after proving the current passphrase.

Idempotent: a profile that is not enrolled is reported unchanged rather than refused, because the operator’s intent (“no recovery on this profile”) already holds. The passphrase is still proved first, so the unchanged answer never discloses enrolment state to someone without it.

Return type:

ProfileRecoveryEnrollmentOutcome

Parameters:
  • profile_id (UUID)

  • current_passphrase (str)

  • root (Path | None)

reset_profile_passphrase_with_recovery(*, profile_id, recovery_code, new_passphrase, new_passphrase_confirmation, root=None, profile_decode_context)[source]

Replace a forgotten passphrase by proving the enrolled recovery code.

The recovery code unwraps the DEK from the enrolled wrapper, the result is proved against the capsule’s own sentinel, and only then is the password envelope re-minted under new_passphrase through the same primitive an ordinary rotation uses. The DEK epoch is preserved, so the recovery wrapper stays enrolled and the same code keeps working.

Fails closed at every step before the swap: a profile without recovery, a wrong code, a new passphrase outside the profile-password contract, or a mismatched confirmation all refuse with the committed envelope untouched.

Raises:

ProfileRecoveryError – When recovery is not enrolled, the confirmation does not match, the new passphrase is invalid, or the code does not open the enrolled wrapper.

Return type:

ProfilePassphraseResetOutcome

Parameters: