1use crate::error::AdminError;
2use async_trait::async_trait;
3use std::{
4 collections::HashMap,
5 sync::{Arc, RwLock},
6};
7use uuid::Uuid;
8
9#[derive(Debug, Clone)]
10pub struct AdminUser {
11 pub username: String,
12 pub session_id: String,
13 pub is_superuser: bool,
15}
16
17impl AdminUser {
18 pub fn superuser(username: &str, session_id: &str) -> Self {
19 Self {
20 username: username.to_string(),
21 session_id: session_id.to_string(),
22 is_superuser: true,
23 }
24 }
25}
26
27#[async_trait]
28pub trait AdminAuth: Send + Sync {
29 async fn authenticate(
30 &self,
31 username: &str,
32 password: &str,
33 ) -> Result<AdminUser, AdminError>;
34
35 async fn get_session(&self, session_id: &str) -> Result<Option<AdminUser>, AdminError>;
36}
37
38pub struct DefaultAdminAuth {
40 credentials: Arc<RwLock<HashMap<String, String>>>,
41 sessions: Arc<RwLock<HashMap<String, AdminUser>>>,
42}
43
44impl DefaultAdminAuth {
45 pub fn new() -> Self {
46 Self {
47 credentials: Arc::new(RwLock::new(HashMap::new())),
48 sessions: Arc::new(RwLock::new(HashMap::new())),
49 }
50 }
51
52 pub fn add_user(self, username: &str, password: &str) -> Self {
53 let hash = bcrypt::hash(password, bcrypt::DEFAULT_COST)
54 .expect("bcrypt hash failed");
55 self.credentials
56 .write()
57 .unwrap()
58 .insert(username.to_string(), hash);
59 self
60 }
61}
62
63impl Default for DefaultAdminAuth {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69#[async_trait]
70impl AdminAuth for DefaultAdminAuth {
71 async fn authenticate(&self, username: &str, password: &str) -> Result<AdminUser, AdminError> {
72 let hash = {
73 let creds = self.credentials.read().unwrap();
74 creds.get(username).cloned()
75 };
76
77 let hash = hash.ok_or(AdminError::Unauthorized)?;
78
79 let valid = bcrypt::verify(password, &hash).unwrap_or(false);
80 if !valid {
81 return Err(AdminError::Unauthorized);
82 }
83
84 let session_id = Uuid::new_v4().to_string();
85 let user = AdminUser {
86 username: username.to_string(),
87 session_id: session_id.clone(),
88 is_superuser: true,
89 };
90
91 self.sessions
92 .write()
93 .unwrap()
94 .insert(session_id, user.clone());
95
96 Ok(user)
97 }
98
99 async fn get_session(&self, session_id: &str) -> Result<Option<AdminUser>, AdminError> {
100 let sessions = self.sessions.read().unwrap();
101 Ok(sessions.get(session_id).cloned())
102 }
103}
104
105#[cfg(feature = "seaorm")]
111pub async fn check_permission(
112 user: &AdminUser,
113 required: &Option<String>,
114 enforcer: Option<&std::sync::Arc<tokio::sync::RwLock<casbin::Enforcer>>>,
115) -> bool {
116 use casbin::CoreApi;
117 if user.is_superuser {
118 return true;
119 }
120 let enforcer = match enforcer {
121 Some(e) => e,
122 None => return required.is_none(),
124 };
125 let perm = match required {
127 None => return false,
128 Some(p) => p,
129 };
130 let parts: Vec<&str> = perm.splitn(2, '.').collect();
131 let (obj, act) = if parts.len() == 2 {
132 (parts[0], parts[1])
133 } else {
134 (perm.as_str(), "")
135 };
136 let guard = enforcer.read().await;
137 guard.enforce((user.username.as_str(), obj, act)).unwrap_or(false)
138}
139
140#[cfg(feature = "seaorm")]
143pub async fn check_entity_permission(
144 user: &AdminUser,
145 entity_name: &str,
146 action: &str,
147 required: &Option<String>,
148 enforcer: Option<&std::sync::Arc<tokio::sync::RwLock<casbin::Enforcer>>>,
149) -> bool {
150 use casbin::CoreApi;
151 if user.is_superuser {
152 return true;
153 }
154 let enforcer = match enforcer {
155 Some(e) => e,
156 None => return required.is_none(),
157 };
158 let perm_owned;
160 let perm = match required {
161 Some(p) => p.as_str(),
162 None => {
163 perm_owned = format!("{}.{}", entity_name, action);
164 &perm_owned
165 }
166 };
167 let parts: Vec<&str> = perm.splitn(2, '.').collect();
168 let (obj, act) = if parts.len() == 2 {
169 (parts[0], parts[1])
170 } else {
171 (perm, "")
172 };
173 let guard = enforcer.read().await;
174 guard.enforce((user.username.as_str(), obj, act)).unwrap_or(false)
175}
176
177#[cfg(not(feature = "seaorm"))]
178pub fn check_permission(
179 user: &AdminUser,
180 required: &Option<String>,
181 _enforcer: Option<&()>,
182) -> bool {
183 match required {
184 None => true,
185 Some(_) => user.is_superuser,
186 }
187}