diff --git a/crates/app/src/routes/api/v1/auth/profile.rs b/crates/app/src/routes/api/v1/auth/profile.rs index be550e2..816a82b 100644 --- a/crates/app/src/routes/api/v1/auth/profile.rs +++ b/crates/app/src/routes/api/v1/auth/profile.rs @@ -3,8 +3,8 @@ use crate::{ get_user_from_token, model::{ApiReturn, Error}, routes::api::v1::{ - AppendAssociations, DeleteUser, DisableTotp, RefreshGrantToken, UpdateUserIsVerified, - UpdateUserPassword, UpdateUserRole, UpdateUserUsername, + AppendAssociations, DeleteUser, DisableTotp, RefreshGrantToken, UpdateSecondaryUserRole, + UpdateUserIsVerified, UpdateUserPassword, UpdateUserRole, UpdateUserUsername, }, State, }; @@ -359,6 +359,34 @@ pub async fn update_user_role_request( } } +/// Update the role of the given user. +/// +/// Does not support third-party grants. +pub async fn update_user_secondary_role_request( + jar: CookieJar, + Path(id): Path, + Extension(data): Extension, + Json(req): Json, +) -> impl IntoResponse { + let data = &(data.read().await).0; + let user = match get_user_from_token!(jar, data) { + Some(ua) => ua, + None => return Json(Error::NotAllowed.into()), + }; + + match data + .update_user_secondary_role(id, req.role, user, false) + .await + { + Ok(_) => Json(ApiReturn { + ok: true, + message: "User updated".to_string(), + payload: (), + }), + Err(e) => Json(e.into()), + } +} + /// Update the current user's last seen value. pub async fn seen_request(jar: CookieJar, Extension(data): Extension) -> impl IntoResponse { let data = &(data.read().await).0; diff --git a/crates/app/src/routes/api/v1/mod.rs b/crates/app/src/routes/api/v1/mod.rs index ea45ffd..d235b68 100644 --- a/crates/app/src/routes/api/v1/mod.rs +++ b/crates/app/src/routes/api/v1/mod.rs @@ -26,7 +26,7 @@ use tetratto_core::model::{ communities_permissions::CommunityPermission, journals::JournalPrivacyPermission, oauth::AppScope, - permissions::FinePermission, + permissions::{FinePermission, SecondaryPermission}, reactions::AssetType, stacks::{StackMode, StackPrivacy, StackSort}, }; @@ -296,6 +296,10 @@ pub fn routes() -> Router { "/auth/user/{id}/role", post(auth::profile::update_user_role_request), ) + .route( + "/auth/user/{id}/role/2", + post(auth::profile::update_user_secondary_role_request), + ) .route( "/auth/user/{id}", delete(auth::profile::delete_user_request), @@ -738,6 +742,11 @@ pub struct UpdateUserRole { pub role: FinePermission, } +#[derive(Deserialize)] +pub struct UpdateSecondaryUserRole { + pub role: SecondaryPermission, +} + #[derive(Deserialize)] pub struct DeleteUser { pub password: String, diff --git a/crates/core/src/database/auth.rs b/crates/core/src/database/auth.rs index b7bdd77..4fb78f7 100644 --- a/crates/core/src/database/auth.rs +++ b/crates/core/src/database/auth.rs @@ -16,10 +16,73 @@ use tetratto_shared::{ unix_epoch_timestamp, }; use crate::{auto_method, DataManager}; +use oiseau::{PostgresRow, execute, get, query_row, params}; -use oiseau::PostgresRow; +macro_rules! update_role_fn { + ($name:ident, $role_ty:ty, $col:literal) => { + pub async fn $name( + &self, + id: usize, + role: $role_ty, + user: User, + force: bool, + ) -> Result<()> { + let other_user = self.get_user_by_id(id).await?; -use oiseau::{execute, get, query_row, params}; + if !force { + // check permission + if !user.permissions.check(FinePermission::MANAGE_USERS) { + return Err(Error::NotAllowed); + } + + if other_user.permissions.check_manager() && !user.permissions.check_admin() { + return Err(Error::MiscError( + "Cannot manage the role of other managers".to_string(), + )); + } + + if other_user.permissions == user.permissions { + return Err(Error::MiscError( + "Cannot manage users of equal level to you".to_string(), + )); + } + } + + // ... + let conn = match self.0.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = execute!( + &conn, + &format!("UPDATE users SET {} = $1 WHERE id = $2", $col), + params![&(role.bits() as i32), &(id as i64)] + ); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + self.cache_clear_user(&other_user).await; + + // create audit log entry + self.create_audit_log_entry(AuditLogEntry::new( + user.id, + format!( + "invoked `{}` with x value `{}` and y value `{}`", + $col, + other_user.id, + role.bits() + ), + )) + .await?; + + // ... + Ok(()) + } + }; +} impl DataManager { /// Get a [`User`] from an SQL row. @@ -47,7 +110,7 @@ impl DataManager { grants: serde_json::from_str(&get!(x->19(String)).to_string()).unwrap(), associated: serde_json::from_str(&get!(x->20(String)).to_string()).unwrap(), invite_code: get!(x->21(i64)) as usize, - secondary_permissions: SecondaryPermission::from_bits(get!(x->7(i32)) as u32).unwrap(), + secondary_permissions: SecondaryPermission::from_bits(get!(x->22(i32)) as u32).unwrap(), } } @@ -623,67 +686,6 @@ impl DataManager { Ok(()) } - pub async fn update_user_role( - &self, - id: usize, - role: FinePermission, - user: User, - force: bool, - ) -> Result<()> { - let other_user = self.get_user_by_id(id).await?; - - if !force { - // check permission - if !user.permissions.check(FinePermission::MANAGE_USERS) { - return Err(Error::NotAllowed); - } - - if other_user.permissions.check_manager() && !user.permissions.check_admin() { - return Err(Error::MiscError( - "Cannot manage the role of other managers".to_string(), - )); - } - - if other_user.permissions == user.permissions { - return Err(Error::MiscError( - "Cannot manage users of equal level to you".to_string(), - )); - } - } - - // ... - let conn = match self.0.connect().await { - Ok(c) => c, - Err(e) => return Err(Error::DatabaseConnection(e.to_string())), - }; - - let res = execute!( - &conn, - "UPDATE users SET permissions = $1 WHERE id = $2", - params![&(role.bits() as i32), &(id as i64)] - ); - - if let Err(e) = res { - return Err(Error::DatabaseError(e.to_string())); - } - - self.cache_clear_user(&other_user).await; - - // create audit log entry - self.create_audit_log_entry(AuditLogEntry::new( - user.id, - format!( - "invoked `update_user_role` with x value `{}` and y value `{}`", - other_user.id, - role.bits() - ), - )) - .await?; - - // ... - Ok(()) - } - pub async fn seen_user(&self, user: &User) -> Result<()> { let conn = match self.0.connect().await { Ok(c) => c, @@ -843,6 +845,13 @@ impl DataManager { .await; } + update_role_fn!(update_user_role, FinePermission, "permissions"); + update_role_fn!( + update_user_secondary_role, + SecondaryPermission, + "secondary_permissions" + ); + auto_method!(update_user_tokens(Vec)@get_user_by_id -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user); auto_method!(update_user_grants(Vec)@get_user_by_id -> "UPDATE users SET grants = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user); auto_method!(update_user_settings(UserSettings)@get_user_by_id -> "UPDATE users SET settings = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);