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::{CloudClient, Result};
53use serde::{Deserialize, Serialize};
54use serde_json::Value;
55use std::collections::HashMap;
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    #[serde(skip_serializing_if = "Option::is_none")]
66    pub user_id: Option<i32>,
67
68    /// The account user's name.
69    pub name: String,
70
71    /// Changes the account user's role. See [Team management roles](https://redis.io/docs/latest/operate/rc/security/access-control/access-management/#team-management-roles) to learn about available account roles.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub role: Option<String>,
74
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub command_type: Option<String>,
77
78    /// Additional fields from the API
79    #[serde(flatten)]
80    pub extra: Value,
81}
82
83/// RedisLabs list of users in current account
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct AccountUsers {
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub account: Option<i32>,
88
89    /// Additional fields from the API
90    #[serde(flatten)]
91    pub extra: Value,
92}
93
94/// ProcessorResponse
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct ProcessorResponse {
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub resource_id: Option<i32>,
100
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub additional_resource_id: Option<i32>,
103
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub resource: Option<HashMap<String, Value>>,
106
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub error: Option<String>,
109
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub additional_info: Option<String>,
112
113    /// Additional fields from the API
114    #[serde(flatten)]
115    pub extra: Value,
116}
117
118/// RedisLabs User options information
119#[derive(Debug, Clone, Serialize, Deserialize)]
120#[serde(rename_all = "camelCase")]
121pub struct AccountUserOptions {
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub billing: Option<bool>,
124
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub email_alerts: Option<bool>,
127
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub operational_emails: Option<bool>,
130
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub mfa_enabled: Option<bool>,
133
134    /// Additional fields from the API
135    #[serde(flatten)]
136    pub extra: Value,
137}
138
139/// TaskStateUpdate
140#[derive(Debug, Clone, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub struct TaskStateUpdate {
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub task_id: Option<String>,
145
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub command_type: Option<String>,
148
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub status: Option<String>,
151
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub description: Option<String>,
154
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub timestamp: Option<String>,
157
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub response: Option<ProcessorResponse>,
160
161    /// HATEOAS links
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub links: Option<Vec<HashMap<String, Value>>>,
164
165    /// Additional fields from the API
166    #[serde(flatten)]
167    pub extra: Value,
168}
169
170/// RedisLabs User information
171#[derive(Debug, Clone, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct AccountUser {
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub id: Option<i32>,
176
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub name: Option<String>,
179
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub email: Option<String>,
182
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub role: Option<String>,
185
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub sign_up: Option<String>,
188
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub user_type: Option<String>,
191
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub has_api_key: Option<bool>,
194
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub options: Option<AccountUserOptions>,
197
198    /// Additional fields from the API
199    #[serde(flatten)]
200    pub extra: Value,
201}
202
203// ============================================================================
204// Handler
205// ============================================================================
206
207/// Handler for user management operations
208///
209/// Manages user accounts, roles, permissions, invitations,
210/// and authentication settings including MFA configuration.
211pub struct UsersHandler {
212    client: CloudClient,
213}
214
215impl UsersHandler {
216    /// Create a new handler
217    pub fn new(client: CloudClient) -> Self {
218        Self { client }
219    }
220
221    /// Get users
222    /// Gets a list of all account users.
223    ///
224    /// GET /users
225    pub async fn get_all_users(&self) -> Result<AccountUsers> {
226        self.client.get("/users").await
227    }
228
229    /// Delete user
230    /// Deletes a user from this account.
231    ///
232    /// DELETE /users/{userId}
233    pub async fn delete_user_by_id(&self, user_id: i32) -> Result<TaskStateUpdate> {
234        let response = self
235            .client
236            .delete_raw(&format!("/users/{}", user_id))
237            .await?;
238        serde_json::from_value(response).map_err(Into::into)
239    }
240
241    /// Get a single user
242    /// Gets details about a single account user.
243    ///
244    /// GET /users/{userId}
245    pub async fn get_user_by_id(&self, user_id: i32) -> Result<AccountUser> {
246        self.client.get(&format!("/users/{}", user_id)).await
247    }
248
249    /// Update a user
250    /// Updates an account user's name or role.
251    ///
252    /// PUT /users/{userId}
253    pub async fn update_user(
254        &self,
255        user_id: i32,
256        request: &AccountUserUpdateRequest,
257    ) -> Result<TaskStateUpdate> {
258        self.client
259            .put(&format!("/users/{}", user_id), request)
260            .await
261    }
262}