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, UlidExt as _, 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, Some("en".to_owned()))
1062 .await
1063 .unwrap();
1064 assert!(!grant.is_pending());
1065 assert!(grant.is_fulfilled());
1066 assert_eq!(grant.locale.as_deref(), Some("en"));
1067
1068 let res = repo
1070 .oauth2_device_code_grant()
1071 .reject(&clock, grant, &browser_session)
1072 .await;
1073 assert!(res.is_err());
1074
1075 let grant = repo
1077 .oauth2_device_code_grant()
1078 .lookup(id)
1079 .await
1080 .unwrap()
1081 .unwrap();
1082
1083 assert_eq!(grant.locale.as_deref(), Some("en"));
1085
1086 let res = repo
1088 .oauth2_device_code_grant()
1089 .fulfill(&clock, grant, &browser_session, None)
1090 .await;
1091 assert!(res.is_err());
1092
1093 let grant = repo
1095 .oauth2_device_code_grant()
1096 .lookup(id)
1097 .await
1098 .unwrap()
1099 .unwrap();
1100
1101 let session = repo
1103 .oauth2_session()
1104 .add_from_browser_session(&mut rng, &clock, &client, &browser_session, scope.clone())
1105 .await
1106 .unwrap();
1107
1108 let grant = repo
1110 .oauth2_device_code_grant()
1111 .exchange(&clock, grant, &session)
1112 .await
1113 .unwrap();
1114 assert!(!grant.is_pending());
1115 assert!(!grant.is_fulfilled());
1116 assert!(grant.is_exchanged());
1117
1118 let res = repo
1120 .oauth2_device_code_grant()
1121 .exchange(&clock, grant, &session)
1122 .await;
1123 assert!(res.is_err());
1124
1125 let grant = repo
1127 .oauth2_device_code_grant()
1128 .add(
1129 &mut rng,
1130 &clock,
1131 OAuth2DeviceCodeGrantParams {
1132 client: &client,
1133 scope: scope.clone(),
1134 device_code: "second_devicecode".to_owned(),
1135 user_code: "second_usercode".to_owned(),
1136 expires_in: Duration::try_minutes(5).unwrap(),
1137 ip_address: None,
1138 user_agent: None,
1139 },
1140 )
1141 .await
1142 .unwrap();
1143
1144 let id = grant.id;
1145
1146 let grant = repo
1148 .oauth2_device_code_grant()
1149 .reject(&clock, grant, &browser_session)
1150 .await
1151 .unwrap();
1152 assert!(!grant.is_pending());
1153 assert!(grant.is_rejected());
1154
1155 let res = repo
1157 .oauth2_device_code_grant()
1158 .reject(&clock, grant, &browser_session)
1159 .await;
1160 assert!(res.is_err());
1161
1162 let grant = repo
1164 .oauth2_device_code_grant()
1165 .lookup(id)
1166 .await
1167 .unwrap()
1168 .unwrap();
1169
1170 let res = repo
1172 .oauth2_device_code_grant()
1173 .fulfill(&clock, grant, &browser_session, None)
1174 .await;
1175 assert!(res.is_err());
1176
1177 let grant = repo
1179 .oauth2_device_code_grant()
1180 .lookup(id)
1181 .await
1182 .unwrap()
1183 .unwrap();
1184
1185 let res = repo
1187 .oauth2_device_code_grant()
1188 .exchange(&clock, grant, &session)
1189 .await;
1190 assert!(res.is_err());
1191 }
1192
1193 #[sqlx::test(migrator = "crate::MIGRATOR")]
1196 async fn test_list_clients(pool: PgPool) {
1197 let mut rng = ChaChaRng::seed_from_u64(42);
1198 let clock = MockClock::default();
1199 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
1200
1201 let filter = OAuth2ClientFilter::new();
1203 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 0);
1204
1205 let page = repo
1206 .oauth2_client()
1207 .list(filter, Pagination::first(10))
1208 .await
1209 .unwrap();
1210 assert!(page.edges.is_empty());
1211 assert!(!page.has_next_page);
1212
1213 let client1 = repo
1215 .oauth2_client()
1216 .add(
1217 &mut rng,
1218 &clock,
1219 vec!["https://first.example.com/redirect".parse().unwrap()],
1220 None,
1221 None,
1222 None,
1223 vec![GrantType::AuthorizationCode],
1224 Some("First client".to_owned()),
1225 None,
1226 Some("https://first.example.com/".parse().unwrap()),
1227 None,
1228 None,
1229 None,
1230 None,
1231 None,
1232 None,
1233 None,
1234 None,
1235 None,
1236 )
1237 .await
1238 .unwrap();
1239 clock.advance(Duration::try_minutes(1).unwrap());
1240
1241 let client2 = repo
1242 .oauth2_client()
1243 .add(
1244 &mut rng,
1245 &clock,
1246 vec!["https://second.example.com/redirect".parse().unwrap()],
1247 None,
1248 None,
1249 None,
1250 vec![GrantType::AuthorizationCode],
1251 Some("Second client".to_owned()),
1252 None,
1253 Some("https://second.example.com/".parse().unwrap()),
1254 None,
1255 None,
1256 None,
1257 None,
1258 None,
1259 None,
1260 None,
1261 None,
1262 None,
1263 )
1264 .await
1265 .unwrap();
1266
1267 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 2);
1268
1269 let page = repo
1270 .oauth2_client()
1271 .list(filter, Pagination::first(10))
1272 .await
1273 .unwrap();
1274 assert!(!page.has_next_page);
1275 assert_eq!(page.edges.len(), 2);
1276 assert_eq!(page.edges[0].node, client1);
1277 assert_eq!(page.edges[1].node, client2);
1278
1279 let static_id = Ulid::from_datetime_with_rng(clock.now(), &mut rng);
1281 repo.oauth2_client()
1282 .upsert_static(
1283 static_id,
1284 Some("Static client".to_owned()),
1285 OAuthClientAuthenticationMethod::None,
1286 None,
1287 None,
1288 None,
1289 vec!["https://static.example.com/redirect".parse().unwrap()],
1290 )
1291 .await
1292 .unwrap();
1293 let static_client = repo
1295 .oauth2_client()
1296 .lookup(static_id)
1297 .await
1298 .unwrap()
1299 .expect("static client just inserted");
1300
1301 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 3);
1302
1303 let filter = OAuth2ClientFilter::new().only_static_clients();
1305 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1306 let page = repo
1307 .oauth2_client()
1308 .list(filter, Pagination::first(10))
1309 .await
1310 .unwrap();
1311 assert_eq!(page.edges.len(), 1);
1312 assert_eq!(page.edges[0].node, static_client);
1313
1314 let filter = OAuth2ClientFilter::new().only_dynamic_clients();
1316 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 2);
1317 let page = repo
1318 .oauth2_client()
1319 .list(filter, Pagination::first(10))
1320 .await
1321 .unwrap();
1322 assert_eq!(page.edges.len(), 2);
1323 assert_eq!(page.edges[0].node, client1);
1324 assert_eq!(page.edges[1].node, client2);
1325
1326 let filter = OAuth2ClientFilter::new().matching_client_name("first");
1328 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1329 let page = repo
1330 .oauth2_client()
1331 .list(filter, Pagination::first(10))
1332 .await
1333 .unwrap();
1334 assert_eq!(page.edges.len(), 1);
1335 assert_eq!(page.edges[0].node, client1);
1336
1337 let filter = OAuth2ClientFilter::new().matching_client_name("CLIENT");
1339 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 3);
1340
1341 let filter = OAuth2ClientFilter::new().matching_client_uri("second");
1343 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1344 let page = repo
1345 .oauth2_client()
1346 .list(filter, Pagination::first(10))
1347 .await
1348 .unwrap();
1349 assert_eq!(page.edges.len(), 1);
1350 assert_eq!(page.edges[0].node, client2);
1351
1352 let filter = OAuth2ClientFilter::new().matching_client_uri("EXAMPLE.COM");
1354 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 2);
1355 }
1356
1357 #[sqlx::test(migrator = "crate::MIGRATOR")]
1359 async fn test_list_clients_by_grant_type(pool: PgPool) {
1360 let mut rng = ChaChaRng::seed_from_u64(42);
1361 let clock = MockClock::default();
1362 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
1363
1364 let auth_code_client = repo
1366 .oauth2_client()
1367 .add(
1368 &mut rng,
1369 &clock,
1370 vec!["https://code.example.com/redirect".parse().unwrap()],
1371 None,
1372 None,
1373 None,
1374 vec![GrantType::AuthorizationCode, GrantType::RefreshToken],
1375 Some("Authorization code client".to_owned()),
1376 None,
1377 None,
1378 None,
1379 None,
1380 None,
1381 None,
1382 None,
1383 None,
1384 None,
1385 None,
1386 None,
1387 )
1388 .await
1389 .unwrap();
1390
1391 let client_credentials_client = repo
1393 .oauth2_client()
1394 .add(
1395 &mut rng,
1396 &clock,
1397 vec![],
1398 None,
1399 None,
1400 None,
1401 vec![GrantType::ClientCredentials],
1402 Some("Client credentials client".to_owned()),
1403 None,
1404 None,
1405 None,
1406 None,
1407 None,
1408 None,
1409 None,
1410 None,
1411 None,
1412 None,
1413 None,
1414 )
1415 .await
1416 .unwrap();
1417
1418 let filter = OAuth2ClientFilter::new().with_grant_type(&GrantType::AuthorizationCode);
1420 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1421 let page = repo
1422 .oauth2_client()
1423 .list(filter, Pagination::first(10))
1424 .await
1425 .unwrap();
1426 assert_eq!(page.edges.len(), 1);
1427 assert_eq!(page.edges[0].node, auth_code_client);
1428
1429 let filter = OAuth2ClientFilter::new().with_grant_type(&GrantType::ClientCredentials);
1431 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1432 let page = repo
1433 .oauth2_client()
1434 .list(filter, Pagination::first(10))
1435 .await
1436 .unwrap();
1437 assert_eq!(page.edges.len(), 1);
1438 assert_eq!(page.edges[0].node, client_credentials_client);
1439
1440 let filter = OAuth2ClientFilter::new().with_grant_type(&GrantType::RefreshToken);
1442 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1443
1444 let filter = OAuth2ClientFilter::new().with_grant_type(&GrantType::DeviceCode);
1446 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 0);
1447
1448 let filter = OAuth2ClientFilter::new().with_grant_type(&GrantType::Implicit);
1450 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 0);
1451 }
1452
1453 #[sqlx::test(migrator = "crate::MIGRATOR")]
1455 async fn test_list_clients_by_active_sessions(pool: PgPool) {
1456 let mut rng = ChaChaRng::seed_from_u64(42);
1457 let clock = MockClock::default();
1458 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
1459
1460 let with_session = repo
1462 .oauth2_client()
1463 .add(
1464 &mut rng,
1465 &clock,
1466 vec![],
1467 None,
1468 None,
1469 None,
1470 vec![GrantType::ClientCredentials],
1471 Some("Client with session".to_owned()),
1472 None,
1473 None,
1474 None,
1475 None,
1476 None,
1477 None,
1478 None,
1479 None,
1480 None,
1481 None,
1482 None,
1483 )
1484 .await
1485 .unwrap();
1486
1487 let without_session = repo
1489 .oauth2_client()
1490 .add(
1491 &mut rng,
1492 &clock,
1493 vec![],
1494 None,
1495 None,
1496 None,
1497 vec![GrantType::ClientCredentials],
1498 Some("Client without session".to_owned()),
1499 None,
1500 None,
1501 None,
1502 None,
1503 None,
1504 None,
1505 None,
1506 None,
1507 None,
1508 None,
1509 None,
1510 )
1511 .await
1512 .unwrap();
1513
1514 let session = repo
1515 .oauth2_session()
1516 .add_from_client_credentials(
1517 &mut rng,
1518 &clock,
1519 &with_session,
1520 Scope::from_iter([OPENID]),
1521 )
1522 .await
1523 .unwrap();
1524
1525 let filter = OAuth2ClientFilter::new().with_active_sessions(true);
1527 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1528 let page = repo
1529 .oauth2_client()
1530 .list(filter, Pagination::first(10))
1531 .await
1532 .unwrap();
1533 assert_eq!(page.edges.len(), 1);
1534 assert_eq!(page.edges[0].node, with_session);
1535
1536 let filter = OAuth2ClientFilter::new().with_active_sessions(false);
1538 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 1);
1539 let page = repo
1540 .oauth2_client()
1541 .list(filter, Pagination::first(10))
1542 .await
1543 .unwrap();
1544 assert_eq!(page.edges.len(), 1);
1545 assert_eq!(page.edges[0].node, without_session);
1546
1547 repo.oauth2_session().finish(&clock, session).await.unwrap();
1549
1550 let filter = OAuth2ClientFilter::new().with_active_sessions(true);
1551 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 0);
1552 let filter = OAuth2ClientFilter::new().with_active_sessions(false);
1553 assert_eq!(repo.oauth2_client().count(filter).await.unwrap(), 2);
1554 }
1555}