oauth2_broker/ext/
token_lease.rs

1//! Token leasing contracts that let callers borrow access tokens for short
2//! windows while the broker controls refresh lifetimes.
3
4// self
5use crate::{
6	_prelude::*,
7	auth::{ScopeSet, TokenFamily},
8};
9
10/// Boxed future returned by [`TokenLeaseExt::lease`].
11pub type TokenLeaseFuture<'a, Lease, Error> =
12	Pin<Box<dyn Future<Output = Result<TokenLeaseState<Lease>, Error>> + 'a + Send>>;
13
14/// Contract for cache providers that want to loan out access tokens while the broker governs
15/// refresh lifetimes.
16pub trait TokenLeaseExt<Lease, Error>: Send + Sync {
17	/// Attempts to borrow a token for the provided context.
18	fn lease(&self, context: TokenLeaseContext) -> TokenLeaseFuture<'_, Lease, Error>;
19}
20
21/// Metadata describing what kind of lease the caller is requesting.
22#[derive(Clone, Debug)]
23pub struct TokenLeaseContext {
24	/// Token family tied to the lease.
25	pub family: TokenFamily,
26	/// Scope set tied to the lease.
27	pub scope: ScopeSet,
28	/// Instant that should be treated as "now" for freshness checks.
29	pub requested_at: OffsetDateTime,
30	/// Minimum TTL the caller wants to guarantee.
31	pub minimum_ttl: Duration,
32	/// Optional annotation that can flow into logs/metrics.
33	pub reason: Option<String>,
34}
35impl TokenLeaseContext {
36	/// Creates a new context for the provided token family + scope set.
37	pub fn new(family: TokenFamily, scope: ScopeSet) -> Self {
38		Self {
39			family,
40			scope,
41			requested_at: OffsetDateTime::now_utc(),
42			minimum_ttl: Duration::ZERO,
43			reason: None,
44		}
45	}
46
47	/// Overrides the instant used for freshness calculations.
48	pub fn with_requested_at(mut self, instant: OffsetDateTime) -> Self {
49		self.requested_at = instant;
50
51		self
52	}
53
54	/// Ensures the lease is only granted if the token will remain valid for at
55	/// least the provided TTL.
56	pub fn with_minimum_ttl(mut self, ttl: Duration) -> Self {
57		self.minimum_ttl = ttl;
58
59		self
60	}
61
62	/// Adds an optional human-readable reason for observability.
63	pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
64		self.reason = Some(reason.into());
65
66		self
67	}
68}
69
70/// Result of attempting to lease a token.
71pub enum TokenLeaseState<Lease> {
72	/// A guard was produced. Dropping the guard should release the lease.
73	Granted {
74		/// User-defined guard that owns the lease lifetime.
75		lease: Lease,
76		/// Expiry instant for the leased token record.
77		expires_at: OffsetDateTime,
78	},
79	/// A lease will be available later; callers should retry after the delay.
80	Pending {
81		/// Duration callers should wait before retrying.
82		retry_in: Duration,
83	},
84	/// No usable token exists; flows should refresh or mint a new one.
85	NeedsRefresh,
86}
87impl<Lease> Debug for TokenLeaseState<Lease> {
88	fn fmt(&self, f: &mut Formatter) -> FmtResult {
89		match self {
90			Self::Granted { expires_at, .. } =>
91				f.debug_struct("TokenLeaseState::Granted").field("expires_at", expires_at).finish(),
92			Self::Pending { retry_in } =>
93				f.debug_struct("TokenLeaseState::Pending").field("retry_in", retry_in).finish(),
94			Self::NeedsRefresh => f.debug_struct("TokenLeaseState::NeedsRefresh").finish(),
95		}
96	}
97}