Pre pull fix
This commit is contained in:
@@ -22,7 +22,7 @@ tower = { version = "0.5.2", features = ["tokio", "tracing"] }
|
|||||||
tower-http = { version = "0.6.7", features = ["cors"] }
|
tower-http = { version = "0.6.7", features = ["cors"] }
|
||||||
tracing = { version = "0.1.43", features = ["max_level_debug"] }
|
tracing = { version = "0.1.43", features = ["max_level_debug"] }
|
||||||
tracing-subscriber = "0.3.22"
|
tracing-subscriber = "0.3.22"
|
||||||
uuid = { version = "1.18.1", features = ["v4", "serde"] }
|
uuid = { version = "1.19.0", features = ["v4", "serde"] }
|
||||||
validator = { version = "0.20.0", features = ["derive"] }
|
validator = { version = "0.20.0", features = ["derive"] }
|
||||||
mineguard = {path = "../../../MineGuard/"}
|
mineguard = {path = "../../../MineGuard/"}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
CREATE TABLE user_permissions(
|
CREATE TABLE user_permissions(
|
||||||
uuid UUID PRIMARY KEY,
|
uuid UUID PRIMARY KEY,
|
||||||
root BOOL NOT NULL,
|
root BOOL NOT NULL,
|
||||||
manage_users BOOL NOT NULL,
|
permissions JSON NOT NULL,
|
||||||
login BOOL NOT NULL
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use axum::http::Method;
|
use axum::http::Method;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::domain::user_prems::UserActions;
|
use crate::domain::{
|
||||||
|
user::User,
|
||||||
|
user_prems::{UserActions, UserPermissions},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
|
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
|
||||||
pub struct RouteKey {
|
pub struct RouteKey {
|
||||||
@@ -12,7 +15,7 @@ pub struct RouteKey {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AppCfg {
|
pub struct AppCfg {
|
||||||
pub db_path: String,
|
pub db_path: String,
|
||||||
pub route_perms: HashMap<RouteKey, Vec<UserActions>>,
|
pub route_perms: HashMap<RouteKey, UserPermissions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppCfg {
|
impl AppCfg {
|
||||||
@@ -27,7 +30,7 @@ impl AppCfg {
|
|||||||
&mut self,
|
&mut self,
|
||||||
method: Method,
|
method: Method,
|
||||||
path: impl Into<String>,
|
path: impl Into<String>,
|
||||||
perms: Vec<UserActions>,
|
perms: UserPermissions,
|
||||||
) {
|
) {
|
||||||
let key = RouteKey {
|
let key = RouteKey {
|
||||||
method,
|
method,
|
||||||
@@ -37,7 +40,7 @@ impl AppCfg {
|
|||||||
self.route_perms.insert(key, perms);
|
self.route_perms.insert(key, perms);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_route_perms(&self, method: &Method, path: &str) -> Option<&Vec<UserActions>> {
|
pub fn get_route_perms(&self, method: &Method, path: &str) -> Option<UserPermissions> {
|
||||||
let key = RouteKey {
|
let key = RouteKey {
|
||||||
method: method.clone(),
|
method: method.clone(),
|
||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
@@ -46,25 +49,22 @@ impl AppCfg {
|
|||||||
self.route_perms.get(&key)
|
self.route_perms.get(&key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn route_allows(&self, method: &Method, path: &str, user_perms: &[UserActions]) -> bool {
|
pub fn route_allows(&self, method: &Method, path: &str, user_perms: UserPermissions) -> bool {
|
||||||
if user_perms.contains(&UserActions::Root) {
|
let req_perms = self
|
||||||
|
.get_route_perms(method, path)
|
||||||
|
.ok_or_else(return false)?;
|
||||||
|
|
||||||
|
if req_perms.root == true {
|
||||||
|
if user_perms.root == true {
|
||||||
return true;
|
return true;
|
||||||
}
|
} else {
|
||||||
|
|
||||||
let key = RouteKey {
|
|
||||||
method: method.clone(),
|
|
||||||
path: path.to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let required = match self.route_perms.get(&key) {
|
|
||||||
Some(perms) => perms,
|
|
||||||
None => return true, // no perms required → allow
|
|
||||||
};
|
|
||||||
|
|
||||||
if required.contains(&UserActions::Root) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
required.iter().all(|p| user_perms.contains(p))
|
req_perms
|
||||||
|
.permissions
|
||||||
|
.iter()
|
||||||
|
.all(|action| user_perms.permissions.contains(action))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::prelude::FromRow;
|
use sqlx::prelude::FromRow;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub enum UserActions {
|
pub enum UserActions {
|
||||||
Root,
|
|
||||||
ManageUsers,
|
ManageUsers,
|
||||||
Login,
|
Login,
|
||||||
}
|
}
|
||||||
@@ -12,16 +13,14 @@ pub enum UserActions {
|
|||||||
#[derive(Debug, Clone, Deserialize, Serialize, FromRow)]
|
#[derive(Debug, Clone, Deserialize, Serialize, FromRow)]
|
||||||
pub struct UserPermissions {
|
pub struct UserPermissions {
|
||||||
pub root: bool,
|
pub root: bool,
|
||||||
pub manage_users: bool,
|
pub permissions: HashSet<UserActions>,
|
||||||
pub login: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UserPermissions {
|
impl Default for UserPermissions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: false,
|
root: false,
|
||||||
manage_users: false,
|
permissions: Vec::new(),
|
||||||
login: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,8 +29,7 @@ impl UserPermissions {
|
|||||||
pub fn root() -> Self {
|
pub fn root() -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: true,
|
root: true,
|
||||||
manage_users: true,
|
permissions: Vec::new(),
|
||||||
login: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,33 +24,28 @@ pub async fn auth(
|
|||||||
|
|
||||||
debug!(method = ?request_method, path = request_path, "authenticate request started");
|
debug!(method = ?request_method, path = request_path, "authenticate request started");
|
||||||
|
|
||||||
// 1) Extract Authorization header
|
|
||||||
let auth_header = req
|
let auth_header = req
|
||||||
.headers()
|
.headers()
|
||||||
.get(http::header::AUTHORIZATION)
|
.get(http::header::AUTHORIZATION)
|
||||||
.ok_or(StatusCode::FORBIDDEN)?; // no header at all
|
.ok_or(StatusCode::FORBIDDEN)?;
|
||||||
|
|
||||||
let auth_header = auth_header.to_str().map_err(|e| {
|
let auth_header = auth_header.to_str().map_err(|e| {
|
||||||
error!(error = %e, method = ?request_method, path = request_path, "authorization header parse failed");
|
error!(error = %e, method = ?request_method, path = request_path, "authorization header parse failed");
|
||||||
StatusCode::FORBIDDEN
|
StatusCode::FORBIDDEN
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// 2) Expect "Bearer <token>"
|
|
||||||
let mut parts = auth_header.split_whitespace();
|
let mut parts = auth_header.split_whitespace();
|
||||||
let (scheme, token) = match (parts.next(), parts.next()) {
|
let (scheme, token) = match (parts.next(), parts.next()) {
|
||||||
(Some(scheme), Some(token)) if scheme.eq_ignore_ascii_case("bearer") => (scheme, token),
|
(Some(scheme), Some(token)) if scheme.eq_ignore_ascii_case("bearer") => (scheme, token),
|
||||||
_ => {
|
_ => {
|
||||||
// either wrong scheme or missing token
|
|
||||||
warn!(method = ?request_method, path = request_path, "authorization header missing bearer token");
|
warn!(method = ?request_method, path = request_path, "authorization header missing bearer token");
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
return Err(StatusCode::UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3) Verify JWT
|
let token_data = verify_jwt(token.to_string())?;
|
||||||
let token_data = verify_jwt(token.to_string())?; // verify_jwt(&str) -> Result<TokenData<AuthClaims>, StatusCode>
|
|
||||||
let username = &token_data.claims.username;
|
let username = &token_data.claims.username;
|
||||||
|
|
||||||
// 4) Load current user from DB
|
|
||||||
let current_user = match user_routines::get_by_username(state, username)
|
let current_user = match user_routines::get_by_username(state, username)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -63,27 +58,8 @@ pub async fn auth(
|
|||||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 5) Attach user to request extensions so handlers can grab it
|
|
||||||
req.extensions_mut().insert(current_user);
|
req.extensions_mut().insert(current_user);
|
||||||
|
|
||||||
// 6) Continue down the stack
|
|
||||||
Ok(next.run(req).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn perms(
|
|
||||||
State(state): State<Arc<AppState>>,
|
|
||||||
Extension(user): Extension<InternalUser>,
|
|
||||||
mut req: Request,
|
|
||||||
next: Next,
|
|
||||||
) -> Result<Response, StatusCode> {
|
|
||||||
let method: Method = req.method().clone();
|
|
||||||
|
|
||||||
let path = req
|
|
||||||
.extensions()
|
|
||||||
.get::<MatchedPath>()
|
|
||||||
.map(|p| p.as_str().to_string())
|
|
||||||
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
||||||
|
|
||||||
Ok(next.run(req).await)
|
Ok(next.run(req).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,3 +70,21 @@ pub fn cors() -> CorsLayer {
|
|||||||
.allow_methods([Method::GET, Method::POST])
|
.allow_methods([Method::GET, Method::POST])
|
||||||
.allow_headers([AUTHORIZATION])
|
.allow_headers([AUTHORIZATION])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn permissions(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Extension(user): Extension<InternalUser>,
|
||||||
|
mut req: Request,
|
||||||
|
next: Next,
|
||||||
|
) -> Result<Response, StatusCode> {
|
||||||
|
warn!("Calling into permissions with user {}", user);
|
||||||
|
let method: Method = req.method().clone();
|
||||||
|
|
||||||
|
let path = req
|
||||||
|
.extensions()
|
||||||
|
.get::<MatchedPath>()
|
||||||
|
.map(|p| p.as_str().to_string())
|
||||||
|
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
Ok(next.run(req).await)
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ macro_rules! middleware {
|
|||||||
axum::middleware::from_fn_with_state($state, crate::router::middleware::auth),
|
axum::middleware::from_fn_with_state($state, crate::router::middleware::auth),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
(cors_auth_perms: $state:expr) => {
|
||||||
|
(
|
||||||
|
crate::router::middleware::cors(),
|
||||||
|
axum::middleware::from_fn_with_state($state, crate::router::middleware::auth),
|
||||||
|
axum::middleware::from_fn_with_state($state, crate::router::middleware::perms),
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
||||||
|
|||||||
Reference in New Issue
Block a user