1use std::net::IpAddr;
8
9use async_trait::async_trait;
10use chrono::{DateTime, Utc};
11use mas_data_model::{
12 Clock, UlidExt as _, UpstreamOAuthAuthorizationSession, UserEmailAuthentication,
13 UserRegistration, UserRegistrationPassword, UserRegistrationToken,
14};
15use mas_storage::user::UserRegistrationRepository;
16use rand::Rng;
17use sqlx::PgConnection;
18use ulid::Ulid;
19use url::Url;
20use uuid::Uuid;
21
22use crate::{DatabaseError, DatabaseInconsistencyError, ExecuteExt as _};
23
24pub struct PgUserRegistrationRepository<'c> {
27 conn: &'c mut PgConnection,
28}
29
30impl<'c> PgUserRegistrationRepository<'c> {
31 pub fn new(conn: &'c mut PgConnection) -> Self {
34 Self { conn }
35 }
36}
37
38struct UserRegistrationLookup {
39 user_registration_id: Uuid,
40 ip_address: Option<IpAddr>,
41 user_agent: Option<String>,
42 post_auth_action: Option<serde_json::Value>,
43 username: String,
44 display_name: Option<String>,
45 terms_url: Option<String>,
46 email_authentication_id: Option<Uuid>,
47 user_registration_token_id: Option<Uuid>,
48 hashed_password: Option<String>,
49 hashed_password_version: Option<i32>,
50 upstream_oauth_authorization_session_id: Option<Uuid>,
51 created_at: DateTime<Utc>,
52 completed_at: Option<DateTime<Utc>>,
53}
54
55impl TryFrom<UserRegistrationLookup> for UserRegistration {
56 type Error = DatabaseInconsistencyError;
57
58 fn try_from(value: UserRegistrationLookup) -> Result<Self, Self::Error> {
59 let id = Ulid::from(value.user_registration_id);
60
61 let password = match (value.hashed_password, value.hashed_password_version) {
62 (Some(hashed_password), Some(version)) => {
63 let version = version.try_into().map_err(|e| {
64 DatabaseInconsistencyError::on("user_registrations")
65 .column("hashed_password_version")
66 .row(id)
67 .source(e)
68 })?;
69
70 Some(UserRegistrationPassword {
71 hashed_password,
72 version,
73 })
74 }
75 (None, None) => None,
76 _ => {
77 return Err(DatabaseInconsistencyError::on("user_registrations")
78 .column("hashed_password")
79 .row(id));
80 }
81 };
82
83 let terms_url = value
84 .terms_url
85 .map(|u| u.parse())
86 .transpose()
87 .map_err(|e| {
88 DatabaseInconsistencyError::on("user_registrations")
89 .column("terms_url")
90 .row(id)
91 .source(e)
92 })?;
93
94 Ok(UserRegistration {
95 id,
96 ip_address: value.ip_address,
97 user_agent: value.user_agent,
98 post_auth_action: value.post_auth_action,
99 username: value.username,
100 display_name: value.display_name,
101 terms_url,
102 email_authentication_id: value.email_authentication_id.map(Ulid::from),
103 user_registration_token_id: value.user_registration_token_id.map(Ulid::from),
104 password,
105 upstream_oauth_authorization_session_id: value
106 .upstream_oauth_authorization_session_id
107 .map(Ulid::from),
108 created_at: value.created_at,
109 completed_at: value.completed_at,
110 })
111 }
112}
113
114#[async_trait]
115impl UserRegistrationRepository for PgUserRegistrationRepository<'_> {
116 type Error = DatabaseError;
117
118 #[tracing::instrument(
119 name = "db.user_registration.lookup",
120 skip_all,
121 fields(
122 db.query.text,
123 user_registration.id = %id,
124 ),
125 )]
126 async fn lookup(&mut self, id: Ulid) -> Result<Option<UserRegistration>, Self::Error> {
127 let res = sqlx::query_as!(
128 UserRegistrationLookup,
129 r#"
130 SELECT user_registration_id
131 , ip_address as "ip_address: IpAddr"
132 , user_agent
133 , post_auth_action
134 , username
135 , display_name
136 , terms_url
137 , email_authentication_id
138 , user_registration_token_id
139 , hashed_password
140 , hashed_password_version
141 , upstream_oauth_authorization_session_id
142 , created_at
143 , completed_at
144 FROM user_registrations
145 WHERE user_registration_id = $1
146 "#,
147 Uuid::from(id),
148 )
149 .traced()
150 .fetch_optional(&mut *self.conn)
151 .await?;
152
153 let Some(res) = res else { return Ok(None) };
154
155 Ok(Some(res.try_into()?))
156 }
157
158 #[tracing::instrument(
159 name = "db.user_registration.add",
160 skip_all,
161 fields(
162 db.query.text,
163 user_registration.id,
164 ),
165 )]
166 async fn add(
167 &mut self,
168 rng: &mut (dyn Rng + Send),
169 clock: &dyn Clock,
170 username: String,
171 ip_address: Option<IpAddr>,
172 user_agent: Option<String>,
173 post_auth_action: Option<serde_json::Value>,
174 ) -> Result<UserRegistration, Self::Error> {
175 let created_at = clock.now();
176 let id = Ulid::from_datetime_with_rng(created_at, rng);
177 tracing::Span::current().record("user_registration.id", tracing::field::display(id));
178
179 sqlx::query!(
180 r#"
181 INSERT INTO user_registrations
182 ( user_registration_id
183 , ip_address
184 , user_agent
185 , post_auth_action
186 , username
187 , created_at
188 )
189 VALUES ($1, $2, $3, $4, $5, $6)
190 "#,
191 Uuid::from(id),
192 ip_address as Option<IpAddr>,
193 user_agent.as_deref(),
194 post_auth_action,
195 username,
196 created_at,
197 )
198 .traced()
199 .execute(&mut *self.conn)
200 .await?;
201
202 Ok(UserRegistration {
203 id,
204 ip_address,
205 user_agent,
206 post_auth_action,
207 created_at,
208 completed_at: None,
209 username,
210 display_name: None,
211 terms_url: None,
212 email_authentication_id: None,
213 user_registration_token_id: None,
214 password: None,
215 upstream_oauth_authorization_session_id: None,
216 })
217 }
218
219 #[tracing::instrument(
220 name = "db.user_registration.set_display_name",
221 skip_all,
222 fields(
223 db.query.text,
224 user_registration.id = %user_registration.id,
225 user_registration.display_name = display_name,
226 ),
227 )]
228 async fn set_display_name(
229 &mut self,
230 mut user_registration: UserRegistration,
231 display_name: String,
232 ) -> Result<UserRegistration, Self::Error> {
233 let res = sqlx::query!(
234 r#"
235 UPDATE user_registrations
236 SET display_name = $2
237 WHERE user_registration_id = $1 AND completed_at IS NULL
238 "#,
239 Uuid::from(user_registration.id),
240 display_name,
241 )
242 .traced()
243 .execute(&mut *self.conn)
244 .await?;
245
246 DatabaseError::ensure_affected_rows(&res, 1)?;
247
248 user_registration.display_name = Some(display_name);
249
250 Ok(user_registration)
251 }
252
253 #[tracing::instrument(
254 name = "db.user_registration.set_terms_url",
255 skip_all,
256 fields(
257 db.query.text,
258 user_registration.id = %user_registration.id,
259 user_registration.terms_url = %terms_url,
260 ),
261 )]
262 async fn set_terms_url(
263 &mut self,
264 mut user_registration: UserRegistration,
265 terms_url: Url,
266 ) -> Result<UserRegistration, Self::Error> {
267 let res = sqlx::query!(
268 r#"
269 UPDATE user_registrations
270 SET terms_url = $2
271 WHERE user_registration_id = $1 AND completed_at IS NULL
272 "#,
273 Uuid::from(user_registration.id),
274 terms_url.as_str(),
275 )
276 .traced()
277 .execute(&mut *self.conn)
278 .await?;
279
280 DatabaseError::ensure_affected_rows(&res, 1)?;
281
282 user_registration.terms_url = Some(terms_url);
283
284 Ok(user_registration)
285 }
286
287 #[tracing::instrument(
288 name = "db.user_registration.set_email_authentication",
289 skip_all,
290 fields(
291 db.query.text,
292 %user_registration.id,
293 %user_email_authentication.id,
294 %user_email_authentication.email,
295 ),
296 )]
297 async fn set_email_authentication(
298 &mut self,
299 mut user_registration: UserRegistration,
300 user_email_authentication: &UserEmailAuthentication,
301 ) -> Result<UserRegistration, Self::Error> {
302 let res = sqlx::query!(
303 r#"
304 UPDATE user_registrations
305 SET email_authentication_id = $2
306 WHERE user_registration_id = $1 AND completed_at IS NULL
307 "#,
308 Uuid::from(user_registration.id),
309 Uuid::from(user_email_authentication.id),
310 )
311 .traced()
312 .execute(&mut *self.conn)
313 .await?;
314
315 DatabaseError::ensure_affected_rows(&res, 1)?;
316
317 user_registration.email_authentication_id = Some(user_email_authentication.id);
318
319 Ok(user_registration)
320 }
321
322 #[tracing::instrument(
323 name = "db.user_registration.set_password",
324 skip_all,
325 fields(
326 db.query.text,
327 user_registration.id = %user_registration.id,
328 user_registration.hashed_password = hashed_password,
329 user_registration.hashed_password_version = version,
330 ),
331 )]
332 async fn set_password(
333 &mut self,
334 mut user_registration: UserRegistration,
335 hashed_password: String,
336 version: u16,
337 ) -> Result<UserRegistration, Self::Error> {
338 let res = sqlx::query!(
339 r#"
340 UPDATE user_registrations
341 SET hashed_password = $2, hashed_password_version = $3
342 WHERE user_registration_id = $1 AND completed_at IS NULL
343 "#,
344 Uuid::from(user_registration.id),
345 hashed_password,
346 i32::from(version),
347 )
348 .traced()
349 .execute(&mut *self.conn)
350 .await?;
351
352 DatabaseError::ensure_affected_rows(&res, 1)?;
353
354 user_registration.password = Some(UserRegistrationPassword {
355 hashed_password,
356 version,
357 });
358
359 Ok(user_registration)
360 }
361
362 #[tracing::instrument(
363 name = "db.user_registration.set_registration_token",
364 skip_all,
365 fields(
366 db.query.text,
367 %user_registration.id,
368 %user_registration_token.id,
369 ),
370 )]
371 async fn set_registration_token(
372 &mut self,
373 mut user_registration: UserRegistration,
374 user_registration_token: &UserRegistrationToken,
375 ) -> Result<UserRegistration, Self::Error> {
376 let res = sqlx::query!(
377 r#"
378 UPDATE user_registrations
379 SET user_registration_token_id = $2
380 WHERE user_registration_id = $1 AND completed_at IS NULL
381 "#,
382 Uuid::from(user_registration.id),
383 Uuid::from(user_registration_token.id),
384 )
385 .traced()
386 .execute(&mut *self.conn)
387 .await?;
388
389 DatabaseError::ensure_affected_rows(&res, 1)?;
390
391 user_registration.user_registration_token_id = Some(user_registration_token.id);
392
393 Ok(user_registration)
394 }
395
396 #[tracing::instrument(
397 name = "db.user_registration.set_upstream_oauth_authorization_session",
398 skip_all,
399 fields(
400 db.query.text,
401 %user_registration.id,
402 %upstream_oauth_authorization_session.id,
403 ),
404 )]
405 async fn set_upstream_oauth_authorization_session(
406 &mut self,
407 mut user_registration: UserRegistration,
408 upstream_oauth_authorization_session: &UpstreamOAuthAuthorizationSession,
409 ) -> Result<UserRegistration, Self::Error> {
410 let res = sqlx::query!(
411 r#"
412 UPDATE user_registrations
413 SET upstream_oauth_authorization_session_id = $2
414 WHERE user_registration_id = $1 AND completed_at IS NULL
415 "#,
416 Uuid::from(user_registration.id),
417 Uuid::from(upstream_oauth_authorization_session.id),
418 )
419 .traced()
420 .execute(&mut *self.conn)
421 .await?;
422
423 DatabaseError::ensure_affected_rows(&res, 1)?;
424
425 user_registration.upstream_oauth_authorization_session_id =
426 Some(upstream_oauth_authorization_session.id);
427
428 Ok(user_registration)
429 }
430
431 #[tracing::instrument(
432 name = "db.user_registration.complete",
433 skip_all,
434 fields(
435 db.query.text,
436 user_registration.id = %user_registration.id,
437 ),
438 )]
439 async fn complete(
440 &mut self,
441 clock: &dyn Clock,
442 mut user_registration: UserRegistration,
443 ) -> Result<UserRegistration, Self::Error> {
444 let completed_at = clock.now();
445 let res = sqlx::query!(
446 r#"
447 UPDATE user_registrations
448 SET completed_at = $2
449 WHERE user_registration_id = $1 AND completed_at IS NULL
450 "#,
451 Uuid::from(user_registration.id),
452 completed_at,
453 )
454 .traced()
455 .execute(&mut *self.conn)
456 .await?;
457
458 DatabaseError::ensure_affected_rows(&res, 1)?;
459
460 user_registration.completed_at = Some(completed_at);
461
462 Ok(user_registration)
463 }
464
465 #[tracing::instrument(
466 name = "db.user_registration.cleanup",
467 skip_all,
468 fields(
469 db.query.text,
470 ),
471 )]
472 async fn cleanup(
473 &mut self,
474 since: Option<Ulid>,
475 until: Ulid,
476 limit: usize,
477 ) -> Result<(usize, Option<Ulid>), Self::Error> {
478 let res = sqlx::query_scalar!(
483 r#"
484 WITH to_delete AS (
485 SELECT user_registration_id
486 FROM user_registrations
487 WHERE ($1::uuid IS NULL OR user_registration_id > $1)
488 AND user_registration_id <= $2
489 ORDER BY user_registration_id
490 LIMIT $3
491 )
492 DELETE FROM user_registrations
493 USING to_delete
494 WHERE user_registrations.user_registration_id = to_delete.user_registration_id
495 RETURNING user_registrations.user_registration_id
496 "#,
497 since.map(Uuid::from),
498 Uuid::from(until),
499 i64::try_from(limit).unwrap_or(i64::MAX)
500 )
501 .traced()
502 .fetch_all(&mut *self.conn)
503 .await?;
504
505 let count = res.len();
506 let max_id = res.into_iter().max();
507
508 Ok((count, max_id.map(Ulid::from)))
509 }
510}
511
512#[cfg(test)]
513mod tests {
514 use std::net::{IpAddr, Ipv4Addr};
515
516 use mas_data_model::{
517 Clock, UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderDiscoveryMode,
518 UpstreamOAuthProviderOnBackchannelLogout, UpstreamOAuthProviderPkceMode,
519 UpstreamOAuthProviderTokenAuthMethod, UserRegistrationPassword, clock::MockClock,
520 };
521 use mas_iana::jose::JsonWebSignatureAlg;
522 use mas_storage::upstream_oauth2::UpstreamOAuthProviderParams;
523 use oauth2_types::scope::Scope;
524 use rand::SeedableRng;
525 use rand_chacha::ChaChaRng;
526 use sqlx::PgPool;
527
528 use crate::PgRepository;
529
530 #[sqlx::test(migrator = "crate::MIGRATOR")]
531 async fn test_create_lookup_complete(pool: PgPool) {
532 let mut rng = ChaChaRng::seed_from_u64(42);
533 let clock = MockClock::default();
534
535 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
536
537 let registration = repo
538 .user_registration()
539 .add(&mut rng, &clock, "alice".to_owned(), None, None, None)
540 .await
541 .unwrap();
542
543 assert_eq!(registration.created_at, clock.now());
544 assert_eq!(registration.completed_at, None);
545 assert_eq!(registration.username, "alice");
546 assert_eq!(registration.display_name, None);
547 assert_eq!(registration.terms_url, None);
548 assert_eq!(registration.email_authentication_id, None);
549 assert_eq!(registration.password, None);
550 assert_eq!(registration.user_agent, None);
551 assert_eq!(registration.ip_address, None);
552 assert_eq!(registration.post_auth_action, None);
553
554 let lookup = repo
555 .user_registration()
556 .lookup(registration.id)
557 .await
558 .unwrap()
559 .unwrap();
560
561 assert_eq!(lookup.id, registration.id);
562 assert_eq!(lookup.created_at, registration.created_at);
563 assert_eq!(lookup.completed_at, registration.completed_at);
564 assert_eq!(lookup.username, registration.username);
565 assert_eq!(lookup.display_name, registration.display_name);
566 assert_eq!(lookup.terms_url, registration.terms_url);
567 assert_eq!(
568 lookup.email_authentication_id,
569 registration.email_authentication_id
570 );
571 assert_eq!(lookup.password, registration.password);
572 assert_eq!(lookup.user_agent, registration.user_agent);
573 assert_eq!(lookup.ip_address, registration.ip_address);
574 assert_eq!(lookup.post_auth_action, registration.post_auth_action);
575
576 let registration = repo
578 .user_registration()
579 .complete(&clock, registration)
580 .await
581 .unwrap();
582 assert_eq!(registration.completed_at, Some(clock.now()));
583
584 let lookup = repo
586 .user_registration()
587 .lookup(registration.id)
588 .await
589 .unwrap()
590 .unwrap();
591 assert_eq!(lookup.completed_at, registration.completed_at);
592
593 let res = repo
595 .user_registration()
596 .complete(&clock, registration)
597 .await;
598 assert!(res.is_err());
599 }
600
601 #[sqlx::test(migrator = "crate::MIGRATOR")]
602 async fn test_create_useragent_ipaddress(pool: PgPool) {
603 let mut rng = ChaChaRng::seed_from_u64(42);
604 let clock = MockClock::default();
605
606 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
607
608 let registration = repo
609 .user_registration()
610 .add(
611 &mut rng,
612 &clock,
613 "alice".to_owned(),
614 Some(IpAddr::V4(Ipv4Addr::LOCALHOST)),
615 Some("Mozilla/5.0".to_owned()),
616 Some(serde_json::json!({"action": "continue_compat_sso_login", "id": "01FSHN9AG0MKGTBNZ16RDR3PVY"})),
617 )
618 .await
619 .unwrap();
620
621 assert_eq!(registration.user_agent, Some("Mozilla/5.0".to_owned()));
622 assert_eq!(
623 registration.ip_address,
624 Some(IpAddr::V4(Ipv4Addr::LOCALHOST))
625 );
626 assert_eq!(
627 registration.post_auth_action,
628 Some(
629 serde_json::json!({"action": "continue_compat_sso_login", "id": "01FSHN9AG0MKGTBNZ16RDR3PVY"})
630 )
631 );
632
633 let lookup = repo
634 .user_registration()
635 .lookup(registration.id)
636 .await
637 .unwrap()
638 .unwrap();
639
640 assert_eq!(lookup.user_agent, registration.user_agent);
641 assert_eq!(lookup.ip_address, registration.ip_address);
642 assert_eq!(lookup.post_auth_action, registration.post_auth_action);
643 }
644
645 #[sqlx::test(migrator = "crate::MIGRATOR")]
646 async fn test_set_display_name(pool: PgPool) {
647 let mut rng = ChaChaRng::seed_from_u64(42);
648 let clock = MockClock::default();
649
650 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
651
652 let registration = repo
653 .user_registration()
654 .add(&mut rng, &clock, "alice".to_owned(), None, None, None)
655 .await
656 .unwrap();
657
658 assert_eq!(registration.display_name, None);
659
660 let registration = repo
661 .user_registration()
662 .set_display_name(registration, "Alice".to_owned())
663 .await
664 .unwrap();
665
666 assert_eq!(registration.display_name, Some("Alice".to_owned()));
667
668 let lookup = repo
669 .user_registration()
670 .lookup(registration.id)
671 .await
672 .unwrap()
673 .unwrap();
674
675 assert_eq!(lookup.display_name, registration.display_name);
676
677 let registration = repo
679 .user_registration()
680 .set_display_name(registration, "Bob".to_owned())
681 .await
682 .unwrap();
683
684 assert_eq!(registration.display_name, Some("Bob".to_owned()));
685
686 let lookup = repo
687 .user_registration()
688 .lookup(registration.id)
689 .await
690 .unwrap()
691 .unwrap();
692
693 assert_eq!(lookup.display_name, registration.display_name);
694
695 let registration = repo
697 .user_registration()
698 .complete(&clock, registration)
699 .await
700 .unwrap();
701
702 let res = repo
703 .user_registration()
704 .set_display_name(registration, "Charlie".to_owned())
705 .await;
706 assert!(res.is_err());
707 }
708
709 #[sqlx::test(migrator = "crate::MIGRATOR")]
710 async fn test_set_terms_url(pool: PgPool) {
711 let mut rng = ChaChaRng::seed_from_u64(42);
712 let clock = MockClock::default();
713
714 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
715
716 let registration = repo
717 .user_registration()
718 .add(&mut rng, &clock, "alice".to_owned(), None, None, None)
719 .await
720 .unwrap();
721
722 assert_eq!(registration.terms_url, None);
723
724 let registration = repo
725 .user_registration()
726 .set_terms_url(registration, "https://example.com/terms".parse().unwrap())
727 .await
728 .unwrap();
729
730 assert_eq!(
731 registration.terms_url,
732 Some("https://example.com/terms".parse().unwrap())
733 );
734
735 let lookup = repo
736 .user_registration()
737 .lookup(registration.id)
738 .await
739 .unwrap()
740 .unwrap();
741
742 assert_eq!(lookup.terms_url, registration.terms_url);
743
744 let registration = repo
746 .user_registration()
747 .set_terms_url(registration, "https://example.com/terms2".parse().unwrap())
748 .await
749 .unwrap();
750
751 assert_eq!(
752 registration.terms_url,
753 Some("https://example.com/terms2".parse().unwrap())
754 );
755
756 let lookup = repo
757 .user_registration()
758 .lookup(registration.id)
759 .await
760 .unwrap()
761 .unwrap();
762
763 assert_eq!(lookup.terms_url, registration.terms_url);
764
765 let registration = repo
767 .user_registration()
768 .complete(&clock, registration)
769 .await
770 .unwrap();
771
772 let res = repo
773 .user_registration()
774 .set_terms_url(registration, "https://example.com/terms3".parse().unwrap())
775 .await;
776 assert!(res.is_err());
777 }
778
779 #[sqlx::test(migrator = "crate::MIGRATOR")]
780 async fn test_set_email_authentication(pool: PgPool) {
781 let mut rng = ChaChaRng::seed_from_u64(42);
782 let clock = MockClock::default();
783
784 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
785
786 let registration = repo
787 .user_registration()
788 .add(&mut rng, &clock, "alice".to_owned(), None, None, None)
789 .await
790 .unwrap();
791
792 assert_eq!(registration.email_authentication_id, None);
793
794 let authentication = repo
795 .user_email()
796 .add_authentication_for_registration(
797 &mut rng,
798 &clock,
799 "alice@example.com".to_owned(),
800 ®istration,
801 )
802 .await
803 .unwrap();
804
805 let registration = repo
806 .user_registration()
807 .set_email_authentication(registration, &authentication)
808 .await
809 .unwrap();
810
811 assert_eq!(
812 registration.email_authentication_id,
813 Some(authentication.id)
814 );
815
816 let lookup = repo
817 .user_registration()
818 .lookup(registration.id)
819 .await
820 .unwrap()
821 .unwrap();
822
823 assert_eq!(
824 lookup.email_authentication_id,
825 registration.email_authentication_id
826 );
827
828 let registration = repo
830 .user_registration()
831 .set_email_authentication(registration, &authentication)
832 .await
833 .unwrap();
834
835 assert_eq!(
836 registration.email_authentication_id,
837 Some(authentication.id)
838 );
839
840 let lookup = repo
841 .user_registration()
842 .lookup(registration.id)
843 .await
844 .unwrap()
845 .unwrap();
846
847 assert_eq!(
848 lookup.email_authentication_id,
849 registration.email_authentication_id
850 );
851
852 let registration = repo
854 .user_registration()
855 .complete(&clock, registration)
856 .await
857 .unwrap();
858
859 let res = repo
860 .user_registration()
861 .set_email_authentication(registration, &authentication)
862 .await;
863 assert!(res.is_err());
864 }
865
866 #[sqlx::test(migrator = "crate::MIGRATOR")]
867 async fn test_set_password(pool: PgPool) {
868 let mut rng = ChaChaRng::seed_from_u64(42);
869 let clock = MockClock::default();
870
871 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
872
873 let registration = repo
874 .user_registration()
875 .add(&mut rng, &clock, "alice".to_owned(), None, None, None)
876 .await
877 .unwrap();
878
879 assert_eq!(registration.password, None);
880
881 let registration = repo
882 .user_registration()
883 .set_password(registration, "fakehashedpassword".to_owned(), 1)
884 .await
885 .unwrap();
886
887 assert_eq!(
888 registration.password,
889 Some(UserRegistrationPassword {
890 hashed_password: "fakehashedpassword".to_owned(),
891 version: 1,
892 })
893 );
894
895 let lookup = repo
896 .user_registration()
897 .lookup(registration.id)
898 .await
899 .unwrap()
900 .unwrap();
901
902 assert_eq!(lookup.password, registration.password);
903
904 let registration = repo
906 .user_registration()
907 .set_password(registration, "fakehashedpassword2".to_owned(), 2)
908 .await
909 .unwrap();
910
911 assert_eq!(
912 registration.password,
913 Some(UserRegistrationPassword {
914 hashed_password: "fakehashedpassword2".to_owned(),
915 version: 2,
916 })
917 );
918
919 let lookup = repo
920 .user_registration()
921 .lookup(registration.id)
922 .await
923 .unwrap()
924 .unwrap();
925
926 assert_eq!(lookup.password, registration.password);
927
928 let registration = repo
930 .user_registration()
931 .complete(&clock, registration)
932 .await
933 .unwrap();
934
935 let res = repo
936 .user_registration()
937 .set_password(registration, "fakehashedpassword3".to_owned(), 3)
938 .await;
939 assert!(res.is_err());
940 }
941
942 #[sqlx::test(migrator = "crate::MIGRATOR")]
943 async fn test_set_upstream_oauth_session(pool: PgPool) {
944 let mut rng = ChaChaRng::seed_from_u64(42);
945 let clock = MockClock::default();
946
947 let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed();
948
949 let registration = repo
950 .user_registration()
951 .add(&mut rng, &clock, "alice".to_owned(), None, None, None)
952 .await
953 .unwrap();
954
955 assert_eq!(registration.upstream_oauth_authorization_session_id, None);
956
957 let provider = repo
958 .upstream_oauth_provider()
959 .add(
960 &mut rng,
961 &clock,
962 UpstreamOAuthProviderParams {
963 issuer: Some("https://example.com/".to_owned()),
964 human_name: Some("Example Ltd.".to_owned()),
965 brand_name: None,
966 scope: Scope::from_iter([oauth2_types::scope::OPENID]),
967 token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::None,
968 token_endpoint_signing_alg: None,
969 id_token_signed_response_alg: JsonWebSignatureAlg::Rs256,
970 client_id: "client".to_owned(),
971 encrypted_client_secret: None,
972 claims_imports: UpstreamOAuthProviderClaimsImports::default(),
973 authorization_endpoint_override: None,
974 token_endpoint_override: None,
975 userinfo_endpoint_override: None,
976 fetch_userinfo: false,
977 userinfo_signed_response_alg: None,
978 jwks_uri_override: None,
979 discovery_mode: UpstreamOAuthProviderDiscoveryMode::Oidc,
980 pkce_mode: UpstreamOAuthProviderPkceMode::Auto,
981 response_mode: None,
982 additional_authorization_parameters: Vec::new(),
983 forward_login_hint: false,
984 ui_order: 0,
985 on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
986 registration_token_required: false,
987 },
988 )
989 .await
990 .unwrap();
991
992 let session = repo
993 .upstream_oauth_session()
994 .add(&mut rng, &clock, &provider, "state".to_owned(), None, None)
995 .await
996 .unwrap();
997
998 let registration = repo
999 .user_registration()
1000 .set_upstream_oauth_authorization_session(registration, &session)
1001 .await
1002 .unwrap();
1003
1004 assert_eq!(
1005 registration.upstream_oauth_authorization_session_id,
1006 Some(session.id)
1007 );
1008
1009 let lookup = repo
1010 .user_registration()
1011 .lookup(registration.id)
1012 .await
1013 .unwrap()
1014 .unwrap();
1015
1016 assert_eq!(
1017 lookup.upstream_oauth_authorization_session_id,
1018 registration.upstream_oauth_authorization_session_id
1019 );
1020
1021 let registration = repo
1023 .user_registration()
1024 .set_upstream_oauth_authorization_session(registration, &session)
1025 .await
1026 .unwrap();
1027
1028 assert_eq!(
1029 registration.upstream_oauth_authorization_session_id,
1030 Some(session.id)
1031 );
1032
1033 let lookup = repo
1034 .user_registration()
1035 .lookup(registration.id)
1036 .await
1037 .unwrap()
1038 .unwrap();
1039
1040 assert_eq!(
1041 lookup.upstream_oauth_authorization_session_id,
1042 registration.upstream_oauth_authorization_session_id
1043 );
1044
1045 let registration = repo
1047 .user_registration()
1048 .complete(&clock, registration)
1049 .await
1050 .unwrap();
1051
1052 let res = repo
1053 .user_registration()
1054 .set_upstream_oauth_authorization_session(registration, &session)
1055 .await;
1056 assert!(res.is_err());
1057 }
1058}