Pre pull fix

This commit is contained in:
2025-12-05 22:17:24 +01:00
parent 78b6215ffb
commit 2b31e36060
6 changed files with 57 additions and 59 deletions

View File

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

View File

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

View File

@@ -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
return true; .get_route_perms(method, path)
.ok_or_else(return false)?;
if req_perms.root == true {
if user_perms.root == true {
return true;
} else {
return false;
}
} }
let key = RouteKey { req_perms
method: method.clone(), .permissions
path: path.to_string(), .iter()
}; .all(|action| user_perms.permissions.contains(action))
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;
}
required.iter().all(|p| user_perms.contains(p))
} }
} }

View File

@@ -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,
} }
} }
} }

View File

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

View File

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