diff --git a/src/backend/migrations/0003_create_perms.sql b/src/backend/migrations/0003_create_perms.sql index 2f896c2..49bb29d 100644 --- a/src/backend/migrations/0003_create_perms.sql +++ b/src/backend/migrations/0003_create_perms.sql @@ -1,5 +1,5 @@ CREATE TABLE user_permissions( uuid UUID PRIMARY KEY, root BOOL NOT NULL, - permissions JSON NOT NULL, + permissions JSON NOT NULL ); diff --git a/src/backend/src/config.rs b/src/backend/src/config.rs index f48a53f..80c834d 100644 --- a/src/backend/src/config.rs +++ b/src/backend/src/config.rs @@ -30,14 +30,20 @@ impl AppCfg { &mut self, method: Method, path: impl Into, - perms: UserPermissions, + root: bool, + perms: Vec, ) { let key = RouteKey { method, path: path.into(), }; - self.route_perms.insert(key, perms); + let user_perms = UserPermissions { + root, + permissions: perms.into_iter().collect(), // Vec → HashSet + }; + + self.route_perms.insert(key, user_perms); } pub fn get_route_perms(&self, method: &Method, path: &str) -> Option { @@ -46,13 +52,19 @@ impl AppCfg { path: path.to_string(), }; - self.route_perms.get(&key) + let perm = match self.route_perms.get(&key) { + Some(val) => val.clone(), + None => return None, + }; + + Some(perm) } pub fn route_allows(&self, method: &Method, path: &str, user_perms: UserPermissions) -> bool { - let req_perms = self - .get_route_perms(method, path) - .ok_or_else(return false)?; + let req_perms = match self.get_route_perms(method, path) { + Some(val) => val, + None => return false, + }; if req_perms.root == true { if user_perms.root == true { diff --git a/src/backend/src/domain/user_prems.rs b/src/backend/src/domain/user_prems.rs index b3b2092..9ef669d 100644 --- a/src/backend/src/domain/user_prems.rs +++ b/src/backend/src/domain/user_prems.rs @@ -1,26 +1,49 @@ use std::collections::HashSet; use serde::{Deserialize, Serialize}; -use sqlx::prelude::FromRow; +use sqlx::{prelude::FromRow, types::Json}; use uuid::Uuid; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum UserActions { ManageUsers, - Login, } -#[derive(Debug, Clone, Deserialize, Serialize, FromRow)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct UserPermissions { pub root: bool, pub permissions: HashSet, } +#[derive(Debug, Clone, FromRow)] +pub struct UserPermissionsRow { + pub root: bool, + pub permissions: Json>, +} + +impl From for UserPermissionsRow { + fn from(value: UserPermissions) -> Self { + Self { + root: value.root, + permissions: Json(value.permissions), + } + } +} + +impl From for UserPermissions { + fn from(value: UserPermissionsRow) -> Self { + Self { + root: value.root, + permissions: value.permissions.0, + } + } +} + impl Default for UserPermissions { fn default() -> Self { Self { root: false, - permissions: Vec::new(), + permissions: HashSet::new(), } } } @@ -29,7 +52,7 @@ impl UserPermissions { pub fn root() -> Self { Self { root: true, - permissions: Vec::new(), + permissions: HashSet::new(), } } } diff --git a/src/backend/src/infra/db/perms.rs b/src/backend/src/infra/db/perms.rs index de67532..b04d933 100644 --- a/src/backend/src/infra/db/perms.rs +++ b/src/backend/src/infra/db/perms.rs @@ -2,7 +2,10 @@ use anyhow::Result; use sqlx::PgPool; use uuid::Uuid; -use crate::{domain::user_prems::UserPermissions, prelude::*}; +use crate::{ + domain::user_prems::{UserPermissions, UserPermissionsRow}, + prelude::*, +}; pub async fn create( pool: &PgPool, @@ -10,29 +13,29 @@ pub async fn create( new_perms: UserPermissions, ) -> Result { debug!(user_uuid = %uuid, "insert user permissions started"); - let perms = sqlx::query_as::<_, UserPermissions>( + let insert = UserPermissionsRow::from(new_perms); + let perms = sqlx::query_as::<_, UserPermissionsRow>( r#" - INSERT INTO user_permissions (uuid, root, manage_users, login) - VALUES ($1, $2, $3, $4) - RETURNING uuid, root, manage_users, login + INSERT INTO user_permissions (uuid, root, permissions) + VALUES ($1, $2, $3) + RETURNING root, permissions "#, ) .bind(uuid) - .bind(new_perms.root) - .bind(new_perms.manage_users) - .bind(new_perms.login) + .bind(insert.root) + .bind(insert.permissions) .fetch_one(pool) .await?; debug!(user_uuid = %uuid, "insert user permissions completed"); - Ok(perms) + Ok(UserPermissions::from(perms)) } pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result> { debug!(user_uuid = %uuid, "fetch user permissions by uuid started"); - let perms = sqlx::query_as::<_, UserPermissions>( + let perms = sqlx::query_as::<_, UserPermissionsRow>( r#" - SELECT uuid, root, manage_users, login + SELECT uuid, root, permissions FROM user_permissions WHERE uuid = $1 "#, @@ -42,7 +45,10 @@ pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result return Ok(Some(UserPermissions::from(val))), + None => return Ok(None), + } } pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result { @@ -52,7 +58,7 @@ pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result { SELECT EXISTS( SELECT 1 FROM user_permissions - WHERE uuis = $1 + WHERE uuid = $1 ) "#, ) diff --git a/src/backend/src/main.rs b/src/backend/src/main.rs index 1fbae79..51f8324 100644 --- a/src/backend/src/main.rs +++ b/src/backend/src/main.rs @@ -64,8 +64,12 @@ async fn main() -> Result<()> { route_perms: HashMap::new(), }; - config.insert_route_perms(Method::GET, "/api/login", vec![UserActions::Login]); - config.insert_route_perms(Method::GET, "/api/users", vec![UserActions::Login]); + config.insert_route_perms( + Method::GET, + "/api/users", + false, + vec![UserActions::ManageUsers], + ); let state = Arc::new(AppState::new(config).await); check_root(state.clone()).await; diff --git a/src/backend/src/router/middleware.rs b/src/backend/src/router/middleware.rs index bc407d5..828c992 100644 --- a/src/backend/src/router/middleware.rs +++ b/src/backend/src/router/middleware.rs @@ -77,7 +77,11 @@ pub async fn permissions( mut req: Request, next: Next, ) -> Result { - warn!("Calling into permissions with user {}", user); + let request_method = req.method().clone(); + let request_path = req.uri().path().to_string(); + + debug!(method = ?request_method, path = request_path, "permissions request started"); + debug!("Calling user {}", user.username.clone()); let method: Method = req.method().clone(); let path = req @@ -86,5 +90,11 @@ pub async fn permissions( .map(|p| p.as_str().to_string()) .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?; - Ok(next.run(req).await) + match state + .config + .route_allows(&method, path.as_str(), user.permissions.clone()) + { + true => return Ok(next.run(req).await), + false => return Err(StatusCode::UNAUTHORIZED), + }; } diff --git a/src/backend/src/router/mod.rs b/src/backend/src/router/mod.rs index 8ec6040..947cd72 100644 --- a/src/backend/src/router/mod.rs +++ b/src/backend/src/router/mod.rs @@ -24,11 +24,11 @@ macro_rules! middleware { axum::middleware::from_fn_with_state($state, crate::router::middleware::auth), ) }; - (cors_auth_perms: $state:expr) => { + (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), + axum::middleware::from_fn_with_state($state, crate::router::middleware::permissions), ) }; } @@ -41,10 +41,10 @@ pub async fn init_router(app_state: Arc) -> Router { .route( "/api/users", post(user_routes::create) - .layer(middleware!(cors_auth, app_state.clone())) + .layer(middleware!(cors_auth_perms, app_state.clone())) .with_state(app_state.clone()) .get(user_routes::get_all) - .layer(middleware!(cors_auth, app_state.clone())) + .layer(middleware!(cors_auth_perms, app_state.clone())) .with_state(app_state.clone()), ) .route(