Skip to main content

snap_control/server/
auth.rs

1// Copyright 2025 Anapaya Systems
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! SNAP control plane API authentication middleware.
15
16use std::{
17    fmt::Display,
18    future::Future,
19    pin::Pin,
20    sync::Arc,
21    task::{Context, Poll},
22};
23
24use axum::body::Body;
25use http::{Request, Response};
26use thiserror::Error;
27use tower::{Layer, Service};
28
29use crate::server::token_verifier::SnapTokenVerifier;
30
31#[derive(Clone)]
32pub(crate) struct AuthMiddlewareLayer {
33    verifier: Arc<SnapTokenVerifier>,
34}
35
36impl AuthMiddlewareLayer {
37    pub(crate) fn new(verifier: SnapTokenVerifier) -> Self {
38        Self {
39            verifier: Arc::new(verifier),
40        }
41    }
42}
43
44impl<S> Layer<S> for AuthMiddlewareLayer {
45    type Service = AuthMiddleware<S>;
46
47    fn layer(&self, inner: S) -> Self::Service {
48        AuthMiddleware::new(inner, self.verifier.clone())
49    }
50}
51
52#[derive(Clone)]
53pub(crate) struct AuthMiddleware<S> {
54    inner: S,
55    verifier: Arc<SnapTokenVerifier>,
56}
57
58impl<S> AuthMiddleware<S> {
59    pub(crate) fn new(inner: S, verifier: Arc<SnapTokenVerifier>) -> Self {
60        Self { inner, verifier }
61    }
62}
63
64impl<S> Service<Request<Body>> for AuthMiddleware<S>
65where
66    S: Service<Request<Body>, Response = Response<Body>> + Send + Clone + 'static,
67    S::Future: Send + 'static,
68{
69    type Response = Response<Body>;
70    type Error = S::Error;
71    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
72
73    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
74        self.inner.poll_ready(cx)
75    }
76
77    fn call(&mut self, mut request: Request<Body>) -> Self::Future {
78        let token = match extract_bearer_token(&request) {
79            Ok(token) => token,
80            Err(err) => {
81                tracing::debug!(%err, "Extract bearer token");
82                return Box::pin(async { Ok(build_unauthorized_response(err)) });
83            }
84        };
85
86        let verifier = self.verifier.clone();
87        let mut inner = self.inner.clone();
88        Box::pin(async move {
89            match verifier.verify(&token).await {
90                Ok(token_claims) => {
91                    request.extensions_mut().insert(token_claims);
92                    inner.call(request).await
93                }
94                Err(err) => {
95                    tracing::debug!(%err, "Invalid Token");
96                    Ok(build_unauthorized_response(err))
97                }
98            }
99        })
100    }
101}
102
103fn build_unauthorized_response<E: Display>(err: E) -> Response<Body> {
104    Response::builder()
105        .status(http::StatusCode::UNAUTHORIZED)
106        .body(Body::from(format!("SNAP Token validation failed: {err}")))
107        .expect("no fail")
108}
109
110/// Extracts the bearer token from the `Authorization` header of the request.
111pub fn extract_bearer_token(req: &Request<Body>) -> Result<String, ExtractBearerTokenError> {
112    let auth_header = match req.headers().get("authorization") {
113        Some(header) => header,
114        None => return Err(ExtractBearerTokenError::AuthHeaderMissing),
115    };
116
117    let auth_str = match auth_header.to_str() {
118        Ok(str) => str,
119        Err(_) => return Err(ExtractBearerTokenError::AuthHeaderInvalidUtf8),
120    };
121
122    match auth_str.strip_prefix("Bearer ") {
123        Some(token) => Ok(token.to_string()),
124        None => Err(ExtractBearerTokenError::AuthHeaderNotBearer),
125    }
126}
127
128/// Bearer token extraction error.
129#[derive(Debug, Error)]
130pub enum ExtractBearerTokenError {
131    /// Authorization header is missing.
132    #[error("authorization header is missing")]
133    AuthHeaderMissing,
134    /// Authorization header is not valid UTF-8.
135    #[error("authorization header is not valid UTF-8")]
136    AuthHeaderInvalidUtf8,
137    /// Authorization header is not a Bearer token.
138    #[error("authorization header is not a bearer token")]
139    AuthHeaderNotBearer,
140}