mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +00:00
Add auth module
This commit is contained in:
14
crates/auth/Cargo.toml
Normal file
14
crates/auth/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "rustical_auth"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "4.4.0"
|
||||||
|
actix-web-httpauth = "0.8.0"
|
||||||
|
derive_more = "0.99.17"
|
||||||
|
futures-util = "0.3.28"
|
||||||
|
password-auth = "1.0.0"
|
||||||
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
33
crates/auth/src/error.rs
Normal file
33
crates/auth/src/error.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
use actix_web::{http::StatusCode, HttpResponse};
|
||||||
|
use derive_more::{Display, Error};
|
||||||
|
|
||||||
|
#[derive(Debug, Display, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[display(fmt = "Internal server error")]
|
||||||
|
InternalError,
|
||||||
|
#[display(fmt = "Not found")]
|
||||||
|
NotFound,
|
||||||
|
#[display(fmt = "Bad request")]
|
||||||
|
BadRequest,
|
||||||
|
Unauthorized,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl actix_web::error::ResponseError for Error {
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match *self {
|
||||||
|
Self::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Self::NotFound => StatusCode::NOT_FOUND,
|
||||||
|
Self::BadRequest => StatusCode::BAD_REQUEST,
|
||||||
|
Self::Unauthorized => StatusCode::UNAUTHORIZED,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
match self {
|
||||||
|
Error::Unauthorized => HttpResponse::build(self.status_code())
|
||||||
|
.append_header(("WWW-Authenticate", "Basic"))
|
||||||
|
.body(self.to_string()),
|
||||||
|
_ => HttpResponse::build(self.status_code()).body(self.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
crates/auth/src/extractor.rs
Normal file
64
crates/auth/src/extractor.rs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
use actix_web::{dev::Payload, web::Data, FromRequest, HttpRequest};
|
||||||
|
use futures_util::{Future, FutureExt};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use super::{CheckAuthentication, AuthInfo};
|
||||||
|
|
||||||
|
pub struct AuthInfoExtractor<A: CheckAuthentication> {
|
||||||
|
pub inner: AuthInfo,
|
||||||
|
pub _provider_type: PhantomData<A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: CheckAuthentication> From<AuthInfo> for AuthInfoExtractor<T> {
|
||||||
|
fn from(value: AuthInfo) -> Self {
|
||||||
|
AuthInfoExtractor {
|
||||||
|
inner: value,
|
||||||
|
_provider_type: PhantomData::<T>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AuthInfoExtractorFuture<A>
|
||||||
|
where
|
||||||
|
A: CheckAuthentication,
|
||||||
|
{
|
||||||
|
future: Pin<Box<A::Future>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> Future for AuthInfoExtractorFuture<A>
|
||||||
|
where
|
||||||
|
A: CheckAuthentication,
|
||||||
|
{
|
||||||
|
type Output = Result<AuthInfoExtractor<A>, A::Error>;
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Self::Output> {
|
||||||
|
match self.get_mut().future.poll_unpin(cx) {
|
||||||
|
std::task::Poll::Pending => std::task::Poll::Pending,
|
||||||
|
std::task::Poll::Ready(result) => {
|
||||||
|
std::task::Poll::Ready(result.map(|auth_info| auth_info.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> FromRequest for AuthInfoExtractor<A>
|
||||||
|
where
|
||||||
|
A: CheckAuthentication,
|
||||||
|
{
|
||||||
|
type Error = A::Error;
|
||||||
|
type Future = AuthInfoExtractorFuture<A>;
|
||||||
|
|
||||||
|
fn extract(req: &HttpRequest) -> Self::Future {
|
||||||
|
let a = req.app_data::<Data<A>>().unwrap().validate(req);
|
||||||
|
Self::Future {
|
||||||
|
future: Box::pin(a),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
|
||||||
|
Self::extract(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
52
crates/auth/src/htpasswd.rs
Normal file
52
crates/auth/src/htpasswd.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use actix_web::{http::header::Header, HttpRequest};
|
||||||
|
use actix_web_httpauth::headers::authorization::{Authorization, Basic};
|
||||||
|
use futures_util::future::{err, ok, Ready};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::{AuthInfo, CheckAuthentication};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HtpasswdAuth {
|
||||||
|
pub config: HtpasswdAuthConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct HtpasswdAuthUserConfig {
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct HtpasswdAuthConfig {
|
||||||
|
pub users: HashMap<String, HtpasswdAuthUserConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckAuthentication for HtpasswdAuth {
|
||||||
|
type Error = crate::error::Error;
|
||||||
|
type Future = Ready<Result<AuthInfo, Self::Error>>;
|
||||||
|
|
||||||
|
fn validate(&self, req: &HttpRequest) -> Self::Future {
|
||||||
|
if let Ok(auth) = Authorization::<Basic>::parse(req) {
|
||||||
|
let user_id = auth.as_ref().user_id();
|
||||||
|
// Map None to empty password
|
||||||
|
let password = auth.as_ref().password().unwrap_or_default();
|
||||||
|
|
||||||
|
let user_config = if let Some(user_config) = self.config.users.get(user_id) {
|
||||||
|
user_config
|
||||||
|
} else {
|
||||||
|
return err(crate::error::Error::Unauthorized);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = password_auth::verify_password(password, &user_config.password) {
|
||||||
|
dbg!(e);
|
||||||
|
return err(crate::error::Error::Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(AuthInfo {
|
||||||
|
user_id: user_id.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
err(crate::error::Error::Unauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
crates/auth/src/lib.rs
Normal file
46
crates/auth/src/lib.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use actix_web::{HttpRequest, ResponseError};
|
||||||
|
use futures_util::{future::Ready, Future};
|
||||||
|
|
||||||
|
pub use extractor::AuthInfoExtractor;
|
||||||
|
pub use htpasswd::{HtpasswdAuth, HtpasswdAuthConfig};
|
||||||
|
pub use none::NoneAuth;
|
||||||
|
pub mod error;
|
||||||
|
pub mod extractor;
|
||||||
|
pub mod htpasswd;
|
||||||
|
pub mod none;
|
||||||
|
|
||||||
|
pub struct AuthInfo {
|
||||||
|
pub user_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CheckAuthentication: Send + Sync + 'static {
|
||||||
|
type Error: ResponseError;
|
||||||
|
type Future: Future<Output = Result<AuthInfo, Self::Error>>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
|
fn validate(&self, req: &HttpRequest) -> Self::Future
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AuthProvider {
|
||||||
|
Htpasswd(HtpasswdAuth),
|
||||||
|
None(NoneAuth),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckAuthentication for AuthProvider {
|
||||||
|
type Error = crate::error::Error;
|
||||||
|
type Future = Ready<Result<AuthInfo, Self::Error>>;
|
||||||
|
|
||||||
|
fn validate(&self, req: &HttpRequest) -> Self::Future
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Htpasswd(auth) => auth.validate(req),
|
||||||
|
Self::None(auth) => auth.validate(req),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
crates/auth/src/none.rs
Normal file
23
crates/auth/src/none.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
use actix_web::{http::header::Header, HttpRequest};
|
||||||
|
use actix_web_httpauth::headers::authorization::{Authorization, Basic};
|
||||||
|
use futures_util::future::{err, ok, Ready};
|
||||||
|
|
||||||
|
use super::{AuthInfo, CheckAuthentication};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct NoneAuth;
|
||||||
|
|
||||||
|
impl CheckAuthentication for NoneAuth {
|
||||||
|
type Error = crate::error::Error;
|
||||||
|
type Future = Ready<Result<AuthInfo, Self::Error>>;
|
||||||
|
|
||||||
|
fn validate(&self, req: &HttpRequest) -> Self::Future {
|
||||||
|
if let Ok(auth) = Authorization::<Basic>::parse(req) {
|
||||||
|
ok(AuthInfo {
|
||||||
|
user_id: auth.as_ref().user_id().to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
err(crate::error::Error::Unauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user