chore: add app_auth_filters

This commit is contained in:
Jesús Pérez Lorenzo 2021-09-01 19:02:55 +01:00
parent d25b23f4ab
commit 55004c6744
5 changed files with 352 additions and 0 deletions

10
app_auth_filters/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
/target
target
Cargo.lock
.cache
.temp
.env
*.log
.DS_Store
logs
tmp

View File

@ -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"

View File

@ -0,0 +1,3 @@
# App auth filters library
Filters as configure routes and webservices for [warp](https://github.com/seanmonstar/warp)

5
app_auth_filters/TODO.md Normal file
View File

@ -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)

278
app_auth_filters/src/lib.rs Normal file
View File

@ -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())
}