1use std::fmt;
2use std::sync::Arc;
3use std::time::{Duration, SystemTime};
4
5use bytes::Bytes;
6use secrecy::{ExposeSecret as _, SecretString};
7use tokio::sync::Mutex;
8use tokio::time::Instant;
9use tonic::transport::Channel;
10
11use crate::device_transport::connect_device_channel;
12use crate::proto::localdevice::permission_manager_client::PermissionManagerClient;
13use crate::proto::localdevice::{RequestAuthTokenRequest, RequestAuthTokenResponse};
14use crate::{AuthenticatedService, ClientCredentials, Error};
15
16const TOKEN_REFRESH_MARGIN: Duration = Duration::from_secs(30);
17
18pub struct AuthToken {
20 token: SecretString,
21 deadline: SystemTime,
22 refresh_at: Instant,
23}
24
25impl AuthToken {
26 #[must_use]
28 pub fn expose_secret(&self) -> &str {
29 self.token.expose_secret()
30 }
31
32 #[must_use]
34 pub fn deadline(&self) -> SystemTime {
35 self.deadline
36 }
37
38 fn should_refresh(&self, now: Instant) -> bool {
39 now >= self.refresh_at
40 }
41
42 fn from_response(response: RequestAuthTokenResponse) -> Result<Self, Error> {
43 let deadline = response.deadline.ok_or(Error::MissingTokenDeadline)?;
44 let deadline = SystemTime::try_from(deadline).map_err(|_| Error::InvalidTokenDeadline)?;
45 let now_system = SystemTime::now();
46 let now_instant = Instant::now();
47 let lifetime = deadline.duration_since(now_system).unwrap_or_default();
48 let refresh_at = now_instant + lifetime.saturating_sub(TOKEN_REFRESH_MARGIN);
49 Ok(Self {
50 token: response.token.into(),
51 deadline,
52 refresh_at,
53 })
54 }
55}
56
57impl fmt::Debug for AuthToken {
58 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59 formatter
60 .debug_struct("AuthToken")
61 .field("token", &"<redacted>")
62 .field("deadline", &self.deadline)
63 .finish_non_exhaustive()
64 }
65}
66
67#[derive(Clone)]
69pub struct TokenProvider {
70 inner: Arc<TokenProviderInner>,
71}
72
73struct TokenProviderInner {
74 channel: Channel,
75 credentials: ClientCredentials,
76 cached: Mutex<Option<Arc<AuthToken>>>,
77}
78
79impl TokenProvider {
80 pub async fn connect(api_url: &str, credentials: ClientCredentials) -> Result<Self, Error> {
87 let channel = connect_device_channel(api_url, &credentials).await?;
88 Ok(Self::new(channel, credentials))
89 }
90
91 #[must_use]
93 pub fn new(channel: Channel, credentials: ClientCredentials) -> Self {
94 Self {
95 inner: Arc::new(TokenProviderInner {
96 channel,
97 credentials,
98 cached: Mutex::new(None),
99 }),
100 }
101 }
102
103 pub async fn token(&self) -> Result<Arc<AuthToken>, Error> {
109 let mut cached = self.inner.cached.lock().await;
110 if let Some(token) = cached.as_ref() {
111 if !token.should_refresh(Instant::now()) {
112 return Ok(Arc::clone(token));
113 }
114 }
115
116 let token = Arc::new(
117 request_auth_token(self.inner.channel.clone(), &self.inner.credentials).await?,
118 );
119 *cached = Some(Arc::clone(&token));
120 Ok(token)
121 }
122
123 #[must_use]
125 pub fn authenticated_service(&self) -> AuthenticatedService<Channel> {
126 AuthenticatedService::new(self.inner.channel.clone(), self.clone())
127 }
128}
129
130impl fmt::Debug for TokenProvider {
131 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
132 formatter
133 .debug_struct("TokenProvider")
134 .field("credentials", &self.inner.credentials)
135 .field("cached_token", &"<redacted>")
136 .finish_non_exhaustive()
137 }
138}
139
140pub async fn request_auth_token(
147 channel: Channel,
148 credentials: &ClientCredentials,
149) -> Result<AuthToken, Error> {
150 let material = credentials.auth_request_material()?;
151 let request = RequestAuthTokenRequest {
152 box_cert: Bytes::copy_from_slice(material.box_certificate),
153 app_cert: Bytes::copy_from_slice(material.application_certificate),
154 signature: Bytes::from(material.signature),
155 };
156 let response = PermissionManagerClient::new(channel)
157 .request_auth_token(request)
158 .await?
159 .into_inner();
160 AuthToken::from_response(response)
161}