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}