diff options
author | metamuffin <metamuffin@disroot.org> | 2024-05-30 00:09:11 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-05-30 00:09:11 +0200 |
commit | 532cc431d1c5ca1ffcf429a4ccb94edc7848fe7a (patch) | |
tree | c4422c4d54e01f63bae391cd95788cad74f59fbb /src/filters/auth | |
parent | 8b39940a58c28bc1bbe291eb5229e9ce1444e33c (diff) | |
download | gnix-532cc431d1c5ca1ffcf429a4ccb94edc7848fe7a.tar gnix-532cc431d1c5ca1ffcf429a4ccb94edc7848fe7a.tar.bz2 gnix-532cc431d1c5ca1ffcf429a4ccb94edc7848fe7a.tar.zst |
rename filters dir
Diffstat (limited to 'src/filters/auth')
-rw-r--r-- | src/filters/auth/basic.rs | 68 | ||||
-rw-r--r-- | src/filters/auth/cookie.rs | 182 | ||||
-rw-r--r-- | src/filters/auth/login.html | 35 | ||||
-rw-r--r-- | src/filters/auth/mod.rs | 90 |
4 files changed, 0 insertions, 375 deletions
diff --git a/src/filters/auth/basic.rs b/src/filters/auth/basic.rs deleted file mode 100644 index a7a74c8..0000000 --- a/src/filters/auth/basic.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::{ - config::DynNode, - error::ServiceError, - filters::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}, -}; -use base64::Engine; -use futures::Future; -use http_body_util::{combinators::BoxBody, BodyExt}; -use hyper::{ - header::{HeaderValue, AUTHORIZATION, WWW_AUTHENTICATE}, - Response, StatusCode, -}; -use log::debug; -use serde::Deserialize; -use serde_yaml::Value; -use std::{collections::HashSet, pin::Pin, sync::Arc}; - -pub struct HttpBasicAuthKind; -impl NodeKind for HttpBasicAuthKind { - fn name(&self) -> &'static str { - "http_basic_auth" - } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yaml::from_value::<HttpBasicAuth>(config)?)) - } -} - -#[derive(Deserialize)] -pub struct HttpBasicAuth { - realm: String, - valid: HashSet<String>, - next: DynNode, -} - -impl Node for HttpBasicAuth { - fn handle<'a>( - &'a self, - context: &'a mut NodeContext, - request: NodeRequest, - ) -> Pin<Box<dyn Future<Output = Result<NodeResponse, ServiceError>> + Send + Sync + 'a>> { - Box::pin(async move { - if let Some(auth) = request.headers().get(AUTHORIZATION) { - let k = auth - .as_bytes() - .strip_prefix(b"Basic ") - .ok_or(ServiceError::BadAuth)?; - let k = base64::engine::general_purpose::STANDARD.decode(k)?; - let k = String::from_utf8(k)?; - if self.valid.contains(&k) { - debug!("valid auth"); - return self.next.handle(context, request).await; - } else { - debug!("invalid auth"); - } - } - debug!("unauthorized; sending auth challenge"); - let mut r = Response::new(BoxBody::<_, ServiceError>::new( - String::new().map_err(|_| unreachable!()), - )); - *r.status_mut() = StatusCode::UNAUTHORIZED; - r.headers_mut().insert( - WWW_AUTHENTICATE, - HeaderValue::from_str(&format!("Basic realm=\"{}\"", self.realm)).unwrap(), - ); - Ok(r) - }) - } -} diff --git a/src/filters/auth/cookie.rs b/src/filters/auth/cookie.rs deleted file mode 100644 index 620911d..0000000 --- a/src/filters/auth/cookie.rs +++ /dev/null @@ -1,182 +0,0 @@ -use crate::{ - config::{return_true, DynNode}, - error::ServiceError, - filters::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}, -}; -use aes_gcm_siv::{ - aead::{Aead, Payload}, - Nonce, -}; -use base64::Engine; -use futures::Future; -use headers::{Cookie, HeaderMapExt}; -use http_body_util::{combinators::BoxBody, BodyExt}; -use hyper::{ - header::{HeaderValue, LOCATION, REFERER, SET_COOKIE}, - Method, Response, StatusCode, -}; -use log::debug; -use percent_encoding::{percent_decode_str, percent_encode, NON_ALPHANUMERIC}; -use rand::random; -use serde::Deserialize; -use serde_yaml::Value; -use std::fmt::Write; -use std::{pin::Pin, sync::Arc, time::SystemTime}; - -use super::Credentials; - -pub struct CookieAuthKind; -impl NodeKind for CookieAuthKind { - fn name(&self) -> &'static str { - "cookie_auth" - } - fn instanciate(&self, config: Value) -> anyhow::Result<Arc<dyn Node>> { - Ok(Arc::new(serde_yaml::from_value::<CookieAuth>(config)?)) - } -} - -#[derive(Deserialize)] -pub struct CookieAuth { - users: Credentials, - expire: Option<u64>, - #[serde(default = "return_true")] - secure: bool, - next: DynNode, - fail: DynNode, -} - -impl Node for CookieAuth { - fn handle<'a>( - &'a self, - context: &'a mut NodeContext, - request: NodeRequest, - ) -> Pin<Box<dyn Future<Output = Result<NodeResponse, ServiceError>> + Send + Sync + 'a>> { - Box::pin(async move { - if request.method() == Method::POST && request.uri().path() == "/_gnix_login" { - let referrer = request.headers().get(REFERER).cloned(); - let d = request - .into_body() - .collect() - .await - .map_err(|_| todo!()) - .unwrap(); - let d = String::from_utf8(d.to_bytes().to_vec()).unwrap(); - - // TODO proper parser - let mut username = "user"; - let mut password = ""; - for kv in d.split("&") { - let (key, value) = kv.split_once("=").ok_or(ServiceError::BadAuth)?; - match key { - "username" => username = value, - "password" => password = value, - _ => (), - } - } - let mut r = Response::new(BoxBody::<_, ServiceError>::new( - String::new().clone().map_err(|_| unreachable!()), - )); - *r.status_mut() = StatusCode::FOUND; - debug!("login attempt for {username:?}"); - if self.users.authentificate(username, password) { - debug!("login success"); - let nonce = [(); 12].map(|_| random::<u8>()); - let plaintext = unix_seconds().to_le_bytes(); - let mut ciphertext = context - .state - .crypto_key - .encrypt( - Nonce::from_slice(&nonce), - Payload { - msg: &plaintext, - aad: username.as_bytes(), - }, - ) - .unwrap(); - - ciphertext.extend(nonce); - let auth = base64::engine::general_purpose::URL_SAFE.encode(ciphertext); - - let mut cookie_opts = String::new(); - if let Some(e) = self.expire { - write!(cookie_opts, "; Expire={e}").unwrap(); - } - if self.secure { - write!(cookie_opts, "; Secure").unwrap(); - } - - r.headers_mut().append( - SET_COOKIE, - HeaderValue::from_str(&format!( - "gnix_username={}{}", - percent_encode(username.as_bytes(), NON_ALPHANUMERIC), - cookie_opts - )) - .unwrap(), - ); - r.headers_mut().append( - SET_COOKIE, - HeaderValue::from_str(&format!("gnix_auth={}{}", auth, cookie_opts)) - .unwrap(), - ); - } else { - debug!("login fail"); - } - r.headers_mut() - .append(LOCATION, referrer.unwrap_or(HeaderValue::from_static("/"))); - - Ok(r) - } else { - if let Some(cookie) = request.headers().typed_get::<Cookie>() { - if let Some(auth) = cookie.get("gnix_auth") { - let username = - percent_decode_str(cookie.get("gnix_username").unwrap_or("user")) - .decode_utf8()?; - - let auth = base64::engine::general_purpose::URL_SAFE.decode(auth)?; - if auth.len() < 12 { - return Err(ServiceError::BadAuth); - } - let (msg, nonce) = auth.split_at(auth.len() - 12); - let plaintext = context.state.crypto_key.decrypt( - Nonce::from_slice(nonce), - Payload { - msg, - aad: username.as_bytes(), - }, - ); - if let Ok(plaintext) = plaintext { - let created = u64::from_le_bytes(plaintext[0..8].try_into().unwrap()); - - if self - .expire - .map(|e| created + e > unix_seconds()) - .unwrap_or(true) - { - debug!("valid auth for {username:?}"); - return self.next.handle(context, request).await; - } else { - debug!("auth expired"); - } - } else { - debug!("aead invalid"); - } - } else { - debug!("no auth cookie"); - } - } - debug!("unauthorized"); - let mut r = self.fail.handle(context, request).await?; - *r.status_mut() = StatusCode::UNAUTHORIZED; - Ok(r) - } - }) - } -} - -fn unix_seconds() -> u64 { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs() -} diff --git a/src/filters/auth/login.html b/src/filters/auth/login.html deleted file mode 100644 index c7782bd..0000000 --- a/src/filters/auth/login.html +++ /dev/null @@ -1,35 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Gnix Login</title> - <style> - body { - background-color: grey; - } - form { - margin: auto; - width: 200px; - padding: 20px; - border: 2px solid black; - background-color: white; - } - input[type="text"], - input[type="password"] { - box-sizing: border-box; - width: 100%; - margin-bottom: 1em; - } - </style> - </head> - <body> - <form action="/_gnix_login" method="post"> - <label for="username">Username: </label><br /> - <input type="text" name="username" id="username" /><br /> - <label for="password">Password: </label><br /> - <input type="password" name="password" id="password" /><br /> - <input type="submit" value="Login" /> - </form> - </body> -</html> diff --git a/src/filters/auth/mod.rs b/src/filters/auth/mod.rs deleted file mode 100644 index d6e1a35..0000000 --- a/src/filters/auth/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -use argon2::PasswordVerifier; -use argon2::{ - password_hash::{Encoding, PasswordHashString}, - Algorithm, Argon2, Params, PasswordHash, Version, -}; -use serde::de::MapAccess; -use serde::{ - de::{value, Error, Visitor}, - Deserialize, -}; -use std::{collections::HashMap, fmt, fs::read_to_string}; - -pub mod basic; -pub mod cookie; - -struct Credentials { - wrong_user: PasswordHashString, - hashes: HashMap<String, PasswordHashString>, -} - -impl Credentials { - fn get(&self, usernamme: &str) -> &PasswordHashString { - self.hashes.get(usernamme).unwrap_or(&self.wrong_user) - } - pub fn authentificate(&self, usernamme: &str, password: &str) -> bool { - let algo = Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default()); - let hash = self.get(usernamme); - match hash.algorithm().as_str() { - "argon2id" => algo - .verify_password( - password.as_bytes(), - &PasswordHash::parse(hash.as_str(), hash.encoding()).unwrap(), - ) - .is_ok(), - "never" => false, - _ => false, - } - } -} - -impl<'de> Deserialize<'de> for Credentials { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: serde::Deserializer<'de>, - { - struct StringOrMap; - impl<'de> Visitor<'de> for StringOrMap { - type Value = HashMap<String, String>; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("credentials map or file path") - } - fn visit_str<E>(self, val: &str) -> Result<Self::Value, E> - where - E: Error, - { - let path = String::deserialize(value::StrDeserializer::new(val))?; - let c = serde_yaml::from_str(&read_to_string(path).map_err(|io| { - serde::de::Error::custom(format!("cannot read creds file: {io:?}")) - })?) - .map_err(|e| serde::de::Error::custom(format!("cannot parse creds file: {e:?}")))?; - Ok(c) - } - fn visit_map<A>(self, val: A) -> Result<Self::Value, A::Error> - where - A: MapAccess<'de>, - { - Ok(HashMap::deserialize(value::MapAccessDeserializer::new( - val, - ))?) - } - } - let k = deserializer.deserialize_any(StringOrMap)?; - Ok(Credentials { - wrong_user: PasswordHashString::parse("$never", Encoding::B64).unwrap(), - hashes: k - .into_iter() - .map(|(k, v)| { - let hash = PasswordHash::parse(&v, Encoding::B64) - .map_err(|e| { - serde::de::Error::custom(format!( - "phc string for user {k:?} is invalid: {e:?}" - )) - })? - .serialize(); - Ok((k, hash)) - }) - .try_collect()?, - }) - } -} |