1use std::future::Future;
2use std::pin::Pin;
3
4use crate::error::AuthError;
5use crate::handle::AllowThem;
6use crate::types::{PermissionName, RoleName, SessionToken, User, UserId};
7
8pub type AuthFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, AuthError>> + Send + 'a>>;
10
11pub trait AuthClient: Send + Sync {
21 fn validate_session<'a>(&'a self, token: &'a SessionToken) -> AuthFuture<'a, Option<User>>;
26
27 fn check_role<'a>(&'a self, user_id: &'a UserId, role: &'a RoleName) -> AuthFuture<'a, bool>;
29
30 fn check_permission<'a>(
32 &'a self,
33 user_id: &'a UserId,
34 permission: &'a PermissionName,
35 ) -> AuthFuture<'a, bool>;
36
37 fn resolve_highest_role<'a>(
41 &'a self,
42 user_id: &'a UserId,
43 hierarchy: &'a [&str],
44 ) -> AuthFuture<'a, Option<String>>;
45
46 fn logout<'a>(&'a self, token: &'a SessionToken) -> AuthFuture<'a, ()>;
48
49 fn login_url(&self) -> &str;
51
52 fn session_cookie_name(&self) -> &str;
54}
55
56pub struct EmbeddedAuthClient {
62 ath: AllowThem,
63 login_url: String,
64}
65
66impl EmbeddedAuthClient {
67 pub fn new(ath: AllowThem, login_url: impl Into<String>) -> Self {
72 Self {
73 ath,
74 login_url: login_url.into(),
75 }
76 }
77}
78
79impl AuthClient for EmbeddedAuthClient {
80 fn validate_session<'a>(&'a self, token: &'a SessionToken) -> AuthFuture<'a, Option<User>> {
81 Box::pin(async move {
82 let ttl = self.ath.session_config().ttl;
83 let session = match self.ath.db().validate_session(token, ttl).await? {
84 Some(s) => s,
85 None => return Ok(None),
86 };
87 match self.ath.db().get_user(session.user_id).await {
88 Ok(user) if user.is_active => Ok(Some(user)),
89 Ok(_) => Ok(None), Err(AuthError::NotFound) => Ok(None), Err(e) => Err(e),
92 }
93 })
94 }
95
96 fn check_role<'a>(&'a self, user_id: &'a UserId, role: &'a RoleName) -> AuthFuture<'a, bool> {
97 Box::pin(async move { self.ath.db().has_role(user_id, role).await })
98 }
99
100 fn check_permission<'a>(
101 &'a self,
102 user_id: &'a UserId,
103 permission: &'a PermissionName,
104 ) -> AuthFuture<'a, bool> {
105 Box::pin(async move { self.ath.db().has_permission(user_id, permission).await })
106 }
107
108 fn resolve_highest_role<'a>(
109 &'a self,
110 user_id: &'a UserId,
111 hierarchy: &'a [&str],
112 ) -> AuthFuture<'a, Option<String>> {
113 Box::pin(async move { self.ath.db().resolve_highest_role(user_id, hierarchy).await })
114 }
115
116 fn logout<'a>(&'a self, token: &'a SessionToken) -> AuthFuture<'a, ()> {
117 Box::pin(async move {
118 let _ = self.ath.db().delete_session(token).await?;
119 Ok(())
120 })
121 }
122
123 fn login_url(&self) -> &str {
124 &self.login_url
125 }
126
127 fn session_cookie_name(&self) -> &str {
128 self.ath.session_config().cookie_name
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use std::sync::Arc;
135
136 use chrono::{Duration, Utc};
137
138 use super::*;
139 use crate::handle::AllowThemBuilder;
140 use crate::sessions::{generate_token, hash_token};
141 use crate::types::{Email, PermissionName, RoleName};
142
143 async fn setup() -> EmbeddedAuthClient {
144 let ath = AllowThemBuilder::new("sqlite::memory:")
145 .cookie_secure(false)
146 .build()
147 .await
148 .unwrap();
149 EmbeddedAuthClient::new(ath, "/login")
150 }
151
152 #[tokio::test]
153 async fn validate_session_valid_token_returns_user() {
154 let client = setup().await;
155 let email = Email::new("valid@example.com".into()).unwrap();
156 let user = client
157 .ath
158 .db()
159 .create_user(email, "password123", None, None)
160 .await
161 .unwrap();
162
163 let token = generate_token();
164 let token_hash = hash_token(&token);
165 let expires = Utc::now() + Duration::hours(24);
166 client
167 .ath
168 .db()
169 .create_session(user.id, token_hash, None, None, expires)
170 .await
171 .unwrap();
172
173 let result = client.validate_session(&token).await.unwrap();
174 assert!(result.is_some());
175 assert_eq!(result.unwrap().email.as_str(), "valid@example.com");
176 }
177
178 #[tokio::test]
179 async fn validate_session_expired_token_returns_none() {
180 let client = setup().await;
181 let email = Email::new("expired@example.com".into()).unwrap();
182 let user = client
183 .ath
184 .db()
185 .create_user(email, "password123", None, None)
186 .await
187 .unwrap();
188
189 let token = generate_token();
190 let token_hash = hash_token(&token);
191 let expires = Utc::now() - Duration::hours(1);
192 client
193 .ath
194 .db()
195 .create_session(user.id, token_hash, None, None, expires)
196 .await
197 .unwrap();
198
199 let result = client.validate_session(&token).await.unwrap();
200 assert!(result.is_none());
201 }
202
203 #[tokio::test]
204 async fn validate_session_invalid_token_returns_none() {
205 let client = setup().await;
206 let token = generate_token();
207 let result = client.validate_session(&token).await.unwrap();
208 assert!(result.is_none());
209 }
210
211 #[tokio::test]
212 async fn validate_session_inactive_user_returns_none() {
213 let client = setup().await;
214 let email = Email::new("inactive@example.com".into()).unwrap();
215 let user = client
216 .ath
217 .db()
218 .create_user(email, "password123", None, None)
219 .await
220 .unwrap();
221
222 let token = generate_token();
223 let token_hash = hash_token(&token);
224 let expires = Utc::now() + Duration::hours(24);
225 client
226 .ath
227 .db()
228 .create_session(user.id, token_hash, None, None, expires)
229 .await
230 .unwrap();
231
232 client
233 .ath
234 .db()
235 .update_user_active(user.id, false)
236 .await
237 .unwrap();
238
239 let result = client.validate_session(&token).await.unwrap();
240 assert!(result.is_none());
241 }
242
243 #[tokio::test]
244 async fn validate_session_deleted_user_returns_none() {
245 let client = setup().await;
246 let email = Email::new("deleted@example.com".into()).unwrap();
247 let user = client
248 .ath
249 .db()
250 .create_user(email, "password123", None, None)
251 .await
252 .unwrap();
253
254 let token = generate_token();
255 let token_hash = hash_token(&token);
256 let expires = Utc::now() + Duration::hours(24);
257 client
258 .ath
259 .db()
260 .create_session(user.id, token_hash, None, None, expires)
261 .await
262 .unwrap();
263
264 client.ath.db().delete_user(user.id).await.unwrap();
265
266 let result = client.validate_session(&token).await.unwrap();
267 assert!(result.is_none());
268 }
269
270 #[tokio::test]
271 async fn check_role_returns_true_when_assigned() {
272 let client = setup().await;
273 let email = Email::new("roleuser@example.com".into()).unwrap();
274 let user = client
275 .ath
276 .db()
277 .create_user(email, "password123", None, None)
278 .await
279 .unwrap();
280
281 let rn = RoleName::new("admin");
282 let role = client.ath.db().create_role(&rn, None).await.unwrap();
283 client
284 .ath
285 .db()
286 .assign_role(&user.id, &role.id)
287 .await
288 .unwrap();
289
290 let result = client.check_role(&user.id, &rn).await.unwrap();
291 assert!(result);
292 }
293
294 #[tokio::test]
295 async fn check_role_returns_false_when_not_assigned() {
296 let client = setup().await;
297 let email = Email::new("norole@example.com".into()).unwrap();
298 let user = client
299 .ath
300 .db()
301 .create_user(email, "password123", None, None)
302 .await
303 .unwrap();
304
305 let rn = RoleName::new("admin");
306 let result = client.check_role(&user.id, &rn).await.unwrap();
307 assert!(!result);
308 }
309
310 #[tokio::test]
311 async fn check_permission_returns_true_direct() {
312 let client = setup().await;
313 let email = Email::new("permdirect@example.com".into()).unwrap();
314 let user = client
315 .ath
316 .db()
317 .create_user(email, "password123", None, None)
318 .await
319 .unwrap();
320
321 let pn = PermissionName::new("posts:write");
322 let perm = client.ath.db().create_permission(&pn, None).await.unwrap();
323 client
324 .ath
325 .db()
326 .assign_permission_to_user(&user.id, &perm.id)
327 .await
328 .unwrap();
329
330 let result = client.check_permission(&user.id, &pn).await.unwrap();
331 assert!(result);
332 }
333
334 #[tokio::test]
335 async fn check_permission_returns_true_via_role() {
336 let client = setup().await;
337 let email = Email::new("permviarole@example.com".into()).unwrap();
338 let user = client
339 .ath
340 .db()
341 .create_user(email, "password123", None, None)
342 .await
343 .unwrap();
344
345 let rn = RoleName::new("editor");
346 let role = client.ath.db().create_role(&rn, None).await.unwrap();
347
348 let pn = PermissionName::new("posts:read");
349 let perm = client.ath.db().create_permission(&pn, None).await.unwrap();
350 client
351 .ath
352 .db()
353 .assign_permission_to_role(&role.id, &perm.id)
354 .await
355 .unwrap();
356
357 client
358 .ath
359 .db()
360 .assign_role(&user.id, &role.id)
361 .await
362 .unwrap();
363
364 let result = client.check_permission(&user.id, &pn).await.unwrap();
365 assert!(result);
366 }
367
368 #[tokio::test]
369 async fn check_permission_returns_false_when_missing() {
370 let client = setup().await;
371 let email = Email::new("noperm@example.com".into()).unwrap();
372 let user = client
373 .ath
374 .db()
375 .create_user(email, "password123", None, None)
376 .await
377 .unwrap();
378
379 let pn = PermissionName::new("posts:delete");
380 let result = client.check_permission(&user.id, &pn).await.unwrap();
381 assert!(!result);
382 }
383
384 #[tokio::test]
385 async fn logout_deletes_session() {
386 let client = setup().await;
387 let email = Email::new("logout@example.com".into()).unwrap();
388 let user = client
389 .ath
390 .db()
391 .create_user(email, "password123", None, None)
392 .await
393 .unwrap();
394
395 let token = generate_token();
396 let token_hash = hash_token(&token);
397 let expires = Utc::now() + Duration::hours(24);
398 client
399 .ath
400 .db()
401 .create_session(user.id, token_hash, None, None, expires)
402 .await
403 .unwrap();
404
405 client.logout(&token).await.unwrap();
406
407 let result = client.validate_session(&token).await.unwrap();
408 assert!(result.is_none());
409 }
410
411 #[tokio::test]
412 async fn logout_nonexistent_token_succeeds() {
413 let client = setup().await;
414 let token = generate_token();
415 let result = client.logout(&token).await;
416 assert!(result.is_ok());
417 }
418
419 #[tokio::test]
420 async fn login_url_returns_configured_path() {
421 let ath = AllowThemBuilder::new("sqlite::memory:")
422 .build()
423 .await
424 .unwrap();
425 let client = EmbeddedAuthClient::new(ath, "/login");
426 assert_eq!(client.login_url(), "/login");
427 }
428
429 #[tokio::test]
430 async fn session_cookie_name_returns_config_name() {
431 let ath = AllowThemBuilder::new("sqlite::memory:")
432 .build()
433 .await
434 .unwrap();
435 let client = EmbeddedAuthClient::new(ath, "/login");
436 assert_eq!(client.session_cookie_name(), "allowthem_session");
437
438 let ath_custom = AllowThemBuilder::new("sqlite::memory:")
439 .cookie_name("my_session")
440 .build()
441 .await
442 .unwrap();
443 let client_custom = EmbeddedAuthClient::new(ath_custom, "/login");
444 assert_eq!(client_custom.session_cookie_name(), "my_session");
445 }
446
447 #[tokio::test]
449 async fn works_as_arc_dyn_auth_client() {
450 let ath = AllowThemBuilder::new("sqlite::memory:")
451 .build()
452 .await
453 .unwrap();
454 let _client: Arc<dyn AuthClient> = Arc::new(EmbeddedAuthClient::new(ath, "/login"));
455 }
456
457 #[tokio::test]
458 async fn resolve_highest_role_returns_correct_role_via_trait() {
459 let client = setup().await;
460 let email = Email::new("roletest@example.com".into()).unwrap();
461 let user = client
462 .ath
463 .db()
464 .create_user(email, "password123", None, None)
465 .await
466 .unwrap();
467 let roles = client
468 .ath
469 .db()
470 .bootstrap_roles(&["admin", "editor"])
471 .await
472 .unwrap();
473 client
474 .ath
475 .db()
476 .assign_role(&user.id, &roles[1].id) .await
478 .unwrap();
479 let result = client
480 .resolve_highest_role(&user.id, &["admin", "editor"])
481 .await
482 .unwrap();
483 assert_eq!(result, Some("editor".to_owned()));
484 }
485
486 #[tokio::test]
487 async fn resolve_highest_role_returns_none_via_trait() {
488 let client = setup().await;
489 let email = Email::new("noroletest@example.com".into()).unwrap();
490 let user = client
491 .ath
492 .db()
493 .create_user(email, "password123", None, None)
494 .await
495 .unwrap();
496 let result = client
497 .resolve_highest_role(&user.id, &["admin", "editor"])
498 .await
499 .unwrap();
500 assert!(result.is_none());
501 }
502
503 #[tokio::test]
504 async fn resolve_highest_role_works_as_arc_dyn() {
505 let client = setup().await;
506 let email = Email::new("arcdyn@example.com".into()).unwrap();
507 let user = client
508 .ath
509 .db()
510 .create_user(email, "password123", None, None)
511 .await
512 .unwrap();
513 let roles = client
514 .ath
515 .db()
516 .bootstrap_roles(&["admin", "editor"])
517 .await
518 .unwrap();
519 client
520 .ath
521 .db()
522 .assign_role(&user.id, &roles[0].id) .await
524 .unwrap();
525 let arc_client: Arc<dyn AuthClient> = Arc::new(client);
526 let result = arc_client
527 .resolve_highest_role(&user.id, &["admin", "editor"])
528 .await
529 .unwrap();
530 assert_eq!(result, Some("admin".to_owned()));
531 }
532}