1use crate::RTokenError;
2use crate::models::RTokenInfo;
3use chrono::Utc;
4use std::{
5 collections::HashMap,
6 sync::{Arc, Mutex},
7};
8
9#[derive(Clone, Default)]
30pub struct RTokenManager {
31 store: Arc<Mutex<HashMap<String, RTokenInfo>>>,
38}
39
40impl RTokenManager {
41 pub fn new() -> Self {
47 Self {
48 store: Arc::new(Mutex::new(HashMap::new())),
49 }
50 }
51
52 pub fn login(&self, id: &str, expire_time: u64) -> Result<String, RTokenError> {
68 let token = uuid::Uuid::new_v4().to_string();
69 let now = Utc::now();
74 let ttl = chrono::Duration::seconds(expire_time as i64);
75 let deadline = now + ttl;
76 let expire_time = deadline.timestamp_millis() as u64;
77 let info = RTokenInfo {
78 user_id: id.to_string(),
79 expire_at: expire_time,
80 roles: Vec::new(),
81 };
82 self.store
83 .lock()
84 .map_err(|_| RTokenError::MutexPoisoned)?
85 .insert(token.clone(), info);
86 Ok(token)
87 }
88
89 #[cfg(feature = "rbac")]
90 pub fn login_with_roles(
91 &self,
92 id: &str,
93 expire_time: u64,
94 role: impl Into<Vec<String>>,
95 ) -> Result<String, RTokenError> {
96 let token = uuid::Uuid::new_v4().to_string();
97 let now = Utc::now();
98 let ttl = chrono::Duration::seconds(expire_time as i64);
99 let deadline = now + ttl;
100 let expire_time = deadline.timestamp_millis() as u64;
101 let info = RTokenInfo {
102 user_id: id.to_string(),
103 expire_at: expire_time,
104 roles: role.into(),
105 };
106 self.store
107 .lock()
108 .map_err(|_| RTokenError::MutexPoisoned)?
109 .insert(token.clone(), info);
110 Ok(token)
111 }
112
113 #[cfg(feature = "rbac")]
115 pub fn set_roles(&self, token: &str, roles: impl Into<Vec<String>>) -> Result<(), RTokenError> {
116 let mut store = self.store.lock().map_err(|_| RTokenError::MutexPoisoned)?;
117 if let Some(info) = store.get_mut(token) {
118 info.roles = roles.into();
119 }
120 Ok(())
121 }
122
123 #[cfg(feature = "rbac")]
124 pub fn get_roles(&self, token: &str) -> Result<Option<Vec<String>>, RTokenError> {
125 let store = self.store.lock().map_err(|_| RTokenError::MutexPoisoned)?;
126 Ok(store.get(token).map(|info| info.roles.clone()))
127 }
128
129 pub fn logout(&self, token: &str) -> Result<(), RTokenError> {
141 self.store
143 .lock()
144 .map_err(|_| RTokenError::MutexPoisoned)?
145 .remove(token);
146 Ok(())
147 }
148
149 pub fn validate(&self, token: &str) -> Result<Option<String>, RTokenError> {
150 #[cfg(feature = "rbac")]
151 {
152 Ok(self
153 .validate_with_roles(token)?
154 .map(|(user_id, _roles)| user_id))
155 }
156
157 #[cfg(not(feature = "rbac"))]
158 {
159 let mut store = self.store.lock().map_err(|_| RTokenError::MutexPoisoned)?;
160 let Some(info) = store.get(token) else { return Ok(None) };
161
162 if info.expire_at < Utc::now().timestamp_millis() as u64 {
163 store.remove(token);
164 return Ok(None);
165 }
166
167 Ok(Some(info.user_id.clone()))
168 }
169 }
170
171 #[cfg(feature = "rbac")]
172 pub fn validate_with_roles(
173 &self,
174 token: &str,
175 ) -> Result<Option<(String, Vec<String>)>, RTokenError> {
176 let mut store = self.store.lock().map_err(|_| RTokenError::MutexPoisoned)?;
177 let Some(info) = store.get(token) else { return Ok(None) };
178
179 if info.expire_at < Utc::now().timestamp_millis() as u64 {
180 store.remove(token);
181 return Ok(None);
182 }
183
184 Ok(Some((info.user_id.clone(), info.roles.clone())))
185 }
186}
187
188#[cfg(feature = "actix")]
210#[derive(Debug)]
211pub struct RUser {
212 pub id: String,
218
219 pub token: String,
225 #[cfg(feature = "rbac")]
226 pub roles: Vec<String>,
227}
228
229#[cfg(feature = "rbac")]
230impl RUser {
231 pub fn has_role(&self, role: &str) -> bool {
232 self.roles.iter().any(|r| r == role)
233 }
234}
235
236#[cfg(feature = "actix")]
250impl actix_web::FromRequest for RUser {
251 type Error = actix_web::Error;
252 type Future = std::future::Ready<Result<Self, Self::Error>>;
253
254 fn from_request(
255 req: &actix_web::HttpRequest,
256 _payload: &mut actix_web::dev::Payload,
257 ) -> Self::Future {
258 use actix_web::web;
259
260 let manager = match req.app_data::<web::Data<RTokenManager>>() {
262 Some(m) => m,
263 None => {
264 return std::future::ready(Err(actix_web::error::ErrorInternalServerError(
265 "Token manager not found",
266 )));
267 }
268 };
269 let token = match crate::extract_token_from_request(req) {
270 Some(token) => token,
271 None => {
272 return std::future::ready(Err(actix_web::error::ErrorUnauthorized(
273 "Unauthorized",
274 )));
275 }
276 };
277
278 #[cfg(feature = "rbac")]
279 {
280 let user_info = match manager.validate_with_roles(&token) {
281 Ok(user_info) => user_info,
282 Err(_) => {
283 return std::future::ready(Err(actix_web::error::ErrorInternalServerError(
284 "Mutex poisoned",
285 )));
286 }
287 };
288
289 if let Some((user_id, roles)) = user_info {
290 return std::future::ready(Ok(RUser {
291 id: user_id,
292 token,
293 roles,
294 }));
295 }
296
297 std::future::ready(Err(actix_web::error::ErrorUnauthorized("Invalid token")))
298 }
299
300 #[cfg(not(feature = "rbac"))]
301 {
302 let user_id = match manager.validate(&token) {
303 Ok(user_id) => user_id,
304 Err(_) => {
305 return std::future::ready(Err(actix_web::error::ErrorInternalServerError(
306 "Mutex poisoned",
307 )));
308 }
309 };
310
311 if let Some(user_id) = user_id {
312 return std::future::ready(Ok(RUser { id: user_id, token }));
313 }
314
315 std::future::ready(Err(actix_web::error::ErrorUnauthorized("Invalid token")))
316 }
317 }
318}