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
15use crate::Result;
16use http::Extensions;
17use std::collections::HashMap;
18use tokio::time::Instant;
19
20/// Represents an auth token.
21#[derive(Clone, PartialEq)]
22pub(crate) struct Token {
23 /// The actual token string.
24 ///
25 /// This is the value used in `Authorization:` header.
26 pub token: String,
27
28 /// The type of the token.
29 ///
30 /// The most common type is `"Bearer"` but other types may appear in the
31 /// future.
32 pub token_type: String,
33
34 /// The instant at which the token expires.
35 ///
36 /// If `None`, the token does not expire.
37 ///
38 /// Note that the `Instant` is not valid across processes. It is
39 /// recommended to let the authentication library refresh tokens within a
40 /// process instead of handling expirations yourself. If you do need to
41 /// copy an expiration across processes, consider converting it to a
42 /// `time::OffsetDateTime` first:
43 ///
44 /// ```
45 /// # let expires_at = Some(std::time::Instant::now());
46 /// expires_at.map(|i| time::OffsetDateTime::now_utc() + (i - std::time::Instant::now()));
47 /// ```
48 pub expires_at: Option<Instant>,
49
50 /// Optional metadata associated with the token.
51 ///
52 /// This might include information like granted scopes or other claims.
53 pub metadata: Option<HashMap<String, String>>,
54}
55
56impl std::fmt::Debug for Token {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct("Token")
59 .field("token", &"[censored]")
60 .field("token_type", &self.token_type)
61 .field("expires_at", &self.expires_at)
62 .field("metadata", &self.metadata)
63 .finish()
64 }
65}
66
67#[async_trait::async_trait]
68pub(crate) trait TokenProvider: std::fmt::Debug + Send + Sync {
69 async fn token(&self) -> Result<Token>;
70}
71
72#[async_trait::async_trait]
73pub(crate) trait CachedTokenProvider: std::fmt::Debug + Send + Sync {
74 async fn token(&self, extensions: Extensions) -> Result<Token>;
75}
76
77#[cfg(test)]
78pub(crate) mod test {
79 use super::*;
80 use std::time::Duration;
81
82 // Used by tests in other modules.
83 mockall::mock! {
84 #[derive(Debug)]
85 pub TokenProvider { }
86
87 #[async_trait::async_trait]
88 impl TokenProvider for TokenProvider {
89 async fn token(&self) -> Result<Token>;
90 }
91 }
92
93 #[test]
94 fn debug() {
95 let expires_at = Instant::now() + Duration::from_secs(3600);
96 let metadata =
97 HashMap::from([("a", "test-only")].map(|(k, v)| (k.to_string(), v.to_string())));
98
99 let token = Token {
100 token: "token-test-only".into(),
101 token_type: "token-type-test-only".into(),
102 expires_at: Some(expires_at),
103 metadata: Some(metadata.clone()),
104 };
105 let got = format!("{token:?}");
106 assert!(!got.contains("token-test-only"), "{got}");
107 assert!(got.contains("token: \"[censored]\""), "{got}");
108 assert!(got.contains("token_type: \"token-type-test-only"), "{got}");
109 assert!(
110 got.contains(&format!("expires_at: Some({expires_at:?}")),
111 "{got}"
112 );
113 assert!(
114 got.contains(&format!("metadata: Some({metadata:?}")),
115 "{got}"
116 );
117 }
118}