mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 19:22:26 +00:00
Checkpoint: Migration to axum
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user