Skip to main content

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}