mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 08:12:24 +00:00
completely rebuilt the auth implementation to support OIDC in the future
This commit is contained in:
@@ -3,8 +3,6 @@ name = "rustical_store"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0", features = ["backtrace"] }
|
||||
async-trait = "0.1"
|
||||
@@ -27,3 +25,6 @@ lazy_static = "1.5"
|
||||
rstest = "0.23"
|
||||
rstest_reuse = "0.7"
|
||||
thiserror = "1.0"
|
||||
password-auth = "1.0"
|
||||
actix-web = "4.9"
|
||||
actix-web-httpauth = "0.8"
|
||||
|
||||
90
crates/store/src/auth/middleware.rs
Normal file
90
crates/store/src/auth/middleware.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use actix_web::{
|
||||
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
|
||||
error::ErrorUnauthorized,
|
||||
http::header::Header,
|
||||
HttpMessage,
|
||||
};
|
||||
use actix_web_httpauth::headers::authorization::{Authorization, Basic};
|
||||
use std::{
|
||||
future::{ready, Future, Ready},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use super::AuthenticationProvider;
|
||||
|
||||
pub struct AuthenticationMiddleware<AP: AuthenticationProvider> {
|
||||
auth_provider: Arc<AP>,
|
||||
}
|
||||
|
||||
impl<AP: AuthenticationProvider> AuthenticationMiddleware<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>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ready(Ok(InnerAuthenticationMiddleware {
|
||||
service: Arc::new(service),
|
||||
auth_provider: Arc::clone(&self.auth_provider),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InnerAuthenticationMiddleware<S, AP: AuthenticationProvider> {
|
||||
service: Arc<S>,
|
||||
auth_provider: Arc<AP>,
|
||||
}
|
||||
|
||||
impl<S, B, AP> Service<ServiceRequest> for InnerAuthenticationMiddleware<S, AP>
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
|
||||
S::Future: 'static,
|
||||
AP: AuthenticationProvider + 'static,
|
||||
{
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = actix_web::Error;
|
||||
// type Future = Pin<Box<dyn Future<Output>>>;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||
|
||||
forward_ready!(service);
|
||||
|
||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||
let service = Arc::clone(&self.service);
|
||||
let auth_provider = Arc::clone(&self.auth_provider);
|
||||
|
||||
Box::pin(async move {
|
||||
if let Ok(auth) = Authorization::<Basic>::parse(req.request()) {
|
||||
let user_id = auth.as_ref().user_id();
|
||||
let password = auth
|
||||
.as_ref()
|
||||
.password()
|
||||
.ok_or(ErrorUnauthorized("no password"))?;
|
||||
|
||||
let user = auth_provider
|
||||
.validate_user_token(user_id, password)
|
||||
.await
|
||||
.map_err(|_| ErrorUnauthorized(""))?
|
||||
.ok_or(ErrorUnauthorized(""))?;
|
||||
|
||||
req.extensions_mut().insert(user);
|
||||
service.call(req).await
|
||||
} else {
|
||||
Err(ErrorUnauthorized(""))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
16
crates/store/src/auth/mod.rs
Normal file
16
crates/store/src/auth/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
pub mod middleware;
|
||||
pub mod static_user_store;
|
||||
pub mod user;
|
||||
pub mod user_store;
|
||||
use crate::error::Error;
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AuthenticationProvider {
|
||||
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;
|
||||
}
|
||||
|
||||
pub use middleware::AuthenticationMiddleware;
|
||||
pub use static_user_store::{StaticUserStore, StaticUserStoreConfig};
|
||||
pub use user::User;
|
||||
|
||||
43
crates/store/src/auth/static_user_store.rs
Normal file
43
crates/store/src/auth/static_user_store.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::{auth::User, error::Error};
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::AuthenticationProvider;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct StaticUserStoreConfig {
|
||||
users: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct StaticUserStore {
|
||||
pub users: HashMap<String, User>,
|
||||
}
|
||||
|
||||
impl StaticUserStore {
|
||||
pub fn new(config: StaticUserStoreConfig) -> Self {
|
||||
Self {
|
||||
users: HashMap::from_iter(config.users.into_iter().map(|user| (user.id.clone(), user))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthenticationProvider for StaticUserStore {
|
||||
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
|
||||
let user: User = match self.users.get(user_id) {
|
||||
Some(user) => user.clone(),
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let password = match &user.password {
|
||||
Some(password) => password,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
Ok(password_auth::verify_password(token, password)
|
||||
.map(|()| user)
|
||||
.ok())
|
||||
}
|
||||
}
|
||||
27
crates/store/src/auth/user.rs
Normal file
27
crates/store/src/auth/user.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use actix_web::{error::ErrorUnauthorized, FromRequest, HttpMessage};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::future::{ready, Ready};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub displayname: Option<String>,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
impl FromRequest for User {
|
||||
type Error = actix_web::Error;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
|
||||
fn from_request(
|
||||
req: &actix_web::HttpRequest,
|
||||
_payload: &mut actix_web::dev::Payload,
|
||||
) -> Self::Future {
|
||||
ready(
|
||||
req.extensions()
|
||||
.get::<User>()
|
||||
.cloned()
|
||||
.ok_or(ErrorUnauthorized("")),
|
||||
)
|
||||
}
|
||||
}
|
||||
8
crates/store/src/auth/user_store.rs
Normal file
8
crates/store/src/auth/user_store.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use crate::{auth::User, error::Error};
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserStore: Send + Sync + 'static {
|
||||
async fn get_user(&self, id: &str) -> Result<Option<User>, Error>;
|
||||
async fn put_user(&self, user: User) -> Result<(), Error>;
|
||||
}
|
||||
@@ -5,4 +5,4 @@ pub mod sqlite_store;
|
||||
pub mod timestamp;
|
||||
pub use calendar_store::CalendarStore;
|
||||
pub use error::Error;
|
||||
|
||||
pub mod auth;
|
||||
|
||||
Reference in New Issue
Block a user