1mod access_token;
12mod authorization_grant;
13mod client;
14mod device_code_grant;
15mod refresh_token;
16mod session;
17
18pub use self::{
19 access_token::PgOAuth2AccessTokenRepository,
20 authorization_grant::PgOAuth2AuthorizationGrantRepository, client::PgOAuth2ClientRepository,
21 device_code_grant::PgOAuth2DeviceCodeGrantRepository,
22 refresh_token::PgOAuth2RefreshTokenRepository, session::PgOAuth2SessionRepository,
23};
24
25#[cfg(test)]
26mod tests {
27 use chrono::Duration;
28 use mas_data_model::{AuthorizationCode, Clock, clock::MockClock};
29 use mas_iana::oauth::OAuthClientAuthenticationMethod;
30 use mas_storage::{
31 Pagination,
32 oauth2::{
33 OAuth2ClientFilter, OAuth2DeviceCodeGrantParams, OAuth2SessionFilter,
34 OAuth2SessionRepository,
35 },
36 };
37 use oauth2_types::{
38 requests::{GrantType, ResponseMode},
39 scope::{EMAIL, OPENID, PROFILE, Scope},
40 };
41 use rand::SeedableRng;
42 use rand_chacha::ChaChaRng;
43 use sqlx::PgPool;
44 use ulid::Ulid;
45
46 use crate::PgRepository;
47
48 #[sqlx::test(migrator = "crate::MIGRATOR")]
49 async fn test_repositories(pool: PgPool) {
50 let mut rng = ChaChaRng::seed_from_u64(42);
51 let clock = MockClock::default();
52 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
53
54 let client = repo.oauth2_client().lookup(Ulid::nil()).await.unwrap();
56 assert_eq!(client, None);
57
58 let client = repo
60 .oauth2_client()
61 .find_by_client_id("some-client-id")
62 .await
63 .unwrap();
64 assert_eq!(client, None);
65
66 let client = repo
68 .oauth2_client()
69 .add(
70 &mut rng,
71 &clock,
72 vec!["https://example.com/redirect".parse().unwrap()],
73 None,
74 None,
75 None,
76 vec![GrantType::AuthorizationCode],
77 Some("Test client".to_owned()),
78 Some("https://example.com/logo.png".parse().unwrap()),
79 Some("https://example.com/".parse().unwrap()),
80 Some("https://example.com/policy".parse().unwrap()),
81 Some("https://example.com/tos".parse().unwrap()),
82 Some("https://example.com/jwks.json".parse().unwrap()),
83 None,
84 None,
85 None,
86 None,
87 None,
88 Some("https://example.com/login".parse().unwrap()),
89 )
90 .await
91 .unwrap();
92
93 let client_lookup = repo
95 .oauth2_client()
96 .lookup(client.id)
97 .await
98 .unwrap()
99 .expect("client not found");
100 assert_eq!(client, client_lookup);
101
102 let client_lookup = repo
104 .oauth2_client()
105 .find_by_client_id(&client.client_id)
106 .await
107 .unwrap()
108 .expect("client not found");
109 assert_eq!(client, client_lookup);
110
111 let grant = repo
113 .oauth2_authorization_grant()
114 .lookup(Ulid::nil())
115 .await
116 .unwrap();
117 assert_eq!(grant, None);
118
119 let grant = repo
121 .oauth2_authorization_grant()
122 .find_by_code("code")
123 .await
124 .unwrap();
125 assert_eq!(grant, None);
126
127 let raw_parameters = std::collections::BTreeMap::from([
129 ("client_id".to_owned(), "client".to_owned()),
130 ("foo".to_owned(), "bar".to_owned()),
131 ]);
132 let grant = repo
133 .oauth2_authorization_grant()
134 .add(
135 &mut rng,
136 &clock,
137 &client,
138 "https://example.com/redirect".parse().unwrap(),
139 Scope::from_iter([OPENID]),
140 Some(AuthorizationCode {
141 code: "code".to_owned(),
142 pkce: None,
143 }),
144 Some("state".to_owned()),
145 Some("nonce".to_owned()),
146 ResponseMode::Query,
147 true,
148 None,
149 None,
150 raw_parameters.clone(),
151 )
152 .await
153 .unwrap();
154 assert!(grant.is_pending());
155 assert_eq!(grant.raw_parameters, raw_parameters);
156
157 let grant_lookup = repo
159 .oauth2_authorization_grant()
160 .lookup(grant.id)
161 .await
162 .unwrap()
163 .expect("grant not found");
164 assert_eq!(grant, grant_lookup);
165
166 let grant_lookup = repo
168 .oauth2_authorization_grant()
169 .find_by_code("code")
170 .await
171 .unwrap()
172 .expect("grant not found");
173 assert_eq!(grant, grant_lookup);
174
175 let user = repo
177 .user()
178 .add(&mut rng, &clock, "john".to_owned())
179 .await
180 .unwrap();
181 let user_session = repo
182 .browser_session()
183 .add(&mut rng, &clock, &user, None)
184 .await
185 .unwrap();
186
187 let session = repo.oauth2_session().lookup(Ulid::nil()).await.unwrap();
189 assert_eq!(session, None);
190
191 let session = repo
193 .oauth2_session()
194 .add_from_browser_session(
195 &mut rng,
196 &clock,
197 &client,
198 &user_session,
199 grant.scope.clone(),
200 )
201 .await
202 .unwrap();
203
204 let grant = repo
206 .oauth2_authorization_grant()
207 .fulfill(&clock, &session, grant)
208 .await
209 .unwrap();
210 assert!(grant.is_fulfilled());
211
212 let session_lookup = repo
214 .oauth2_session()
215 .lookup(session.id)
216 .await
217 .unwrap()
218 .expect("session not found");
219 assert_eq!(session, session_lookup);
220
221 let grant = repo
223 .oauth2_authorization_grant()
224 .exchange(&clock, grant)
225 .await
226 .unwrap();
227 assert!(grant.is_exchanged());
228
229 let token = repo
231 .oauth2_access_token()
232 .lookup(Ulid::nil())
233 .await
234 .unwrap();
235 assert_eq!(token, None);
236
237 let token = repo
239 .oauth2_access_token()
240 .find_by_token("aabbcc")
241 .await
242 .unwrap();
243 assert_eq!(token, None);
244
245 let access_token = repo
247 .oauth2_access_token()
248 .add(
249 &mut rng,
250 &clock,
251 &session,
252 "aabbcc".to_owned(),
253 Some(Duration::try_minutes(5).unwrap()),
254 )
255 .await
256 .unwrap();
257
258 let access_token_lookup = repo
260 .oauth2_access_token()
261 .lookup(access_token.id)
262 .await
263 .unwrap()
264 .expect("token not found");
265 assert_eq!(access_token, access_token_lookup);
266
267 let access_token_lookup = repo
269 .oauth2_access_token()
270 .find_by_token("aabbcc")
271 .await
272 .unwrap()
273 .expect("token not found");
274 assert_eq!(access_token, access_token_lookup);
275
276 let refresh_token = repo
278 .oauth2_refresh_token()
279 .lookup(Ulid::nil())
280 .await
281 .unwrap();
282 assert_eq!(refresh_token, None);
283
284 let refresh_token = repo
286 .oauth2_refresh_token()
287 .find_by_token("aabbcc")
288 .await
289 .unwrap();
290 assert_eq!(refresh_token, None);
291
292 let refresh_token = repo
294 .oauth2_refresh_token()
295 .add(
296 &mut rng,
297 &clock,
298 &session,
299 &access_token,
300 "aabbcc".to_owned(),
301 )
302 .await
303 .unwrap();
304
305 let refresh_token_lookup = repo
307 .oauth2_refresh_token()
308 .lookup(refresh_token.id)
309 .await
310 .unwrap()
311 .expect("refresh token not found");
312 assert_eq!(refresh_token, refresh_token_lookup);
313
314 let refresh_token_lookup = repo
316 .oauth2_refresh_token()
317 .find_by_token("aabbcc")
318 .await
319 .unwrap()
320 .expect("refresh token not found");
321 assert_eq!(refresh_token, refresh_token_lookup);
322
323 assert!(access_token.is_valid(clock.now()));
324 clock.advance(Duration::try_minutes(6).unwrap());
325 assert!(!access_token.is_valid(clock.now()));
326
327 clock.advance(Duration::try_minutes(-6).unwrap()); assert!(access_token.is_valid(clock.now()));
330
331 let new_refresh_token = repo
333 .oauth2_refresh_token()
334 .add(
335 &mut rng,
336 &clock,
337 &session,
338 &access_token,
339 "ddeeff".to_owned(),
340 )
341 .await
342 .unwrap();
343
344 let access_token = repo
346 .oauth2_access_token()
347 .revoke(&clock, access_token)
348 .await
349 .unwrap();
350 assert!(!access_token.is_valid(clock.now()));
351
352 assert!(refresh_token.is_valid());
354 let refresh_token = repo
355 .oauth2_refresh_token()
356 .consume(&clock, refresh_token, &new_refresh_token)
357 .await
358 .unwrap();
359 assert!(!refresh_token.is_valid());
360
361 assert!(session.user_agent.is_none());
363 let session = repo
364 .oauth2_session()
365 .record_user_agent(session, "Mozilla/5.0".to_owned())
366 .await
367 .unwrap();
368 assert_eq!(session.user_agent.as_deref(), Some("Mozilla/5.0"));
369
370 let session = repo
372 .oauth2_session()
373 .lookup(session.id)
374 .await
375 .unwrap()
376 .expect("session not found");
377 assert_eq!(session.user_agent.as_deref(), Some("Mozilla/5.0"));
378
379 assert!(session.is_valid());
381 let session = repo.oauth2_session().finish(&clock, session).await.unwrap();
382 assert!(!session.is_valid());
383 }
384
385 #[sqlx::test(migrator = "crate::MIGRATOR")]
388 async fn test_list_sessions(pool: PgPool) {
389 let mut rng = ChaChaRng::seed_from_u64(42);
390 let clock = MockClock::default();
391 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
392
393 let user1 = repo
395 .user()
396 .add(&mut rng, &clock, "alice".to_owned())
397 .await
398 .unwrap();
399 let user1_session = repo
400 .browser_session()
401 .add(&mut rng, &clock, &user1, None)
402 .await
403 .unwrap();
404
405 let user2 = repo
406 .user()
407 .add(&mut rng, &clock, "bob".to_owned())
408 .await
409 .unwrap();
410 let user2_session = repo
411 .browser_session()
412 .add(&mut rng, &clock, &user2, None)
413 .await
414 .unwrap();
415
416 let client1 = repo
418 .oauth2_client()
419 .add(
420 &mut rng,
421 &clock,
422 vec!["https://first.example.com/redirect".parse().unwrap()],
423 None,
424 None,
425 None,
426 vec![GrantType::AuthorizationCode],
427 Some("First client".to_owned()),
428 Some("https://first.example.com/logo.png".parse().unwrap()),
429 Some("https://first.example.com/".parse().unwrap()),
430 Some("https://first.example.com/policy".parse().unwrap()),
431 Some("https://first.example.com/tos".parse().unwrap()),
432 Some("https://first.example.com/jwks.json".parse().unwrap()),
433 None,
434 None,
435 None,
436 None,
437 None,
438 Some("https://first.example.com/login".parse().unwrap()),
439 )
440 .await
441 .unwrap();
442 let client2 = repo
443 .oauth2_client()
444 .add(
445 &mut rng,
446 &clock,
447 vec!["https://second.example.com/redirect".parse().unwrap()],
448 None,
449 None,
450 None,
451 vec![GrantType::AuthorizationCode],
452 Some("Second client".to_owned()),
453 Some("https://second.example.com/logo.png".parse().unwrap()),
454 Some("https://second.example.com/".parse().unwrap()),
455 Some("https://second.example.com/policy".parse().unwrap()),
456 Some("https://second.example.com/tos".parse().unwrap()),
457 Some("https://second.example.com/jwks.json".parse().unwrap()),
458 None,
459 None,
460 None,
461 None,
462 None,
463 Some("https://second.example.com/login".parse().unwrap()),
464 )
465 .await
466 .unwrap();
467
468 let scope = Scope::from_iter([OPENID, EMAIL]);
469 let scope2 = Scope::from_iter([OPENID, PROFILE]);
470
471 let session11 = repo
475 .oauth2_session()
476 .add_from_browser_session(&mut rng, &clock, &client1, &user1_session, scope.clone())
477 .await
478 .unwrap();
479 clock.advance(Duration::try_minutes(1).unwrap());
480
481 let session12 = repo
482 .oauth2_session()
483 .add_from_browser_session(&mut rng, &clock, &client1, &user2_session, scope.clone())
484 .await
485 .unwrap();
486 clock.advance(Duration::try_minutes(1).unwrap());
487
488 let session21 = repo
489 .oauth2_session()
490 .add_from_browser_session(&mut rng, &clock, &client2, &user1_session, scope2.clone())
491 .await
492 .unwrap();
493 clock.advance(Duration::try_minutes(1).unwrap());
494
495 let session22 = repo
496 .oauth2_session()
497 .add_from_browser_session(&mut rng, &clock, &client2, &user2_session, scope2.clone())
498 .await
499 .unwrap();
500 clock.advance(Duration::try_minutes(1).unwrap());
501
502 let session11 = repo
504 .oauth2_session()
505 .finish(&clock, session11)
506 .await
507 .unwrap();
508 let session22 = repo
509 .oauth2_session()
510 .finish(&clock, session22)
511 .await
512 .unwrap();
513
514 let pagination = Pagination::first(10);
515
516 let filter = OAuth2SessionFilter::new().for_any_user();
518 let list = repo
519 .oauth2_session()
520 .list(filter, pagination)
521 .await
522 .unwrap();
523 assert!(!list.has_next_page);
524 assert_eq!(list.edges.len(), 4);
525 assert_eq!(list.edges[0].node, session11);
526 assert_eq!(list.edges[1].node, session12);
527 assert_eq!(list.edges[2].node, session21);
528 assert_eq!(list.edges[3].node, session22);
529
530 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 4);
531
532 let filter = OAuth2SessionFilter::new().for_user(&user1);
534 let list = repo
535 .oauth2_session()
536 .list(filter, pagination)
537 .await
538 .unwrap();
539 assert!(!list.has_next_page);
540 assert_eq!(list.edges.len(), 2);
541 assert_eq!(list.edges[0].node, session11);
542 assert_eq!(list.edges[1].node, session21);
543
544 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
545
546 let filter = OAuth2SessionFilter::new().for_client(&client1);
548 let list = repo
549 .oauth2_session()
550 .list(filter, pagination)
551 .await
552 .unwrap();
553 assert!(!list.has_next_page);
554 assert_eq!(list.edges.len(), 2);
555 assert_eq!(list.edges[0].node, session11);
556 assert_eq!(list.edges[1].node, session12);
557
558 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
559
560 let filter = OAuth2SessionFilter::new()
562 .for_user(&user2)
563 .for_client(&client2);
564 let list = repo
565 .oauth2_session()
566 .list(filter, pagination)
567 .await
568 .unwrap();
569 assert!(!list.has_next_page);
570 assert_eq!(list.edges.len(), 1);
571 assert_eq!(list.edges[0].node, session22);
572
573 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
574
575 let filter = OAuth2SessionFilter::new().active_only();
577 let list = repo
578 .oauth2_session()
579 .list(filter, pagination)
580 .await
581 .unwrap();
582 assert!(!list.has_next_page);
583 assert_eq!(list.edges.len(), 2);
584 assert_eq!(list.edges[0].node, session12);
585 assert_eq!(list.edges[1].node, session21);
586
587 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
588
589 let filter = OAuth2SessionFilter::new().finished_only();
591 let list = repo
592 .oauth2_session()
593 .list(filter, pagination)
594 .await
595 .unwrap();
596 assert!(!list.has_next_page);
597 assert_eq!(list.edges.len(), 2);
598 assert_eq!(list.edges[0].node, session11);
599 assert_eq!(list.edges[1].node, session22);
600
601 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
602
603 let filter = OAuth2SessionFilter::new().finished_only().for_user(&user2);
605 let list = repo
606 .oauth2_session()
607 .list(filter, pagination)
608 .await
609 .unwrap();
610 assert!(!list.has_next_page);
611 assert_eq!(list.edges.len(), 1);
612 assert_eq!(list.edges[0].node, session22);
613
614 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
615
616 let filter = OAuth2SessionFilter::new()
618 .finished_only()
619 .for_client(&client2);
620 let list = repo
621 .oauth2_session()
622 .list(filter, pagination)
623 .await
624 .unwrap();
625 assert!(!list.has_next_page);
626 assert_eq!(list.edges.len(), 1);
627 assert_eq!(list.edges[0].node, session22);
628
629 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
630
631 let filter = OAuth2SessionFilter::new().active_only().for_user(&user2);
633 let list = repo
634 .oauth2_session()
635 .list(filter, pagination)
636 .await
637 .unwrap();
638 assert!(!list.has_next_page);
639 assert_eq!(list.edges.len(), 1);
640 assert_eq!(list.edges[0].node, session12);
641
642 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
643
644 let filter = OAuth2SessionFilter::new()
646 .active_only()
647 .for_client(&client2);
648 let list = repo
649 .oauth2_session()
650 .list(filter, pagination)
651 .await
652 .unwrap();
653 assert!(!list.has_next_page);
654 assert_eq!(list.edges.len(), 1);
655 assert_eq!(list.edges[0].node, session21);
656
657 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
658
659 let scope = Scope::from_iter([OPENID]);
661 let filter = OAuth2SessionFilter::new().with_scope(&scope);
662 let list = repo
663 .oauth2_session()
664 .list(filter, pagination)
665 .await
666 .unwrap();
667 assert!(!list.has_next_page);
668 assert_eq!(list.edges.len(), 4);
669 assert_eq!(list.edges[0].node, session11);
670 assert_eq!(list.edges[1].node, session12);
671 assert_eq!(list.edges[2].node, session21);
672 assert_eq!(list.edges[3].node, session22);
673 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 4);
674
675 let scope = Scope::from_iter([OPENID, EMAIL]);
677 let filter = OAuth2SessionFilter::new().with_scope(&scope);
678 let list = repo
679 .oauth2_session()
680 .list(filter, pagination)
681 .await
682 .unwrap();
683 assert!(!list.has_next_page);
684 assert_eq!(list.edges.len(), 2);
685 assert_eq!(list.edges[0].node, session11);
686 assert_eq!(list.edges[1].node, session12);
687 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
688
689 let filter = OAuth2SessionFilter::new()
691 .with_scope(&scope)
692 .for_user(&user1);
693 let list = repo
694 .oauth2_session()
695 .list(filter, pagination)
696 .await
697 .unwrap();
698 assert_eq!(list.edges.len(), 1);
699 assert_eq!(list.edges[0].node, session11);
700 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
701
702 let affected = repo
704 .oauth2_session()
705 .finish_bulk(
706 &clock,
707 OAuth2SessionFilter::new()
708 .for_client(&client1)
709 .active_only(),
710 )
711 .await
712 .unwrap();
713 assert_eq!(affected, 1);
714
715 assert_eq!(
717 repo.oauth2_session()
718 .count(OAuth2SessionFilter::new().finished_only())
719 .await
720 .unwrap(),
721 3
722 );
723
724 assert_eq!(
726 repo.oauth2_session()
727 .count(OAuth2SessionFilter::new().active_only())
728 .await
729 .unwrap(),
730 1
731 );
732 }
733
734 #[sqlx::test(migrator = "crate::MIGRATOR")]
736 async fn test_list_sessions_by_created_at(pool: PgPool) {
737 let mut rng = ChaChaRng::seed_from_u64(42);
738 let clock = MockClock::default();
739 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
740
741 let user = repo
742 .user()
743 .add(&mut rng, &clock, "alice".to_owned())
744 .await
745 .unwrap();
746 let user_session = repo
747 .browser_session()
748 .add(&mut rng, &clock, &user, None)
749 .await
750 .unwrap();
751 let client = repo
752 .oauth2_client()
753 .add(
754 &mut rng,
755 &clock,
756 vec!["https://example.com/redirect".parse().unwrap()],
757 None,
758 None,
759 None,
760 vec![GrantType::AuthorizationCode],
761 Some("Test client".to_owned()),
762 Some("https://example.com/logo.png".parse().unwrap()),
763 Some("https://example.com/".parse().unwrap()),
764 Some("https://example.com/policy".parse().unwrap()),
765 Some("https://example.com/tos".parse().unwrap()),
766 Some("https://example.com/jwks.json".parse().unwrap()),
767 None,
768 None,
769 None,
770 None,
771 None,
772 Some("https://example.com/login".parse().unwrap()),
773 )
774 .await
775 .unwrap();
776
777 let scope = Scope::from_iter([OPENID]);
778
779 let session1 = repo
782 .oauth2_session()
783 .add_from_browser_session(&mut rng, &clock, &client, &user_session, scope.clone())
784 .await
785 .unwrap();
786 clock.advance(Duration::try_minutes(1).unwrap());
787
788 let session2 = repo
789 .oauth2_session()
790 .add_from_browser_session(&mut rng, &clock, &client, &user_session, scope.clone())
791 .await
792 .unwrap();
793 clock.advance(Duration::try_minutes(1).unwrap());
794
795 let cutoff = clock.now();
796
797 clock.advance(Duration::try_minutes(1).unwrap());
798 let session3 = repo
799 .oauth2_session()
800 .add_from_browser_session(&mut rng, &clock, &client, &user_session, scope.clone())
801 .await
802 .unwrap();
803
804 let pagination = Pagination::first(10);
805
806 let filter = OAuth2SessionFilter::new().with_created_before(cutoff);
808 let list = repo
809 .oauth2_session()
810 .list(filter, pagination)
811 .await
812 .unwrap();
813 assert_eq!(list.edges.len(), 2);
814 assert_eq!(list.edges[0].node, session1);
815 assert_eq!(list.edges[1].node, session2);
816 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
817
818 let filter = OAuth2SessionFilter::new().with_created_after(cutoff);
820 let list = repo
821 .oauth2_session()
822 .list(filter, pagination)
823 .await
824 .unwrap();
825 assert_eq!(list.edges.len(), 1);
826 assert_eq!(list.edges[0].node, session3);
827 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
828 }
829
830 #[sqlx::test(migrator = "crate::MIGRATOR")]
832 async fn test_list_sessions_for_clients(pool: PgPool) {
833 let mut rng = ChaChaRng::seed_from_u64(42);
834 let clock = MockClock::default();
835 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
836
837 let user = repo
839 .user()
840 .add(&mut rng, &clock, "alice".to_owned())
841 .await
842 .unwrap();
843 let user_session = repo
844 .browser_session()
845 .add(&mut rng, &clock, &user, None)
846 .await
847 .unwrap();
848
849 let mut clients = Vec::new();
851 for label in ["first", "second", "third"] {
852 let client = repo
853 .oauth2_client()
854 .add(
855 &mut rng,
856 &clock,
857 vec![
858 format!("https://{label}.example.com/redirect")
859 .parse()
860 .unwrap(),
861 ],
862 None,
863 None,
864 None,
865 vec![GrantType::AuthorizationCode],
866 Some(format!("{label} client")),
867 Some(
868 format!("https://{label}.example.com/logo.png")
869 .parse()
870 .unwrap(),
871 ),
872 Some(format!("https://{label}.example.com/").parse().unwrap()),
873 Some(
874 format!("https://{label}.example.com/policy")
875 .parse()
876 .unwrap(),
877 ),
878 Some(format!("https://{label}.example.com/tos").parse().unwrap()),
879 Some(
880 format!("https://{label}.example.com/jwks.json")
881 .parse()
882 .unwrap(),
883 ),
884 None,
885 None,
886 None,
887 None,
888 None,
889 Some(
890 format!("https://{label}.example.com/login")
891 .parse()
892 .unwrap(),
893 ),
894 )
895 .await
896 .unwrap();
897 clients.push(client);
898 }
899 let [client1, client2, client3] = <[_; 3]>::try_from(clients).ok().unwrap();
900
901 let scope = Scope::from_iter([OPENID]);
902
903 let session1 = repo
905 .oauth2_session()
906 .add_from_browser_session(&mut rng, &clock, &client1, &user_session, scope.clone())
907 .await
908 .unwrap();
909 clock.advance(Duration::try_minutes(1).unwrap());
910
911 let session2 = repo
912 .oauth2_session()
913 .add_from_browser_session(&mut rng, &clock, &client2, &user_session, scope.clone())
914 .await
915 .unwrap();
916 clock.advance(Duration::try_minutes(1).unwrap());
917
918 let _session3 = repo
919 .oauth2_session()
920 .add_from_browser_session(&mut rng, &clock, &client3, &user_session, scope.clone())
921 .await
922 .unwrap();
923
924 let pagination = Pagination::first(10);
925
926 let two_clients = [&client1, &client2];
928 let filter = OAuth2SessionFilter::new().for_clients(&two_clients);
929 let list = repo
930 .oauth2_session()
931 .list(filter, pagination)
932 .await
933 .unwrap();
934 assert!(!list.has_next_page);
935 assert_eq!(list.edges.len(), 2);
936 assert_eq!(list.edges[0].node, session1);
937 assert_eq!(list.edges[1].node, session2);
938 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
939
940 let one_client = [&client2];
942 let filter = OAuth2SessionFilter::new().for_clients(&one_client);
943 let list = repo
944 .oauth2_session()
945 .list(filter, pagination)
946 .await
947 .unwrap();
948 assert_eq!(list.edges.len(), 1);
949 assert_eq!(list.edges[0].node, session2);
950 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
951
952 let no_clients: [&mas_data_model::Client; 0] = [];
954 let filter = OAuth2SessionFilter::new().for_clients(&no_clients);
955 let list = repo
956 .oauth2_session()
957 .list(filter, pagination)
958 .await
959 .unwrap();
960 assert!(list.edges.is_empty());
961 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 0);
962 }
963
964 #[sqlx::test(migrator = "crate::MIGRATOR")]
966 async fn test_device_code_grant_repository(pool: PgPool) {
967 let mut rng = ChaChaRng::seed_from_u64(42);
968 let clock = MockClock::default();
969 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
970
971 let client = repo
973 .oauth2_client()
974 .add(
975 &mut rng,
976 &clock,
977 vec!["https://example.com/redirect".parse().unwrap()],
978 None,
979 None,
980 None,
981 vec![GrantType::AuthorizationCode],
982 Some("Example".to_owned()),
983 Some("https://example.com/logo.png".parse().unwrap()),
984 Some("https://example.com/".parse().unwrap()),
985 Some("https://example.com/policy".parse().unwrap()),
986 Some("https://example.com/tos".parse().unwrap()),
987 Some("https://example.com/jwks.json".parse().unwrap()),
988 None,
989 None,
990 None,
991 None,
992 None,
993 Some("https://example.com/login".parse().unwrap()),
994 )
995 .await
996 .unwrap();
997
998 let user = repo
1000 .user()
1001 .add(&mut rng, &clock, "john".to_owned())
1002 .await
1003 .unwrap();
1004
1005 let browser_session = repo
1007 .browser_session()
1008 .add(&mut rng, &clock, &user, None)
1009 .await
1010 .unwrap();
1011
1012 let user_code = "usercode";
1013 let device_code = "devicecode";
1014 let scope = Scope::from_iter([OPENID, EMAIL]);
1015
1016 let grant = repo
1018 .oauth2_device_code_grant()
1019 .add(
1020 &mut rng,
1021 &clock,
1022 OAuth2DeviceCodeGrantParams {
1023 client: &client,
1024 scope: scope.clone(),
1025 device_code: device_code.to_owned(),
1026 user_code: user_code.to_owned(),
1027 expires_in: Duration::try_minutes(5).unwrap(),
1028 ip_address: None,
1029 user_agent: None,
1030 },
1031 )
1032 .await
1033 .unwrap();
1034
1035 assert!(grant.is_pending());
1036
1037 let id = grant.id;
1039 let lookup = repo.oauth2_device_code_grant().lookup(id).await.unwrap();
1040 assert_eq!(lookup.as_ref(), Some(&grant));
1041
1042 let lookup = repo
1044 .oauth2_device_code_grant()
1045 .find_by_device_code(device_code)
1046 .await
1047 .unwrap();
1048 assert_eq!(lookup.as_ref(), Some(&grant));
1049
1050 let lookup = repo
1052 .oauth2_device_code_grant()
1053 .find_by_user_code(user_code)
1054 .await
1055 .unwrap();
1056 assert_eq!(lookup.as_ref(), Some(&grant));
1057
1058 let grant = repo
1060 .oauth2_device_code_grant()
1061 .fulfill(&clock, grant, &browser_session)
1062 .await
1063 .unwrap();
1064 assert!(!grant.is_pending());
1065 assert!(grant.is_fulfilled());
1066
1067 let res = repo
1069 .oauth2_device_code_grant()
1070 .reject(&clock, grant, &browser_session)
1071 .await;
1072 assert!(res.is_err());
1073
1074 let grant = repo
1076 .oauth2_device_code_grant()
1077 .lookup(id)
1078 .await
1079 .unwrap()
1080 .unwrap();
1081
1082 let res = repo
1084 .oauth2_device_code_grant()
1085 .fulfill(&clock, grant, &browser_session)
1086 .await;
1087 assert!(res.is_err());
1088
1089 let grant = repo
1091 .oauth2_device_code_grant()
1092 .lookup(id)
1093 .await
1094 .unwrap()
1095 .unwrap();
1096
1097 let session = repo
1099 .oauth2_session()
1100 .add_from_browser_session(&mut rng, &clock, &client, &browser_session, scope.clone())
1101 .await
1102 .unwrap();
1103
1104 let grant = repo
1106 .oauth2_device_code_grant()
1107 .exchange(&clock, grant, &session)
1108 .await
1109 .unwrap();
1110 assert!(!grant.is_pending());
1111 assert!(!grant.is_fulfilled());
1112 assert!(grant.is_exchanged());
1113
1114 let res = repo
1116 .oauth2_device_code_grant()
1117 .exchange(&clock, grant, &session)
1118 .await;
1119 assert!(res.is_err());
1120
1121 let grant = repo
1123 .oauth2_device_code_grant()
1124 .add(
1125 &mut rng,
1126 &clock,
1127 OAuth2DeviceCodeGrantParams {
1128 client: &client,
1129 scope: scope.clone(),
1130 device_code: "second_devicecode".to_owned(),
1131 user_code: "second_usercode".to_owned(),
1132 expires_in: Duration::try_minutes(5).unwrap(),
1133 ip_address: None,
1134 user_agent: None,
1135 },
1136 )
1137 .await
1138 .unwrap();
1139
1140 let id = grant.id;
1141
1142 let grant = repo
1144 .oauth2_device_code_grant()
1145 .reject(&clock, grant, &browser_session)
1146 .await
1147 .unwrap();
1148 assert!(!grant.is_pending());
1149 assert!(grant.is_rejected());
1150
1151 let res = repo
1153 .oauth2_device_code_grant()
1154 .reject(&clock, grant, &browser_session)
1155 .await;
1156 assert!(res.is_err());
1157
1158 let grant = repo
1160 .oauth2_device_code_grant()
1161 .lookup(id)
1162 .await
1163 .unwrap()
1164 .unwrap();
1165
1166 let res = repo
1168 .oauth2_device_code_grant()
1169 .fulfill(&clock, grant, &browser_session)
1170 .await;
1171 assert!(res.is_err());
1172
1173 let grant = repo
1175 .oauth2_device_code_grant()
1176 .lookup(id)
1177 .await
1178 .unwrap()
1179 .unwrap();
1180
1181 let res = repo
1183 .oauth2_device_code_grant()
1184 .exchange(&clock, grant, &session)
1185 .await;
1186 assert!(res.is_err());
1187 }
1188
1189 #[sqlx::test(migrator = "crate::MIGRATOR")]
1192 async fn test_list_clients(pool: PgPool) {
1193 let mut rng = ChaChaRng::seed_from_u64(42);
1194 let clock = MockClock::default();
1195 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
1196
1197 let filter = OAuth2ClientFilter::new();
1199 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 0);
1200
1201 let page = repo
1202 .oauth2_client()
1203 .list(filter, Pagination::first(10))
1204 .await
1205 .unwrap();
1206 assert!(page.edges.is_empty());
1207 assert!(!page.has_next_page);
1208
1209 let client1 = repo
1211 .oauth2_client()
1212 .add(
1213 &mut rng,
1214 &clock,
1215 vec!["https://first.example.com/redirect".parse().unwrap()],
1216 None,
1217 None,
1218 None,
1219 vec![GrantType::AuthorizationCode],
1220 Some("First client".to_owned()),
1221 None,
1222 Some("https://first.example.com/".parse().unwrap()),
1223 None,
1224 None,
1225 None,
1226 None,
1227 None,
1228 None,
1229 None,
1230 None,
1231 None,
1232 )
1233 .await
1234 .unwrap();
1235 clock.advance(Duration::try_minutes(1).unwrap());
1236
1237 let client2 = repo
1238 .oauth2_client()
1239 .add(
1240 &mut rng,
1241 &clock,
1242 vec!["https://second.example.com/redirect".parse().unwrap()],
1243 None,
1244 None,
1245 None,
1246 vec![GrantType::AuthorizationCode],
1247 Some("Second client".to_owned()),
1248 None,
1249 Some("https://second.example.com/".parse().unwrap()),
1250 None,
1251 None,
1252 None,
1253 None,
1254 None,
1255 None,
1256 None,
1257 None,
1258 None,
1259 )
1260 .await
1261 .unwrap();
1262
1263 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 2);
1264
1265 let page = repo
1266 .oauth2_client()
1267 .list(filter, Pagination::first(10))
1268 .await
1269 .unwrap();
1270 assert!(!page.has_next_page);
1271 assert_eq!(page.edges.len(), 2);
1272 assert_eq!(page.edges[0].node, client1);
1273 assert_eq!(page.edges[1].node, client2);
1274
1275 let static_id = Ulid::from_datetime_with_source(clock.now().into(), &mut rng);
1277 repo.oauth2_client()
1278 .upsert_static(
1279 static_id,
1280 Some("Static client".to_owned()),
1281 OAuthClientAuthenticationMethod::None,
1282 None,
1283 None,
1284 None,
1285 vec!["https://static.example.com/redirect".parse().unwrap()],
1286 )
1287 .await
1288 .unwrap();
1289 let static_client = repo
1291 .oauth2_client()
1292 .lookup(static_id)
1293 .await
1294 .unwrap()
1295 .expect("static client just inserted");
1296
1297 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 3);
1298
1299 let filter = OAuth2ClientFilter::new().only_static_clients();
1301 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1302 let page = repo
1303 .oauth2_client()
1304 .list(filter, Pagination::first(10))
1305 .await
1306 .unwrap();
1307 assert_eq!(page.edges.len(), 1);
1308 assert_eq!(page.edges[0].node, static_client);
1309
1310 let filter = OAuth2ClientFilter::new().only_dynamic_clients();
1312 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 2);
1313 let page = repo
1314 .oauth2_client()
1315 .list(filter, Pagination::first(10))
1316 .await
1317 .unwrap();
1318 assert_eq!(page.edges.len(), 2);
1319 assert_eq!(page.edges[0].node, client1);
1320 assert_eq!(page.edges[1].node, client2);
1321
1322 let filter = OAuth2ClientFilter::new().matching_client_name("first");
1324 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1325 let page = repo
1326 .oauth2_client()
1327 .list(filter, Pagination::first(10))
1328 .await
1329 .unwrap();
1330 assert_eq!(page.edges.len(), 1);
1331 assert_eq!(page.edges[0].node, client1);
1332
1333 let filter = OAuth2ClientFilter::new().matching_client_name("CLIENT");
1335 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 3);
1336
1337 let filter = OAuth2ClientFilter::new().matching_client_uri("second");
1339 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1340 let page = repo
1341 .oauth2_client()
1342 .list(filter, Pagination::first(10))
1343 .await
1344 .unwrap();
1345 assert_eq!(page.edges.len(), 1);
1346 assert_eq!(page.edges[0].node, client2);
1347
1348 let filter = OAuth2ClientFilter::new().matching_client_uri("EXAMPLE.COM");
1350 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 2);
1351 }
1352
1353 #[sqlx::test(migrator = "crate::MIGRATOR")]
1355 async fn test_list_clients_by_grant_type(pool: PgPool) {
1356 let mut rng = ChaChaRng::seed_from_u64(42);
1357 let clock = MockClock::default();
1358 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
1359
1360 let auth_code_client = repo
1362 .oauth2_client()
1363 .add(
1364 &mut rng,
1365 &clock,
1366 vec!["https://code.example.com/redirect".parse().unwrap()],
1367 None,
1368 None,
1369 None,
1370 vec![GrantType::AuthorizationCode, GrantType::RefreshToken],
1371 Some("Authorization code client".to_owned()),
1372 None,
1373 None,
1374 None,
1375 None,
1376 None,
1377 None,
1378 None,
1379 None,
1380 None,
1381 None,
1382 None,
1383 )
1384 .await
1385 .unwrap();
1386
1387 let client_credentials_client = repo
1389 .oauth2_client()
1390 .add(
1391 &mut rng,
1392 &clock,
1393 vec![],
1394 None,
1395 None,
1396 None,
1397 vec![GrantType::ClientCredentials],
1398 Some("Client credentials client".to_owned()),
1399 None,
1400 None,
1401 None,
1402 None,
1403 None,
1404 None,
1405 None,
1406 None,
1407 None,
1408 None,
1409 None,
1410 )
1411 .await
1412 .unwrap();
1413
1414 let filter = OAuth2ClientFilter::new().with_grant_type(&GrantType::AuthorizationCode);
1416 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1417 let page = repo
1418 .oauth2_client()
1419 .list(filter, Pagination::first(10))
1420 .await
1421 .unwrap();
1422 assert_eq!(page.edges.len(), 1);
1423 assert_eq!(page.edges[0].node, auth_code_client);
1424
1425 let filter = OAuth2ClientFilter::new().with_grant_type(&GrantType::ClientCredentials);
1427 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1428 let page = repo
1429 .oauth2_client()
1430 .list(filter, Pagination::first(10))
1431 .await
1432 .unwrap();
1433 assert_eq!(page.edges.len(), 1);
1434 assert_eq!(page.edges[0].node, client_credentials_client);
1435
1436 let filter = OAuth2ClientFilter::new().with_grant_type(&GrantType::RefreshToken);
1438 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1439
1440 let filter = OAuth2ClientFilter::new().with_grant_type(&GrantType::DeviceCode);
1442 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 0);
1443
1444 let filter = OAuth2ClientFilter::new().with_grant_type(&GrantType::Implicit);
1446 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 0);
1447 }
1448
1449 #[sqlx::test(migrator = "crate::MIGRATOR")]
1451 async fn test_list_clients_by_active_sessions(pool: PgPool) {
1452 let mut rng = ChaChaRng::seed_from_u64(42);
1453 let clock = MockClock::default();
1454 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
1455
1456 let with_session = repo
1458 .oauth2_client()
1459 .add(
1460 &mut rng,
1461 &clock,
1462 vec![],
1463 None,
1464 None,
1465 None,
1466 vec![GrantType::ClientCredentials],
1467 Some("Client with session".to_owned()),
1468 None,
1469 None,
1470 None,
1471 None,
1472 None,
1473 None,
1474 None,
1475 None,
1476 None,
1477 None,
1478 None,
1479 )
1480 .await
1481 .unwrap();
1482
1483 let without_session = repo
1485 .oauth2_client()
1486 .add(
1487 &mut rng,
1488 &clock,
1489 vec![],
1490 None,
1491 None,
1492 None,
1493 vec![GrantType::ClientCredentials],
1494 Some("Client without session".to_owned()),
1495 None,
1496 None,
1497 None,
1498 None,
1499 None,
1500 None,
1501 None,
1502 None,
1503 None,
1504 None,
1505 None,
1506 )
1507 .await
1508 .unwrap();
1509
1510 let session = repo
1511 .oauth2_session()
1512 .add_from_client_credentials(
1513 &mut rng,
1514 &clock,
1515 &with_session,
1516 Scope::from_iter([OPENID]),
1517 )
1518 .await
1519 .unwrap();
1520
1521 let filter = OAuth2ClientFilter::new().with_active_sessions(true);
1523 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1524 let page = repo
1525 .oauth2_client()
1526 .list(filter, Pagination::first(10))
1527 .await
1528 .unwrap();
1529 assert_eq!(page.edges.len(), 1);
1530 assert_eq!(page.edges[0].node, with_session);
1531
1532 let filter = OAuth2ClientFilter::new().with_active_sessions(false);
1534 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1535 let page = repo
1536 .oauth2_client()
1537 .list(filter, Pagination::first(10))
1538 .await
1539 .unwrap();
1540 assert_eq!(page.edges.len(), 1);
1541 assert_eq!(page.edges[0].node, without_session);
1542
1543 repo.oauth2_session().finish(&clock, session).await.unwrap();
1545
1546 let filter = OAuth2ClientFilter::new().with_active_sessions(true);
1547 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 0);
1548 let filter = OAuth2ClientFilter::new().with_active_sessions(false);
1549 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 2);
1550 }
1551}