add: update user secondary role api

This commit is contained in:
trisua 2025-06-23 19:49:52 -04:00
parent 9528d71b2a
commit 0ae64de989
3 changed files with 113 additions and 67 deletions

View file

@ -3,8 +3,8 @@ use crate::{
get_user_from_token, get_user_from_token,
model::{ApiReturn, Error}, model::{ApiReturn, Error},
routes::api::v1::{ routes::api::v1::{
AppendAssociations, DeleteUser, DisableTotp, RefreshGrantToken, UpdateUserIsVerified, AppendAssociations, DeleteUser, DisableTotp, RefreshGrantToken, UpdateSecondaryUserRole,
UpdateUserPassword, UpdateUserRole, UpdateUserUsername, UpdateUserIsVerified, UpdateUserPassword, UpdateUserRole, UpdateUserUsername,
}, },
State, 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<usize>,
Extension(data): Extension<State>,
Json(req): Json<UpdateSecondaryUserRole>,
) -> 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. /// Update the current user's last seen value.
pub async fn seen_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse { pub async fn seen_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
let data = &(data.read().await).0; let data = &(data.read().await).0;

View file

@ -26,7 +26,7 @@ use tetratto_core::model::{
communities_permissions::CommunityPermission, communities_permissions::CommunityPermission,
journals::JournalPrivacyPermission, journals::JournalPrivacyPermission,
oauth::AppScope, oauth::AppScope,
permissions::FinePermission, permissions::{FinePermission, SecondaryPermission},
reactions::AssetType, reactions::AssetType,
stacks::{StackMode, StackPrivacy, StackSort}, stacks::{StackMode, StackPrivacy, StackSort},
}; };
@ -296,6 +296,10 @@ pub fn routes() -> Router {
"/auth/user/{id}/role", "/auth/user/{id}/role",
post(auth::profile::update_user_role_request), post(auth::profile::update_user_role_request),
) )
.route(
"/auth/user/{id}/role/2",
post(auth::profile::update_user_secondary_role_request),
)
.route( .route(
"/auth/user/{id}", "/auth/user/{id}",
delete(auth::profile::delete_user_request), delete(auth::profile::delete_user_request),
@ -738,6 +742,11 @@ pub struct UpdateUserRole {
pub role: FinePermission, pub role: FinePermission,
} }
#[derive(Deserialize)]
pub struct UpdateSecondaryUserRole {
pub role: SecondaryPermission,
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct DeleteUser { pub struct DeleteUser {
pub password: String, pub password: String,

View file

@ -16,10 +16,73 @@ use tetratto_shared::{
unix_epoch_timestamp, unix_epoch_timestamp,
}; };
use crate::{auto_method, DataManager}; 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 { impl DataManager {
/// Get a [`User`] from an SQL row. /// 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(), grants: serde_json::from_str(&get!(x->19(String)).to_string()).unwrap(),
associated: serde_json::from_str(&get!(x->20(String)).to_string()).unwrap(), associated: serde_json::from_str(&get!(x->20(String)).to_string()).unwrap(),
invite_code: get!(x->21(i64)) as usize, 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(()) 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<()> { pub async fn seen_user(&self, user: &User) -> Result<()> {
let conn = match self.0.connect().await { let conn = match self.0.connect().await {
Ok(c) => c, Ok(c) => c,
@ -843,6 +845,13 @@ impl DataManager {
.await; .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<Token>)@get_user_by_id -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user); auto_method!(update_user_tokens(Vec<Token>)@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<AuthGrant>)@get_user_by_id -> "UPDATE users SET grants = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user); auto_method!(update_user_grants(Vec<AuthGrant>)@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); 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);