Skip to content

Commit f5a3404

Browse files
committed
allow importing existing users when the localpart matches in upstream OAuth 2.0 logins
1 parent a3202a6 commit f5a3404

21 files changed

+146
-63
lines changed

crates/cli/src/sync.rs

Lines changed: 1 addition & 0 deletions
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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,13 @@ pub struct Provider {
536536
#[serde(default, skip_serializing_if = "ClaimsImports::is_default")]
537537
pub claims_imports: ClaimsImports,
538538

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

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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ mod test_utils {
4646
token_endpoint_override: None,
4747
userinfo_endpoint_override: None,
4848
jwks_uri_override: None,
49+
allow_existing_users: true,
4950
additional_authorization_parameters: Vec::new(),
5051
ui_order: 0,
5152
}

crates/handlers/src/upstream_oauth2/cache.rs

Lines changed: 1 addition & 0 deletions
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

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,9 @@ pub(crate) async fn get(
465465
.await
466466
.map_err(RouteError::HomeserverConnection)?;
467467

468-
if maybe_existing_user.is_some() || !is_available {
468+
if !provider.allow_existing_users
469+
&& (maybe_existing_user.is_some() || !is_available)
470+
{
469471
if let Some(existing_user) = maybe_existing_user {
470472
// The mapper returned a username which already exists, but isn't
471473
// linked to this upstream user.
@@ -742,15 +744,16 @@ pub(crate) async fn post(
742744
mas_templates::UpstreamRegisterFormField::Username,
743745
FieldError::Required,
744746
);
745-
} else if repo.user().exists(&username).await? {
747+
} else if !provider.allow_existing_users && repo.user().exists(&username).await? {
746748
form_state.add_error_on_field(
747749
mas_templates::UpstreamRegisterFormField::Username,
748750
FieldError::Exists,
749751
);
750-
} else if !homeserver
751-
.is_localpart_available(&username)
752-
.await
753-
.map_err(RouteError::HomeserverConnection)?
752+
} else if !provider.allow_existing_users
753+
&& !homeserver
754+
.is_localpart_available(&username)
755+
.await
756+
.map_err(RouteError::HomeserverConnection)?
754757
{
755758
// The user already exists on the homeserver
756759
tracing::warn!(
@@ -830,10 +833,22 @@ pub(crate) async fn post(
830833
.into_response());
831834
}
832835

833-
REGISTRATION_COUNTER.add(1, &[KeyValue::new(PROVIDER, provider.id.to_string())]);
834-
835-
// Now we can create the user
836-
let user = repo.user().add(&mut rng, &clock, username).await?;
836+
let user = if provider.allow_existing_users {
837+
// If the provider allows existing users, we can use the existing user
838+
let existing_user = repo.user().find_by_username(&username).await?;
839+
if existing_user.is_some() {
840+
existing_user.unwrap()
841+
} else {
842+
REGISTRATION_COUNTER
843+
.add(1, &[KeyValue::new(PROVIDER, provider.id.to_string())]);
844+
// This case should not happen
845+
repo.user().add(&mut rng, &clock, username).await?
846+
}
847+
} else {
848+
REGISTRATION_COUNTER.add(1, &[KeyValue::new(PROVIDER, provider.id.to_string())]);
849+
// Now we can create the user
850+
repo.user().add(&mut rng, &clock, username).await?
851+
};
837852

838853
if let Some(terms_url) = &site_config.tos_uri {
839854
repo.user_terms()
@@ -975,6 +990,7 @@ mod tests {
975990
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
976991
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
977992
response_mode: None,
993+
allow_existing_users: true,
978994
additional_authorization_parameters: Vec::new(),
979995
ui_order: 0,
980996
},

crates/handlers/src/views/login.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ mod test {
494494
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
495495
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
496496
response_mode: None,
497+
allow_existing_users: true,
497498
additional_authorization_parameters: Vec::new(),
498499
ui_order: 0,
499500
},
@@ -535,6 +536,7 @@ mod test {
535536
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
536537
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
537538
response_mode: None,
539+
allow_existing_users: true,
538540
additional_authorization_parameters: Vec::new(),
539541
ui_order: 1,
540542
},

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

Lines changed: 8 additions & 2 deletions
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 renamed to crates/storage-pg/.sqlx/query-6e14a326d9c75e0ee0cb7d450badbd180cbb1f74749d4859e2fad5c48a1ef2bd.json

Lines changed: 3 additions & 2 deletions
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

Lines changed: 0 additions & 44 deletions
This file was deleted.

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

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)