diff --git a/CHANGELOG.md b/CHANGELOG.md index 16b22a5..a37a96d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,20 @@ SPDX-License-Identifier: Apache-2.0 All notable changes to this project will be documented in this file. +## [0.12.2] - 2026-05-22 + +### Features + +- Add `create_machine_user` + +### Bug Fixes + +- Correct the url for get_password_complexity_policy + +### Release + +- V0.12.2 + ## [0.12.1] - 2026-05-05 ### Features diff --git a/Cargo.lock b/Cargo.lock index 3d10f4a..79fa08c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -734,7 +734,7 @@ dependencies = [ [[package]] name = "famedly-zitadel-rust-client" -version = "0.12.1" +version = "0.12.2" dependencies = [ "anyhow", "anyhow_ext", diff --git a/Cargo.toml b/Cargo.toml index 8d3555c..971cba4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "famedly-zitadel-rust-client" description = "HTTP client for Zitadel IdP built by Famedly" -version = "0.12.1" +version = "0.12.2" authors = [] edition = "2024" resolver = "2" diff --git a/src/v2/management/mod.rs b/src/v2/management/mod.rs index 8b141ce..a02c15e 100644 --- a/src/v2/management/mod.rs +++ b/src/v2/management/mod.rs @@ -29,6 +29,23 @@ impl Zitadel { Ok(self.send_request(request).await?) } + /// Create a new user with the type machine for your API, service or device. + /// [Docs](https://zitadel.com/docs/apis/resources/mgmt/management-service-add-machine-user) + pub async fn create_machine_user( + &self, + org_id: Option, + body: V1AddMachineUserRequest, + ) -> Result { + let request = self + .client + .post(self.make_url("management/v1/users/machine")?) + .chain_opt(org_id, |req, org_id| req.header(HEADER_ZITADEL_ORGANIZATION_ID, org_id)) + .json(&body) + .build()?; + + Ok(self.send_request(request).await?) + } + /// Create actions. [Docs](https://zitadel.com/docs/apis/resources/mgmt/management-service-create-action) pub async fn create_action( &self, @@ -570,7 +587,7 @@ impl Zitadel { ) -> Result { let request = self .client - .get(self.make_url("/policies/password/complexity")?) + .get(self.make_url("management/v1/policies/password/complexity")?) .chain_opt(org_id, |req, org_id| req.header(HEADER_ZITADEL_ORGANIZATION_ID, org_id)) .build()?; Ok(self.send_request(request).await?) diff --git a/tests/e2e.rs b/tests/e2e.rs index 5b33845..d7ac4a3 100644 --- a/tests/e2e.rs +++ b/tests/e2e.rs @@ -795,6 +795,52 @@ async fn test_e2e_create_human_user() -> Result<()> { Ok(()) } +#[test(tokio::test)] +#[test_log(default_log_filter = "debug")] +async fn test_e2e_create_user_human_and_machine() -> Result<()> { + let zitadel = mk_zitadel_client().await?; + + let suffix = Alphanumeric.sample_string(&mut rand::rng(), 12); + + let human_id = create_user(&zitadel, &format!("Jane{suffix}"), "Doe").await?; + let fetched_human = zitadel + .get_user_by_id(&human_id) + .await? + .user() + .cloned() + .expect("fetched human user missing from response"); + assert!(fetched_human.human().is_some(), "expected human payload on created human user"); + assert!(fetched_human.machine().is_none(), "did not expect machine payload on human user"); + + let machine_username = format!("bot-{suffix}"); + let machine_req = V1AddMachineUserRequest::new(machine_username.clone(), "CI Bot".to_owned()) + .with_description("Created by e2e test".to_owned()); + let machine_resp = zitadel.create_machine_user(None, machine_req).await?; + let machine_id = machine_resp.user_id().expect("machine user id missing from response").clone(); + + let fetched_machine = zitadel + .get_user_by_id(&machine_id) + .await? + .user() + .cloned() + .expect("fetched machine user missing from response"); + assert_eq!(fetched_machine.username().map(String::as_str), Some(machine_username.as_str())); + assert!( + fetched_machine.machine().is_some(), + "expected machine payload on created machine user" + ); + assert!(fetched_machine.human().is_none(), "did not expect human payload on machine user"); + let machine = fetched_machine.machine().unwrap(); + assert_eq!(machine.name().map(String::as_str), Some("CI Bot")); + assert_eq!(machine.description().map(String::as_str), Some("Created by e2e test")); + + // tear_down only cleans up human users; remove the machine one explicitly. + zitadel.delete_user(&machine_id).await?; + tear_down(&zitadel).await; + + Ok(()) +} + #[test(tokio::test)] #[test_log(default_log_filter = "debug")] async fn test_e2e_list_human_users() -> Result<()> { @@ -1469,3 +1515,20 @@ async fn test_e2e_organization_scoped_operations() -> Result<()> { Ok(()) } + +#[test(tokio::test)] +#[test_log(default_log_filter = "debug")] +async fn test_e2e_get_password_complexity_policy() -> Result<()> { + let zitadel = mk_zitadel_client().await?; + + let response = zitadel.get_password_complexity_policy(None).await?; + let policy = response.policy().expect("password complexity policy should be present"); + + assert!(policy.min_length().is_some()); + assert!(policy.has_uppercase().is_some()); + assert!(policy.has_lowercase().is_some()); + assert!(policy.has_number().is_some()); + assert!(policy.has_symbol().is_some()); + + Ok(()) +}