pub struct OneTimeSecret { /* private fields */ }Expand description
A TTL-bound secret that can be consumed at most once.
Typical use is an out-of-band one-time token: the server mints a value,
hands it to the operator/user once (console line, invite link, e-mail), and
the endpoint that accepts it calls consume. The
check-and-execute window is a single Mutex critical section, so N
concurrent requests carrying the same value produce exactly one true
(compare-and-swap, not “check now, mark later”).
§Semantics
issuereplaces the slot and re-arms it; only one secret is active at a time. Empty values are rejected withError::Validationand leave the current state untouched.consumereturnstrueexactly once per issued secret, only while it is neither expired nor consumed. A wrong candidate consumes nothing, so the legitimate holder can still use the secret.- The clock is
Instant: a TTL is wall-clock independent and survives system time adjustments; the store itself is process-local, so a consumed or expired secret dies with the process.
§Example
let setup = OneTimeSecret::new(Duration::from_secs(30 * 60));
setup.issue("install-token").unwrap();
assert_eq!(setup.state(), OneTimeSecretState::Pending);
assert!(!setup.consume("wrong-token"), "a wrong guess burns nothing");
assert!(setup.consume("install-token"));
assert!(!setup.consume("install-token"), "the second use fails");
assert_eq!(setup.state(), OneTimeSecretState::Consumed);
// A consumed token can still be recognized for replay diagnostics (410).
assert!(setup.matches("install-token"));§Expiry
A secret is valid while less than the TTL has elapsed:
let expired = OneTimeSecret::new(Duration::ZERO);
expired.issue("too-late").unwrap();
assert_eq!(expired.state(), OneTimeSecretState::Expired);
assert!(!expired.consume("too-late"));
assert!(!expired.matches("too-late"));(Use a zero TTL only in tests — a real secret needs a positive lifetime.)
Implementations§
Source§impl OneTimeSecret
impl OneTimeSecret
Sourcepub fn new(ttl: Duration) -> Self
pub fn new(ttl: Duration) -> Self
Create an empty slot whose secrets live for ttl.
A zero TTL expires the next issued secret immediately; that is useful in tests and useless in production, so pick a real lifetime.
Sourcepub fn issue(&self, secret: impl Into<String>) -> Result<()>
pub fn issue(&self, secret: impl Into<String>) -> Result<()>
Arm the slot with secret, replacing (and thereby revoking) any
previous value.
The caller supplies the value — see the module-level docs for the RNG and hash-at-rest recipes.
An empty secret is rejected with Error::Validation because it would
make consume("") succeed; the previous secret stays in place so a
failed re-issue cannot silently extend or shorten the current one.
Sourcepub fn consume(&self, candidate: &str) -> bool
pub fn consume(&self, candidate: &str) -> bool
Consume the slot if candidate matches the issued secret.
Atomic check-and-consume: among concurrent callers with the same
candidate exactly one observes true. Returns false for an empty
slot, a consumed secret, an expired secret, or a mismatch.
Sourcepub fn matches(&self, candidate: &str) -> bool
pub fn matches(&self, candidate: &str) -> bool
Whether candidate is the issued secret, without consuming it.
Meant for classifying a failed consume — e.g. an
installation endpoint that answers 410 Gone when the consumed token is
replayed instead of 401. Do not use it as the authorization gate:
it does not consume, so two concurrent requests could both pass it.
Sourcepub fn state(&self) -> OneTimeSecretState
pub fn state(&self) -> OneTimeSecretState
Current lifecycle state (diagnostics, tests, 401-vs-410 decisions).