qrush_engine/services/
basic_auth_service.rs

1// src/services/basic_auth_service.rs
2//
3// Optional Actix middleware for QRush metrics/routes.
4// Enabled when you embed qrush-engine routes into an Actix app.
5// (qrush-engine itself does not require Actix to run.)
6
7use actix_web::{
8    body::EitherBody,
9    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
10    Error,
11};
12use base64::engine::general_purpose::STANDARD;
13use base64::Engine;
14use futures_util::future::{ready, Either, Ready};
15use std::future::Ready as StdReady;
16
17use crate::config::get_basic_auth;
18
19pub struct BasicAuthMiddleware;
20
21impl<S, B> Transform<S, ServiceRequest> for BasicAuthMiddleware
22where
23    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
24    B: actix_web::body::MessageBody + 'static,
25{
26    type Response = ServiceResponse<EitherBody<B>>;
27    type Error = Error;
28    type Transform = BasicAuthMiddlewareService<S>;
29    type InitError = ();
30    type Future = StdReady<Result<Self::Transform, Self::InitError>>;
31
32    fn new_transform(&self, service: S) -> Self::Future {
33        std::future::ready(Ok(BasicAuthMiddlewareService { service }))
34    }
35}
36
37pub struct BasicAuthMiddlewareService<S> {
38    service: S,
39}
40
41impl<S, B> Service<ServiceRequest> for BasicAuthMiddlewareService<S>
42where
43    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
44    B: actix_web::body::MessageBody + 'static,
45{
46    type Response = ServiceResponse<EitherBody<B>>;
47    type Error = Error;
48    type Future = Either<
49        futures_util::future::LocalBoxFuture<'static, Result<Self::Response, Self::Error>>,
50        Ready<Result<Self::Response, Self::Error>>,
51    >;
52
53    forward_ready!(service);
54
55    fn call(&self, req: ServiceRequest) -> Self::Future {
56        // If no auth configured, allow.
57        let Some(cfg) = get_basic_auth() else {
58            let fut = self.service.call(req);
59            return Either::Left(Box::pin(async move {
60                let res = fut.await?;
61                Ok(res.map_into_left_body())
62            }));
63        };
64
65        // Extract "Authorization: Basic base64(user:pass)"
66        let auth_hdr = req
67            .headers()
68            .get(actix_web::http::header::AUTHORIZATION)
69            .and_then(|v| v.to_str().ok())
70            .unwrap_or("");
71
72        if let Some(encoded) = auth_hdr.strip_prefix("Basic ").map(str::trim) {
73            if let Ok(decoded) = STANDARD.decode(encoded) {
74                if let Ok(pair) = String::from_utf8(decoded) {
75                    let mut it = pair.splitn(2, ':');
76                    let user = it.next().unwrap_or("");
77                    let pass = it.next().unwrap_or("");
78                    if user == cfg.username && pass == cfg.password {
79                        let fut = self.service.call(req);
80                        return Either::Left(Box::pin(async move {
81                            let res = fut.await?;
82                            Ok(res.map_into_left_body())
83                        }));
84                    }
85                }
86            }
87        }
88
89        let res = actix_web::HttpResponse::Unauthorized()
90            .finish()
91            .map_into_right_body();
92
93        Either::Right(ready(Ok(req.into_response(res))))
94    }
95}