1use std::collections::BTreeSet;
4
5use chrono::Utc;
6use uuid::Uuid;
7
8use crate::entities::{ApiKeyScope, NewRefreshToken, NewUser, Page, User};
9use crate::error::StoreError;
10use crate::store::StoreFuture;
11use crate::user_store::UserStore;
12
13use super::{InMemoryStore, StoredRefreshToken};
14
15impl UserStore for InMemoryStore {
16 fn create_user(&self, req: NewUser) -> StoreFuture<'_, User> {
17 Box::pin(async move {
18 let mut state = self.state.write().await;
19
20 let email_exists = state.users.values().any(|u| u.email == req.email);
21 if email_exists {
22 return Err(StoreError::DuplicateEmail(req.email));
23 }
24
25 let username_exists = state.users.values().any(|u| u.username == req.username);
26 if username_exists {
27 return Err(StoreError::DuplicateUsername(req.username));
28 }
29
30 let is_admin = req.is_admin.unwrap_or(state.users.is_empty());
33
34 let now = Utc::now();
35 let user = User {
36 id: Uuid::now_v7(),
37 email: req.email,
38 username: req.username,
39 password_hash: req.password_hash,
40 is_admin,
41 token_version: 0,
42 created_at: now,
43 updated_at: now,
44 };
45
46 state.users.insert(user.id, user.clone());
47 Ok(user)
48 })
49 }
50
51 fn find_user_by_email(&self, email: &str) -> StoreFuture<'_, Option<User>> {
52 let email = email.to_string();
53 Box::pin(async move {
54 let state = self.state.read().await;
55 Ok(state.users.values().find(|u| u.email == email).cloned())
56 })
57 }
58
59 fn find_user_by_username(&self, username: &str) -> StoreFuture<'_, Option<User>> {
60 let username = username.to_string();
61 Box::pin(async move {
62 let state = self.state.read().await;
63 Ok(state
64 .users
65 .values()
66 .find(|u| u.username == username)
67 .cloned())
68 })
69 }
70
71 fn find_user_by_id(&self, id: Uuid) -> StoreFuture<'_, Option<User>> {
72 Box::pin(async move {
73 let state = self.state.read().await;
74 Ok(state.users.get(&id).cloned())
75 })
76 }
77
78 fn count_users(&self) -> StoreFuture<'_, u64> {
79 Box::pin(async move {
80 let state = self.state.read().await;
81 Ok(state.users.len() as u64)
82 })
83 }
84
85 fn list_users(&self, page: u32, per_page: u32) -> StoreFuture<'_, Page<User>> {
86 Box::pin(async move {
87 let state = self.state.read().await;
88 let mut users: Vec<User> = state.users.values().cloned().collect();
89 users.sort_by_key(|u| std::cmp::Reverse(u.created_at));
90
91 let total = users.len() as u64;
92 let offset = ((page.saturating_sub(1)) as usize) * (per_page as usize);
93 let items: Vec<User> = users
94 .into_iter()
95 .skip(offset)
96 .take(per_page as usize)
97 .collect();
98
99 Ok(Page {
100 items,
101 total,
102 page,
103 per_page,
104 })
105 })
106 }
107
108 fn delete_user(&self, id: Uuid) -> StoreFuture<'_, ()> {
109 Box::pin(async move {
110 let mut state = self.state.write().await;
111 state
112 .users
113 .remove(&id)
114 .ok_or(StoreError::UserNotFound(id))?;
115 state.user_groups.remove(&id);
116 state.refresh_tokens.retain(|_, t| t.user_id != id);
117 state.api_keys.retain(|_, k| k.user_id != id);
119 Ok(())
120 })
121 }
122
123 fn update_user_role(&self, id: Uuid, is_admin: bool) -> StoreFuture<'_, User> {
124 Box::pin(async move {
125 let mut state = self.state.write().await;
126 let user = state
127 .users
128 .get_mut(&id)
129 .ok_or(StoreError::UserNotFound(id))?;
130 user.is_admin = is_admin;
131 user.token_version += 1;
132 user.updated_at = Utc::now();
133 let user = user.clone();
134 state.refresh_tokens.retain(|_, t| t.user_id != id);
135 if !is_admin {
136 let now = Utc::now();
137 let allowed = ApiKeyScope::member_allowed();
138 for key in state.api_keys.values_mut().filter(|k| k.user_id == id) {
139 let before = key.scopes.len();
140 key.scopes.retain(|s| allowed.contains(s));
141 if key.scopes.len() != before {
142 if key.scopes.is_empty() {
143 key.is_active = false;
144 }
145 key.updated_at = now;
146 }
147 }
148 }
149 Ok(user)
150 })
151 }
152
153 fn update_user_password(&self, id: Uuid, password_hash: String) -> StoreFuture<'_, ()> {
154 Box::pin(async move {
155 let mut state = self.state.write().await;
156 let user = state
157 .users
158 .get_mut(&id)
159 .ok_or(StoreError::UserNotFound(id))?;
160 user.password_hash = password_hash;
161 user.token_version += 1;
162 user.updated_at = Utc::now();
163 state.refresh_tokens.retain(|_, t| t.user_id != id);
164 Ok(())
165 })
166 }
167
168 fn list_user_groups(&self, user_id: Uuid) -> StoreFuture<'_, Vec<String>> {
169 Box::pin(async move {
170 let state = self.state.read().await;
171 Ok(state
172 .user_groups
173 .get(&user_id)
174 .map(|groups| groups.iter().cloned().collect())
175 .unwrap_or_default())
176 })
177 }
178
179 fn set_user_groups(&self, user_id: Uuid, groups: Vec<String>) -> StoreFuture<'_, Vec<String>> {
180 Box::pin(async move {
181 let mut state = self.state.write().await;
182 if !state.users.contains_key(&user_id) {
183 return Err(StoreError::UserNotFound(user_id));
184 }
185 let groups: BTreeSet<String> = groups.into_iter().collect();
186 let sorted: Vec<String> = groups.iter().cloned().collect();
187 if groups.is_empty() {
188 state.user_groups.remove(&user_id);
189 } else {
190 state.user_groups.insert(user_id, groups);
191 }
192 Ok(sorted)
193 })
194 }
195
196 fn revoke_user_sessions(&self, id: Uuid) -> StoreFuture<'_, i64> {
197 Box::pin(async move {
198 let mut state = self.state.write().await;
199 let user = state
200 .users
201 .get_mut(&id)
202 .ok_or(StoreError::UserNotFound(id))?;
203 user.token_version += 1;
204 user.updated_at = Utc::now();
205 let version = user.token_version;
206 state.refresh_tokens.retain(|_, t| t.user_id != id);
207 Ok(version)
208 })
209 }
210
211 fn store_refresh_token(&self, token: NewRefreshToken) -> StoreFuture<'_, ()> {
212 Box::pin(async move {
213 let mut state = self.state.write().await;
214 if !state.users.contains_key(&token.user_id) {
215 return Err(StoreError::UserNotFound(token.user_id));
216 }
217 let now = Utc::now();
218 state
219 .refresh_tokens
220 .retain(|_, t| t.user_id != token.user_id || t.expires_at > now);
221 state.refresh_tokens.insert(
222 token.token_hash,
223 StoredRefreshToken {
224 user_id: token.user_id,
225 expires_at: token.expires_at,
226 },
227 );
228 Ok(())
229 })
230 }
231
232 fn consume_refresh_token(&self, token_hash: &str) -> StoreFuture<'_, Option<Uuid>> {
233 let token_hash = token_hash.to_string();
234 Box::pin(async move {
235 let mut state = self.state.write().await;
236 Ok(state
237 .refresh_tokens
238 .remove(&token_hash)
239 .filter(|t| t.expires_at > Utc::now())
240 .map(|t| t.user_id))
241 })
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use chrono::Duration;
248
249 use crate::api_key_store::ApiKeyStore;
250 use crate::entities::NewApiKey;
251
252 use super::*;
253
254 fn refresh_token(hash: &str, user_id: Uuid, ttl: Duration) -> NewRefreshToken {
255 NewRefreshToken {
256 token_hash: hash.to_string(),
257 user_id,
258 expires_at: Utc::now() + ttl,
259 }
260 }
261
262 #[tokio::test]
263 async fn consume_refresh_token_is_single_use() {
264 let store = InMemoryStore::new();
265 let user = store
266 .create_user(new_user("alice@example.com", "alice"))
267 .await
268 .unwrap();
269 store
270 .store_refresh_token(refresh_token("h1", user.id, Duration::hours(1)))
271 .await
272 .unwrap();
273
274 assert_eq!(
275 store.consume_refresh_token("h1").await.unwrap(),
276 Some(user.id)
277 );
278 assert_eq!(store.consume_refresh_token("h1").await.unwrap(), None);
279 }
280
281 #[tokio::test]
282 async fn consume_unknown_refresh_token_returns_none() {
283 let store = InMemoryStore::new();
284 assert_eq!(store.consume_refresh_token("missing").await.unwrap(), None);
285 }
286
287 #[tokio::test]
288 async fn consume_expired_refresh_token_returns_none() {
289 let store = InMemoryStore::new();
290 let user = store
291 .create_user(new_user("alice@example.com", "alice"))
292 .await
293 .unwrap();
294 store
295 .store_refresh_token(refresh_token("old", user.id, Duration::seconds(-5)))
296 .await
297 .unwrap();
298
299 assert_eq!(store.consume_refresh_token("old").await.unwrap(), None);
300 }
301
302 #[tokio::test]
303 async fn store_refresh_token_drops_expired_rows_of_the_user() {
304 let store = InMemoryStore::new();
305 let user = store
306 .create_user(new_user("alice@example.com", "alice"))
307 .await
308 .unwrap();
309 store
310 .store_refresh_token(refresh_token("old", user.id, Duration::seconds(-5)))
311 .await
312 .unwrap();
313 store
314 .store_refresh_token(refresh_token("new", user.id, Duration::hours(1)))
315 .await
316 .unwrap();
317
318 let state = store.state.read().await;
319 assert!(!state.refresh_tokens.contains_key("old"));
320 assert!(state.refresh_tokens.contains_key("new"));
321 }
322
323 #[tokio::test]
324 async fn store_refresh_token_unknown_user_errors() {
325 let store = InMemoryStore::new();
326 let err = store
327 .store_refresh_token(refresh_token("h1", Uuid::now_v7(), Duration::hours(1)))
328 .await
329 .unwrap_err();
330 assert!(matches!(err, StoreError::UserNotFound(_)));
331 }
332
333 #[tokio::test]
334 async fn revoke_user_sessions_bumps_version_and_drops_refresh_tokens() {
335 let store = InMemoryStore::new();
336 let alice = store
337 .create_user(new_user("alice@example.com", "alice"))
338 .await
339 .unwrap();
340 let bob = store
341 .create_user(new_user("bob@example.com", "bob"))
342 .await
343 .unwrap();
344 store
345 .store_refresh_token(refresh_token("a1", alice.id, Duration::hours(1)))
346 .await
347 .unwrap();
348 store
349 .store_refresh_token(refresh_token("b1", bob.id, Duration::hours(1)))
350 .await
351 .unwrap();
352
353 assert_eq!(store.revoke_user_sessions(alice.id).await.unwrap(), 1);
354 assert_eq!(store.revoke_user_sessions(alice.id).await.unwrap(), 2);
355
356 let found = store.find_user_by_id(alice.id).await.unwrap().unwrap();
357 assert_eq!(found.token_version, 2);
358 assert_eq!(store.consume_refresh_token("a1").await.unwrap(), None);
359 assert_eq!(
361 store.consume_refresh_token("b1").await.unwrap(),
362 Some(bob.id)
363 );
364 }
365
366 #[tokio::test]
367 async fn revoke_user_sessions_unknown_user_errors() {
368 let store = InMemoryStore::new();
369 let err = store
370 .revoke_user_sessions(Uuid::now_v7())
371 .await
372 .unwrap_err();
373 assert!(matches!(err, StoreError::UserNotFound(_)));
374 }
375
376 #[tokio::test]
377 async fn update_user_role_bumps_token_version() {
378 let store = InMemoryStore::new();
379 let admin = store
380 .create_user(new_user("admin@example.com", "admin"))
381 .await
382 .unwrap();
383 assert_eq!(admin.token_version, 0);
384 store
385 .store_refresh_token(refresh_token("h1", admin.id, Duration::hours(1)))
386 .await
387 .unwrap();
388
389 let demoted = store.update_user_role(admin.id, false).await.unwrap();
390
391 assert_eq!(demoted.token_version, 1);
392 assert_eq!(store.consume_refresh_token("h1").await.unwrap(), None);
393 }
394
395 #[tokio::test]
396 async fn update_user_password_bumps_token_version() {
397 let store = InMemoryStore::new();
398 let user = store
399 .create_user(new_user("alice@example.com", "alice"))
400 .await
401 .unwrap();
402 store
403 .store_refresh_token(refresh_token("h1", user.id, Duration::hours(1)))
404 .await
405 .unwrap();
406
407 store
408 .update_user_password(user.id, "newhash".to_string())
409 .await
410 .unwrap();
411
412 let found = store.find_user_by_id(user.id).await.unwrap().unwrap();
413 assert_eq!(found.token_version, 1);
414 assert_eq!(found.password_hash, "newhash");
415 assert_eq!(store.consume_refresh_token("h1").await.unwrap(), None);
416 }
417
418 #[tokio::test]
419 async fn delete_user_drops_refresh_tokens() {
420 let store = InMemoryStore::new();
421 let user = store
422 .create_user(new_user("alice@example.com", "alice"))
423 .await
424 .unwrap();
425 store
426 .store_refresh_token(refresh_token("h1", user.id, Duration::hours(1)))
427 .await
428 .unwrap();
429
430 store.delete_user(user.id).await.unwrap();
431
432 assert_eq!(store.consume_refresh_token("h1").await.unwrap(), None);
433 }
434
435 fn new_user(email: &str, username: &str) -> NewUser {
436 NewUser {
437 email: email.to_string(),
438 username: username.to_string(),
439 password_hash: "argon2hash".to_string(),
440 is_admin: None,
441 }
442 }
443
444 #[tokio::test]
445 async fn create_user_first_user_is_admin() {
446 let store = InMemoryStore::new();
447 let user = store
448 .create_user(new_user("alice@example.com", "alice"))
449 .await
450 .unwrap();
451
452 assert_eq!(user.email, "alice@example.com");
453 assert_eq!(user.username, "alice");
454 assert_eq!(user.password_hash, "argon2hash");
455 assert!(user.is_admin);
456 }
457
458 #[tokio::test]
459 async fn create_user_second_user_is_not_admin() {
460 let store = InMemoryStore::new();
461 store
462 .create_user(new_user("alice@example.com", "alice"))
463 .await
464 .unwrap();
465
466 let second = store
467 .create_user(new_user("bob@example.com", "bob"))
468 .await
469 .unwrap();
470
471 assert!(!second.is_admin);
472 }
473
474 #[tokio::test]
475 async fn create_user_explicit_admin_flag() {
476 let store = InMemoryStore::new();
477 let first = store
479 .create_user(NewUser {
480 email: "alice@example.com".to_string(),
481 username: "alice".to_string(),
482 password_hash: "argon2hash".to_string(),
483 is_admin: Some(false),
484 })
485 .await
486 .unwrap();
487 assert!(!first.is_admin);
488
489 let second = store
491 .create_user(NewUser {
492 email: "bob@example.com".to_string(),
493 username: "bob".to_string(),
494 password_hash: "argon2hash".to_string(),
495 is_admin: Some(true),
496 })
497 .await
498 .unwrap();
499 assert!(second.is_admin);
500 }
501
502 #[tokio::test]
503 async fn create_user_duplicate_email_returns_error() {
504 let store = InMemoryStore::new();
505 store
506 .create_user(new_user("alice@example.com", "alice"))
507 .await
508 .unwrap();
509
510 let err = store
511 .create_user(new_user("alice@example.com", "bob"))
512 .await
513 .unwrap_err();
514
515 assert!(
516 matches!(err, StoreError::DuplicateEmail(ref e) if e == "alice@example.com"),
517 "expected DuplicateEmail, got: {err}"
518 );
519 }
520
521 #[tokio::test]
522 async fn create_user_duplicate_username_returns_error() {
523 let store = InMemoryStore::new();
524 store
525 .create_user(new_user("alice@example.com", "alice"))
526 .await
527 .unwrap();
528
529 let err = store
530 .create_user(new_user("bob@example.com", "alice"))
531 .await
532 .unwrap_err();
533
534 assert!(
535 matches!(err, StoreError::DuplicateUsername(ref u) if u == "alice"),
536 "expected DuplicateUsername, got: {err}"
537 );
538 }
539
540 #[tokio::test]
541 async fn find_user_by_email_existing() {
542 let store = InMemoryStore::new();
543 let created = store
544 .create_user(new_user("alice@example.com", "alice"))
545 .await
546 .unwrap();
547
548 let found = store
549 .find_user_by_email("alice@example.com")
550 .await
551 .unwrap()
552 .expect("user should exist");
553
554 assert_eq!(found.id, created.id);
555 assert_eq!(found.email, "alice@example.com");
556 }
557
558 #[tokio::test]
559 async fn find_user_by_email_missing_returns_none() {
560 let store = InMemoryStore::new();
561 let found = store
562 .find_user_by_email("nobody@example.com")
563 .await
564 .unwrap();
565
566 assert!(found.is_none());
567 }
568
569 #[tokio::test]
570 async fn find_user_by_username_existing() {
571 let store = InMemoryStore::new();
572 let created = store
573 .create_user(new_user("alice@example.com", "alice"))
574 .await
575 .unwrap();
576
577 let found = store
578 .find_user_by_username("alice")
579 .await
580 .unwrap()
581 .expect("user should exist");
582
583 assert_eq!(found.id, created.id);
584 }
585
586 #[tokio::test]
587 async fn find_user_by_username_missing_returns_none() {
588 let store = InMemoryStore::new();
589 store
590 .create_user(new_user("alice@example.com", "alice"))
591 .await
592 .unwrap();
593
594 let found = store.find_user_by_username("Alice").await.unwrap();
595
596 assert!(found.is_none());
597 }
598
599 #[tokio::test]
600 async fn find_user_by_id_existing() {
601 let store = InMemoryStore::new();
602 let created = store
603 .create_user(new_user("alice@example.com", "alice"))
604 .await
605 .unwrap();
606
607 let found = store
608 .find_user_by_id(created.id)
609 .await
610 .unwrap()
611 .expect("user should exist");
612
613 assert_eq!(found.email, "alice@example.com");
614 assert_eq!(found.username, "alice");
615 }
616
617 #[tokio::test]
618 async fn find_user_by_id_missing_returns_none() {
619 let store = InMemoryStore::new();
620 let found = store.find_user_by_id(Uuid::now_v7()).await.unwrap();
621 assert!(found.is_none());
622 }
623
624 #[tokio::test]
625 async fn count_users_empty_store() {
626 let store = InMemoryStore::new();
627 assert_eq!(store.count_users().await.unwrap(), 0);
628 }
629
630 #[tokio::test]
631 async fn count_users_with_users() {
632 let store = InMemoryStore::new();
633 store
634 .create_user(new_user("alice@example.com", "alice"))
635 .await
636 .unwrap();
637 store
638 .create_user(new_user("bob@example.com", "bob"))
639 .await
640 .unwrap();
641 assert_eq!(store.count_users().await.unwrap(), 2);
642 }
643
644 #[tokio::test]
645 async fn list_users_paginated() {
646 let store = InMemoryStore::new();
647 for i in 0..5 {
648 store
649 .create_user(new_user(
650 &format!("user{i}@example.com"),
651 &format!("user{i}"),
652 ))
653 .await
654 .unwrap();
655 }
656
657 let page = store.list_users(1, 2).await.unwrap();
658 assert_eq!(page.items.len(), 2);
659 assert_eq!(page.total, 5);
660 assert_eq!(page.page, 1);
661 assert_eq!(page.per_page, 2);
662
663 let page2 = store.list_users(3, 2).await.unwrap();
664 assert_eq!(page2.items.len(), 1);
665 }
666
667 #[tokio::test]
668 async fn list_users_empty_store() {
669 let store = InMemoryStore::new();
670 let page = store.list_users(1, 20).await.unwrap();
671 assert!(page.items.is_empty());
672 assert_eq!(page.total, 0);
673 }
674
675 #[tokio::test]
676 async fn delete_user_existing() {
677 let store = InMemoryStore::new();
678 let user = store
679 .create_user(new_user("alice@example.com", "alice"))
680 .await
681 .unwrap();
682
683 store.delete_user(user.id).await.unwrap();
684 assert_eq!(store.count_users().await.unwrap(), 0);
685 }
686
687 #[tokio::test]
688 async fn delete_user_not_found() {
689 let store = InMemoryStore::new();
690 let err = store.delete_user(Uuid::now_v7()).await.unwrap_err();
691 assert!(matches!(err, StoreError::UserNotFound(_)));
692 }
693
694 #[tokio::test]
695 async fn update_user_role_promote() {
696 let store = InMemoryStore::new();
697 let _admin = store
699 .create_user(new_user("admin@example.com", "admin"))
700 .await
701 .unwrap();
702 let member = store
703 .create_user(new_user("member@example.com", "member"))
704 .await
705 .unwrap();
706 assert!(!member.is_admin);
707
708 let promoted = store.update_user_role(member.id, true).await.unwrap();
709 assert!(promoted.is_admin);
710 }
711
712 #[tokio::test]
713 async fn update_user_role_demote() {
714 let store = InMemoryStore::new();
715 let admin = store
716 .create_user(new_user("admin@example.com", "admin"))
717 .await
718 .unwrap();
719 assert!(admin.is_admin);
720
721 let demoted = store.update_user_role(admin.id, false).await.unwrap();
722 assert!(!demoted.is_admin);
723 }
724
725 #[tokio::test]
726 async fn update_user_role_not_found() {
727 let store = InMemoryStore::new();
728 let err = store
729 .update_user_role(Uuid::now_v7(), true)
730 .await
731 .unwrap_err();
732 assert!(matches!(err, StoreError::UserNotFound(_)));
733 }
734
735 #[tokio::test]
736 async fn set_user_groups_replaces_sorts_and_dedups() {
737 let store = InMemoryStore::new();
738 let user = store
739 .create_user(new_user("alice@example.com", "alice"))
740 .await
741 .unwrap();
742
743 let groups = store
744 .set_user_groups(
745 user.id,
746 vec!["sre".to_string(), "finance".to_string(), "sre".to_string()],
747 )
748 .await
749 .unwrap();
750 assert_eq!(groups, vec!["finance".to_string(), "sre".to_string()]);
751 assert_eq!(store.list_user_groups(user.id).await.unwrap(), groups);
752
753 let replaced = store
754 .set_user_groups(user.id, vec!["legal".to_string()])
755 .await
756 .unwrap();
757 assert_eq!(replaced, vec!["legal".to_string()]);
758 assert_eq!(
759 store.list_user_groups(user.id).await.unwrap(),
760 vec!["legal".to_string()]
761 );
762
763 let cleared = store.set_user_groups(user.id, Vec::new()).await.unwrap();
764 assert!(cleared.is_empty());
765 assert!(store.list_user_groups(user.id).await.unwrap().is_empty());
766 }
767
768 #[tokio::test]
769 async fn set_user_groups_unknown_user_is_not_found() {
770 let store = InMemoryStore::new();
771 let err = store
772 .set_user_groups(Uuid::now_v7(), vec!["sre".to_string()])
773 .await
774 .unwrap_err();
775 assert!(matches!(err, StoreError::UserNotFound(_)));
776 }
777
778 #[tokio::test]
779 async fn list_user_groups_unknown_user_is_empty() {
780 let store = InMemoryStore::new();
781 assert!(
782 store
783 .list_user_groups(Uuid::now_v7())
784 .await
785 .unwrap()
786 .is_empty()
787 );
788 }
789
790 #[tokio::test]
791 async fn delete_user_removes_group_membership() {
792 let store = InMemoryStore::new();
793 let user = store
794 .create_user(new_user("alice@example.com", "alice"))
795 .await
796 .unwrap();
797 store
798 .set_user_groups(user.id, vec!["sre".to_string()])
799 .await
800 .unwrap();
801
802 store.delete_user(user.id).await.unwrap();
803
804 assert!(store.list_user_groups(user.id).await.unwrap().is_empty());
805 }
806
807 fn new_key(user_id: Uuid, prefix: &str, scopes: Vec<ApiKeyScope>) -> NewApiKey {
808 NewApiKey {
809 user_id,
810 name: format!("key-{prefix}"),
811 key_hash: "argon2hash".to_string(),
812 key_prefix: prefix.to_string(),
813 scopes,
814 expires_at: None,
815 rate_limit_override: None,
816 }
817 }
818
819 #[tokio::test]
820 async fn update_user_role_demote_strips_non_member_scopes_from_api_keys() {
821 let store = InMemoryStore::new();
822 let admin = store
823 .create_user(new_user("admin@example.com", "admin"))
824 .await
825 .unwrap();
826 let key = store
827 .create_api_key(new_key(
828 admin.id,
829 "irfl_aaaa",
830 vec![
831 ApiKeyScope::RunsRead,
832 ApiKeyScope::SignalsSend,
833 ApiKeyScope::Admin,
834 ],
835 ))
836 .await
837 .unwrap();
838
839 store.update_user_role(admin.id, false).await.unwrap();
840
841 let found = store.find_api_key_by_id(key.id).await.unwrap().unwrap();
842 assert_eq!(found.scopes, vec![ApiKeyScope::RunsRead]);
843 assert!(found.is_active);
844 assert!(ApiKeyScope::all_allowed_for_member(&found.scopes));
845 }
846
847 #[tokio::test]
848 async fn update_user_role_demote_deactivates_api_keys_left_without_scope() {
849 let store = InMemoryStore::new();
850 let admin = store
851 .create_user(new_user("admin@example.com", "admin"))
852 .await
853 .unwrap();
854 let key = store
855 .create_api_key(new_key(admin.id, "irfl_bbbb", vec![ApiKeyScope::Admin]))
856 .await
857 .unwrap();
858
859 store.update_user_role(admin.id, false).await.unwrap();
860
861 let found = store.find_api_key_by_id(key.id).await.unwrap().unwrap();
862 assert!(found.scopes.is_empty());
863 assert!(!found.is_active);
864 assert!(
865 store
866 .find_api_key_by_prefix("irfl_bbbb")
867 .await
868 .unwrap()
869 .is_none()
870 );
871 }
872
873 #[tokio::test]
874 async fn update_user_role_demote_leaves_other_users_keys_untouched() {
875 let store = InMemoryStore::new();
876 let admin = store
877 .create_user(new_user("admin@example.com", "admin"))
878 .await
879 .unwrap();
880 let other = store
881 .create_user(NewUser {
882 is_admin: Some(true),
883 ..new_user("other@example.com", "other")
884 })
885 .await
886 .unwrap();
887 let other_key = store
888 .create_api_key(new_key(other.id, "irfl_cccc", vec![ApiKeyScope::Admin]))
889 .await
890 .unwrap();
891
892 store.update_user_role(admin.id, false).await.unwrap();
893
894 let found = store
895 .find_api_key_by_id(other_key.id)
896 .await
897 .unwrap()
898 .unwrap();
899 assert_eq!(found.scopes, vec![ApiKeyScope::Admin]);
900 assert!(found.is_active);
901 assert_eq!(found.updated_at, other_key.updated_at);
902 }
903
904 #[tokio::test]
905 async fn update_user_role_promote_keeps_api_keys_unchanged() {
906 let store = InMemoryStore::new();
907 let _admin = store
908 .create_user(new_user("admin@example.com", "admin"))
909 .await
910 .unwrap();
911 let member = store
912 .create_user(new_user("member@example.com", "member"))
913 .await
914 .unwrap();
915 let key = store
916 .create_api_key(new_key(member.id, "irfl_dddd", vec![ApiKeyScope::RunsRead]))
917 .await
918 .unwrap();
919
920 store.update_user_role(member.id, true).await.unwrap();
921
922 let found = store.find_api_key_by_id(key.id).await.unwrap().unwrap();
923 assert_eq!(found.scopes, vec![ApiKeyScope::RunsRead]);
924 assert!(found.is_active);
925 assert_eq!(found.updated_at, key.updated_at);
926 }
927
928 #[tokio::test]
929 async fn delete_user_drops_api_keys() {
930 let store = InMemoryStore::new();
931 let user = store
932 .create_user(new_user("alice@example.com", "alice"))
933 .await
934 .unwrap();
935 let key = store
936 .create_api_key(new_key(user.id, "irfl_eeee", vec![ApiKeyScope::RunsRead]))
937 .await
938 .unwrap();
939
940 store.delete_user(user.id).await.unwrap();
941
942 assert!(store.find_api_key_by_id(key.id).await.unwrap().is_none());
943 assert!(
944 store
945 .list_api_keys_by_user(user.id)
946 .await
947 .unwrap()
948 .is_empty()
949 );
950 }
951}