1use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
7use serde::{Deserialize, Serialize};
8use std::collections::HashSet;
9use std::time::{SystemTime, UNIX_EPOCH};
10use uuid::Uuid;
11
12#[derive(Debug, thiserror::Error)]
14pub enum AuthError {
15 #[error("Invalid credentials")]
16 InvalidCredentials,
17
18 #[error("Invalid token: {0}")]
19 InvalidToken(String),
20
21 #[error("Token expired")]
22 TokenExpired,
23
24 #[error("Insufficient permissions")]
25 InsufficientPermissions,
26
27 #[error("User not found")]
28 UserNotFound,
29
30 #[error("Hashing error: {0}")]
31 HashError(String),
32
33 #[error("JWT error: {0}")]
34 JwtError(#[from] jsonwebtoken::errors::Error),
35}
36
37pub type AuthResult<T> = Result<T, AuthError>;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
41#[serde(rename_all = "lowercase")]
42pub enum Role {
43 Admin,
45 User,
47 ReadOnly,
49}
50
51impl Role {
52 pub fn has_permission(&self, required: Role) -> bool {
54 matches!(
55 (self, required),
56 (Role::Admin, _)
57 | (Role::User, Role::User)
58 | (Role::User, Role::ReadOnly)
59 | (Role::ReadOnly, Role::ReadOnly)
60 )
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
66#[serde(rename_all = "snake_case")]
67pub enum Permission {
68 BlockRead,
70 BlockWrite,
71 BlockDelete,
72
73 SemanticIndex,
75 SemanticSearch,
76
77 LogicRead,
79 LogicWrite,
80
81 NetworkRead,
83 NetworkWrite,
84
85 SystemRead,
87 SystemWrite,
88 SystemAdmin,
89}
90
91impl Permission {
92 pub fn for_role(role: Role) -> HashSet<Permission> {
94 match role {
95 Role::Admin => {
96 vec![
98 Permission::BlockRead,
99 Permission::BlockWrite,
100 Permission::BlockDelete,
101 Permission::SemanticIndex,
102 Permission::SemanticSearch,
103 Permission::LogicRead,
104 Permission::LogicWrite,
105 Permission::NetworkRead,
106 Permission::NetworkWrite,
107 Permission::SystemRead,
108 Permission::SystemWrite,
109 Permission::SystemAdmin,
110 ]
111 .into_iter()
112 .collect()
113 }
114 Role::User => {
115 vec![
117 Permission::BlockRead,
118 Permission::BlockWrite,
119 Permission::SemanticIndex,
120 Permission::SemanticSearch,
121 Permission::LogicRead,
122 Permission::LogicWrite,
123 Permission::NetworkRead,
124 Permission::SystemRead,
125 ]
126 .into_iter()
127 .collect()
128 }
129 Role::ReadOnly => {
130 vec![
132 Permission::BlockRead,
133 Permission::SemanticSearch,
134 Permission::LogicRead,
135 Permission::NetworkRead,
136 Permission::SystemRead,
137 ]
138 .into_iter()
139 .collect()
140 }
141 }
142 }
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct User {
148 pub id: Uuid,
150 pub username: String,
152 #[serde(skip_serializing)]
154 pub password_hash: String,
155 pub role: Role,
157 pub custom_permissions: HashSet<Permission>,
159 pub active: bool,
161 pub created_at: u64,
163}
164
165impl User {
166 pub fn new(username: String, password: &str, role: Role) -> AuthResult<Self> {
168 let password_hash = bcrypt::hash(password, bcrypt::DEFAULT_COST)
169 .map_err(|e| AuthError::HashError(e.to_string()))?;
170
171 Ok(Self {
172 id: Uuid::new_v4(),
173 username,
174 password_hash,
175 role,
176 custom_permissions: HashSet::new(),
177 active: true,
178 created_at: SystemTime::now()
179 .duration_since(UNIX_EPOCH)
180 .unwrap()
181 .as_secs(),
182 })
183 }
184
185 pub fn verify_password(&self, password: &str) -> AuthResult<bool> {
187 bcrypt::verify(password, &self.password_hash)
188 .map_err(|e| AuthError::HashError(e.to_string()))
189 }
190
191 pub fn permissions(&self) -> HashSet<Permission> {
193 let mut perms = Permission::for_role(self.role);
194 perms.extend(self.custom_permissions.iter().copied());
195 perms
196 }
197
198 pub fn has_permission(&self, permission: Permission) -> bool {
200 self.permissions().contains(&permission)
201 }
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct Claims {
207 pub sub: String,
209 pub username: String,
211 pub role: Role,
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub scope: Option<String>,
216 pub iat: u64,
218 pub exp: u64,
220}
221
222impl Claims {
223 pub fn new(user: &User, expiration_hours: u64) -> Self {
225 let now = SystemTime::now()
226 .duration_since(UNIX_EPOCH)
227 .unwrap()
228 .as_secs();
229
230 Self {
231 sub: user.id.to_string(),
232 username: user.username.clone(),
233 role: user.role,
234 scope: None,
235 iat: now,
236 exp: now + (expiration_hours * 3600),
237 }
238 }
239
240 pub fn new_with_scopes(sub: &str, scope: &str, expiration_hours: usize) -> Self {
242 let now = SystemTime::now()
243 .duration_since(UNIX_EPOCH)
244 .unwrap()
245 .as_secs();
246
247 Self {
248 sub: sub.to_string(),
249 username: sub.to_string(), role: Role::User, scope: Some(scope.to_string()),
252 iat: now,
253 exp: now + ((expiration_hours as u64) * 3600),
254 }
255 }
256
257 pub fn is_expired(&self) -> bool {
259 let now = SystemTime::now()
260 .duration_since(UNIX_EPOCH)
261 .unwrap()
262 .as_secs();
263 now > self.exp
264 }
265}
266
267#[derive(Clone)]
269pub struct JwtManager {
270 encoding_key: EncodingKey,
271 decoding_key: DecodingKey,
272 validation: Validation,
273}
274
275impl JwtManager {
276 pub fn new(secret: &[u8]) -> Self {
278 let mut validation = Validation::new(Algorithm::HS256);
279 validation.validate_exp = true;
280
281 Self {
282 encoding_key: EncodingKey::from_secret(secret),
283 decoding_key: DecodingKey::from_secret(secret),
284 validation,
285 }
286 }
287
288 pub fn generate_token(&self, user: &User, expiration_hours: u64) -> AuthResult<String> {
290 let claims = Claims::new(user, expiration_hours);
291 let token = encode(&Header::default(), &claims, &self.encoding_key)?;
292 Ok(token)
293 }
294
295 pub fn generate_token_with_scopes(
297 &self,
298 sub: &str,
299 scope: &str,
300 expiration_hours: usize,
301 ) -> AuthResult<String> {
302 let claims = Claims::new_with_scopes(sub, scope, expiration_hours);
303 let token = encode(&Header::default(), &claims, &self.encoding_key)?;
304 Ok(token)
305 }
306
307 pub fn validate_token(&self, token: &str) -> AuthResult<Claims> {
309 let token_data = decode::<Claims>(token, &self.decoding_key, &self.validation)?;
310
311 if token_data.claims.is_expired() {
312 return Err(AuthError::TokenExpired);
313 }
314
315 Ok(token_data.claims)
316 }
317}
318
319#[derive(Clone)]
321pub struct UserStore {
322 users: dashmap::DashMap<String, User>,
323}
324
325impl UserStore {
326 pub fn new() -> Self {
328 Self {
329 users: dashmap::DashMap::new(),
330 }
331 }
332
333 pub fn add_user(&self, user: User) -> AuthResult<()> {
335 if self.users.contains_key(&user.username) {
336 return Err(AuthError::InvalidCredentials);
337 }
338 self.users.insert(user.username.clone(), user);
339 Ok(())
340 }
341
342 pub fn get_user(&self, username: &str) -> AuthResult<User> {
344 self.users
345 .get(username)
346 .map(|entry| entry.clone())
347 .ok_or(AuthError::UserNotFound)
348 }
349
350 pub fn get_by_id(&self, user_id: &uuid::Uuid) -> AuthResult<User> {
352 for entry in self.users.iter() {
353 let user = entry.value();
354 if user.id == *user_id {
355 return Ok(user.clone());
356 }
357 }
358 Err(AuthError::UserNotFound)
359 }
360
361 pub fn authenticate(&self, username: &str, password: &str) -> AuthResult<User> {
363 let user = self.get_user(username)?;
364
365 if !user.active {
366 return Err(AuthError::InvalidCredentials);
367 }
368
369 if !user.verify_password(password)? {
370 return Err(AuthError::InvalidCredentials);
371 }
372
373 Ok(user)
374 }
375
376 pub fn update_permissions(
378 &self,
379 username: &str,
380 permissions: HashSet<Permission>,
381 ) -> AuthResult<()> {
382 self.users
383 .get_mut(username)
384 .map(|mut entry| {
385 entry.custom_permissions = permissions;
386 })
387 .ok_or(AuthError::UserNotFound)
388 }
389
390 pub fn deactivate_user(&self, username: &str) -> AuthResult<()> {
392 self.users
393 .get_mut(username)
394 .map(|mut entry| {
395 entry.active = false;
396 })
397 .ok_or(AuthError::UserNotFound)
398 }
399}
400
401impl Default for UserStore {
402 fn default() -> Self {
403 Self::new()
404 }
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize)]
409pub struct ApiKey {
410 pub id: Uuid,
412 #[serde(skip_serializing)]
414 pub key_hash: String,
415 pub prefix: String,
417 pub user_id: Uuid,
419 pub name: String,
421 pub created_at: u64,
423 pub last_used_at: Option<u64>,
425 pub active: bool,
427}
428
429impl ApiKey {
430 pub fn new(user_id: Uuid, name: String) -> AuthResult<(Self, String)> {
432 let key_id = Uuid::new_v4();
433
434 let key_bytes: [u8; 32] = rand::random();
436 let raw_key = format!("ipfrs_{}", hex::encode(key_bytes));
437
438 let key_hash = bcrypt::hash(&raw_key, bcrypt::DEFAULT_COST)
440 .map_err(|e| AuthError::HashError(e.to_string()))?;
441
442 let prefix = raw_key.chars().take(12).collect();
444
445 let api_key = Self {
446 id: key_id,
447 key_hash,
448 prefix,
449 user_id,
450 name,
451 created_at: SystemTime::now()
452 .duration_since(UNIX_EPOCH)
453 .unwrap()
454 .as_secs(),
455 last_used_at: None,
456 active: true,
457 };
458
459 Ok((api_key, raw_key))
460 }
461
462 pub fn verify(&self, key: &str) -> AuthResult<bool> {
464 bcrypt::verify(key, &self.key_hash).map_err(|e| AuthError::HashError(e.to_string()))
465 }
466
467 pub fn mark_used(&mut self) {
469 self.last_used_at = Some(
470 SystemTime::now()
471 .duration_since(UNIX_EPOCH)
472 .unwrap()
473 .as_secs(),
474 );
475 }
476}
477
478#[derive(Clone)]
480pub struct ApiKeyStore {
481 keys: dashmap::DashMap<Uuid, ApiKey>,
482 user_keys: dashmap::DashMap<Uuid, Vec<Uuid>>, }
484
485impl ApiKeyStore {
486 pub fn new() -> Self {
488 Self {
489 keys: dashmap::DashMap::new(),
490 user_keys: dashmap::DashMap::new(),
491 }
492 }
493
494 pub fn add_key(&self, key: ApiKey) -> AuthResult<()> {
496 let user_id = key.user_id;
497 let key_id = key.id;
498
499 self.keys.insert(key_id, key);
500
501 self.user_keys.entry(user_id).or_default().push(key_id);
503
504 Ok(())
505 }
506
507 pub fn get_key(&self, key_id: &Uuid) -> AuthResult<ApiKey> {
509 self.keys
510 .get(key_id)
511 .map(|entry| entry.clone())
512 .ok_or(AuthError::InvalidCredentials)
513 }
514
515 pub fn authenticate(&self, key: &str) -> AuthResult<(ApiKey, Uuid)> {
517 let prefix: String = key.chars().take(12).collect();
519
520 for entry in self.keys.iter() {
522 let api_key = entry.value();
523 if api_key.prefix == prefix && api_key.active && api_key.verify(key)? {
524 let mut key_mut = self.keys.get_mut(&api_key.id).unwrap();
526 key_mut.mark_used();
527
528 return Ok((api_key.clone(), api_key.user_id));
529 }
530 }
531
532 Err(AuthError::InvalidCredentials)
533 }
534
535 pub fn list_user_keys(&self, user_id: &Uuid) -> Vec<ApiKey> {
537 if let Some(key_ids) = self.user_keys.get(user_id) {
538 key_ids
539 .iter()
540 .filter_map(|id| self.keys.get(id).map(|e| e.clone()))
541 .collect()
542 } else {
543 Vec::new()
544 }
545 }
546
547 pub fn revoke_key(&self, key_id: &Uuid) -> AuthResult<()> {
549 self.keys
550 .get_mut(key_id)
551 .map(|mut entry| {
552 entry.active = false;
553 })
554 .ok_or(AuthError::InvalidCredentials)
555 }
556
557 pub fn delete_key(&self, key_id: &Uuid) -> AuthResult<()> {
559 if let Some((_, key)) = self.keys.remove(key_id) {
560 if let Some(mut user_keys) = self.user_keys.get_mut(&key.user_id) {
562 user_keys.retain(|id| id != key_id);
563 }
564 Ok(())
565 } else {
566 Err(AuthError::InvalidCredentials)
567 }
568 }
569}
570
571impl Default for ApiKeyStore {
572 fn default() -> Self {
573 Self::new()
574 }
575}
576
577#[derive(Clone)]
579pub struct AuthState {
580 pub jwt_manager: JwtManager,
581 pub user_store: UserStore,
582 pub api_key_store: ApiKeyStore,
583}
584
585impl AuthState {
586 pub fn new(secret: &[u8]) -> Self {
588 Self {
589 jwt_manager: JwtManager::new(secret),
590 user_store: UserStore::new(),
591 api_key_store: ApiKeyStore::new(),
592 }
593 }
594
595 pub fn with_default_admin(secret: &[u8], admin_password: &str) -> AuthResult<Self> {
597 let state = Self::new(secret);
598
599 let admin = User::new("admin".to_string(), admin_password, Role::Admin)?;
601 state.user_store.add_user(admin)?;
602
603 Ok(state)
604 }
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610
611 #[test]
612 fn test_role_permissions() {
613 assert!(Role::Admin.has_permission(Role::User));
614 assert!(Role::Admin.has_permission(Role::ReadOnly));
615 assert!(Role::User.has_permission(Role::ReadOnly));
616 assert!(!Role::ReadOnly.has_permission(Role::User));
617 }
618
619 #[test]
620 fn test_user_creation() {
621 let user = User::new("test".to_string(), "password123", Role::User).unwrap();
622 assert_eq!(user.username, "test");
623 assert_eq!(user.role, Role::User);
624 assert!(user.active);
625 assert!(user.verify_password("password123").unwrap());
626 assert!(!user.verify_password("wrong").unwrap());
627 }
628
629 #[test]
630 fn test_user_permissions() {
631 let user = User::new("test".to_string(), "password123", Role::User).unwrap();
632 assert!(user.has_permission(Permission::BlockRead));
633 assert!(user.has_permission(Permission::BlockWrite));
634 assert!(!user.has_permission(Permission::BlockDelete));
635 assert!(!user.has_permission(Permission::SystemAdmin));
636 }
637
638 #[test]
639 fn test_jwt_generation_and_validation() {
640 let secret = b"test_secret_key_32_bytes_long!!!";
641 let manager = JwtManager::new(secret);
642 let user = User::new("test".to_string(), "password123", Role::User).unwrap();
643
644 let token = manager.generate_token(&user, 24).unwrap();
645 let claims = manager.validate_token(&token).unwrap();
646
647 assert_eq!(claims.username, "test");
648 assert_eq!(claims.role, Role::User);
649 assert!(!claims.is_expired());
650 }
651
652 #[test]
653 fn test_user_store() {
654 let store = UserStore::new();
655 let user = User::new("test".to_string(), "password123", Role::User).unwrap();
656
657 store.add_user(user).unwrap();
658
659 let authenticated = store.authenticate("test", "password123").unwrap();
660 assert_eq!(authenticated.username, "test");
661
662 assert!(store.authenticate("test", "wrong").is_err());
663 assert!(store.authenticate("nonexistent", "password123").is_err());
664 }
665}