Skip to main content

aep_tower/
authentication.rs

1use std::{
2    sync::Arc,
3    task::{Context, Poll},
4};
5
6use aep_service::{ProtectedResourceAuthentication, ProtectedResourceRequest, Service};
7use futures::{FutureExt as _, future::BoxFuture};
8use http::{Request, Response};
9use http_body_util::{Either, Full};
10use tower::{Layer, Service as TowerService};
11use url::Url;
12
13use crate::{
14    TowerError,
15    response::{internal_error, service_response},
16    url::{request_url, validate_origin},
17};
18
19#[derive(Clone, Debug)]
20pub struct AuthenticationOptions {
21    pub allow_insecure_loopback: bool,
22    pub origin: Url,
23}
24
25impl AuthenticationOptions {
26    pub fn new(origin: Url) -> Self {
27        Self {
28            allow_insecure_loopback: false,
29            origin,
30        }
31    }
32}
33
34#[derive(Clone)]
35pub struct AuthenticationLayer {
36    options: AuthenticationOptions,
37    service: Arc<Service>,
38}
39
40impl AuthenticationLayer {
41    pub fn new(service: Arc<Service>, options: AuthenticationOptions) -> Result<Self, TowerError> {
42        validate_origin(&options.origin, options.allow_insecure_loopback)?;
43        Ok(Self { options, service })
44    }
45}
46
47impl<S> Layer<S> for AuthenticationLayer {
48    type Service = AuthenticationService<S>;
49
50    fn layer(&self, inner: S) -> Self::Service {
51        AuthenticationService {
52            inner,
53            options: self.options.clone(),
54            service: self.service.clone(),
55        }
56    }
57}
58
59#[derive(Clone)]
60pub struct AuthenticationService<S> {
61    inner: S,
62    options: AuthenticationOptions,
63    service: Arc<Service>,
64}
65
66impl<S, RequestBody, ResponseBody> TowerService<Request<RequestBody>> for AuthenticationService<S>
67where
68    S: TowerService<Request<RequestBody>, Response = Response<ResponseBody>>
69        + Clone
70        + Send
71        + 'static,
72    S::Future: Send + 'static,
73    S::Error: Send + 'static,
74    RequestBody: Send + 'static,
75    ResponseBody: Send + 'static,
76{
77    type Response = Response<Either<Full<bytes::Bytes>, ResponseBody>>;
78    type Error = S::Error;
79    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
80
81    fn poll_ready(&mut self, context: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
82        self.inner.poll_ready(context)
83    }
84
85    fn call(&mut self, mut request: Request<RequestBody>) -> Self::Future {
86        let clone = self.inner.clone();
87        let mut inner = std::mem::replace(&mut self.inner, clone);
88        let options = self.options.clone();
89        let service = self.service.clone();
90        async move {
91            let authentication = match build_protected_resource_request(&options, &request) {
92                Ok(request) => service
93                    .authenticate_protected_resource(request)
94                    .await
95                    .map_err(TowerError::from),
96                Err(error) => Err(error),
97            };
98            match authentication {
99                Ok(ProtectedResourceAuthentication::Authenticated(principal)) => {
100                    request.extensions_mut().insert(principal);
101                    let response = inner.call(request).await?;
102                    Ok(response.map(Either::Right))
103                }
104                Ok(ProtectedResourceAuthentication::Rejected(response)) => {
105                    Ok(service_response(response)
106                        .unwrap_or_else(|_| internal_error())
107                        .map(Either::Left))
108                }
109                Err(_) => Ok(internal_error().map(Either::Left)),
110            }
111        }
112        .boxed()
113    }
114}
115
116pub fn protected_resource_request<B>(
117    options: &AuthenticationOptions,
118    request: &Request<B>,
119) -> Result<ProtectedResourceRequest, TowerError> {
120    validate_origin(&options.origin, options.allow_insecure_loopback)?;
121    build_protected_resource_request(options, request)
122}
123
124fn build_protected_resource_request<B>(
125    options: &AuthenticationOptions,
126    request: &Request<B>,
127) -> Result<ProtectedResourceRequest, TowerError> {
128    Ok(ProtectedResourceRequest {
129        headers: request.headers().clone(),
130        method: request.method().clone(),
131        url: request_url(&options.origin, request.uri())?,
132    })
133}