diff --git a/crates/storage-pg/src/compat/mod.rs b/crates/storage-pg/src/compat/mod.rs index ae2a40b2..ab4ed33e 100644 --- a/crates/storage-pg/src/compat/mod.rs +++ b/crates/storage-pg/src/compat/mod.rs @@ -35,11 +35,12 @@ mod tests { CompatAccessTokenRepository, CompatRefreshTokenRepository, CompatSessionRepository, }, user::UserRepository, - Clock, Repository, RepositoryAccess, + Clock, Pagination, Repository, RepositoryAccess, }; use rand::SeedableRng; use rand_chacha::ChaChaRng; use sqlx::PgPool; + use ulid::Ulid; use crate::PgRepository; @@ -323,4 +324,126 @@ mod tests { repo.save().await.unwrap(); } + + #[sqlx::test(migrator = "crate::MIGRATOR")] + async fn test_compat_sso_login_repository(pool: PgPool) { + let mut rng = ChaChaRng::seed_from_u64(42); + let clock = MockClock::default(); + let mut repo = PgRepository::from_pool(&pool).await.unwrap().boxed(); + + // Create a user + let user = repo + .user() + .add(&mut rng, &clock, "john".to_owned()) + .await + .unwrap(); + + // Lookup an unknown SSO login + let login = repo.compat_sso_login().lookup(Ulid::nil()).await.unwrap(); + assert_eq!(login, None); + + // Lookup an unknown login token + let login = repo + .compat_sso_login() + .find_by_token("login-token") + .await + .unwrap(); + assert_eq!(login, None); + + // Start a new SSO login + let login = repo + .compat_sso_login() + .add( + &mut rng, + &clock, + "login-token".to_owned(), + "https://example.com/callback".parse().unwrap(), + ) + .await + .unwrap(); + assert!(login.is_pending()); + + // Lookup the login by ID + let login_lookup = repo + .compat_sso_login() + .lookup(login.id) + .await + .unwrap() + .expect("login not found"); + assert_eq!(login_lookup, login); + + // Find the login by token + let login_lookup = repo + .compat_sso_login() + .find_by_token("login-token") + .await + .unwrap() + .expect("login not found"); + assert_eq!(login_lookup, login); + + // Exchanging before fulfilling should not work + // Note: It should also not poison the SQL transaction + let res = repo + .compat_sso_login() + .exchange(&clock, login.clone()) + .await; + assert!(res.is_err()); + + // Start a compat session for that user + let device = Device::generate(&mut rng); + let session = repo + .compat_session() + .add(&mut rng, &clock, &user, device) + .await + .unwrap(); + + // Associate the login with the session + let login = repo + .compat_sso_login() + .fulfill(&clock, login, &session) + .await + .unwrap(); + assert!(login.is_fulfilled()); + + // Fulfilling again should not work + // Note: It should also not poison the SQL transaction + let res = repo + .compat_sso_login() + .fulfill(&clock, login.clone(), &session) + .await; + assert!(res.is_err()); + + // Exchange that login + let login = repo + .compat_sso_login() + .exchange(&clock, login) + .await + .unwrap(); + assert!(login.is_exchanged()); + + // Exchange again should not work + // Note: It should also not poison the SQL transaction + let res = repo + .compat_sso_login() + .exchange(&clock, login.clone()) + .await; + assert!(res.is_err()); + + // Fulfilling after exchanging should not work + // Note: It should also not poison the SQL transaction + let res = repo + .compat_sso_login() + .fulfill(&clock, login.clone(), &session) + .await; + assert!(res.is_err()); + + // List the logins for the user + let logins = repo + .compat_sso_login() + .list_paginated(&user, Pagination::first(10)) + .await + .unwrap(); + assert!(!logins.has_next_page); + assert_eq!(logins.edges, vec![login]); + } } diff --git a/crates/storage-pg/src/compat/sso_login.rs b/crates/storage-pg/src/compat/sso_login.rs index ae9ca083..328bd789 100644 --- a/crates/storage-pg/src/compat/sso_login.rs +++ b/crates/storage-pg/src/compat/sso_login.rs @@ -323,12 +323,12 @@ impl<'c> CompatSsoLoginRepository for PgCompatSsoLoginRepository<'c> { , cl.compat_session_id FROM compat_sso_logins cl - INNER JOIN compat_sessions ON compat_session_id + INNER JOIN compat_sessions cs USING (compat_session_id) "#, ); query - .push(" WHERE user_id = ") + .push(" WHERE cs.user_id = ") .push_bind(Uuid::from(user.id)) .generate_pagination("cl.compat_sso_login_id", pagination); diff --git a/crates/storage-pg/src/oauth2/mod.rs b/crates/storage-pg/src/oauth2/mod.rs index c0659aa4..120fca6c 100644 --- a/crates/storage-pg/src/oauth2/mod.rs +++ b/crates/storage-pg/src/oauth2/mod.rs @@ -176,6 +176,29 @@ mod tests { .await .unwrap(); + // Lookup the consent the user gave to the client + let consent = repo + .oauth2_client() + .get_consent_for_user(&client, &user) + .await + .unwrap(); + assert!(consent.is_empty()); + + // Give consent to the client + let scope = Scope::from_iter([OPENID]); + repo.oauth2_client() + .give_consent_for_user(&mut rng, &clock, &client, &user, &scope) + .await + .unwrap(); + + // Lookup the consent the user gave to the client + let consent = repo + .oauth2_client() + .get_consent_for_user(&client, &user) + .await + .unwrap(); + assert_eq!(scope, consent); + // Lookup a non-existing session let session = repo.oauth2_session().lookup(Ulid::nil()).await.unwrap(); assert_eq!(session, None); diff --git a/crates/storage-pg/src/user/email.rs b/crates/storage-pg/src/user/email.rs index 28b9d395..147542d9 100644 --- a/crates/storage-pg/src/user/email.rs +++ b/crates/storage-pg/src/user/email.rs @@ -249,7 +249,7 @@ impl<'c> UserEmailRepository for PgUserEmailRepository<'c> { query .push(" WHERE user_id = ") .push_bind(Uuid::from(user.id)) - .generate_pagination("ue.user_email_id", pagination); + .generate_pagination("user_email_id", pagination); let edges: Vec = query .build_query_as() diff --git a/crates/storage-pg/src/user/tests.rs b/crates/storage-pg/src/user/tests.rs index 29ebe19f..9aec949d 100644 --- a/crates/storage-pg/src/user/tests.rs +++ b/crates/storage-pg/src/user/tests.rs @@ -16,7 +16,7 @@ use chrono::Duration; use mas_storage::{ clock::MockClock, user::{BrowserSessionRepository, UserEmailRepository, UserPasswordRepository, UserRepository}, - Repository, RepositoryAccess, + Pagination, Repository, RepositoryAccess, }; use rand::SeedableRng; use rand_chacha::ChaChaRng; @@ -230,6 +230,16 @@ async fn test_user_email_repo(pool: PgPool) { .unwrap() .is_some()); + // Listing the user emails should work + let emails = repo + .user_email() + .list_paginated(&user, Pagination::first(10)) + .await + .unwrap(); + assert!(!emails.has_next_page); + assert_eq!(emails.edges.len(), 1); + assert_eq!(emails.edges[0], user_email); + // Deleting the user email should work repo.user_email().remove(user_email).await.unwrap(); assert_eq!(repo.user_email().count(&user).await.unwrap(), 0);