diff --git a/ee/tabby-db/src/users.rs b/ee/tabby-db/src/users.rs index 7db6834d81e7..32513ad740f7 100644 --- a/ee/tabby-db/src/users.rs +++ b/ee/tabby-db/src/users.rs @@ -173,6 +173,24 @@ impl DbConn { Ok(()) } } + + pub async fn update_user_role(&self, id: i32, is_admin: bool) -> Result<()> { + let not_admin = !is_admin; + let changed = query!( + "UPDATE users SET is_admin = ? WHERE id = ? AND is_admin = ?", + is_admin, + id, + not_admin + ) + .execute(&self.pool) + .await? + .rows_affected(); + if changed != 1 { + Err(anyhow!("user admin status was not changed")) + } else { + Ok(()) + } + } } fn generate_auth_token() -> String { diff --git a/ee/tabby-webserver/src/schema/auth.rs b/ee/tabby-webserver/src/schema/auth.rs index 19276d232a1e..c476cb19aa68 100644 --- a/ee/tabby-webserver/src/schema/auth.rs +++ b/ee/tabby-webserver/src/schema/auth.rs @@ -420,6 +420,7 @@ pub trait AuthenticationService: Send + Sync { async fn delete_oauth_credential(&self, provider: OAuthProvider) -> Result<()>; async fn update_user_active(&self, id: &ID, active: bool) -> Result<()>; + async fn update_user_role(&self, id: &ID, is_admin: bool) -> Result<()>; } #[cfg(test)] diff --git a/ee/tabby-webserver/src/schema/mod.rs b/ee/tabby-webserver/src/schema/mod.rs index 3488a863b99d..dd50fe0e5dc7 100644 --- a/ee/tabby-webserver/src/schema/mod.rs +++ b/ee/tabby-webserver/src/schema/mod.rs @@ -308,6 +308,12 @@ impl Mutation { Ok(true) } + async fn update_user_role(ctx: &Context, id: ID, is_admin: bool) -> Result { + check_admin(ctx)?; + ctx.locator.auth().update_user_role(&id, is_admin).await?; + Ok(true) + } + async fn register( ctx: &Context, email: String, diff --git a/ee/tabby-webserver/src/service/auth.rs b/ee/tabby-webserver/src/service/auth.rs index 929eb0516c82..8b3a2463699e 100644 --- a/ee/tabby-webserver/src/service/auth.rs +++ b/ee/tabby-webserver/src/service/auth.rs @@ -300,6 +300,14 @@ impl AuthenticationService for AuthenticationServiceImpl { Ok(!admin.is_empty()) } + async fn update_user_role(&self, id: &ID, is_admin: bool) -> Result<()> { + let id = id.as_rowid()?; + if id == 1 { + return Err(anyhow!("The owner's admin status may not be changed")); + } + self.db.update_user_role(id, is_admin).await + } + async fn get_user_by_email(&self, email: &str) -> Result { let user = self.db.get_user_by_email(email).await?; if let Some(user) = user { @@ -803,6 +811,32 @@ mod tests { .is_err()); } + #[tokio::test] + async fn test_update_role() { + let service = test_authentication_service().await; + let admin_id = service + .db + .create_user("admin@example.com".into(), "".into(), true) + .await + .unwrap(); + + let user_id = service + .db + .create_user("user@example.com".into(), "".into(), false) + .await + .unwrap(); + + assert!(service + .update_user_role(&user_id.as_id(), true) + .await + .is_ok()); + + assert!(service + .update_user_role(&admin_id.as_id(), false) + .await + .is_err()); + } + #[tokio::test] async fn test_pagination() { let service = test_authentication_service().await;