Skip to content

Commit 8f7d65b

Browse files
committed
allow importing existing users when the localpart matches in upstream OAuth 2.0 logins
1 parent 5fc97d1 commit 8f7d65b

22 files changed

+144
-62
lines changed

crates/cli/src/sync.rs

+1
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ pub async fn config_sync(
292292
fetch_userinfo: provider.fetch_userinfo,
293293
userinfo_signed_response_alg: provider.userinfo_signed_response_alg,
294294
response_mode,
295+
allow_existing_users: provider.allow_existing_users,
295296
additional_authorization_parameters: provider
296297
.additional_authorization_parameters
297298
.into_iter()

crates/config/src/sections/upstream_oauth2.rs

+7
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,13 @@ pub struct Provider {
535535
#[serde(default, skip_serializing_if = "ClaimsImports::is_default")]
536536
pub claims_imports: ClaimsImports,
537537

538+
/// Whether to allow a user logging in via OIDC to match a pre-existing
539+
/// account instead of failing. This could be used if switching from
540+
/// password logins to OIDC.
541+
//Defaults to false.
542+
#[serde(default)]
543+
pub allow_existing_users: bool,
544+
538545
/// Additional parameters to include in the authorization request
539546
///
540547
/// Orders of the keys are not preserved.

crates/data-model/src/upstream_oauth2/provider.rs

+1
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ pub struct UpstreamOAuthProvider {
240240
pub created_at: DateTime<Utc>,
241241
pub disabled_at: Option<DateTime<Utc>>,
242242
pub claims_imports: ClaimsImports,
243+
pub allow_existing_users: bool,
243244
pub additional_authorization_parameters: Vec<(String, String)>,
244245
}
245246

crates/handlers/src/admin/v1/upstream_oauth_links/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ mod test_utils {
4242
token_endpoint_override: None,
4343
userinfo_endpoint_override: None,
4444
jwks_uri_override: None,
45+
allow_existing_users: true,
4546
additional_authorization_parameters: Vec::new(),
4647
ui_order: 0,
4748
}

crates/handlers/src/upstream_oauth2/cache.rs

+1
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ mod tests {
422422
created_at: clock.now(),
423423
disabled_at: None,
424424
claims_imports: UpstreamOAuthProviderClaimsImports::default(),
425+
allow_existing_users: false,
425426
additional_authorization_parameters: Vec::new(),
426427
};
427428

crates/handlers/src/upstream_oauth2/link.rs

+23-8
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,9 @@ pub(crate) async fn get(
440440
.await
441441
.map_err(RouteError::HomeserverConnection)?;
442442

443-
if maybe_existing_user.is_some() || !is_available {
443+
if !provider.allow_existing_users
444+
&& (maybe_existing_user.is_some() || !is_available)
445+
{
444446
if let Some(existing_user) = maybe_existing_user {
445447
// The mapper returned a username which already exists, but isn't
446448
// linked to this upstream user.
@@ -717,15 +719,16 @@ pub(crate) async fn post(
717719
mas_templates::UpstreamRegisterFormField::Username,
718720
FieldError::Required,
719721
);
720-
} else if repo.user().exists(&username).await? {
722+
} else if !provider.allow_existing_users && repo.user().exists(&username).await? {
721723
form_state.add_error_on_field(
722724
mas_templates::UpstreamRegisterFormField::Username,
723725
FieldError::Exists,
724726
);
725-
} else if !homeserver
726-
.is_localpart_available(&username)
727-
.await
728-
.map_err(RouteError::HomeserverConnection)?
727+
} else if !provider.allow_existing_users
728+
&& !homeserver
729+
.is_localpart_available(&username)
730+
.await
731+
.map_err(RouteError::HomeserverConnection)?
729732
{
730733
// The user already exists on the homeserver
731734
tracing::warn!(
@@ -805,8 +808,19 @@ pub(crate) async fn post(
805808
.into_response());
806809
}
807810

808-
// Now we can create the user
809-
let user = repo.user().add(&mut rng, &clock, username).await?;
811+
let user = if provider.allow_existing_users {
812+
// If the provider allows existing users, we can use the existing user
813+
let existing_user = repo.user().find_by_username(&username).await?;
814+
if existing_user.is_some() {
815+
existing_user.unwrap()
816+
} else {
817+
// This case should not happen
818+
repo.user().add(&mut rng, &clock, username).await?
819+
}
820+
} else {
821+
// Now we can create the user
822+
repo.user().add(&mut rng, &clock, username).await?
823+
};
810824

811825
if let Some(terms_url) = &site_config.tos_uri {
812826
repo.user_terms()
@@ -948,6 +962,7 @@ mod tests {
948962
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
949963
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
950964
response_mode: None,
965+
allow_existing_users: true,
951966
additional_authorization_parameters: Vec::new(),
952967
ui_order: 0,
953968
},

crates/handlers/src/views/login.rs

+2
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ mod test {
452452
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
453453
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
454454
response_mode: None,
455+
allow_existing_users: true,
455456
additional_authorization_parameters: Vec::new(),
456457
ui_order: 0,
457458
},
@@ -493,6 +494,7 @@ mod test {
493494
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
494495
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
495496
response_mode: None,
497+
allow_existing_users: true,
496498
additional_authorization_parameters: Vec::new(),
497499
ui_order: 1,
498500
},

crates/storage-pg/.sqlx/query-1d758df58ccfead4cb39ee8f88f60b382b7881e9c4ead31ff257ff5ff4414b6e.json crates/storage-pg/.sqlx/query-0ffcf354f8b7f00691812d4b8d86999d97ebe799d87737b4dfec1585edc0d0f9.json

+8-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/storage-pg/.sqlx/query-e25af41189846e26da99e5d8a1462eab5efe330f60ef8c6c813c747424ba7ec9.json crates/storage-pg/.sqlx/query-6e14a326d9c75e0ee0cb7d450badbd180cbb1f74749d4859e2fad5c48a1ef2bd.json

+3-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/storage-pg/.sqlx/query-72de26d5e3c56f4b0658685a95b45b647bb6637e55b662a5a548aa3308c62a8a.json

-44
This file was deleted.

crates/storage-pg/.sqlx/query-922eba626e453a12eb58ba460465de12d3f72073844306210b3aeaf3247db06c.json

+45
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)