1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct UserId(String);
10
11impl UserId {
12 pub fn new(id: impl Into<String>) -> Self {
14 Self(id.into())
15 }
16
17 pub fn from_email(email: &str) -> Self {
19 Self(email.to_lowercase())
20 }
21
22 pub fn random() -> Self {
24 Self(Uuid::new_v4().to_string())
25 }
26
27 pub fn as_str(&self) -> &str {
29 &self.0
30 }
31}
32
33impl std::fmt::Display for UserId {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 write!(f, "{}", self.0)
36 }
37}
38
39impl From<String> for UserId {
40 fn from(s: String) -> Self {
41 Self(s)
42 }
43}
44
45impl From<&str> for UserId {
46 fn from(s: &str) -> Self {
47 Self(s.to_string())
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
53pub enum UserRole {
54 Admin,
56 #[default]
58 User,
59 Limited,
61 ReadOnly,
63 Custom(String),
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct User {
70 pub id: UserId,
72 pub email: Option<String>,
74 pub name: Option<String>,
76 pub role: UserRole,
78 pub enabled: bool,
80 pub oauth_provider: Option<String>,
82 pub oauth_subject: Option<String>,
84 pub groups: Vec<String>,
86 pub static_ip: Option<crate::VpnAddress>,
88 pub custom_routes: Vec<crate::Route>,
90 pub max_sessions: u32,
92 pub created_at: DateTime<Utc>,
94 pub last_login: Option<DateTime<Utc>>,
96 pub expires_at: Option<DateTime<Utc>>,
98 pub metadata: std::collections::HashMap<String, String>,
100}
101
102impl User {
103 pub fn new(id: UserId) -> Self {
105 Self {
106 id,
107 email: None,
108 name: None,
109 role: UserRole::default(),
110 enabled: true,
111 oauth_provider: None,
112 oauth_subject: None,
113 groups: vec![],
114 static_ip: None,
115 custom_routes: vec![],
116 max_sessions: 3,
117 created_at: Utc::now(),
118 last_login: None,
119 expires_at: None,
120 metadata: std::collections::HashMap::new(),
121 }
122 }
123
124 pub fn from_oauth(
126 provider: &str,
127 subject: &str,
128 email: Option<&str>,
129 name: Option<&str>,
130 groups: Vec<String>,
131 ) -> Self {
132 let id = email
133 .map(UserId::from_email)
134 .unwrap_or_else(|| UserId::new(format!("{}:{}", provider, subject)));
135
136 Self {
137 id,
138 email: email.map(String::from),
139 name: name.map(String::from),
140 role: UserRole::User,
141 enabled: true,
142 oauth_provider: Some(provider.to_string()),
143 oauth_subject: Some(subject.to_string()),
144 groups,
145 static_ip: None,
146 custom_routes: vec![],
147 max_sessions: 3,
148 created_at: Utc::now(),
149 last_login: Some(Utc::now()),
150 expires_at: None,
151 metadata: std::collections::HashMap::new(),
152 }
153 }
154
155 pub fn is_expired(&self) -> bool {
157 self.expires_at
158 .map(|exp| Utc::now() > exp)
159 .unwrap_or(false)
160 }
161
162 pub fn can_connect(&self) -> bool {
164 self.enabled && !self.is_expired()
165 }
166
167 pub fn in_group(&self, group: &str) -> bool {
169 self.groups.iter().any(|g| g == group)
170 }
171
172 pub fn is_admin(&self) -> bool {
174 matches!(self.role, UserRole::Admin)
175 }
176
177 pub fn record_login(&mut self) {
179 self.last_login = Some(Utc::now());
180 }
181
182 pub fn with_email(mut self, email: &str) -> Self {
184 self.email = Some(email.to_string());
185 self
186 }
187
188 pub fn with_name(mut self, name: &str) -> Self {
190 self.name = Some(name.to_string());
191 self
192 }
193
194 pub fn with_role(mut self, role: UserRole) -> Self {
196 self.role = role;
197 self
198 }
199
200 pub fn add_group(&mut self, group: &str) {
202 if !self.in_group(group) {
203 self.groups.push(group.to_string());
204 }
205 }
206
207 pub fn remove_group(&mut self, group: &str) {
209 self.groups.retain(|g| g != group);
210 }
211}
212
213#[async_trait::async_trait]
215pub trait UserStore: Send + Sync {
216 async fn get_user(&self, id: &UserId) -> Option<User>;
218
219 async fn get_user_by_email(&self, email: &str) -> Option<User>;
221
222 async fn get_user_by_oauth(&self, provider: &str, subject: &str) -> Option<User>;
224
225 async fn upsert_user(&self, user: &User) -> crate::Result<()>;
227
228 async fn delete_user(&self, id: &UserId) -> crate::Result<()>;
230
231 async fn list_users(&self) -> Vec<User>;
233
234 async fn get_users_in_group(&self, group: &str) -> Vec<User>;
236}
237
238pub struct MemoryUserStore {
240 users: parking_lot::RwLock<std::collections::HashMap<UserId, User>>,
241}
242
243impl MemoryUserStore {
244 pub fn new() -> Self {
246 Self {
247 users: parking_lot::RwLock::new(std::collections::HashMap::new()),
248 }
249 }
250}
251
252impl Default for MemoryUserStore {
253 fn default() -> Self {
254 Self::new()
255 }
256}
257
258#[async_trait::async_trait]
259impl UserStore for MemoryUserStore {
260 async fn get_user(&self, id: &UserId) -> Option<User> {
261 self.users.read().get(id).cloned()
262 }
263
264 async fn get_user_by_email(&self, email: &str) -> Option<User> {
265 let email_lower = email.to_lowercase();
266 self.users
267 .read()
268 .values()
269 .find(|u| u.email.as_ref().map(|e| e.to_lowercase()) == Some(email_lower.clone()))
270 .cloned()
271 }
272
273 async fn get_user_by_oauth(&self, provider: &str, subject: &str) -> Option<User> {
274 self.users
275 .read()
276 .values()
277 .find(|u| {
278 u.oauth_provider.as_deref() == Some(provider)
279 && u.oauth_subject.as_deref() == Some(subject)
280 })
281 .cloned()
282 }
283
284 async fn upsert_user(&self, user: &User) -> crate::Result<()> {
285 self.users.write().insert(user.id.clone(), user.clone());
286 Ok(())
287 }
288
289 async fn delete_user(&self, id: &UserId) -> crate::Result<()> {
290 self.users.write().remove(id);
291 Ok(())
292 }
293
294 async fn list_users(&self) -> Vec<User> {
295 self.users.read().values().cloned().collect()
296 }
297
298 async fn get_users_in_group(&self, group: &str) -> Vec<User> {
299 self.users
300 .read()
301 .values()
302 .filter(|u| u.in_group(group))
303 .cloned()
304 .collect()
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn test_user_creation() {
314 let user = User::new(UserId::from_email("test@example.com"))
315 .with_email("test@example.com")
316 .with_name("Test User")
317 .with_role(UserRole::Admin);
318
319 assert!(user.is_admin());
320 assert!(user.can_connect());
321 assert_eq!(user.email, Some("test@example.com".to_string()));
322 }
323
324 #[test]
325 fn test_user_groups() {
326 let mut user = User::new(UserId::new("test"));
327
328 user.add_group("developers");
329 user.add_group("vpn-users");
330
331 assert!(user.in_group("developers"));
332 assert!(user.in_group("vpn-users"));
333 assert!(!user.in_group("admins"));
334
335 user.remove_group("developers");
336 assert!(!user.in_group("developers"));
337 }
338
339 #[tokio::test]
340 async fn test_memory_user_store() {
341 let store = MemoryUserStore::new();
342
343 let user = User::new(UserId::from_email("test@example.com"))
344 .with_email("test@example.com");
345
346 store.upsert_user(&user).await.unwrap();
347
348 let found = store.get_user_by_email("test@example.com").await;
349 assert!(found.is_some());
350 assert_eq!(found.unwrap().id, user.id);
351 }
352}