Skip to main content

hey_sdk/
auth.rs

1use async_trait::async_trait;
2use bytes::Bytes;
3
4use crate::error::Error;
5use crate::http::header::AUTHORIZATION;
6use crate::http::{HeaderValue, Request};
7use crate::types::SensitiveString;
8
9/// Supplies the access token each request goes out with.
10#[async_trait]
11pub trait TokenProvider: Send + Sync {
12    /// The token to send, asked for on every request. A provider that renews of its own
13    /// accord — handing over a new token ahead of the old one's expiry, as OAuth libraries
14    /// do — need do nothing more: the client takes a token other than the one it last
15    /// signed with for the renewal it is, so a 401 on the old token is answered by resending
16    /// with the new one, and [`refresh`](TokenProvider::refresh) is not asked.
17    async fn access_token(&self) -> Result<String, Error>;
18
19    /// Asked once when a request is answered with 401. Answer `true` when the next
20    /// `access_token` will hand out renewed credentials, and the request is sent again.
21    /// Either answer is for every request signed with the credentials that earned the 401,
22    /// not only the one that asked: a `true` resends them all on the new credentials, and
23    /// a `false` fails them all, so an outage at the token's issuer costs one call per set
24    /// of credentials. A request signed after a `false` asks again. Not asked for a 401 on
25    /// a token this provider has already replaced — one `access_token` no longer hands out,
26    /// whether or not the replacement has signed anything yet — since that is a renewal
27    /// already made: the request is resent with the replacement, and a rotating refresh
28    /// token the provider was just issued is not spent again over the top of it.
29    async fn refresh(&self) -> bool {
30        false
31    }
32}
33
34/// A fixed token, from an environment variable say. It prints as `[REDACTED]`, so a `{:?}`
35/// of the provider — or of anything holding one — cannot put the token in a log.
36#[derive(Debug, Clone)]
37pub struct StaticTokenProvider {
38    /// The token every request goes out with.
39    pub token: SensitiveString,
40}
41
42impl StaticTokenProvider {
43    /// A provider that hands out `token` and nothing else.
44    pub fn new(token: impl Into<SensitiveString>) -> StaticTokenProvider {
45        StaticTokenProvider {
46            token: token.into(),
47        }
48    }
49}
50
51#[async_trait]
52impl TokenProvider for StaticTokenProvider {
53    async fn access_token(&self) -> Result<String, Error> {
54        if self.token.is_empty() {
55            Err(Error::auth("no token configured"))
56        } else {
57            Ok(self.token.expose().to_string())
58        }
59    }
60}
61
62/// A provider behind an `Arc` is a provider, so one can be shared with the client and
63/// kept by the application — to watch its refreshes, say.
64#[async_trait]
65impl<P: TokenProvider + ?Sized> TokenProvider for std::sync::Arc<P> {
66    async fn access_token(&self) -> Result<String, Error> {
67        (**self).access_token().await
68    }
69
70    async fn refresh(&self) -> bool {
71        (**self).refresh().await
72    }
73}
74
75/// Puts credentials on a request. The default, [`BearerAuth`], sets an `Authorization`
76/// header from a [`TokenProvider`]; anything else can plug in here.
77#[async_trait]
78pub trait AuthStrategy: Send + Sync {
79    /// Puts the credentials on a request about to be sent.
80    async fn authenticate(&self, request: &mut Request<Bytes>) -> Result<(), Error>;
81
82    /// Asked once when a request is answered with 401; see [`TokenProvider::refresh`].
83    async fn refresh(&self) -> bool {
84        false
85    }
86}
87
88/// Sends the token as `Authorization: Bearer`, which is how HEY takes one.
89pub struct BearerAuth<P: TokenProvider> {
90    provider: P,
91}
92
93impl<P: TokenProvider> BearerAuth<P> {
94    /// Bearer authentication over the given provider's tokens.
95    pub fn new(provider: P) -> BearerAuth<P> {
96        BearerAuth { provider }
97    }
98}
99
100#[async_trait]
101impl<P: TokenProvider> AuthStrategy for BearerAuth<P> {
102    async fn authenticate(&self, request: &mut Request<Bytes>) -> Result<(), Error> {
103        let token = self.provider.access_token().await?;
104        let value = HeaderValue::from_str(&format!("Bearer {token}"))
105            .map_err(|_| Error::auth("access token is not a valid header value"))?;
106        request.headers_mut().insert(AUTHORIZATION, value);
107        Ok(())
108    }
109
110    async fn refresh(&self) -> bool {
111        self.provider.refresh().await
112    }
113}