pub struct DeviceLifecycleService<S>where
S: DeviceStore,{ /* private fields */ }device only.Expand description
Composes DeviceStore primitives into the lifecycle operations
every axess consumer needs at request/authn boundaries.
Cheap to clone (the inner store is Clone and the optional event
sink is Arc-backed); construct once at startup and share across
handlers.
Implementations§
Source§impl<S> DeviceLifecycleService<S>where
S: DeviceStore,
impl<S> DeviceLifecycleService<S>where
S: DeviceStore,
Sourcepub fn new(store: S) -> Self
pub fn new(store: S) -> Self
Wrap a DeviceStore. Audit events are dropped on the floor
until Self::with_event_sink is called; the underlying
device-state mutations always happen regardless.
Sourcepub fn with_event_sink<E: DeviceEventSink>(self, sink: E) -> Self
pub fn with_event_sink<E: DeviceEventSink>(self, sink: E) -> Self
Wire a DeviceEventSink so device-lifecycle transitions emit
audit events. Typically wraps an
IdentityStore via
IdentityStoreEventSink.
Sourcepub fn store(&self) -> &S
pub fn store(&self) -> &S
Borrow the underlying store. Use sparingly: bypasses the lifecycle invariants this service exists to enforce.
Sourcepub fn ensure_device(
&self,
tenant: &TenantId,
user: Option<&UserId>,
fingerprint: FingerprintHash,
now: DateTime<Utc>,
new_id_fn: impl FnOnce() -> DeviceId + Send,
) -> impl Future<Output = Result<DeviceId, S::Error>> + Send
pub fn ensure_device( &self, tenant: &TenantId, user: Option<&UserId>, fingerprint: FingerprintHash, now: DateTime<Utc>, new_id_fn: impl FnOnce() -> DeviceId + Send, ) -> impl Future<Output = Result<DeviceId, S::Error>> + Send
Look up a device by fingerprint within tenant. If present,
bump last_seen_at = now and return its device_id. If absent,
create a new row at DeviceTrustLevel::Unknown using
new_id_fn() for the device_id and return it.
user is None for guest sessions (pre-authn requests). The
user_id field on the created Device row is set to whatever
is passed in; updates that arrive later (e.g. when authn
completes for a previously-guest device) need to call
DeviceStore::save directly via Self::store; this
helper deliberately doesn’t touch user_id on the existing-
device path to keep the create-vs-find branches symmetric.
new_id_fn is the application’s choice of identifier scheme.
Pass || DeviceId::try_new(Uuid::new_v4().to_string()).unwrap()
for the canonical UUID-v4 shape, or a deterministic generator
driven by MockRng for tests.
Only called on the create path.
Sourcepub fn promote_on_authn(
&self,
tenant: &TenantId,
device_id: &DeviceId,
now: DateTime<Utc>,
) -> impl Future<Output = Result<Option<DeviceTrustLevel>, S::Error>> + Send
pub fn promote_on_authn( &self, tenant: &TenantId, device_id: &DeviceId, now: DateTime<Utc>, ) -> impl Future<Output = Result<Option<DeviceTrustLevel>, S::Error>> + Send
Promote a device’s trust level after a successful authentication ceremony.
State machine:
| Current | After promote_on_authn |
|---|---|
Unknown | Seen (recorded with record_sighting(now)) |
Seen | Seen (no-op; last_seen_at bumped) |
Trusted | Trusted (no-op; last_seen_at bumped) |
Revoked | Revoked (no-op; last_seen_at not bumped) |
Never re-elevates a Revoked device. Revocation is a
terminal state until an admin / user explicitly resurrects the
device via DeviceStore::set_trust_level; passing through
promote_on_authn after a successful login on a revoked
device must not silently undo the revocation. (The application
should reject the login earlier in such cases; this is
defence-in-depth.)
Never demotes a Trusted device. A Trusted device that
authenticates again stays Trusted. This is the standard “user
elevated device once via explicit consent; subsequent logins
don’t downgrade trust” semantic.
Returns the trust level the device is in after the call.
Ok(None) if the device_id doesn’t resolve: defensive,
callers shouldn’t see this if they pass an id from
Self::ensure_device.
Sourcepub fn promote_if_authenticated(
&self,
outcome: &FactorOutcome,
tenant: &TenantId,
device_id: &DeviceId,
now: DateTime<Utc>,
) -> impl Future<Output = Result<Option<DeviceTrustLevel>, S::Error>> + Send
pub fn promote_if_authenticated( &self, outcome: &FactorOutcome, tenant: &TenantId, device_id: &DeviceId, now: DateTime<Utc>, ) -> impl Future<Output = Result<Option<DeviceTrustLevel>, S::Error>> + Send
Convenience composition for the AuthnService glue pattern:
fire Self::promote_on_authn iff the FactorOutcome
indicates the user just completed authentication. No-op on
FactorRequired / InvalidCredential / Locked.
Usage:
let outcome = authn.complete_factor_step(...).await?;
let _ = device_lifecycle
.promote_if_authenticated(&outcome, &tenant, &device_id, now)
.await?;Returns the same Option<DeviceTrustLevel> as
Self::promote_on_authn when the outcome was
Authenticated; Ok(None) otherwise (including the absent-
device-id case, mirroring promote_on_authn semantics).
Sourcepub fn bind_webauthn_credential(
&self,
tenant: &TenantId,
device_id: &DeviceId,
credential_id: String,
attestation_class: AttestationClass,
now: DateTime<Utc>,
) -> impl Future<Output = Result<bool, S::Error>> + Send
Available on crate feature fido2 only.
pub fn bind_webauthn_credential( &self, tenant: &TenantId, device_id: &DeviceId, credential_id: String, attestation_class: AttestationClass, now: DateTime<Utc>, ) -> impl Future<Output = Result<bool, S::Error>> + Send
fido2 only.Record a DeviceBinding::WebAuthn on a device after a successful
FIDO2 registration ceremony.
If the device already carries a WebAuthn binding for the same
credential_id, this is a no-op (idempotent). Otherwise the new
binding is appended and a AuthEventType::DeviceBindingAdded
audit event is emitted.
Returns Ok(true) when a new binding was added, Ok(false) when
deduplicated, Ok(None)-shaped Err when the device doesn’t exist.
Sourcepub fn record_webauthn_usage(
&self,
tenant: &TenantId,
device_id: &DeviceId,
credential_id: &str,
now: DateTime<Utc>,
) -> impl Future<Output = Result<(), S::Error>> + Send
Available on crate feature fido2 only.
pub fn record_webauthn_usage( &self, tenant: &TenantId, device_id: &DeviceId, credential_id: &str, now: DateTime<Utc>, ) -> impl Future<Output = Result<(), S::Error>> + Send
fido2 only.Update the last_used_at timestamp on an existing
DeviceBinding::WebAuthn after a successful FIDO2 assertion.
No-op if the device doesn’t exist or has no WebAuthn binding
matching credential_id.
Trait Implementations§
Source§impl<S> Clone for DeviceLifecycleService<S>where
S: DeviceStore + Clone,
impl<S> Clone for DeviceLifecycleService<S>where
S: DeviceStore + Clone,
Source§fn clone(&self) -> DeviceLifecycleService<S>
fn clone(&self) -> DeviceLifecycleService<S>
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl<S> Freeze for DeviceLifecycleService<S>where
S: Freeze,
impl<S> !RefUnwindSafe for DeviceLifecycleService<S>
impl<S> Send for DeviceLifecycleService<S>
impl<S> Sync for DeviceLifecycleService<S>
impl<S> Unpin for DeviceLifecycleService<S>where
S: Unpin,
impl<S> UnsafeUnpin for DeviceLifecycleService<S>where
S: UnsafeUnpin,
impl<S> !UnwindSafe for DeviceLifecycleService<S>
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.