Skip to main content

redis_enterprise/
users.rs

1//! Users management for Redis Enterprise
2//!
3//! ## Overview
4//! - List and query resources
5//! - Create and update configurations
6//! - Monitor status and metrics
7//!
8//! See [`UserHandler`] for the full API. For a worked CRUD example use
9//! [`CreateUserRequest`] alongside `client.users().create(...)`.
10
11use crate::client::RestClient;
12use crate::error::Result;
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use std::collections::BTreeMap;
16use typed_builder::TypedBuilder;
17
18/// User information
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[non_exhaustive]
21pub struct User {
22    /// Unique identifier (read-only).
23    pub uid: u32,
24    /// User's email address (used as login identifier) - was incorrectly named 'username'
25    pub email: String,
26    /// User's display name
27    pub name: Option<String>,
28    /// User's role
29    pub role: String,
30    /// User status (e.g., "active")
31    pub status: Option<String>,
32    /// Authentication method (e.g., "regular")
33    pub auth_method: Option<String>,
34    /// Certificate subject line for certificate auth
35    pub certificate_subject_line: Option<String>,
36    /// Password issue date
37    pub password_issue_date: Option<String>,
38    /// Whether user receives email alerts
39    pub email_alerts: Option<bool>,
40    /// List of role UIDs
41    pub role_uids: Option<Vec<u32>>,
42    /// Database IDs for alerts
43    pub bdbs: Option<Vec<u32>>,
44    /// Alert for audit database connections
45    pub alert_audit_db_conns: Option<bool>,
46    /// Alert for BDB backup
47    pub alert_bdb_backup: Option<bool>,
48    /// Alert for BDB CRDT source syncer
49    pub alert_bdb_crdt_src_syncer: Option<bool>,
50    /// Password expiration duration in seconds
51    pub password_expiration_duration: Option<u32>,
52    /// UNIX timestamp of the user's last successful authentication.
53    pub last_login: Option<u64>,
54    /// Additive or version-specific user fields.
55    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
56    pub additional_fields: BTreeMap<String, Value>,
57}
58
59/// Create user request
60///
61/// # Examples
62///
63/// ```rust,no_run
64/// use redis_enterprise::CreateUserRequest;
65///
66/// let request = CreateUserRequest::builder()
67///     .email("john.doe@example.com")
68///     .password("secure-password-123")
69///     .role("db_admin") // Or use role_uids([...]) on RBAC-enabled clusters
70///     .name("John Doe")
71///     .email_alerts(true)
72///     .build();
73/// ```
74#[derive(Debug, Serialize, TypedBuilder)]
75pub struct CreateUserRequest {
76    /// User's email address (required, used as login)
77    #[builder(setter(into))]
78    pub email: String,
79    /// User's password (required)
80    #[builder(setter(into))]
81    pub password: String,
82    /// User's role for non-RBAC clusters. For RBAC-enabled clusters, use role_uids instead.
83    /// Exactly one of role or role_uids must be provided.
84    #[serde(skip_serializing_if = "Option::is_none")]
85    #[builder(default, setter(into, strip_option))]
86    pub role: Option<String>,
87    /// User's full name
88    #[serde(skip_serializing_if = "Option::is_none")]
89    #[builder(default, setter(into, strip_option))]
90    pub name: Option<String>,
91    /// Whether user should receive email alerts
92    #[serde(skip_serializing_if = "Option::is_none")]
93    #[builder(default, setter(strip_option))]
94    pub email_alerts: Option<bool>,
95    /// Database IDs for which the user should receive email alerts
96    #[serde(skip_serializing_if = "Option::is_none")]
97    #[builder(default, setter(strip_option))]
98    pub bdbs_email_alerts: Option<Vec<String>>,
99    /// Role IDs for RBAC-enabled clusters
100    /// Exactly one of role or role_uids must be provided.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    #[builder(default, setter(strip_option))]
103    pub role_uids: Option<Vec<u32>>,
104    /// Authentication method (e.g., "regular")
105    #[serde(skip_serializing_if = "Option::is_none")]
106    #[builder(default, setter(into, strip_option))]
107    pub auth_method: Option<String>,
108}
109
110/// Update user request
111///
112/// # Examples
113///
114/// ```rust,no_run
115/// use redis_enterprise::UpdateUserRequest;
116///
117/// let request = UpdateUserRequest::builder()
118///     .password("new-secure-password")
119///     .email_alerts(false)
120///     .build();
121/// ```
122#[derive(Debug, Serialize, TypedBuilder)]
123pub struct UpdateUserRequest {
124    /// New password for the user
125    #[serde(skip_serializing_if = "Option::is_none")]
126    #[builder(default, setter(into, strip_option))]
127    pub password: Option<String>,
128    /// Update user's role
129    #[serde(skip_serializing_if = "Option::is_none")]
130    #[builder(default, setter(into, strip_option))]
131    pub role: Option<String>,
132    /// Update user's email address
133    #[serde(skip_serializing_if = "Option::is_none")]
134    #[builder(default, setter(into, strip_option))]
135    pub email: Option<String>,
136    /// Update user's full name
137    #[serde(skip_serializing_if = "Option::is_none")]
138    #[builder(default, setter(into, strip_option))]
139    pub name: Option<String>,
140    /// Update email alerts preference
141    #[serde(skip_serializing_if = "Option::is_none")]
142    #[builder(default, setter(strip_option))]
143    pub email_alerts: Option<bool>,
144    /// Update database IDs for email alerts
145    #[serde(skip_serializing_if = "Option::is_none")]
146    #[builder(default, setter(strip_option))]
147    pub bdbs_email_alerts: Option<Vec<String>>,
148    /// Update role IDs for RBAC-enabled clusters
149    #[serde(skip_serializing_if = "Option::is_none")]
150    #[builder(default, setter(strip_option))]
151    pub role_uids: Option<Vec<u32>>,
152    /// Update authentication method
153    #[serde(skip_serializing_if = "Option::is_none")]
154    #[builder(default, setter(into, strip_option))]
155    pub auth_method: Option<String>,
156}
157
158/// Role information
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct Role {
161    /// Unique identifier (read-only).
162    pub uid: u32,
163    /// Name.
164    pub name: String,
165    /// Management permission level (e.g. `"admin"`, `"db_member"`).
166    pub management: Option<String>,
167    /// Data access permission level.
168    pub data_access: Option<String>,
169}
170
171/// User handler for managing users
172pub struct UserHandler {
173    client: RestClient,
174}
175
176/// Alias for backwards compatibility and intuitive plural naming
177pub type UsersHandler = UserHandler;
178
179impl UserHandler {
180    /// Create a new handler bound to the given REST client.
181    pub fn new(client: RestClient) -> Self {
182        UserHandler { client }
183    }
184
185    /// List all users
186    pub async fn list(&self) -> Result<Vec<User>> {
187        self.client.get("/v1/users").await
188    }
189
190    /// Get specific user
191    pub async fn get(&self, uid: u32) -> Result<User> {
192        self.client.get(&format!("/v1/users/{}", uid)).await
193    }
194
195    /// Create new user
196    pub async fn create(&self, request: CreateUserRequest) -> Result<User> {
197        let has_role = request
198            .role
199            .as_deref()
200            .map(|role| !role.trim().is_empty())
201            .unwrap_or(false);
202        let has_role_uids = request
203            .role_uids
204            .as_ref()
205            .map(|role_uids| !role_uids.is_empty())
206            .unwrap_or(false);
207
208        if has_role == has_role_uids {
209            return Err(crate::error::RestError::ValidationError(
210                "CreateUserRequest must include exactly one of role or role_uids".to_string(),
211            ));
212        }
213
214        self.client.post("/v1/users", &request).await
215    }
216
217    /// Update user
218    pub async fn update(&self, uid: u32, request: UpdateUserRequest) -> Result<User> {
219        self.client
220            .put(&format!("/v1/users/{}", uid), &request)
221            .await
222    }
223
224    /// Delete user
225    pub async fn delete(&self, uid: u32) -> Result<()> {
226        self.client.delete(&format!("/v1/users/{}", uid)).await
227    }
228
229    /// Get permissions - GET /v1/users/permissions (raw)
230    pub async fn permissions(&self) -> Result<Value> {
231        self.client.get("/v1/users/permissions").await
232    }
233
234    /// Get permission detail - GET /v1/users/permissions/{perm} (raw)
235    pub async fn permission_detail(&self, perm: &str) -> Result<Value> {
236        self.client
237            .get(&format!("/v1/users/permissions/{}", perm))
238            .await
239    }
240
241    /// Authorize user (login) - POST /v1/users/authorize (raw)
242    pub async fn authorize(&self, body: AuthRequest) -> Result<AuthResponse> {
243        self.client.post("/v1/users/authorize", &body).await
244    }
245
246    /// Set password - POST /v1/users/password (raw)
247    pub async fn password_set(&self, body: PasswordSet) -> Result<()> {
248        self.client.post_action("/v1/users/password", &body).await
249    }
250
251    /// Update password - PUT /v1/users/password (raw)
252    pub async fn password_update(&self, body: PasswordUpdate) -> Result<()> {
253        self.client.put("/v1/users/password", &body).await
254    }
255
256    /// Delete password - DELETE /v1/users/password
257    pub async fn password_delete(&self) -> Result<()> {
258        self.client.delete("/v1/users/password").await
259    }
260
261    /// Refresh JWT - POST /v1/users/refresh_jwt (raw)
262    pub async fn refresh_jwt(&self, body: JwtRefreshRequest) -> Result<JwtRefreshResponse> {
263        self.client.post("/v1/users/refresh_jwt", &body).await
264    }
265}
266
267/// Request body for `POST /v1/users/authorize` (user login).
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct AuthRequest {
270    /// Email address (used as login identifier).
271    pub email: String,
272    /// Password.
273    pub password: String,
274}
275
276/// Response from `POST /v1/users/authorize` containing the issued JWT.
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct AuthResponse {
279    /// Encoded JWT.
280    pub jwt: String,
281    /// Expiration timestamp (ISO-8601).
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub expires_at: Option<String>,
284}
285
286/// Request body for `POST /v1/users/password` (set a user's password).
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct PasswordSet {
289    /// Email address (used as login identifier).
290    pub email: String,
291    /// Password.
292    pub password: String,
293}
294
295/// Request body for `PUT /v1/users/password` (update a user's password).
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct PasswordUpdate {
298    /// Current password (required for self-service password change).
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub current_password: Option<String>,
301    /// New password.
302    pub new_password: String,
303}
304
305/// Request body for `POST /v1/users/refresh_jwt`.
306#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct JwtRefreshRequest {
308    /// Encoded JWT.
309    pub jwt: String,
310}
311
312/// Response from `POST /v1/users/refresh_jwt` containing the new JWT.
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct JwtRefreshResponse {
315    /// Encoded JWT.
316    pub jwt: String,
317    /// Expiration timestamp (ISO-8601).
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub expires_at: Option<String>,
320}
321
322/// Role handler for managing roles
323pub struct RoleHandler {
324    client: RestClient,
325}
326
327impl RoleHandler {
328    /// Create a new handler bound to the given REST client.
329    pub fn new(client: RestClient) -> Self {
330        RoleHandler { client }
331    }
332
333    /// List all roles
334    pub async fn list(&self) -> Result<Vec<Role>> {
335        self.client.get("/v1/roles").await
336    }
337
338    /// Get specific role
339    pub async fn get(&self, uid: u32) -> Result<Role> {
340        self.client.get(&format!("/v1/roles/{}", uid)).await
341    }
342}