1use super::sql_utils::escape_sql_string;
2use anyhow::{anyhow, Result};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
6pub struct User {
7 pub name: String,
8 pub password: Option<String>,
10 pub update_password: Option<bool>,
12 pub roles: Vec<String>,
13}
14
15impl User {
16 fn format_username(name: &str) -> Result<String> {
18 if name.is_empty() {
19 return Err(anyhow!("Username cannot be empty"));
20 }
21
22 if !name.chars().all(|c| c.is_alphanumeric() || c == '_') {
24 return Err(anyhow!("Username '{}' contains invalid characters. Only alphanumeric and underscore allowed.", name));
25 }
26
27 if name.chars().next().unwrap().is_numeric() {
29 return Err(anyhow!("Username '{}' cannot start with a number", name));
30 }
31
32 Ok(name.to_string())
33 }
34
35 pub fn to_sql_create(&self) -> Result<String> {
36 let username = Self::format_username(&self.name)?;
37 let password = match &self.password {
38 Some(p) => format!(" WITH PASSWORD '{}'", escape_sql_string(p)),
39 None => "".to_string(),
40 };
41
42 Ok(format!("CREATE USER {}{};", username, password))
43 }
44
45 pub fn to_sql_update(&self) -> Result<String> {
46 let username = Self::format_username(&self.name)?;
47 let password = match &self.password {
48 Some(p) => format!(" WITH PASSWORD '{}'", escape_sql_string(p)),
49 None => "".to_string(),
50 };
51
52 Ok(format!("ALTER USER {}{};", username, password))
53 }
54
55 pub fn to_sql_drop(&self) -> Result<String> {
56 let username = Self::format_username(&self.name)?;
57 Ok(format!("DROP USER IF EXISTS {};", username))
58 }
59
60 pub fn validate(&self) -> Result<()> {
61 Self::format_username(&self.name)?;
63 Ok(())
64 }
65
66 pub fn get_name(&self) -> &str {
67 &self.name
68 }
69
70 pub fn get_password(&self) -> &str {
71 self.password.as_ref().map(|s| s.as_str()).unwrap_or("")
72 }
73
74 pub fn get_roles(&self) -> &[String] {
75 &self.roles
76 }
77}
78
79#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn test_user_to_sql_create() {
86 let user = User {
87 name: "test".to_string(),
88 password: Some("test".to_string()),
89 update_password: Some(true),
90 roles: vec!["test".to_string()],
91 };
92
93 let sql = user.to_sql_create().unwrap();
94 assert_eq!(sql, "CREATE USER test WITH PASSWORD 'test';");
95 }
96
97 #[test]
98 fn test_user_to_sql_update() {
99 let user = User {
100 name: "test".to_string(),
101 password: Some("test".to_string()),
102 update_password: Some(true),
103 roles: vec!["test".to_string()],
104 };
105
106 let sql = user.to_sql_update().unwrap();
107 assert_eq!(sql, "ALTER USER test WITH PASSWORD 'test';");
108 }
109
110 #[test]
111 fn test_user_to_sql_drop() {
112 let user = User {
113 name: "test".to_string(),
114 password: Some("test".to_string()),
115 update_password: Some(true),
116 roles: vec!["test".to_string()],
117 };
118
119 let sql = user.to_sql_drop().unwrap();
120 assert_eq!(sql, "DROP USER IF EXISTS test;");
121 }
122
123 #[test]
124 fn test_sql_injection_prevention_password() {
125 let user = User {
126 name: "test".to_string(),
127 password: Some("'; DROP TABLE users; --".to_string()),
128 update_password: Some(true),
129 roles: vec!["test".to_string()],
130 };
131
132 let sql = user.to_sql_create().unwrap();
133 assert_eq!(
134 sql,
135 "CREATE USER test WITH PASSWORD '''; DROP TABLE users; --';"
136 );
137 }
138
139 #[test]
140 fn test_invalid_username_with_spaces() {
141 let user = User {
142 name: "test user".to_string(),
143 password: Some("password".to_string()),
144 update_password: Some(true),
145 roles: vec!["test".to_string()],
146 };
147
148 assert!(user.to_sql_create().is_err());
149 assert!(user.validate().is_err());
150 }
151
152 #[test]
153 fn test_invalid_username_starting_with_number() {
154 let user = User {
155 name: "1test".to_string(),
156 password: Some("password".to_string()),
157 update_password: Some(true),
158 roles: vec!["test".to_string()],
159 };
160
161 assert!(user.to_sql_create().is_err());
162 assert!(user.validate().is_err());
163 }
164
165 #[test]
166 fn test_user_validate() {
167 let user = User {
168 name: "test".to_string(),
169 password: Some("test".to_string()),
170 update_password: Some(true),
171 roles: vec!["test".to_string()],
172 };
173
174 assert!(user.validate().is_ok());
175 }
176
177 #[test]
178 fn test_user_validate_empty_name() {
179 let user = User {
180 name: "".to_string(),
181 password: Some("test".to_string()),
182 update_password: Some(true),
183 roles: vec!["test".to_string()],
184 };
185
186 assert!(user.validate().is_err());
187 }
188
189 #[test]
190 fn test_user_validate_empty_password() {
191 let user = User {
192 name: "test".to_string(),
193 password: None,
194 update_password: Some(true),
195 roles: vec!["test".to_string()],
196 };
197
198 assert!(user.validate().is_ok());
199 }
200
201 #[test]
202 fn test_user_validate_empty_roles() {
203 let user = User {
204 name: "test".to_string(),
205 password: Some("test".to_string()),
206 update_password: Some(true),
207 roles: vec![],
208 };
209
210 assert!(user.validate().is_ok());
211 }
212
213 #[test]
214 fn test_user_get_name() {
215 let user = User {
216 name: "test".to_string(),
217 password: Some("test".to_string()),
218 update_password: Some(true),
219 roles: vec!["test".to_string()],
220 };
221
222 assert_eq!(user.get_name(), "test");
223 }
224
225 #[test]
226 fn test_user_get_password() {
227 let user = User {
228 name: "test".to_string(),
229 password: Some("test".to_string()),
230 update_password: Some(true),
231 roles: vec!["test".to_string()],
232 };
233
234 assert_eq!(user.get_password(), "test");
235 }
236
237 #[test]
238 fn test_user_get_roles() {
239 let user = User {
240 name: "test".to_string(),
241 password: Some("test".to_string()),
242 update_password: Some(true),
243 roles: vec!["test".to_string()],
244 };
245
246 assert_eq!(user.get_roles(), vec!["test".to_string()]);
247 }
248}