-
Notifications
You must be signed in to change notification settings - Fork 44
allow account linking for existing users when the localpart matches in upstream OAuth 2.0 logins #4193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
9d35eeb
to
d50b6c0
Compare
d50b6c0
to
405abce
Compare
405abce
to
11f17cc
Compare
8f7d65b
to
0307f3c
Compare
0307f3c
to
63f811a
Compare
fa428d9
to
3d64262
Compare
3d64262
to
f5a3404
Compare
e2b0077
to
4405e08
Compare
Wrong configuration of providerWhen activating the option
|
This comment was marked as resolved.
This comment was marked as resolved.
37f772c
to
22b3d5d
Compare
22b3d5d
to
39716c9
Compare
93a61bd
to
3d4d1a6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're almost there! A few nitpicks, and I also wonder whether the confirmation screen is needed in this case or not; we can also change that later
// new oauth link is allowed | ||
let ctx = UpstreamExistingLinkContext::new(existing_user) | ||
.with_csrf(csrf_token.form_value()) | ||
.with_language(locale); | ||
|
||
return Ok(( | ||
cookie_jar, | ||
Html(templates.render_upstream_oauth2_login_link(&ctx)?) | ||
.into_response(), | ||
)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm now wondering if it makes sense to show this intermediate page or not? For the 'active session' linking I think it does, as it's user-initiated and we don't want it to happen automatically, whereas here the intent really is for HS admins to transition from local passwords to upstream IDPs, the users should not care about this.
I'm fine with leaving it like that for now, and I'll clean that up later, as we also would like to make import dialog to be optional in some cases
e910c49
to
00d8171
Compare
… OAuth 2.0 logins
Co-authored-by: Quentin Gliech <quenting@element.io>
Co-authored-by: Quentin Gliech <quenting@element.io>
Co-authored-by: Quentin Gliech <quenting@element.io>
b877b9c
to
ea8c8d2
Compare
@sandhose I'm done with the required modifications, I think we can consider updating the user flow by skipping the screen in a second step. If good with you, we can merge :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Almost there, just a few minor stylistic things, but I think this should make the cut for the next RC on Tuesday!
#[allow(clippy::disallowed_methods)] | ||
let timestamp = chrono::Utc::now().timestamp_millis(); | ||
|
||
//suffix timestamp to generate unique test data | ||
let existing_username = format!("{}{}", "john", timestamp); | ||
let existing_email = format!("{}@{}", existing_username, "example.com"); | ||
|
||
//existing username matches oidc username | ||
let oidc_username = existing_username.clone(); | ||
|
||
//oidc email is different from existing email | ||
let oidc_email: String = format!("{}{}@{}", "any_email", timestamp, "example.com"); | ||
|
||
let subject = format!("{}+{}", "subject", timestamp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The databases are isolated between each test, no need to add timestamps there!
// User already exists, but it is not linked, neither logged in | ||
// Proceed by associating the link and log in the user | ||
// Upstream_session is used to re-render the username as it is the only source | ||
// of truth |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a little reflow and rewording of this comment
// User already exists, but it is not linked, neither logged in | |
// Proceed by associating the link and log in the user | |
// Upstream_session is used to re-render the username as it is the only source | |
// of truth | |
// There is an existing user with the same username, but no link. | |
// If the configuration allows it, the user is prompted to link the | |
// existing account. Note that we cannot trust the user input here, | |
// which is why we have to re-calculate the localpart, instead of | |
// passing it through form data. |
let localpart = render_attribute_template(&env, template, &context, true)?; | ||
|
||
let maybe_user = repo.user().find_by_username(&localpart.unwrap()).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better error out than crash (even if we recover from panics!)
let localpart = render_attribute_template(&env, template, &context, true)?; | |
let maybe_user = repo.user().find_by_username(&localpart.unwrap()).await?; | |
let Some(localpart) = render_attribute_template(&env, template, &context, true)? else { | |
// This should never be the case at this point | |
return Err(RouteError::InvalidFormAction); | |
}; | |
let maybe_user = repo.user().find_by_username(&localpart).await?; |
&state.clock, | ||
&mut repo, | ||
existing_username.clone(), | ||
existing_email.clone(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this test need an email? I don't think the create_user
utility is very helpful here
|
||
impl OnConflict { | ||
#[must_use] | ||
pub fn is_add(&self) -> bool { | ||
matches!(self, Self::Add) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see it used?
impl OnConflict { | |
#[must_use] | |
pub fn is_add(&self) -> bool { | |
matches!(self, Self::Add) | |
} | |
} |
Fixes Point 1 and 4 in #2089
Introducing new configuration
on_conflict
in sectionclaims_imports.localpart
to set the account linking strategyfail
(default value): do nothing, and fail the registration/login (corresponds to allow_existing_users: false in Synapse)add
: import the link without overriding existing links for this user (corresponds to allow_existing_users: true in Synapse)replace
: import the link and replace any existing link for this user => not implemented in this PRset
: import the link if and only if there is no existing link for this user => not implemented in this PRconfirmation page when configuration is

on_conflict: add
: