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