1use std::time::{Duration, SystemTime};
2
3use lexe_api_core::error::{BackendApiError, BackendErrorKind};
4use lexe_common::api::auth::{
5 BearerAuthRequest, BearerAuthRequestWire, BearerAuthToken, Scope,
6 TokenWithExpiration,
7};
8use lexe_crypto::ed25519;
9
10use crate::def::BearerAuthBackendApi;
11
12pub const DEFAULT_USER_TOKEN_LIFETIME_SECS: u32 = 10 * 60; const EXPIRATION_BUFFER: Duration = Duration::from_secs(30);
15
16#[allow(private_interfaces, clippy::large_enum_variant)]
19pub enum BearerAuthenticator {
20 Ephemeral { inner: EphemeralBearerAuthenticator },
21 Static { inner: StaticBearerAuthenticator },
22}
23
24struct EphemeralBearerAuthenticator {
27 user_key_pair: ed25519::KeyPair,
32
33 auth_token_lock: tokio::sync::Mutex<Option<TokenWithExpiration>>,
43
44 scope: Option<Scope>,
46}
47
48struct StaticBearerAuthenticator {
51 token: BearerAuthToken,
53}
54
55impl BearerAuthenticator {
58 pub fn new(
62 user_key_pair: ed25519::KeyPair,
63 maybe_token: Option<TokenWithExpiration>,
64 ) -> Self {
65 let scope = None;
67 Self::new_with_scope(user_key_pair, maybe_token, scope)
68 }
69
70 pub fn new_with_scope(
73 user_key_pair: ed25519::KeyPair,
74 maybe_token: Option<TokenWithExpiration>,
75 scope: Option<Scope>,
76 ) -> Self {
77 Self::Ephemeral {
78 inner: EphemeralBearerAuthenticator {
79 user_key_pair,
80 auth_token_lock: tokio::sync::Mutex::new(maybe_token),
81 scope,
82 },
83 }
84 }
85
86 pub fn new_static_token(token: BearerAuthToken) -> Self {
89 Self::Static {
90 inner: StaticBearerAuthenticator { token },
91 }
92 }
93
94 pub fn user_key_pair(&self) -> Option<&ed25519::KeyPair> {
95 match self {
96 Self::Ephemeral { inner } => Some(&inner.user_key_pair),
97 Self::Static { .. } => None,
98 }
99 }
100
101 pub async fn get_token<T: BearerAuthBackendApi + ?Sized>(
104 &self,
105 api: &T,
106 now: SystemTime,
107 ) -> Result<BearerAuthToken, BackendApiError> {
108 self.get_token_with_exp(api, now)
109 .await
110 .map(|token_with_exp| token_with_exp.token)
111 }
112
113 pub async fn get_token_with_exp<T: BearerAuthBackendApi + ?Sized>(
117 &self,
118 api: &T,
119 now: SystemTime,
120 ) -> Result<TokenWithExpiration, BackendApiError> {
121 match self {
122 Self::Ephemeral { inner } =>
123 inner.get_token_with_exp(api, now).await,
124 Self::Static { inner } => inner.get_token_with_exp(api, now).await,
125 }
126 }
127}
128
129impl EphemeralBearerAuthenticator {
130 async fn get_token_with_exp<T: BearerAuthBackendApi + ?Sized>(
131 &self,
132 api: &T,
133 now: SystemTime,
134 ) -> Result<TokenWithExpiration, BackendApiError> {
135 let mut locked_token = self.auth_token_lock.lock().await;
136
137 if let Some(token) = locked_token.as_ref()
139 && !token_needs_refresh(now, token.expiration)
140 {
141 return Ok(token.clone());
142 }
143
144 let token_with_exp = do_bearer_auth(
146 api,
147 now,
148 &self.user_key_pair,
149 DEFAULT_USER_TOKEN_LIFETIME_SECS,
150 self.scope.clone(),
151 )
152 .await?;
153
154 let token_clone = token_with_exp.clone();
155
156 *locked_token = Some(token_with_exp);
158
159 Ok(token_clone)
160 }
161}
162
163impl StaticBearerAuthenticator {
164 async fn get_token_with_exp<T: BearerAuthBackendApi + ?Sized>(
165 &self,
166 _api: &T,
167 _now: SystemTime,
168 ) -> Result<TokenWithExpiration, BackendApiError> {
169 Ok(TokenWithExpiration {
170 expiration: None,
171 token: self.token.clone(),
172 })
173 }
174}
175
176pub async fn do_bearer_auth<T: BearerAuthBackendApi + ?Sized>(
182 api: &T,
183 now: SystemTime,
184 user_key_pair: &ed25519::KeyPair,
185 lifetime_secs: u32,
186 scope: Option<Scope>,
187) -> Result<TokenWithExpiration, BackendApiError> {
188 let expiration = now + Duration::from_secs(lifetime_secs as u64);
189 let auth_req = BearerAuthRequest::new(now, lifetime_secs, scope);
190 let auth_req_wire = BearerAuthRequestWire::from(auth_req);
191 let (_, signed_req) =
192 user_key_pair.sign_struct(&auth_req_wire).map_err(|err| {
193 BackendApiError {
194 kind: BackendErrorKind::Building,
195 msg: format!("Error signing auth request: {err:#}"),
196 ..Default::default()
197 }
198 })?;
199
200 let resp = api.bearer_auth(&signed_req).await?;
201
202 Ok(TokenWithExpiration {
203 expiration: Some(expiration),
204 token: resp.bearer_auth_token,
205 })
206}
207
208#[inline]
211pub fn token_needs_refresh(
212 now: SystemTime,
213 expiration: Option<SystemTime>,
214) -> bool {
215 match expiration {
217 Some(expiration) => now + EXPIRATION_BUFFER >= expiration,
218 None => false,
219 }
220}