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