google_cloud_auth/
token.rs

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types and functions to work with auth [Tokens].
16//!
17//! [Tokens]: https://cloud.google.com/docs/authentication#token
18
19use crate::Result;
20use crate::credentials::CacheableResource;
21use http::Extensions;
22use std::collections::HashMap;
23use tokio::time::Instant;
24
25/// Represents an auth token.
26#[derive(Clone, PartialEq)]
27pub struct Token {
28    /// The actual token string.
29    ///
30    /// This is the value used in `Authorization:` header.
31    pub token: String,
32
33    /// The type of the token.
34    ///
35    /// The most common type is `"Bearer"` but other types may appear in the
36    /// future.
37    pub token_type: String,
38
39    /// The instant at which the token expires.
40    ///
41    /// If `None`, the token does not expire.
42    ///
43    /// Note that the `Instant` is not valid across processes. It is
44    /// recommended to let the authentication library refresh tokens within a
45    /// process instead of handling expirations yourself. If you do need to
46    /// copy an expiration across processes, consider converting it to a
47    /// `time::OffsetDateTime` first:
48    ///
49    /// ```
50    /// # let expires_at = Some(std::time::Instant::now());
51    /// expires_at.map(|i| time::OffsetDateTime::now_utc() + (i - std::time::Instant::now()));
52    /// ```
53    pub expires_at: Option<Instant>,
54
55    /// Optional metadata associated with the token.
56    ///
57    /// This might include information like granted scopes or other claims.
58    pub metadata: Option<HashMap<String, String>>,
59}
60
61impl std::fmt::Debug for Token {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        f.debug_struct("Token")
64            .field("token", &"[censored]")
65            .field("token_type", &self.token_type)
66            .field("expires_at", &self.expires_at)
67            .field("metadata", &self.metadata)
68            .finish()
69    }
70}
71
72#[async_trait::async_trait]
73pub(crate) trait TokenProvider: std::fmt::Debug + Send + Sync {
74    async fn token(&self) -> Result<Token>;
75}
76
77#[async_trait::async_trait]
78pub(crate) trait CachedTokenProvider: std::fmt::Debug + Send + Sync {
79    async fn token(&self, extensions: Extensions) -> Result<CacheableResource<Token>>;
80}
81
82#[cfg(test)]
83pub(crate) mod tests {
84    use super::*;
85    use std::time::Duration;
86
87    // Used by tests in other modules.
88    mockall::mock! {
89        #[derive(Debug)]
90        pub TokenProvider { }
91
92        #[async_trait::async_trait]
93        impl TokenProvider for TokenProvider {
94            async fn token(&self) -> Result<Token>;
95        }
96    }
97
98    #[test]
99    fn debug() {
100        let expires_at = Instant::now() + Duration::from_secs(3600);
101        let metadata =
102            HashMap::from([("a", "test-only")].map(|(k, v)| (k.to_string(), v.to_string())));
103
104        let token = Token {
105            token: "token-test-only".into(),
106            token_type: "token-type-test-only".into(),
107            expires_at: Some(expires_at),
108            metadata: Some(metadata.clone()),
109        };
110        let got = format!("{token:?}");
111        assert!(!got.contains("token-test-only"), "{got}");
112        assert!(got.contains("token: \"[censored]\""), "{got}");
113        assert!(got.contains("token_type: \"token-type-test-only"), "{got}");
114        assert!(
115            got.contains(&format!("expires_at: Some({expires_at:?}")),
116            "{got}"
117        );
118        assert!(
119            got.contains(&format!("metadata: Some({metadata:?}")),
120            "{got}"
121        );
122    }
123}