Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
19 changes: 18 additions & 1 deletion src/v2/management/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
Comment thread
mzaniolo marked this conversation as resolved.
body: V1AddMachineUserRequest,
) -> Result<V1AddMachineUserResponse> {
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,
Expand Down Expand Up @@ -570,7 +587,7 @@ impl Zitadel {
) -> Result<V1GetDefaultPasswordComplexityPolicyResponse> {
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?)
Expand Down
63 changes: 63 additions & 0 deletions tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down Expand Up @@ -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(())
}
Loading