1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! What a device that is not yet paired may present.
//!
//! A fifth `impl` block of [`ServeHandle`], split off for the reason
//! `serve_grace.rs` was: `serve_handle.rs` sits under the file-size budget
//! by less than a method whose contract is longer than the method. The
//! division is by question again — `serve_handle.rs` answers *who may use
//! this listener*, `serve_grace.rs` *what becomes of the machines already
//! using it when that changes*, and this file *how a machine that is not
//! yet one of them gets in once*.
//!
//! Two shapes of the same primitive. [`grant_once`](ServeHandle::grant_once)
//! is the grant as it always was: spent by its own presentation, dead at
//! its deadline, and otherwise untouchable from the far side.
//! [`grant_once_bounded`](ServeHandle::grant_once_bounded) also dies at a
//! number of wrong presentations, which is the property an embedder needs
//! the moment the listener's endpoint key outlives a restart — a code with
//! twenty bits of entropy is safe for two minutes only if guessing it costs
//! the guesser the code.
use std::num::NonZeroU8;
use std::time::Duration;
use crate::serve_error::ServeError;
use crate::serve_handle::ServeHandle;
impl ServeHandle {
/// Admit **one** request bearing `secret` before `ttl` elapses, on top
/// of whatever [`set_token`](Self::set_token) enforces.
///
/// A pairing primitive, not a second key. An embedder that wants a new
/// device to *fetch* the real credential over the encrypted hop mints a
/// short code, grants it here, shows it once, and serves a handshake
/// route behind the tunnel: the device presents the code as its bearer,
/// the edge lets exactly that request through, and the route answers
/// with the key. The code is spent when presented and dead anyway when
/// `ttl` passes, so a photograph of the screen is worth nothing later.
///
/// **While live, a grant is equivalent to the token for the whole
/// tunnel** — the edge cannot scope the one request it admits. Keep
/// `ttl` short and make the secret unguessable for that window. Wrong
/// presentations do not shorten it: this is the grant for a listener
/// whose ticket dies with the process, where the window is the whole
/// defence and a guesser has to find the listener first. A listener
/// whose ticket lasts wants
/// [`grant_once_bounded`](Self::grant_once_bounded) instead. The
/// enforced token is untouched either way: [`token`](Self::token) still
/// reports it, it still admits, and a rotation through
/// [`set_token`](Self::set_token) neither spends nor extends a grant.
///
/// # Errors
///
/// [`ServeError::InvalidToken`] if `secret` is empty or nothing but
/// whitespace, in which case nothing is granted — the value
/// [`set_token`](Self::set_token) refuses, refused for the same reason.
///
/// # Examples
///
/// ```no_run
/// # async fn example(serving: &modelpipe::ServeHandle) -> Result<(), Box<dyn std::error::Error>> {
/// serving.grant_once("483920".to_owned(), std::time::Duration::from_mins(2))?;
/// # Ok(())
/// # }
/// ```
pub fn grant_once(&self, secret: String, ttl: Duration) -> Result<(), ServeError> {
self.grant(secret, ttl, None)
}
/// [`grant_once`](Self::grant_once), except the grant is also gone the
/// moment `burn_after` wrong presentations have been made while it is
/// live.
///
/// A wrong presentation is a well-formed bearer that admits nowhere:
/// not the enforced token, not a key still admitting under
/// [`set_token_with_grace`](Self::set_token_with_grace), and not a
/// grant. A request with no `Authorization` at all, or one with another
/// scheme, is refused without counting — it is not a guess. What is
/// counted counts against every bounded grant at once, because the
/// edge cannot tell which one a guess was aimed at, and a guesser does
/// not get to choose. So a device that is still presenting a key this
/// listener no longer honours can burn an open pairing; that is the
/// intended reading — from the edge, it *is* a guesser — and the cost
/// is one more `grant_once_bounded`.
///
/// This is the grant for a listener whose endpoint key is stored
/// ([`ServeOptions::identity`](crate::ServeOptions::identity)). Such a
/// ticket outlives every restart, so anyone who has seen it once can
/// watch for a pairing to open and then spend the whole window
/// guessing. Six digits are a million possibilities; three wrong
/// guesses is the difference between two minutes of that and a
/// mistyped digit.
///
/// # Errors
///
/// [`ServeError::InvalidToken`] if `secret` is empty or nothing but
/// whitespace — as [`grant_once`](Self::grant_once), and nothing is
/// granted.
///
/// # Examples
///
/// ```no_run
/// # async fn example(serving: &modelpipe::ServeHandle) -> Result<(), Box<dyn std::error::Error>> {
/// let three = std::num::NonZeroU8::new(3).expect("three is not zero");
/// serving.grant_once_bounded("483920".to_owned(), std::time::Duration::from_mins(2), three)?;
/// # Ok(())
/// # }
/// ```
pub fn grant_once_bounded(
&self,
secret: String,
ttl: Duration,
burn_after: NonZeroU8,
) -> Result<(), ServeError> {
self.grant(secret, ttl, Some(burn_after))
}
fn grant(
&self,
secret: String,
ttl: Duration,
burn_after: Option<NonZeroU8>,
) -> Result<(), ServeError> {
if self.state.credential.grant(secret, ttl, burn_after) {
Ok(())
} else {
Err(ServeError::InvalidToken)
}
}
}