redis_cloud/users.rs
1//! User management and authentication
2//!
3//! This module provides comprehensive user management functionality for Redis Cloud,
4//! including user creation, role assignment, password management, and multi-factor
5//! authentication configuration.
6//!
7//! # Overview
8//!
9//! Users in Redis Cloud can have different roles and permissions that control their
10//! access to subscriptions, databases, and account settings. The system supports both
11//! local users and SSO/SAML integrated users.
12//!
13//! # User Roles
14//!
15//! - **Owner**: Full administrative access to all resources
16//! - **Manager**: Can manage subscriptions and databases
17//! - **Viewer**: Read-only access to resources
18//! - **Billing Admin**: Access to billing and payment information
19//! - **Custom Roles**: Organization-specific roles with custom permissions
20//!
21//! # Key Features
22//!
23//! - **User Lifecycle**: Create, update, delete, and invite users
24//! - **Role Management**: Assign and modify user roles
25//! - **Password Policies**: Enforce password complexity and rotation
26//! - **MFA Support**: Two-factor authentication configuration
27//! - **API Access**: Manage programmatic access for users
28//! - **Audit Trail**: Track user actions and changes
29//!
30//! # Example Usage
31//!
32//! ```no_run
33//! use redis_cloud::{CloudClient, UserHandler};
34//!
35//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
36//! let client = CloudClient::builder()
37//! .api_key("your-api-key")
38//! .api_secret("your-api-secret")
39//! .build()?;
40//!
41//! let handler = UserHandler::new(client);
42//!
43//! // List all users
44//! let users = handler.get_all_users().await?;
45//!
46//! // Get specific user details (user ID 123)
47//! let user = handler.get_user_by_id(123).await?;
48//! # Ok(())
49//! # }
50//! ```
51
52use crate::types::Link;
53pub use crate::types::TaskStateUpdate;
54use crate::{CloudClient, Result};
55use serde::{Deserialize, Serialize};
56
57// ============================================================================
58// Models
59// ============================================================================
60
61/// User update request
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct AccountUserUpdateRequest {
65 /// User ID being updated. Server-populated from the path.
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub user_id: Option<i32>,
68
69 /// The account user's name.
70 pub name: String,
71
72 /// Changes the account user's role.
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub role: Option<String>,
75
76 /// Read-only on the response; populated by the server with the
77 /// operation type (e.g. `"UPDATE_ACCOUNT_USER"`).
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub command_type: Option<String>,
80}
81
82/// Account users response
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct AccountUsers {
85 /// Account ID the users belong to.
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub account: Option<i32>,
88
89 /// List of users
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub users: Option<Vec<AccountUser>>,
92
93 /// HATEOAS links
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub links: Option<Vec<Link>>,
96}
97
98/// User options information
99#[derive(Debug, Clone, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct AccountUserOptions {
102 /// Whether the user has access to billing information.
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub billing: Option<bool>,
105
106 /// Whether the user receives email alerts.
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub email_alerts: Option<bool>,
109
110 /// Whether the user receives operational/maintenance emails.
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub operational_emails: Option<bool>,
113
114 /// Whether multi-factor authentication is enabled for this user.
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub mfa_enabled: Option<bool>,
117}
118
119/// User information
120#[derive(Debug, Clone, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct AccountUser {
123 /// Unique user ID.
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub id: Option<i32>,
126
127 /// User's display name.
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub name: Option<String>,
130
131 /// User's email address (also the login identifier).
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub email: Option<String>,
134
135 /// User's role (e.g. `"Owner"`, `"Manager"`, `"Viewer"`, `"Billing Admin"`).
136 #[serde(skip_serializing_if = "Option::is_none")]
137 pub role: Option<String>,
138
139 /// Timestamp the user signed up (ISO-8601).
140 #[serde(skip_serializing_if = "Option::is_none")]
141 pub sign_up: Option<String>,
142
143 /// User type (e.g. `"local"`, `"saml"`).
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub user_type: Option<String>,
146
147 /// Whether the user has at least one API key configured.
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub has_api_key: Option<bool>,
150
151 /// Notification and access option flags for this user.
152 /// See [`AccountUserOptions`].
153 #[serde(skip_serializing_if = "Option::is_none")]
154 pub options: Option<AccountUserOptions>,
155
156 /// HATEOAS links
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub links: Option<Vec<Link>>,
159}
160
161// ============================================================================
162// Handler
163// ============================================================================
164
165/// Handler for user management operations
166///
167/// Manages user accounts, roles, permissions, invitations,
168/// and authentication settings including MFA configuration.
169pub struct UsersHandler {
170 client: CloudClient,
171}
172
173impl UsersHandler {
174 /// Create a new handler
175 #[must_use]
176 pub fn new(client: CloudClient) -> Self {
177 Self { client }
178 }
179
180 /// Get users
181 /// Gets a list of all account users.
182 ///
183 /// GET /users
184 pub async fn get_all_users(&self) -> Result<AccountUsers> {
185 self.client.get("/users").await
186 }
187
188 /// Delete user
189 /// Deletes a user from this account.
190 ///
191 /// DELETE /users/{userId}
192 pub async fn delete_user_by_id(&self, user_id: i32) -> Result<TaskStateUpdate> {
193 let response = self.client.delete_raw(&format!("/users/{user_id}")).await?;
194 serde_json::from_value(response).map_err(Into::into)
195 }
196
197 /// Get a single user
198 /// Gets details about a single account user.
199 ///
200 /// GET /users/{userId}
201 pub async fn get_user_by_id(&self, user_id: i32) -> Result<AccountUser> {
202 self.client.get(&format!("/users/{user_id}")).await
203 }
204
205 /// Update a user
206 /// Updates an account user's name or role.
207 ///
208 /// PUT /users/{userId}
209 pub async fn update_user(
210 &self,
211 user_id: i32,
212 request: &AccountUserUpdateRequest,
213 ) -> Result<TaskStateUpdate> {
214 self.client.put(&format!("/users/{user_id}"), request).await
215 }
216
217 // ============================================================================
218 // Simplified aliases
219 // ============================================================================
220
221 /// List users (simplified)
222 ///
223 /// Alias for [`get_all_users`](Self::get_all_users).
224 ///
225 /// # Example
226 ///
227 /// ```no_run
228 /// use redis_cloud::CloudClient;
229 ///
230 /// # async fn example() -> redis_cloud::Result<()> {
231 /// let client = CloudClient::builder()
232 /// .api_key("your-api-key")
233 /// .api_secret("your-api-secret")
234 /// .build()?;
235 ///
236 /// let users = client.users().list().await?;
237 /// # Ok(())
238 /// # }
239 /// ```
240 pub async fn list(&self) -> Result<AccountUsers> {
241 self.get_all_users().await
242 }
243
244 /// Get a user by ID (simplified)
245 ///
246 /// Alias for [`get_user_by_id`](Self::get_user_by_id).
247 ///
248 /// # Arguments
249 ///
250 /// * `user_id` - The user ID
251 ///
252 /// # Example
253 ///
254 /// ```no_run
255 /// use redis_cloud::CloudClient;
256 ///
257 /// # async fn example() -> redis_cloud::Result<()> {
258 /// let client = CloudClient::builder()
259 /// .api_key("your-api-key")
260 /// .api_secret("your-api-secret")
261 /// .build()?;
262 ///
263 /// let user = client.users().get(123).await?;
264 /// # Ok(())
265 /// # }
266 /// ```
267 pub async fn get(&self, user_id: i32) -> Result<AccountUser> {
268 self.get_user_by_id(user_id).await
269 }
270
271 /// Update a user (simplified)
272 ///
273 /// Alias for [`update_user`](Self::update_user).
274 ///
275 /// # Arguments
276 ///
277 /// * `user_id` - The user ID
278 /// * `request` - The user update request
279 ///
280 /// # Example
281 ///
282 /// ```no_run
283 /// use redis_cloud::CloudClient;
284 /// use redis_cloud::users::AccountUserUpdateRequest;
285 ///
286 /// # async fn example() -> redis_cloud::Result<()> {
287 /// let client = CloudClient::builder()
288 /// .api_key("your-api-key")
289 /// .api_secret("your-api-secret")
290 /// .build()?;
291 ///
292 /// let request = AccountUserUpdateRequest {
293 /// user_id: None,
294 /// name: "Updated Name".to_string(),
295 /// role: Some("Manager".to_string()),
296 /// command_type: None,
297 /// };
298 ///
299 /// let task = client.users().update(123, &request).await?;
300 /// # Ok(())
301 /// # }
302 /// ```
303 pub async fn update(
304 &self,
305 user_id: i32,
306 request: &AccountUserUpdateRequest,
307 ) -> Result<TaskStateUpdate> {
308 self.update_user(user_id, request).await
309 }
310
311 /// Delete a user (simplified)
312 ///
313 /// Alias for [`delete_user_by_id`](Self::delete_user_by_id).
314 ///
315 /// # Arguments
316 ///
317 /// * `user_id` - The user ID
318 ///
319 /// # Example
320 ///
321 /// ```no_run
322 /// use redis_cloud::CloudClient;
323 ///
324 /// # async fn example() -> redis_cloud::Result<()> {
325 /// let client = CloudClient::builder()
326 /// .api_key("your-api-key")
327 /// .api_secret("your-api-secret")
328 /// .build()?;
329 ///
330 /// let task = client.users().delete(123).await?;
331 /// # Ok(())
332 /// # }
333 /// ```
334 pub async fn delete(&self, user_id: i32) -> Result<TaskStateUpdate> {
335 self.delete_user_by_id(user_id).await
336 }
337}