Checkpoint: Migration to axum

This commit is contained in:
Lennart
2025-06-08 14:10:12 +02:00
parent 790c657b08
commit 95889e3df1
60 changed files with 1476 additions and 2205 deletions

View File

@@ -16,12 +16,9 @@ chrono = { workspace = true }
regex = { workspace = true }
lazy_static = { workspace = true }
thiserror = { workspace = true }
actix-web = { workspace = true }
actix-session = { workspace = true }
actix-web-httpauth = { workspace = true }
tracing = { workspace = true }
chrono-tz = { workspace = true }
derive_more = { workspace = true }
derive_more = { workspace = true, features = ["as_ref"] }
rustical_xml.workspace = true
tokio.workspace = true
rand.workspace = true
@@ -29,7 +26,12 @@ uuid.workspace = true
clap.workspace = true
rustical_dav.workspace = true
rustical_ical.workspace = true
axum.workspace = true
http.workspace = true
rrule.workspace = true
headers.workspace = true
tower.workspace = true
futures-core.workspace = true
[dev-dependencies]
rstest = { workspace = true }

View File

@@ -1,103 +1,91 @@
use super::AuthenticationProvider;
use actix_session::Session;
use actix_web::{
FromRequest, HttpMessage,
dev::{Service, ServiceRequest, ServiceResponse, Transform, forward_ready},
http::header::Header,
};
use actix_web_httpauth::headers::authorization::{Authorization, Basic};
use axum::{extract::Request, response::Response};
use futures_core::future::BoxFuture;
use headers::{Authorization, HeaderMapExt, authorization::Basic};
use std::{
future::{Future, Ready, ready},
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use tower::{Layer, Service};
use tracing::{Instrument, info_span};
pub struct AuthenticationMiddleware<AP: AuthenticationProvider> {
pub struct AuthenticationLayer<AP: AuthenticationProvider> {
auth_provider: Arc<AP>,
}
impl<AP: AuthenticationProvider> AuthenticationMiddleware<AP> {
impl<AP: AuthenticationProvider> Clone for AuthenticationLayer<AP> {
fn clone(&self) -> Self {
Self {
auth_provider: self.auth_provider.clone(),
}
}
}
impl<AP: AuthenticationProvider> AuthenticationLayer<AP> {
pub fn new(auth_provider: Arc<AP>) -> Self {
Self { auth_provider }
}
}
impl<AP: AuthenticationProvider, S, B> Transform<S, ServiceRequest> for AuthenticationMiddleware<AP>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
S::Future: 'static,
B: 'static,
AP: 'static,
{
type Error = actix_web::Error;
type Response = ServiceResponse<B>;
type InitError = ();
type Transform = InnerAuthenticationMiddleware<S, AP>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
impl<S, AP: AuthenticationProvider> Layer<S> for AuthenticationLayer<AP> {
type Service = AuthenticationMiddleware<S, AP>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(InnerAuthenticationMiddleware {
service: Arc::new(service),
auth_provider: Arc::clone(&self.auth_provider),
}))
fn layer(&self, inner: S) -> Self::Service {
Self::Service {
inner,
auth_provider: self.auth_provider.clone(),
}
}
}
pub struct InnerAuthenticationMiddleware<S, AP: AuthenticationProvider> {
service: Arc<S>,
pub struct AuthenticationMiddleware<S, AP: AuthenticationProvider> {
inner: S,
auth_provider: Arc<AP>,
}
impl<S, B, AP> Service<ServiceRequest> for InnerAuthenticationMiddleware<S, AP>
impl<S: Clone, AP: AuthenticationProvider> Clone for AuthenticationMiddleware<S, AP> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
auth_provider: self.auth_provider.clone(),
}
}
}
impl<S: Clone, AP: AuthenticationProvider> Service<Request> for AuthenticationMiddleware<S, AP>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
S::Future: 'static,
AP: AuthenticationProvider,
S: Service<Request, Response = Response> + Send + 'static,
S::Future: Send + 'static,
{
type Response = ServiceResponse<B>;
type Error = actix_web::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&self, req: ServiceRequest) -> Self::Future {
let service = Arc::clone(&self.service);
let auth_provider = Arc::clone(&self.auth_provider);
fn call(&mut self, mut request: Request) -> Self::Future {
let auth_header: Option<Authorization<Basic>> = request.headers().typed_get();
let ap = self.auth_provider.clone();
let mut inner = self.inner.clone();
// request.extensions_mut();
Box::pin(async move {
if let Ok(auth) = Authorization::<Basic>::parse(req.request()) {
let user_id = auth.as_ref().user_id();
if let Some(password) = auth.as_ref().password() {
if let Ok(Some(user)) = auth_provider
.validate_app_token(user_id, password)
.instrument(info_span!("validate_user_token"))
.await
{
req.extensions_mut().insert(user);
}
if let Some(auth) = auth_header {
let user_id = auth.username();
let password = auth.password();
if let Ok(Some(user)) = ap
.validate_app_token(user_id, password)
.instrument(info_span!("validate_user_token"))
.await
{
request.extensions_mut().insert(user);
}
}
// Extract user from session cookie
if let Ok(session) = Session::extract(req.request()).await {
match session.get::<String>("user") {
Ok(Some(user_id)) => match auth_provider.get_principal(&user_id).await {
Ok(Some(user)) => {
req.extensions_mut().insert(user);
}
Ok(None) => {}
Err(err) => {
dbg!(err);
}
},
Ok(None) => {}
Err(err) => {
dbg!(err);
}
};
}
service.call(req).await
let response = inner.call(request).await?;
Ok(response)
})
}
}

View File

@@ -1,16 +1,14 @@
use actix_web::{
FromRequest, HttpMessage, HttpResponse, ResponseError,
body::BoxBody,
http::{StatusCode, header},
use axum::{
body::Body,
extract::FromRequestParts,
response::{IntoResponse, Response},
};
use chrono::{DateTime, Utc};
use derive_more::Display;
use http::{HeaderValue, StatusCode, header};
use rustical_xml::ValueSerialize;
use serde::{Deserialize, Serialize};
use std::{
fmt::Display,
future::{Ready, ready},
};
use std::fmt::Display;
use crate::Secret;
@@ -121,33 +119,28 @@ impl rustical_dav::Principal for User {
#[derive(Clone, Debug, Display)]
pub struct UnauthorizedError;
impl ResponseError for UnauthorizedError {
fn status_code(&self) -> actix_web::http::StatusCode {
StatusCode::UNAUTHORIZED
}
fn error_response(&self) -> HttpResponse<BoxBody> {
HttpResponse::build(StatusCode::UNAUTHORIZED)
.insert_header((
header::WWW_AUTHENTICATE,
r#"Basic realm="RustiCal", charset="UTF-8""#,
))
.finish()
impl IntoResponse for UnauthorizedError {
fn into_response(self) -> axum::response::Response {
let mut resp = Response::builder().status(StatusCode::UNAUTHORIZED);
resp.headers_mut().unwrap().insert(
header::WWW_AUTHENTICATE,
HeaderValue::from_static(r#"Basic realm="RustiCal", charset="UTF-8""#),
);
resp.body(Body::empty()).unwrap()
}
}
impl FromRequest for User {
type Error = UnauthorizedError;
type Future = Ready<Result<Self, Self::Error>>;
impl<S: Send + Sync + Clone> FromRequestParts<S> for User {
type Rejection = UnauthorizedError;
fn from_request(
req: &actix_web::HttpRequest,
_payload: &mut actix_web::dev::Payload,
) -> Self::Future {
ready(
req.extensions()
.get::<Self>()
.cloned()
.ok_or(UnauthorizedError),
)
async fn from_request_parts(
parts: &mut http::request::Parts,
_state: &S,
) -> Result<Self, Self::Rejection> {
parts
.extensions
.get::<Self>()
.cloned()
.ok_or(UnauthorizedError)
}
}

View File

@@ -1,4 +1,4 @@
use actix_web::{ResponseError, http::StatusCode};
use http::StatusCode;
#[derive(Debug, thiserror::Error)]
pub enum Error {
@@ -27,8 +27,8 @@ pub enum Error {
IcalError(#[from] rustical_ical::Error),
}
impl ResponseError for Error {
fn status_code(&self) -> actix_web::http::StatusCode {
impl Error {
pub fn status_code(&self) -> StatusCode {
match self {
Self::NotFound => StatusCode::NOT_FOUND,
Self::AlreadyExists => StatusCode::CONFLICT,