1mod access_token;
11mod authorization_grant;
12mod client;
13mod device_code_grant;
14mod refresh_token;
15mod session;
16
17pub use self::{
18 access_token::PgOAuth2AccessTokenRepository,
19 authorization_grant::PgOAuth2AuthorizationGrantRepository, client::PgOAuth2ClientRepository,
20 device_code_grant::PgOAuth2DeviceCodeGrantRepository,
21 refresh_token::PgOAuth2RefreshTokenRepository, session::PgOAuth2SessionRepository,
22};
23
24#[cfg(test)]
25mod tests {
26 use chrono::Duration;
27 use mas_data_model::{AuthorizationCode, UserAgent};
28 use mas_storage::{
29 Clock, Pagination,
30 clock::MockClock,
31 oauth2::{OAuth2DeviceCodeGrantParams, OAuth2SessionFilter, OAuth2SessionRepository},
32 };
33 use oauth2_types::{
34 requests::{GrantType, ResponseMode},
35 scope::{EMAIL, OPENID, PROFILE, Scope},
36 };
37 use rand::SeedableRng;
38 use rand_chacha::ChaChaRng;
39 use sqlx::PgPool;
40 use ulid::Ulid;
41
42 use crate::PgRepository;
43
44 #[sqlx::test(migrator = "crate::MIGRATOR")]
45 async fn test_repositories(pool: PgPool) {
46 let mut rng = ChaChaRng::seed_from_u64(42);
47 let clock = MockClock::default();
48 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
49
50 let client = repo.oauth2_client().lookup(Ulid::nil()).await.unwrap();
52 assert_eq!(client, None);
53
54 let client = repo
56 .oauth2_client()
57 .find_by_client_id("some-client-id")
58 .await
59 .unwrap();
60 assert_eq!(client, None);
61
62 let client = repo
64 .oauth2_client()
65 .add(
66 &mut rng,
67 &clock,
68 vec!["https://example.com/redirect".parse().unwrap()],
69 None,
70 None,
71 vec![GrantType::AuthorizationCode],
72 Some("Test client".to_owned()),
73 Some("https://example.com/logo.png".parse().unwrap()),
74 Some("https://example.com/".parse().unwrap()),
75 Some("https://example.com/policy".parse().unwrap()),
76 Some("https://example.com/tos".parse().unwrap()),
77 Some("https://example.com/jwks.json".parse().unwrap()),
78 None,
79 None,
80 None,
81 None,
82 None,
83 Some("https://example.com/login".parse().unwrap()),
84 )
85 .await
86 .unwrap();
87
88 let client_lookup = repo
90 .oauth2_client()
91 .lookup(client.id)
92 .await
93 .unwrap()
94 .expect("client not found");
95 assert_eq!(client, client_lookup);
96
97 let client_lookup = repo
99 .oauth2_client()
100 .find_by_client_id(&client.client_id)
101 .await
102 .unwrap()
103 .expect("client not found");
104 assert_eq!(client, client_lookup);
105
106 let grant = repo
108 .oauth2_authorization_grant()
109 .lookup(Ulid::nil())
110 .await
111 .unwrap();
112 assert_eq!(grant, None);
113
114 let grant = repo
116 .oauth2_authorization_grant()
117 .find_by_code("code")
118 .await
119 .unwrap();
120 assert_eq!(grant, None);
121
122 let grant = repo
124 .oauth2_authorization_grant()
125 .add(
126 &mut rng,
127 &clock,
128 &client,
129 "https://example.com/redirect".parse().unwrap(),
130 Scope::from_iter([OPENID]),
131 Some(AuthorizationCode {
132 code: "code".to_owned(),
133 pkce: None,
134 }),
135 Some("state".to_owned()),
136 Some("nonce".to_owned()),
137 None,
138 ResponseMode::Query,
139 true,
140 false,
141 None,
142 )
143 .await
144 .unwrap();
145 assert!(grant.is_pending());
146
147 let grant_lookup = repo
149 .oauth2_authorization_grant()
150 .lookup(grant.id)
151 .await
152 .unwrap()
153 .expect("grant not found");
154 assert_eq!(grant, grant_lookup);
155
156 let grant_lookup = repo
158 .oauth2_authorization_grant()
159 .find_by_code("code")
160 .await
161 .unwrap()
162 .expect("grant not found");
163 assert_eq!(grant, grant_lookup);
164
165 let user = repo
167 .user()
168 .add(&mut rng, &clock, "john".to_owned())
169 .await
170 .unwrap();
171 let user_session = repo
172 .browser_session()
173 .add(&mut rng, &clock, &user, None)
174 .await
175 .unwrap();
176
177 let consent = repo
179 .oauth2_client()
180 .get_consent_for_user(&client, &user)
181 .await
182 .unwrap();
183 assert!(consent.is_empty());
184
185 let scope = Scope::from_iter([OPENID]);
187 repo.oauth2_client()
188 .give_consent_for_user(&mut rng, &clock, &client, &user, &scope)
189 .await
190 .unwrap();
191
192 let consent = repo
194 .oauth2_client()
195 .get_consent_for_user(&client, &user)
196 .await
197 .unwrap();
198 assert_eq!(scope, consent);
199
200 let session = repo.oauth2_session().lookup(Ulid::nil()).await.unwrap();
202 assert_eq!(session, None);
203
204 let session = repo
206 .oauth2_session()
207 .add_from_browser_session(
208 &mut rng,
209 &clock,
210 &client,
211 &user_session,
212 grant.scope.clone(),
213 )
214 .await
215 .unwrap();
216
217 let grant = repo
219 .oauth2_authorization_grant()
220 .fulfill(&clock, &session, grant)
221 .await
222 .unwrap();
223 assert!(grant.is_fulfilled());
224
225 let session_lookup = repo
227 .oauth2_session()
228 .lookup(session.id)
229 .await
230 .unwrap()
231 .expect("session not found");
232 assert_eq!(session, session_lookup);
233
234 let grant = repo
236 .oauth2_authorization_grant()
237 .exchange(&clock, grant)
238 .await
239 .unwrap();
240 assert!(grant.is_exchanged());
241
242 let token = repo
244 .oauth2_access_token()
245 .lookup(Ulid::nil())
246 .await
247 .unwrap();
248 assert_eq!(token, None);
249
250 let token = repo
252 .oauth2_access_token()
253 .find_by_token("aabbcc")
254 .await
255 .unwrap();
256 assert_eq!(token, None);
257
258 let access_token = repo
260 .oauth2_access_token()
261 .add(
262 &mut rng,
263 &clock,
264 &session,
265 "aabbcc".to_owned(),
266 Some(Duration::try_minutes(5).unwrap()),
267 )
268 .await
269 .unwrap();
270
271 let access_token_lookup = repo
273 .oauth2_access_token()
274 .lookup(access_token.id)
275 .await
276 .unwrap()
277 .expect("token not found");
278 assert_eq!(access_token, access_token_lookup);
279
280 let access_token_lookup = repo
282 .oauth2_access_token()
283 .find_by_token("aabbcc")
284 .await
285 .unwrap()
286 .expect("token not found");
287 assert_eq!(access_token, access_token_lookup);
288
289 let refresh_token = repo
291 .oauth2_refresh_token()
292 .lookup(Ulid::nil())
293 .await
294 .unwrap();
295 assert_eq!(refresh_token, None);
296
297 let refresh_token = repo
299 .oauth2_refresh_token()
300 .find_by_token("aabbcc")
301 .await
302 .unwrap();
303 assert_eq!(refresh_token, None);
304
305 let refresh_token = repo
307 .oauth2_refresh_token()
308 .add(
309 &mut rng,
310 &clock,
311 &session,
312 &access_token,
313 "aabbcc".to_owned(),
314 )
315 .await
316 .unwrap();
317
318 let refresh_token_lookup = repo
320 .oauth2_refresh_token()
321 .lookup(refresh_token.id)
322 .await
323 .unwrap()
324 .expect("refresh token not found");
325 assert_eq!(refresh_token, refresh_token_lookup);
326
327 let refresh_token_lookup = repo
329 .oauth2_refresh_token()
330 .find_by_token("aabbcc")
331 .await
332 .unwrap()
333 .expect("refresh token not found");
334 assert_eq!(refresh_token, refresh_token_lookup);
335
336 assert!(access_token.is_valid(clock.now()));
337 clock.advance(Duration::try_minutes(6).unwrap());
338 assert!(!access_token.is_valid(clock.now()));
339
340 clock.advance(Duration::try_minutes(-6).unwrap()); assert!(access_token.is_valid(clock.now()));
343
344 let new_refresh_token = repo
346 .oauth2_refresh_token()
347 .add(
348 &mut rng,
349 &clock,
350 &session,
351 &access_token,
352 "ddeeff".to_owned(),
353 )
354 .await
355 .unwrap();
356
357 let access_token = repo
359 .oauth2_access_token()
360 .revoke(&clock, access_token)
361 .await
362 .unwrap();
363 assert!(!access_token.is_valid(clock.now()));
364
365 assert!(refresh_token.is_valid());
367 let refresh_token = repo
368 .oauth2_refresh_token()
369 .consume(&clock, refresh_token, &new_refresh_token)
370 .await
371 .unwrap();
372 assert!(!refresh_token.is_valid());
373
374 assert!(session.user_agent.is_none());
376 let session = repo
377 .oauth2_session()
378 .record_user_agent(session, UserAgent::parse("Mozilla/5.0".to_owned()))
379 .await
380 .unwrap();
381 assert_eq!(session.user_agent.as_deref(), Some("Mozilla/5.0"));
382
383 let session = repo
385 .oauth2_session()
386 .lookup(session.id)
387 .await
388 .unwrap()
389 .expect("session not found");
390 assert_eq!(session.user_agent.as_deref(), Some("Mozilla/5.0"));
391
392 assert!(session.is_valid());
394 let session = repo.oauth2_session().finish(&clock, session).await.unwrap();
395 assert!(!session.is_valid());
396 }
397
398 #[sqlx::test(migrator = "crate::MIGRATOR")]
401 async fn test_list_sessions(pool: PgPool) {
402 let mut rng = ChaChaRng::seed_from_u64(42);
403 let clock = MockClock::default();
404 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
405
406 let user1 = repo
408 .user()
409 .add(&mut rng, &clock, "alice".to_owned())
410 .await
411 .unwrap();
412 let user1_session = repo
413 .browser_session()
414 .add(&mut rng, &clock, &user1, None)
415 .await
416 .unwrap();
417
418 let user2 = repo
419 .user()
420 .add(&mut rng, &clock, "bob".to_owned())
421 .await
422 .unwrap();
423 let user2_session = repo
424 .browser_session()
425 .add(&mut rng, &clock, &user2, None)
426 .await
427 .unwrap();
428
429 let client1 = repo
431 .oauth2_client()
432 .add(
433 &mut rng,
434 &clock,
435 vec!["https://first.example.com/redirect".parse().unwrap()],
436 None,
437 None,
438 vec![GrantType::AuthorizationCode],
439 Some("First client".to_owned()),
440 Some("https://first.example.com/logo.png".parse().unwrap()),
441 Some("https://first.example.com/".parse().unwrap()),
442 Some("https://first.example.com/policy".parse().unwrap()),
443 Some("https://first.example.com/tos".parse().unwrap()),
444 Some("https://first.example.com/jwks.json".parse().unwrap()),
445 None,
446 None,
447 None,
448 None,
449 None,
450 Some("https://first.example.com/login".parse().unwrap()),
451 )
452 .await
453 .unwrap();
454 let client2 = repo
455 .oauth2_client()
456 .add(
457 &mut rng,
458 &clock,
459 vec!["https://second.example.com/redirect".parse().unwrap()],
460 None,
461 None,
462 vec![GrantType::AuthorizationCode],
463 Some("Second client".to_owned()),
464 Some("https://second.example.com/logo.png".parse().unwrap()),
465 Some("https://second.example.com/".parse().unwrap()),
466 Some("https://second.example.com/policy".parse().unwrap()),
467 Some("https://second.example.com/tos".parse().unwrap()),
468 Some("https://second.example.com/jwks.json".parse().unwrap()),
469 None,
470 None,
471 None,
472 None,
473 None,
474 Some("https://second.example.com/login".parse().unwrap()),
475 )
476 .await
477 .unwrap();
478
479 let scope = Scope::from_iter([OPENID, EMAIL]);
480 let scope2 = Scope::from_iter([OPENID, PROFILE]);
481
482 let session11 = repo
486 .oauth2_session()
487 .add_from_browser_session(&mut rng, &clock, &client1, &user1_session, scope.clone())
488 .await
489 .unwrap();
490 clock.advance(Duration::try_minutes(1).unwrap());
491
492 let session12 = repo
493 .oauth2_session()
494 .add_from_browser_session(&mut rng, &clock, &client1, &user2_session, scope.clone())
495 .await
496 .unwrap();
497 clock.advance(Duration::try_minutes(1).unwrap());
498
499 let session21 = repo
500 .oauth2_session()
501 .add_from_browser_session(&mut rng, &clock, &client2, &user1_session, scope2.clone())
502 .await
503 .unwrap();
504 clock.advance(Duration::try_minutes(1).unwrap());
505
506 let session22 = repo
507 .oauth2_session()
508 .add_from_browser_session(&mut rng, &clock, &client2, &user2_session, scope2.clone())
509 .await
510 .unwrap();
511 clock.advance(Duration::try_minutes(1).unwrap());
512
513 let session11 = repo
515 .oauth2_session()
516 .finish(&clock, session11)
517 .await
518 .unwrap();
519 let session22 = repo
520 .oauth2_session()
521 .finish(&clock, session22)
522 .await
523 .unwrap();
524
525 let pagination = Pagination::first(10);
526
527 let filter = OAuth2SessionFilter::new().for_any_user();
529 let list = repo
530 .oauth2_session()
531 .list(filter, pagination)
532 .await
533 .unwrap();
534 assert!(!list.has_next_page);
535 assert_eq!(list.edges.len(), 4);
536 assert_eq!(list.edges[0], session11);
537 assert_eq!(list.edges[1], session12);
538 assert_eq!(list.edges[2], session21);
539 assert_eq!(list.edges[3], session22);
540
541 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 4);
542
543 let filter = OAuth2SessionFilter::new().for_user(&user1);
545 let list = repo
546 .oauth2_session()
547 .list(filter, pagination)
548 .await
549 .unwrap();
550 assert!(!list.has_next_page);
551 assert_eq!(list.edges.len(), 2);
552 assert_eq!(list.edges[0], session11);
553 assert_eq!(list.edges[1], session21);
554
555 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
556
557 let filter = OAuth2SessionFilter::new().for_client(&client1);
559 let list = repo
560 .oauth2_session()
561 .list(filter, pagination)
562 .await
563 .unwrap();
564 assert!(!list.has_next_page);
565 assert_eq!(list.edges.len(), 2);
566 assert_eq!(list.edges[0], session11);
567 assert_eq!(list.edges[1], session12);
568
569 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
570
571 let filter = OAuth2SessionFilter::new()
573 .for_user(&user2)
574 .for_client(&client2);
575 let list = repo
576 .oauth2_session()
577 .list(filter, pagination)
578 .await
579 .unwrap();
580 assert!(!list.has_next_page);
581 assert_eq!(list.edges.len(), 1);
582 assert_eq!(list.edges[0], session22);
583
584 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
585
586 let filter = OAuth2SessionFilter::new().active_only();
588 let list = repo
589 .oauth2_session()
590 .list(filter, pagination)
591 .await
592 .unwrap();
593 assert!(!list.has_next_page);
594 assert_eq!(list.edges.len(), 2);
595 assert_eq!(list.edges[0], session12);
596 assert_eq!(list.edges[1], session21);
597
598 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
599
600 let filter = OAuth2SessionFilter::new().finished_only();
602 let list = repo
603 .oauth2_session()
604 .list(filter, pagination)
605 .await
606 .unwrap();
607 assert!(!list.has_next_page);
608 assert_eq!(list.edges.len(), 2);
609 assert_eq!(list.edges[0], session11);
610 assert_eq!(list.edges[1], session22);
611
612 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
613
614 let filter = OAuth2SessionFilter::new().finished_only().for_user(&user2);
616 let list = repo
617 .oauth2_session()
618 .list(filter, pagination)
619 .await
620 .unwrap();
621 assert!(!list.has_next_page);
622 assert_eq!(list.edges.len(), 1);
623 assert_eq!(list.edges[0], session22);
624
625 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
626
627 let filter = OAuth2SessionFilter::new()
629 .finished_only()
630 .for_client(&client2);
631 let list = repo
632 .oauth2_session()
633 .list(filter, pagination)
634 .await
635 .unwrap();
636 assert!(!list.has_next_page);
637 assert_eq!(list.edges.len(), 1);
638 assert_eq!(list.edges[0], session22);
639
640 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
641
642 let filter = OAuth2SessionFilter::new().active_only().for_user(&user2);
644 let list = repo
645 .oauth2_session()
646 .list(filter, pagination)
647 .await
648 .unwrap();
649 assert!(!list.has_next_page);
650 assert_eq!(list.edges.len(), 1);
651 assert_eq!(list.edges[0], session12);
652
653 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
654
655 let filter = OAuth2SessionFilter::new()
657 .active_only()
658 .for_client(&client2);
659 let list = repo
660 .oauth2_session()
661 .list(filter, pagination)
662 .await
663 .unwrap();
664 assert!(!list.has_next_page);
665 assert_eq!(list.edges.len(), 1);
666 assert_eq!(list.edges[0], session21);
667
668 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
669
670 let scope = Scope::from_iter([OPENID]);
672 let filter = OAuth2SessionFilter::new().with_scope(&scope);
673 let list = repo
674 .oauth2_session()
675 .list(filter, pagination)
676 .await
677 .unwrap();
678 assert!(!list.has_next_page);
679 assert_eq!(list.edges.len(), 4);
680 assert_eq!(list.edges[0], session11);
681 assert_eq!(list.edges[1], session12);
682 assert_eq!(list.edges[2], session21);
683 assert_eq!(list.edges[3], session22);
684 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 4);
685
686 let scope = Scope::from_iter([OPENID, EMAIL]);
688 let filter = OAuth2SessionFilter::new().with_scope(&scope);
689 let list = repo
690 .oauth2_session()
691 .list(filter, pagination)
692 .await
693 .unwrap();
694 assert!(!list.has_next_page);
695 assert_eq!(list.edges.len(), 2);
696 assert_eq!(list.edges[0], session11);
697 assert_eq!(list.edges[1], session12);
698 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 2);
699
700 let filter = OAuth2SessionFilter::new()
702 .with_scope(&scope)
703 .for_user(&user1);
704 let list = repo
705 .oauth2_session()
706 .list(filter, pagination)
707 .await
708 .unwrap();
709 assert_eq!(list.edges.len(), 1);
710 assert_eq!(list.edges[0], session11);
711 assert_eq!(repo.oauth2_session().count(filter).await.unwrap(), 1);
712
713 let affected = repo
715 .oauth2_session()
716 .finish_bulk(
717 &clock,
718 OAuth2SessionFilter::new()
719 .for_client(&client1)
720 .active_only(),
721 )
722 .await
723 .unwrap();
724 assert_eq!(affected, 1);
725
726 assert_eq!(
728 repo.oauth2_session()
729 .count(OAuth2SessionFilter::new().finished_only())
730 .await
731 .unwrap(),
732 3
733 );
734
735 assert_eq!(
737 repo.oauth2_session()
738 .count(OAuth2SessionFilter::new().active_only())
739 .await
740 .unwrap(),
741 1
742 );
743 }
744
745 #[sqlx::test(migrator = "crate::MIGRATOR")]
747 async fn test_device_code_grant_repository(pool: PgPool) {
748 let mut rng = ChaChaRng::seed_from_u64(42);
749 let clock = MockClock::default();
750 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
751
752 let client = repo
754 .oauth2_client()
755 .add(
756 &mut rng,
757 &clock,
758 vec!["https://example.com/redirect".parse().unwrap()],
759 None,
760 None,
761 vec![GrantType::AuthorizationCode],
762 Some("Example".to_owned()),
763 Some("https://example.com/logo.png".parse().unwrap()),
764 Some("https://example.com/".parse().unwrap()),
765 Some("https://example.com/policy".parse().unwrap()),
766 Some("https://example.com/tos".parse().unwrap()),
767 Some("https://example.com/jwks.json".parse().unwrap()),
768 None,
769 None,
770 None,
771 None,
772 None,
773 Some("https://example.com/login".parse().unwrap()),
774 )
775 .await
776 .unwrap();
777
778 let user = repo
780 .user()
781 .add(&mut rng, &clock, "john".to_owned())
782 .await
783 .unwrap();
784
785 let browser_session = repo
787 .browser_session()
788 .add(&mut rng, &clock, &user, None)
789 .await
790 .unwrap();
791
792 let user_code = "usercode";
793 let device_code = "devicecode";
794 let scope = Scope::from_iter([OPENID, EMAIL]);
795
796 let grant = repo
798 .oauth2_device_code_grant()
799 .add(
800 &mut rng,
801 &clock,
802 OAuth2DeviceCodeGrantParams {
803 client: &client,
804 scope: scope.clone(),
805 device_code: device_code.to_owned(),
806 user_code: user_code.to_owned(),
807 expires_in: Duration::try_minutes(5).unwrap(),
808 ip_address: None,
809 user_agent: None,
810 },
811 )
812 .await
813 .unwrap();
814
815 assert!(grant.is_pending());
816
817 let id = grant.id;
819 let lookup = repo.oauth2_device_code_grant().lookup(id).await.unwrap();
820 assert_eq!(lookup.as_ref(), Some(&grant));
821
822 let lookup = repo
824 .oauth2_device_code_grant()
825 .find_by_device_code(device_code)
826 .await
827 .unwrap();
828 assert_eq!(lookup.as_ref(), Some(&grant));
829
830 let lookup = repo
832 .oauth2_device_code_grant()
833 .find_by_user_code(user_code)
834 .await
835 .unwrap();
836 assert_eq!(lookup.as_ref(), Some(&grant));
837
838 let grant = repo
840 .oauth2_device_code_grant()
841 .fulfill(&clock, grant, &browser_session)
842 .await
843 .unwrap();
844 assert!(!grant.is_pending());
845 assert!(grant.is_fulfilled());
846
847 let res = repo
849 .oauth2_device_code_grant()
850 .reject(&clock, grant, &browser_session)
851 .await;
852 assert!(res.is_err());
853
854 let grant = repo
856 .oauth2_device_code_grant()
857 .lookup(id)
858 .await
859 .unwrap()
860 .unwrap();
861
862 let res = repo
864 .oauth2_device_code_grant()
865 .fulfill(&clock, grant, &browser_session)
866 .await;
867 assert!(res.is_err());
868
869 let grant = repo
871 .oauth2_device_code_grant()
872 .lookup(id)
873 .await
874 .unwrap()
875 .unwrap();
876
877 let session = repo
879 .oauth2_session()
880 .add_from_browser_session(&mut rng, &clock, &client, &browser_session, scope.clone())
881 .await
882 .unwrap();
883
884 let grant = repo
886 .oauth2_device_code_grant()
887 .exchange(&clock, grant, &session)
888 .await
889 .unwrap();
890 assert!(!grant.is_pending());
891 assert!(!grant.is_fulfilled());
892 assert!(grant.is_exchanged());
893
894 let res = repo
896 .oauth2_device_code_grant()
897 .exchange(&clock, grant, &session)
898 .await;
899 assert!(res.is_err());
900
901 let grant = repo
903 .oauth2_device_code_grant()
904 .add(
905 &mut rng,
906 &clock,
907 OAuth2DeviceCodeGrantParams {
908 client: &client,
909 scope: scope.clone(),
910 device_code: "second_devicecode".to_owned(),
911 user_code: "second_usercode".to_owned(),
912 expires_in: Duration::try_minutes(5).unwrap(),
913 ip_address: None,
914 user_agent: None,
915 },
916 )
917 .await
918 .unwrap();
919
920 let id = grant.id;
921
922 let grant = repo
924 .oauth2_device_code_grant()
925 .reject(&clock, grant, &browser_session)
926 .await
927 .unwrap();
928 assert!(!grant.is_pending());
929 assert!(grant.is_rejected());
930
931 let res = repo
933 .oauth2_device_code_grant()
934 .reject(&clock, grant, &browser_session)
935 .await;
936 assert!(res.is_err());
937
938 let grant = repo
940 .oauth2_device_code_grant()
941 .lookup(id)
942 .await
943 .unwrap()
944 .unwrap();
945
946 let res = repo
948 .oauth2_device_code_grant()
949 .fulfill(&clock, grant, &browser_session)
950 .await;
951 assert!(res.is_err());
952
953 let grant = repo
955 .oauth2_device_code_grant()
956 .lookup(id)
957 .await
958 .unwrap()
959 .unwrap();
960
961 let res = repo
963 .oauth2_device_code_grant()
964 .exchange(&clock, grant, &session)
965 .await;
966 assert!(res.is_err());
967 }
968}