tame_oauth/token.rs
1use crate::{error::Error, token_cache::CacheableToken};
2use std::time::SystemTime;
3
4/// Represents a access token as returned by `OAuth2` servers.
5///
6/// * It is produced by all authentication flows.
7/// * It authenticates certain operations, and must be refreshed once it has
8/// reached its expiry date.
9///
10/// The type is tuned to be suitable for direct de-serialization from server
11/// replies, as well as for serialization for later reuse. This is the reason
12/// for the two fields dealing with expiry - once in relative in and once in
13/// absolute terms.
14#[derive(Clone, PartialEq, Eq, Debug, serde::Deserialize)]
15pub struct Token {
16 /// used when authenticating calls to oauth2 enabled services.
17 pub access_token: String,
18 /// used to refresh an expired access_token.
19 pub refresh_token: String,
20 /// The token type as string - usually 'Bearer'.
21 pub token_type: String,
22 /// access_token will expire after this amount of time.
23 /// Prefer using expiry_date()
24 pub expires_in: Option<i64>,
25 /// timestamp is seconds since epoch indicating when the token will expire
26 /// in absolute terms.
27 pub expires_in_timestamp: Option<SystemTime>,
28}
29
30impl CacheableToken for Token {
31 /// Returns true if we are expired.
32 #[inline]
33 fn has_expired(&self) -> bool {
34 if self.access_token.is_empty() {
35 return true;
36 }
37
38 let expiry = self.expires_in_timestamp.unwrap_or_else(SystemTime::now);
39
40 expiry <= SystemTime::now()
41 }
42}
43
44#[derive(Debug)]
45pub enum RequestReason {
46 /// An existing token has expired
47 Expired,
48 /// The requested scopes or audience have never been seen before
49 ParametersChanged,
50}
51
52/// Either a valid token, or an HTTP request that can be used to acquire one
53#[derive(Debug)]
54pub enum TokenOrRequest {
55 /// A valid token that can be supplied in an API request
56 Token(Token),
57 Request {
58 /// The parts of an HTTP request that must be sent to acquire the token,
59 /// in the client of your choice
60 request: http::Request<Vec<u8>>,
61 /// The reason we need to retrieve a new token
62 reason: RequestReason,
63 /// An opaque hash of the unique parameters for which the request was constructed
64 scope_hash: u64,
65 },
66}
67
68/// A `TokenProvider` has a single method to implement `get_token_with_subject`.
69/// Implementations are free to perform caching or always return a `Request` in
70/// the `TokenOrRequest`.
71pub trait TokenProvider {
72 /// Attempts to retrieve a token that can be used in an API request, if we
73 /// haven't already retrieved a token for the specified scopes, or the token
74 /// has expired, an HTTP request is returned that can be used to retrieve a
75 /// token.
76 ///
77 /// Note that the scopes are not sorted or in any other way manipulated, so
78 /// any modifications to them will require a new token to be requested.
79 #[inline]
80 fn get_token<'a, S, I>(&self, scopes: I) -> Result<TokenOrRequest, Error>
81 where
82 S: AsRef<str> + 'a,
83 I: IntoIterator<Item = &'a S> + Clone,
84 {
85 self.get_token_with_subject::<S, I, String>(None, scopes)
86 }
87
88 /// Like [`TokenProvider::get_token`], but allows the JWT
89 /// ["subject"](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields)
90 /// to be passed in.
91 fn get_token_with_subject<'a, S, I, T>(
92 &self,
93 subject: Option<T>,
94 scopes: I,
95 ) -> Result<TokenOrRequest, Error>
96 where
97 S: AsRef<str> + 'a,
98 I: IntoIterator<Item = &'a S> + Clone,
99 T: Into<String>;
100
101 /// Once a response has been received for a token request, call this method
102 /// to deserialize the token (and potentially store it in a local cache for
103 /// reuse until it expires).
104 fn parse_token_response<S>(
105 &self,
106 hash: u64,
107 response: http::Response<S>,
108 ) -> Result<Token, Error>
109 where
110 S: AsRef<[u8]>;
111}
112
113impl std::convert::TryInto<http::header::HeaderValue> for Token {
114 type Error = crate::Error;
115
116 fn try_into(self) -> Result<http::header::HeaderValue, crate::Error> {
117 let auth_header_val = format!("{} {}", self.token_type, self.access_token);
118 http::header::HeaderValue::from_str(&auth_header_val)
119 .map_err(|e| crate::Error::from(http::Error::from(e)))
120 }
121}