chore: add app_auth_filters
This commit is contained in:
parent
d25b23f4ab
commit
55004c6744
10
app_auth_filters/.gitignore
vendored
Normal file
10
app_auth_filters/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/target
|
||||
target
|
||||
Cargo.lock
|
||||
.cache
|
||||
.temp
|
||||
.env
|
||||
*.log
|
||||
.DS_Store
|
||||
logs
|
||||
tmp
|
56
app_auth_filters/Cargo.toml
Normal file
56
app_auth_filters/Cargo.toml
Normal 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"
|
3
app_auth_filters/README.md
Normal file
3
app_auth_filters/README.md
Normal 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
5
app_auth_filters/TODO.md
Normal 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
278
app_auth_filters/src/lib.rs
Normal 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())
|
||||
}
|
Loading…
Reference in New Issue
Block a user