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.
13    async fn access_token(&self) -> Result<String, Error>;
14
15    /// Asked once when a request is answered with 401. Answer `true` when the next
16    /// `access_token` will hand out renewed credentials, and the request is sent again.
17    async fn refresh(&self) -> bool {
18        false
19    }
20}
21
22/// A fixed token, from an environment variable say. It prints as `[REDACTED]`, so a `{:?}`
23/// of the provider — or of anything holding one — cannot put the token in a log.
24#[derive(Debug, Clone)]
25pub struct StaticTokenProvider {
26    /// The token every request goes out with.
27    pub token: SensitiveString,
28}
29
30impl StaticTokenProvider {
31    /// A provider that hands out `token` and nothing else.
32    pub fn new(token: impl Into<SensitiveString>) -> StaticTokenProvider {
33        StaticTokenProvider {
34            token: token.into(),
35        }
36    }
37}
38
39#[async_trait]
40impl TokenProvider for StaticTokenProvider {
41    async fn access_token(&self) -> Result<String, Error> {
42        if self.token.is_empty() {
43            Err(Error::auth("no token configured"))
44        } else {
45            Ok(self.token.expose().to_string())
46        }
47    }
48}
49
50/// A provider behind an `Arc` is a provider, so one can be shared with the client and
51/// kept by the application — to watch its refreshes, say.
52#[async_trait]
53impl<P: TokenProvider + ?Sized> TokenProvider for std::sync::Arc<P> {
54    async fn access_token(&self) -> Result<String, Error> {
55        (**self).access_token().await
56    }
57
58    async fn refresh(&self) -> bool {
59        (**self).refresh().await
60    }
61}
62
63/// Puts credentials on a request. The default, [`BearerAuth`], sets an `Authorization`
64/// header from a [`TokenProvider`]; anything else can plug in here.
65#[async_trait]
66pub trait AuthStrategy: Send + Sync {
67    /// Puts the credentials on a request about to be sent.
68    async fn authenticate(&self, request: &mut Request<Bytes>) -> Result<(), Error>;
69
70    /// Asked once when a request is answered with 401; see [`TokenProvider::refresh`].
71    async fn refresh(&self) -> bool {
72        false
73    }
74}
75
76/// Sends the token as `Authorization: Bearer`, which is how HEY takes one.
77pub struct BearerAuth<P: TokenProvider> {
78    provider: P,
79}
80
81impl<P: TokenProvider> BearerAuth<P> {
82    /// Bearer authentication over the given provider's tokens.
83    pub fn new(provider: P) -> BearerAuth<P> {
84        BearerAuth { provider }
85    }
86}
87
88#[async_trait]
89impl<P: TokenProvider> AuthStrategy for BearerAuth<P> {
90    async fn authenticate(&self, request: &mut Request<Bytes>) -> Result<(), Error> {
91        let token = self.provider.access_token().await?;
92        let value = HeaderValue::from_str(&format!("Bearer {token}"))
93            .map_err(|_| Error::auth("access token is not a valid header value"))?;
94        request.headers_mut().insert(AUTHORIZATION, value);
95        Ok(())
96    }
97
98    async fn refresh(&self) -> bool {
99        self.provider.refresh().await
100    }
101}