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}