Add option for trusted providers to associate with existing accounts. (fixes #252)

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk
2026-03-04 09:00:26 +00:00
parent b423e1c5e6
commit 93aee26e11
3 changed files with 52 additions and 6 deletions

View File

@@ -636,14 +636,14 @@ async fn decide_user_id(
];
for choice in choices.into_iter().flatten() {
if let Some(user_id) = try_user_id(services, &choice, false).await {
if let Some(user_id) = try_user_id(services, provider, &choice, false).await {
return Ok(user_id);
}
}
let length = Some(15..23);
let unique_id = truncate_deterministic(unique_id, length).to_lowercase();
if let Some(user_id) = try_user_id(services, &unique_id, true).await {
if let Some(user_id) = try_user_id(services, provider, &unique_id, true).await {
return Ok(user_id);
}
@@ -653,8 +653,9 @@ async fn decide_user_id(
#[tracing::instrument(level = "debug", skip_all, fields(username))]
async fn try_user_id(
services: &Services,
provider: &Provider,
username: &str,
may_exist: bool,
unique_id: bool,
) -> Option<OwnedUserId> {
let server_name = services.globals.server_name();
let user_id = parse_user_id(server_name, username)
@@ -671,7 +672,15 @@ async fn try_user_id(
}
if services.users.exists(&user_id).await {
debug_warn!(?username, "Username exists.");
if provider.trusted {
info!(
?username,
provider = ?provider.brand,
"Authorizing trusted provider access to existing account."
);
return Some(user_id);
}
if services
.users
@@ -680,11 +689,12 @@ async fn try_user_id(
.ok()
.is_none_or(|origin| origin != "sso")
{
debug_warn!(?username, "Username has non-sso origin.");
debug_warn!(?username, "Existing username has non-sso origin.");
return None;
}
if !may_exist {
if !unique_id {
debug_warn!(?username, "Username exists.");
return None;
}
}

View File

@@ -2741,6 +2741,25 @@ pub struct IdentityProvider {
#[serde(default)]
pub userid_claims: BTreeSet<String>,
/// Trusted providers can cause username conflicts (i.e. account hijacking)
/// but this is precisely how an existing matrix account can be associated
/// with a provider. When this option is set to true, the way we compute a
/// Matrix UserId from userinfo claims is inverted: we find the first
/// matching user and grant access to it. Whereas by default, when set to
/// false, we skip matching users and register the first available username;
/// falling-back to random characters to avoid conflicts.
///
/// Only set this option to true for providers you self-host and control.
/// Never set this option to true for the public providers such as GitHub,
/// GitLab, etc.
///
/// Note that associating an existing user with an untrusted provider is
/// still possible but only with the command '!admin query oauth associate'.
///
/// default: false
#[serde(default)]
pub trusted: bool,
/// Optional extra path components after the issuer_url leading to the
/// location of the `.well-known` directory used for discovery. If the path
/// starts with a slash it will be treated as absolute, meaning overwriting

View File

@@ -2343,6 +2343,23 @@
#
#userid_claims = []
# Trusted providers can cause username conflicts (i.e. account hijacking)
# but this is precisely how an existing matrix account can be associated
# with a provider. When this option is set to true, the way we compute a
# Matrix UserId from userinfo claims is inverted: we find the first
# matching user and grant access to it. Whereas by default, when set to
# false, we skip matching users and register the first available username;
# falling-back to random characters to avoid conflicts.
#
# Only set this option to true for providers you self-host and control.
# Never set this option to true for the public providers such as GitHub,
# GitLab, etc.
#
# Note that associating an existing user with an untrusted provider is
# still possible but only with the command '!admin query oauth associate'.
#
#trusted = false
# Optional extra path components after the issuer_url leading to the
# location of the `.well-known` directory used for discovery. If the path
# starts with a slash it will be treated as absolute, meaning overwriting