From 55004c67446876502c053a28d056072bca0245d5 Mon Sep 17 00:00:00 2001
From: JesusPerez <jpl@jesusperez.pro>
Date: Wed, 1 Sep 2021 19:02:55 +0100
Subject: [PATCH] chore: add app_auth_filters

---
 app_auth_filters/.gitignore |  10 ++
 app_auth_filters/Cargo.toml |  56 ++++++++
 app_auth_filters/README.md  |   3 +
 app_auth_filters/TODO.md    |   5 +
 app_auth_filters/src/lib.rs | 278 ++++++++++++++++++++++++++++++++++++
 5 files changed, 352 insertions(+)
 create mode 100644 app_auth_filters/.gitignore
 create mode 100644 app_auth_filters/Cargo.toml
 create mode 100644 app_auth_filters/README.md
 create mode 100644 app_auth_filters/TODO.md
 create mode 100644 app_auth_filters/src/lib.rs

diff --git a/app_auth_filters/.gitignore b/app_auth_filters/.gitignore
new file mode 100644
index 0000000..c3a29c2
--- /dev/null
+++ b/app_auth_filters/.gitignore
@@ -0,0 +1,10 @@
+/target
+target
+Cargo.lock
+.cache
+.temp
+.env
+*.log
+.DS_Store
+logs
+tmp
diff --git a/app_auth_filters/Cargo.toml b/app_auth_filters/Cargo.toml
new file mode 100644
index 0000000..68b5e20
--- /dev/null
+++ b/app_auth_filters/Cargo.toml
@@ -0,0 +1,56 @@
+[package]
+name = "app_auth_filters"
+version = "0.1.0"
+authors = ["JesusPerez <jpl@jesusperez.pro>"]
+edition = "2018"
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.40"
+base64 = "0.13.0"
+casbin = "2.0.7"
+chrono = "0.4"
+dotenv = "0.15.0"
+envmnt = "0.9.0"
+error-chain = "0.12.4"
+glob = "0.3.0"
+json = "0.12.4"
+once_cell = "1.7.2"
+parking_lot = "0.11.1"
+rand = "0.8.3"
+regex = "1.4.3"
+serde = { version = "1.0", features = ["derive"] }
+serde_derive = "1.0.125"
+serde_json = "1.0.64"
+serde_yaml = "0.8.17"
+slab = "0.4.3"
+tempfile = "3.2.0"
+tera = "1.8.0"
+thiserror = "1.0.24"
+toml = "0.5.8"
+yaml-rust = "0.4"
+tokio = { version = "1.5.0", features = ["full"] }
+uuid = { version = "0.8", features = ["serde", "v4"] }
+url = "2.2.1"
+warp = { version = "0.3", features = ["default","websocket","tls","compression"] }
+
+app_tools = { version = "0.1.0", path = "../../utils/app_tools" }
+app_env = { version = "0.1.0", path = "../../defs/app_env" }
+app_auth = { version = "0.1.0", path = "../../defs/app_auth" }
+app_auth_handlers = { version = "0.1.0", path = "../../handlers/app_auth_handlers" }
+
+[dev-dependencies]
+pretty_env_logger = "0.4"
+tracing-subscriber = "0.2.15"
+tracing-log = "0.1"
+serde_derive = "1.0.125"
+handlebars = "3.0.0"
+tokio = { version = "1.5.0", features = ["macros", "rt-multi-thread"] }
+tokio-stream = { version = "0.1.5", features = ["net"] }
+listenfd = "0.3"
+envmnt = "0.9.0"
+
+[build-dependencies]
+envmnt = "0.9.0"
diff --git a/app_auth_filters/README.md b/app_auth_filters/README.md
new file mode 100644
index 0000000..142e294
--- /dev/null
+++ b/app_auth_filters/README.md
@@ -0,0 +1,3 @@
+# App auth filters library
+
+Filters as configure routes and webservices for [warp](https://github.com/seanmonstar/warp)
diff --git a/app_auth_filters/TODO.md b/app_auth_filters/TODO.md
new file mode 100644
index 0000000..5403fb3
--- /dev/null
+++ b/app_auth_filters/TODO.md
@@ -0,0 +1,5 @@
+# App auth filters library
+
+- [ ] Conditional **boxed** with cargo build for production
+
+[Warp and compile times](https://rust-classes.com/chapter_6_3.html)
diff --git a/app_auth_filters/src/lib.rs b/app_auth_filters/src/lib.rs
new file mode 100644
index 0000000..bcf0a1c
--- /dev/null
+++ b/app_auth_filters/src/lib.rs
@@ -0,0 +1,278 @@
+// use std::collections::HashMap;
+// use std::convert::Infallible;
+use std::str::from_utf8;
+// use std::sync::Arc;
+// use tokio::sync::RwLock;
+use casbin::prelude::*;
+
+use warp::{
+    filters::header::headers_cloned,
+    filters::method::method,
+    filters::path::{full, FullPath},
+    filters::BoxedFilter,
+    http::{header::AUTHORIZATION, method::Method, HeaderMap, HeaderValue},
+    Filter, Rejection,
+};
+
+use app_env::AppStore;
+
+use app_auth::{
+  AppAuthDBs,
+  AuthStore,
+  AuthError, 
+  Sessions,
+  UserCtx, 
+  WebResult,
+  BEARER_PREFIX,
+  custom_reject
+};
+
+
+use app_auth_handlers::{login_handler,loginin_handler,checkin_handler,logout_handler};
+
+#[must_use] 
+pub fn auth(
+  app_db: AppStore,
+  auth_db: AuthStore,
+  cors: warp::cors::Builder,
+//) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+) -> BoxedFilter<(impl warp::Reply,)> {
+  let app_auth_dbs = AppAuthDBs { app: app_db, auth: auth_db};
+  login(app_auth_dbs.clone())
+  .or(loginin(app_auth_dbs.clone(),cors.clone()))
+  .or(checkin(app_auth_dbs.clone(),cors.clone()))
+  .or(logout(app_auth_dbs.clone(),cors.clone()))
+  .boxed()
+}
+#[must_use]
+pub fn loginin(
+  db: AppAuthDBs,
+  cors: warp::cors::Builder,
+//) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+) -> BoxedFilter<(impl warp::Reply,)> {
+    // let cors = warp::cors().allow_any_origin();
+  warp::path("loginin")
+    .and(warp::post())
+    .and(warp::body::json())
+    .and(headers_cloned())
+    .and(method())
+    .and(with_dbauth(db))
+//    .with(warp::cors().allow_any_origin())
+    .and_then(loginin_handler)
+    .with(cors)
+    .boxed()
+}
+#[must_use]
+pub fn checkin(
+  db: AppAuthDBs,
+  cors: warp::cors::Builder,
+//) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+) -> BoxedFilter<(impl warp::Reply,)> {
+  warp::path("checkin")
+    .and(warp::post())
+    .and(warp::body::json())
+    .and(headers_cloned())
+    .and(method())
+    .and(with_dbauth(db))
+//    .with(warp::cors().allow_any_origin())
+    .and_then(checkin_handler)
+    .with(cors)
+    .boxed()
+}
+#[must_use]
+pub fn login(
+  db: AppAuthDBs
+//) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+) -> BoxedFilter<(impl warp::Reply,)> {
+  warp::path!("login")
+    .and(headers_cloned())
+    .and(method())
+    .and(with_dbauth(db))
+    .and_then(login_handler)
+    .boxed()
+}
+#[must_use]
+pub fn logout(
+  db: AppAuthDBs,
+  cors: warp::cors::Builder,
+//) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+) -> BoxedFilter<(impl warp::Reply,)> {
+  warp::path!("logout")
+    .and(with_auth(db.app.clone(),db.auth.clone()))
+    .and(headers_cloned())
+    .and(method())
+    .and(with_dbauth(db))
+    //.and(with_sessions(db.auth.sessions))
+    .and_then(logout_handler)
+    .with(cors)
+    .boxed()
+}
+
+// use crate::defs::AppDB;
+
+// use crate::auth::handlers;
+
+/*
+pub type Result<T> = std::result::Result<T, AuthError>;
+
+#[allow(clippy::missing_errors_doc)]
+pub async fn get_routes() 
+  -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+
+    let member_route = warp::path!("member")
+        .and(with_auth(
+            enforcer.clone(),
+            user_map.clone(),
+            sessions.clone(),
+        ))
+        .and_then(handlers::member_handler);
+
+    let admin_route = warp::path!("admin")
+        .and(with_auth(
+            enforcer.clone(),
+            user_map.clone(),
+            sessions.clone(),
+        ))
+        .and_then(handlers::admin_handler);
+
+    let login_route = warp::path!("login")
+        .and(warp::post())
+        .and(warp::body::json())
+        .and(with_user_map(user_map.clone()))
+        .and(with_sessions(sessions.clone()))
+        .and_then(handlers::login_handler);
+
+    let logout_route = warp::path!("logout")
+        .and(with_auth(
+            enforcer.clone(),
+            user_map.clone(),
+            sessions.clone(),
+        ))
+        .and(with_sessions(sessions.clone()))
+        .and_then(handlers::logout_handler);
+
+    member_route
+        .or(admin_route)
+        .or(login_route)
+        .or(logout_route)
+}
+
+
+#[allow(clippy::missing_errors_doc)]
+pub fn with_user_map(
+    user_map: UserMap,
+) -> impl Filter<Extract = (UserMap,), Error = Infallible> + Clone {
+    warp::any().map(move || user_map.clone())
+}
+
+#[allow(clippy::missing_errors_doc)]
+pub fn with_sessions(
+    sessions: Sessions,
+) -> impl Filter<Extract = (Sessions,), Error = Infallible> + Clone {
+    warp::any().map(move || sessions.clone())
+}
+
+*/
+#[allow(clippy::missing_errors_doc)]
+pub fn with_auth(
+  app_db: AppStore,
+  auth_db: AuthStore,
+  //  enforcer: SharedEnforcer,
+  //  user_map: UserMap,
+  //  sessions: Sessions,
+) -> impl Filter<Extract = (UserCtx,), Error = Rejection> + Clone {
+    full()
+        .and(headers_cloned())
+        .and(method())
+        .map(
+            move |path: FullPath, headers: HeaderMap<HeaderValue>, method: Method| {
+                (
+                    path,
+                    headers,
+                    method,
+                    app_db.clone(),
+                    auth_db.clone(),
+                    // db.auth.users.clone(),
+                    // db.auth.enforcer.clone(),
+                    // db.auth.sessions.clone(),
+                )
+            },
+        )
+        .and_then(user_authentication)
+}
+
+#[must_use] 
+pub fn with_dbauth(db: AppAuthDBs) -> impl Filter<Extract = (AppAuthDBs,), Error = std::convert::Infallible> + Clone {
+    warp::any().map(move || db.clone())
+}
+
+#[must_use] 
+pub fn with_sessions(sessions: Sessions) -> impl Filter<Extract = (Sessions,), Error = std::convert::Infallible> + Clone {
+    warp::any().map(move || sessions.clone())
+}
+
+#[allow(clippy::missing_errors_doc)]
+pub async fn user_authentication(
+    args: (
+        FullPath,
+        HeaderMap<HeaderValue>,
+        Method,
+        AppStore,
+        AuthStore,
+        // UserMap,
+        // SharedEnforcer,
+        // Sessions,
+    ),
+) -> WebResult<UserCtx> {
+  //  let (path, headers, method, user_map, enforcer, sessions) = args;
+    let (path, headers, method, _app_db , auth_db) = args;
+    let user_map = auth_db.users.clone();
+    let enforcer = auth_db.enforcer.clone();
+    let sessions = auth_db.sessions.clone();
+
+    let token = token_from_header(&headers).map_err(|e| custom_reject(e))?;
+    let user_id = match sessions.read().await.get(&token) {
+        Some(v) => v.clone(),
+        None => return Err(custom_reject(AuthError::InvalidTokenError)),
+    };
+    let user = match user_map.read().await.get(&user_id) {
+        Some(v) => v.clone(),
+        None => return Err(custom_reject(AuthError::InvalidTokenError)),
+    };
+    match enforcer
+        .enforce((&user.role.as_str(), &path.as_str(), &method.as_str()))
+//        .await
+    {
+      Ok(authorized) => {
+        if authorized {
+          Ok(UserCtx {
+              user_id: user.user_id,
+              token,
+          })
+        } else {
+          Err(custom_reject(AuthError::UnauthorizedError))
+        }
+      }
+      Err(e) => {
+        eprintln!("error during authorization: {}", e);
+        Err(custom_reject(AuthError::AuthorizationError))
+      }
+    }
+}
+
+#[allow(clippy::missing_errors_doc)]
+pub fn token_from_header(headers: &HeaderMap<HeaderValue>) -> anyhow::Result<String> {
+  let header = match headers.get(AUTHORIZATION) {
+      Some(v) => v,
+      None => return Err(AuthError::NoAuthHeaderFoundError.into()),
+  };
+  let auth_header = match from_utf8(header.as_bytes()) {
+      Ok(v) => v,
+      Err(_) => return Err(AuthError::NoAuthHeaderFoundError.into()),
+  };
+  if !auth_header.starts_with(BEARER_PREFIX) {
+      return Err(AuthError::InvalidAuthHeaderFormatError.into());
+  }
+  let without_prefix = auth_header.trim_start_matches(BEARER_PREFIX);
+  Ok(without_prefix.to_owned())
+}