1use async_trait::async_trait;
6use bytes::Bytes;
7use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
8
9use crate::error::Error;
10use crate::http::header::{AUTHORIZATION, COOKIE};
11use crate::http::{HeaderValue, Request};
12use crate::types::SensitiveString;
13
14pub const SESSION_COOKIE: &str = "session_token";
16
17const COOKIE_VALUE: &AsciiSet = &NON_ALPHANUMERIC
21 .remove(b'-')
22 .remove(b'_')
23 .remove(b'.')
24 .remove(b'~');
25
26pub(crate) fn cookie_header(name: &str, value: &str) -> Result<HeaderValue, Error> {
29 let encoded = utf8_percent_encode(value, COOKIE_VALUE);
30 let mut header = HeaderValue::from_str(&format!("{name}={encoded}"))
31 .map_err(|_| Error::auth(format!("{name} is not a valid cookie value")))?;
32 header.set_sensitive(true);
33 Ok(header)
34}
35
36#[async_trait]
38pub trait TokenProvider: Send + Sync {
39 async fn access_token(&self) -> Result<String, Error>;
41}
42
43#[derive(Debug, Clone)]
46pub struct StaticTokenProvider {
47 pub token: SensitiveString,
49}
50
51impl StaticTokenProvider {
52 pub fn new(token: impl Into<SensitiveString>) -> StaticTokenProvider {
54 StaticTokenProvider {
55 token: token.into(),
56 }
57 }
58}
59
60#[async_trait]
61impl TokenProvider for StaticTokenProvider {
62 async fn access_token(&self) -> Result<String, Error> {
63 if self.token.is_empty() {
64 Err(Error::auth("no token configured"))
65 } else {
66 Ok(self.token.expose().to_string())
67 }
68 }
69}
70
71#[async_trait]
74pub trait AuthStrategy: Send + Sync {
75 async fn authenticate(&self, request: &mut Request<Bytes>) -> Result<(), Error>;
77}
78
79pub struct BearerAuth<P: TokenProvider> {
81 provider: P,
82}
83
84impl<P: TokenProvider> BearerAuth<P> {
85 pub fn new(provider: P) -> BearerAuth<P> {
87 BearerAuth { provider }
88 }
89}
90
91#[async_trait]
92impl<P: TokenProvider> AuthStrategy for BearerAuth<P> {
93 async fn authenticate(&self, request: &mut Request<Bytes>) -> Result<(), Error> {
94 let token = self.provider.access_token().await?;
95 let mut value = HeaderValue::from_str(&format!("Bearer {token}"))
96 .map_err(|_| Error::auth("access token is not a valid header value"))?;
97 value.set_sensitive(true);
98 request.headers_mut().insert(AUTHORIZATION, value);
99 Ok(())
100 }
101}
102
103pub struct CookieAuth<P: TokenProvider> {
105 provider: P,
106}
107
108impl<P: TokenProvider> CookieAuth<P> {
109 pub fn new(provider: P) -> CookieAuth<P> {
111 CookieAuth { provider }
112 }
113}
114
115#[async_trait]
116impl<P: TokenProvider> AuthStrategy for CookieAuth<P> {
117 async fn authenticate(&self, request: &mut Request<Bytes>) -> Result<(), Error> {
118 let token = self.provider.access_token().await?;
119 request
120 .headers_mut()
121 .insert(COOKIE, cookie_header(SESSION_COOKIE, &token)?);
122 Ok(())
123 }
124}
125
126#[derive(Debug, Clone, Copy, Default)]
129pub struct NoAuth;
130
131#[async_trait]
132impl AuthStrategy for NoAuth {
133 async fn authenticate(&self, _request: &mut Request<Bytes>) -> Result<(), Error> {
134 Ok(())
135 }
136}