aboutsummaryrefslogtreecommitdiff
path: root/src/filters/auth/cookie.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/filters/auth/cookie.rs')
-rw-r--r--src/filters/auth/cookie.rs182
1 files changed, 0 insertions, 182 deletions
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()
-}