diff --git a/src/backend/src/config.rs b/src/backend/src/config.rs index c8375b9..75e378c 100644 --- a/src/backend/src/config.rs +++ b/src/backend/src/config.rs @@ -1,4 +1,70 @@ +use axum::http::Method; +use std::collections::HashMap; + +use crate::domain::user_prems::UserActions; + +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +pub struct RouteKey { + pub method: Method, + pub path: String, +} + #[derive(Debug)] pub struct AppCfg { pub db_path: String, + pub route_perms: HashMap>, +} + +impl AppCfg { + pub fn new(db_path: String) -> Self { + Self { + db_path, + route_perms: HashMap::new(), + } + } + + pub fn insert_route_perms( + &mut self, + method: Method, + path: impl Into, + perms: Vec, + ) { + let key = RouteKey { + method, + path: path.into(), + }; + + self.route_perms.insert(key, perms); + } + + pub fn get_route_perms(&self, method: &Method, path: &str) -> Option<&Vec> { + let key = RouteKey { + method: method.clone(), + path: path.to_string(), + }; + + self.route_perms.get(&key) + } + + pub fn route_allows(&self, method: &Method, path: &str, user_perms: &[UserActions]) -> bool { + if user_perms.contains(&UserActions::Root) { + return true; + } + + 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; + } + + required.iter().all(|p| user_perms.contains(p)) + } } diff --git a/src/backend/src/domain/user_prems.rs b/src/backend/src/domain/user_prems.rs index 58af8f3..d73f096 100644 --- a/src/backend/src/domain/user_prems.rs +++ b/src/backend/src/domain/user_prems.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use sqlx::prelude::FromRow; use uuid::Uuid; -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub enum UserActions { Root, ManageUsers, diff --git a/src/backend/src/main.rs b/src/backend/src/main.rs index 78710e9..7e8a361 100644 --- a/src/backend/src/main.rs +++ b/src/backend/src/main.rs @@ -1,8 +1,10 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use anyhow::{Ok, Result}; +use axum::http::Method; use rustymine_daemon::{ config::AppCfg, + domain::user_prems::UserActions, router, state::{AppState, check_root}, }; @@ -56,11 +58,15 @@ async fn main() -> Result<()> { ); let db_path: String = "postgres://rustymine:minecraft@localhost:5432/rustymine_dev".to_string(); - let config = AppCfg { + let mut config = AppCfg { db_path: db_path.clone(), + route_perms: HashMap::new(), }; - let state = Arc::new(AppState::new(&config).await); + config.insert_route_perms(Method::GET, "/api/login", vec![UserActions::Login]); + config.insert_route_perms(Method::GET, "/api/users", vec![UserActions::Login]); + + let state = Arc::new(AppState::new(config).await); check_root(state.clone()).await; let app_result = router::init_router(state.clone()).await; diff --git a/src/backend/src/router/middleware.rs b/src/backend/src/router/middleware.rs index ade78e3..475fe60 100644 --- a/src/backend/src/router/middleware.rs +++ b/src/backend/src/router/middleware.rs @@ -1,8 +1,9 @@ -use crate::{core::user_routines, prelude::*}; +use crate::{core::user_routines, domain::user::InternalUser, prelude::*}; use std::sync::Arc; use axum::{ - extract::{Request, State}, + Extension, + extract::{MatchedPath, Request, State}, http::{self, Method, StatusCode, header::AUTHORIZATION}, middleware::{Next, from_fn_with_state}, response::Response, @@ -69,6 +70,23 @@ pub async fn auth( Ok(next.run(req).await) } +pub async fn perms( + State(state): State>, + Extension(user): Extension, + mut req: Request, + next: Next, +) -> Result { + let method: Method = req.method().clone(); + + let path = req + .extensions() + .get::() + .map(|p| p.as_str().to_string()) + .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(next.run(req).await) +} + pub fn cors() -> CorsLayer { debug!("build cors layer"); CorsLayer::new() diff --git a/src/backend/src/state.rs b/src/backend/src/state.rs index 68bde00..81c058e 100644 --- a/src/backend/src/state.rs +++ b/src/backend/src/state.rs @@ -1,8 +1,11 @@ -use std::{process::exit, sync::Arc}; +use std::{collections::HashMap, process::exit, sync::Arc}; use crate::{ core, - domain::user::{InternalNewUser, NewUser}, + domain::{ + user::{InternalNewUser, NewUser}, + user_prems::UserActions, + }, prelude::*, }; @@ -12,10 +15,11 @@ use crate::{config::AppCfg, infra::db}; pub struct AppState { pub db_pool: PgPool, + pub config: AppCfg, } impl AppState { - pub async fn new(config: &AppCfg) -> Self { + pub async fn new(config: AppCfg) -> Self { debug!("init app state"); debug!("establish database connection"); let db_pool = db::connect(&config.db_path) @@ -36,7 +40,10 @@ impl AppState { .unwrap(); info!("database ready after connect and migrate"); - Self { db_pool: db_pool } + Self { + db_pool: db_pool, + config: config, + } } }