cadrumo.application.user_profile.login_session module¶
Transactional profile login over current password custody.
aeat config login resolves a committed capsule, evaluates its throttle,
and authenticates that exact envelope-and-sentinel pair into an unbound
candidate DEK session. It never consults a provider, a shared master key, or
recovery material. Until candidate authentication, pointer compare-and-swap,
and local binding have all succeeded, the active profile remains untouched.
Three distinct artefacts meet in this module, and reading any of them as another has twice produced a wrong conclusion about what the code does. They belong to different custody classes with different key requirements:
the acceleration receipt – keyring key plus a keystore sidecar record, OUTSIDE the encrypted store, wrapping an already-unlocked DEK. Revocable with no unlocked profile, which is why retirement can complete during recovery. Not a session: no counterparty, no protocol.
the live bucket session – the in-process
BucketSessionholding the unlocked DEK. Purely process-local, so it is absent in an ordinary invocation, every one of which is a fresh process.the AEAT authority session – an encrypted row INSIDE the bucket, revocable only with the key. Owned by the auth package; named here only to keep it distinct.
Unqualified “session” in this module means the live bucket session and nothing else.
The optional acceleration receipt is exactly that: acceleration. Its absence or failure leaves a valid live bucket session; it never changes the outcome of password authentication. A prior profile is retired only after B is durably selected and locally bound.
bind_resumed_profile_session() is the read-side counterpart and the
SINGLE resume authority: the login no-op path, the CLI root callback and
the named-profile read path all call it, so the surfaces cannot drift.
See also
cadrumo.adapters.persistence.storage.custody.acceleration_receiptThe split-knowledge receipt this module mints and resumes.
logout_active_profile()The symmetric strong close, which reuses
close_profile_session_artefacts()from here.
- exception ProfileLoginThrottledError(*, remaining_seconds)[source]¶
Bases:
UserProfileErrorRefuse a login attempt while the failed-attempt backoff is in force.
Raised BEFORE any Argon2id derivation runs, so a caller hammering passphrases cannot use the key-derivation function as a timing oracle. The remaining wait rides in
contextso the CLI can render it without re-parsing the message.- Parameters:
remaining_seconds (int)
- Return type:
None
- code¶
- class ProfileCustodySessionOwnerEffect(*values)[source]¶
Bases:
StrEnumVerified outcome of one local custody session-owner operation.
- REVOKED¶
- REMOVED¶
- VERIFIED_ABSENT¶
- class ProfileLoginOutcome(**data)[source]¶
Bases:
BaseModelTyped result of one
logininvocation.Carries no key material: the unlocked
BucketSessionis bound to the process through the active-session context variable, never to this record.- Variables:
bucket_id – Immutable UUID identity of the authenticated profile.
label – Operator-facing display label of that profile.
authenticated_at – UTC instant the session was established. On an idempotent no-op this is the ORIGINAL login instant, never re-stamped.
idle_deadline – Current sliding idle deadline.
absolute_deadline – Immutable absolute session cap.
session_persisted –
Falsewhen the host has no usable OS keychain, so the session is process-scoped only.already_authenticated –
Truewhen a still-valid persisted session was resumed as a no-op (no re-prompt, no new record).closed_previous_bucket_id – Bucket whose session this login closed during a cross-profile handover, or
Nonewhen the login re-entered the profile already selected. Populated whether or not that profile still had a live session in this process.
- Parameters:
data (Any)
- bucket_id: BucketId¶
- label: str¶
- authenticated_at: datetime¶
- idle_deadline: datetime¶
- absolute_deadline: datetime¶
- session_persisted: bool¶
- already_authenticated: bool¶
- closed_previous_bucket_id: BucketId | None¶
- close_profile_session_artefacts(*, storage_root, bucket_id)[source]¶
Tear down every session artefact owned by
bucket_id(idempotent).The single authority for “this profile is no longer logged in”: clears the process-local record authority, then revokes the durable artefacts through
_revoke_profile_session_artefacts(). Composed by the strong close inlogout_active_profile(), so neither surface owns a second teardown path.
- has_live_profile_session()[source]¶
Report whether this process currently holds an open profile session.
A durable active pointer is NOT the same fact: a registered profile keeps its selection across a logout, so the pointer can name a profile whose session is closed. A caller that needs profile-bound encrypted storage – anything that reads or writes through the secure object store – must ask this rather than reading the pointer, because only an open session carries the key that store needs.
- Return type:
- logout_active_profile()[source]¶
Strong-close the selected profile without any provider fallback.
Logout is the explicit session-revocation owner: it clears the live DEK binding, its current record binding, the UUID-paired acceleration receipt, and the durable active pointer while the canonical root lock is held. It returns the authenticated profile UUID when there was a session or selection to revoke, otherwise
Nonefor an idempotent logged-out call.
- revoke_live_profile_secret_for_custody_delete(*, bucket_id)[source]¶
Zeroise this process’s live DEK only when it serves
bucket_id.This is deliberately narrower than logout: a custody deletion must never tear down an unrelated profile’s active session. The active-session owner performs both the identity query and the zeroisation; callers only receive a durable, non-secret outcome they can receipt.
- Return type:
- Parameters:
bucket_id (str)
- remove_profile_session_acceleration_for_custody_delete(*, storage_root, bucket_id)[source]¶
Remove the actual persisted session acceleration and verify its absence.
- Return type:
- Parameters:
- bind_resumed_profile_session(*, bucket_id, now=None, profile_decode_context)[source]¶
Resume
bucket_id’s persisted session and bind it to this process.The SINGLE resume authority, shared by the login idempotence guard, the CLI root callback, and the named-profile read path so they cannot drift. Fail-closed throughout: the adapter deletes stale artefacts and reports a typed refusal, and this function opens NOTHING on any refusal branch.
bucket_idis an explicit target, NOT the active profile. The name once said “active”, which was false of the parameter and cost a real defect: the named-profile read path concluded this authority could not serve a target it had resolved itself, skipped the resume, and reported records it could not open as missing. Binding is what distinguishes this from the substrate’s ownresume_profile_session, which returns material and binds nothing.On success the sliding idle deadline is advanced and re-persisted exactly once, so activity in a later process keeps the session alive without re-writing the record on every storage access.
- Parameters:
bucket_id (
str) – Identifier of the profile whose session to resume.now (
datetime|None) – UTC evaluation instant; the canonical clock when omitted.profile_decode_context (
ProfileDecodeContext) – Decode context supplied by the enclosing pinned authority operation. Resuming a record without it is refused; no bundled-schema fallback exists.
- Return type:
- Returns:
Nonewhen the session resumed and is now the activeBucketSession, otherwise the typedProfileSessionRefusalReasonnaming why not.
- publish_created_profile_session(*, bucket_id, dek, now=None, profile_decode_context)[source]¶
Publish a newly created profile’s session exactly as a login would.
Registration already proves the operator’s credential – it CHOSE the passphrase and the create span unlocked the bucket with it – so requiring that same passphrase again before the new profile can be used is asking a question already answered. The session was simply never published: the create span held its DEK locally and dropped it, so every surface that asked afterwards was correctly told nobody was logged in.
This is deliberately the same publication login performs, and not a second, weaker one: one live bucket session bound process-wide, and the record authority derived beside it from the capsule’s COMMITTED envelope rather than from the in-memory one, so a fact read after registration proves the same two things it proves after a login.
No acceleration receipt is minted. A receipt is what carries a session into the NEXT process, which is a separate decision about how long a credential should last, and creating a profile does not settle it. The operator’s next process therefore authenticates normally.
- Parameters:
bucket_id (
str) – The newly created profile’s UUID.dek (
bytes) – The unlocked data-encryption key the create span established. Copied into the session’s own buffer; the caller keeps ownership of the bytes it passed.now (
datetime|None) – UTC evaluation instant; the canonical clock when omitted.profile_decode_context (
ProfileDecodeContext) – Decode context of the pinned authority operation the registration ran under.
- Return type:
- resolve_login_target(name)[source]¶
Resolve a
login NAMEtarget from an unambiguous UUID or exact label.Delegates to the workflow’s one live-profile resolver. That authority accepts the immutable bucket UUID or the exact operator label (including a sandbox’s canonical
sandbox:<name>label) and excludes tombstoned buckets for both forms. A bare sandbox short name carries nosandbox:prefix, so it remains an unknown profile rather than being implicitly namespaced.Public because the login SCREEN must resolve the same named target to preselect its row, and must refuse an unknown one identically. Left private, that arm would have had to re-derive the resolution — and a second derivation is a second refusal wording and a second answer to “is a bare sandbox name a profile?”, for one question that has one answer.
- Return type:
- Parameters:
name (str)
- login_profile(*, name=None, now=None, passphrase_callback=None, profile_decode_context)[source]¶
Authenticate B before replacing an active A session.
Target resolution and throttle preflight happen before Argon2. Password authentication creates only transaction-owned candidate memory. The exact pointer capture is compared immediately before publication; then B becomes the process binding and optional acceleration is attempted. A is not closed until those operations have succeeded.
- Return type:
- Parameters:
name (str | None)
now (datetime | None)
profile_decode_context (ProfileDecodeContext)
- authenticate_profile_for_invocation(*, name, now=None, passphrase_callback=None, profile_decode_context)[source]¶
Unlock one profile for the current process WITHOUT selecting it.
login_profileis a selection: it publishes the active-profile pointer, journals a handover and retires the profile it displaces. That is correct forconfig login NAME, and wrong for every command that merely names a target for this one invocation –--profile Xor a leaf’s own profile argument. Routing those through the selecting door meant that on a host with no usable keychain, where each command must re-supply its passphrase, reading one profile silently reselected it for every later command.So this door authenticates and binds, and does nothing durable: no pointer transaction is opened, no handover journal is written, no activation event is recorded, no acceleration receipt is minted, and the previously selected profile keeps its pointer entry and its receipt. A different live session is closed in-process only, because the process holds exactly one.
The result is process-scoped by construction, so
session_persistedis alwaysFalse; the caller’s existing not-persisted notice already tells the operator the next process must authenticate again.- Return type:
- Parameters:
name (str)
now (datetime | None)
profile_decode_context (ProfileDecodeContext)