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 = service
92                .authenticate_protected_resource(build_protected_resource_request(
93                    &options, &request,
94                ))
95                .await
96                .map_err(TowerError::from);
97            match authentication {
98                Ok(ProtectedResourceAuthentication::Authenticated(principal)) => {
99                    request.extensions_mut().insert(principal);
100                    let response = inner.call(request).await?;
101                    Ok(response.map(Either::Right))
102                }
103                Ok(ProtectedResourceAuthentication::Rejected(response)) => {
104                    Ok(service_response(response)
105                        .unwrap_or_else(|_| internal_error())
106                        .map(Either::Left))
107                }
108                Err(_) => Ok(internal_error().map(Either::Left)),
109            }
110        }
111        .boxed()
112    }
113}
114
115pub fn protected_resource_request<B>(
116    options: &AuthenticationOptions,
117    request: &Request<B>,
118) -> Result<ProtectedResourceRequest, TowerError> {
119    validate_origin(&options.origin, options.allow_insecure_loopback)?;
120    Ok(build_protected_resource_request(options, request))
121}
122
123fn build_protected_resource_request<B>(
124    options: &AuthenticationOptions,
125    request: &Request<B>,
126) -> ProtectedResourceRequest {
127    ProtectedResourceRequest {
128        headers: request.headers().clone(),
129        method: request.method().clone(),
130        url: request_url(&options.origin, request.uri()),
131    }
132}