mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-13 12:16:54 +00:00
feat: Allow existing legacy accounts to be linked interactively
This commit is contained in:
3 files changed
+128
-48
No files matched your search
+5
-15
@@ -58,9 +58,8 @@ pub struct PendingSession {
|
||||
}
|
||||
|
||||
pub enum SessionCompletionStatus {
|
||||
NeedsUserId,
|
||||
Complete(OwnedUserId),
|
||||
NeedsLocalpart,
|
||||
InvalidLocalpart(String),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -199,7 +198,7 @@ impl Service {
|
||||
pub async fn complete_session(
|
||||
&self,
|
||||
claims: &CoreIdTokenClaims,
|
||||
supplied_username: Option<String>,
|
||||
supplied_user_id: Option<OwnedUserId>,
|
||||
) -> Result<SessionCompletionStatus, &'static str> {
|
||||
let Some(OidcClient { config, .. }) = self.client.as_ref() else {
|
||||
return Err("Delegated authentication is not enabled on this server.");
|
||||
@@ -217,19 +216,10 @@ impl Service {
|
||||
UserId::parse(format!("@{localpart}:{}", self.services.globals.server_name()))
|
||||
.expect("saved localpart should be valid")
|
||||
} else if config.prompt_for_localpart {
|
||||
if let Some(supplied_username) = supplied_username {
|
||||
match self
|
||||
.services
|
||||
.users
|
||||
.determine_registration_user_id(Some(supplied_username), None, None)
|
||||
.await
|
||||
{
|
||||
| Ok(user_id) => user_id,
|
||||
| Err(err) =>
|
||||
return Ok(SessionCompletionStatus::InvalidLocalpart(err.message())),
|
||||
}
|
||||
if let Some(supplied_user_id) = supplied_user_id {
|
||||
supplied_user_id
|
||||
} else {
|
||||
return Ok(SessionCompletionStatus::NeedsLocalpart);
|
||||
return Ok(SessionCompletionStatus::NeedsUserId);
|
||||
}
|
||||
} else if let Some(preferred_username) = claims.preferred_username() {
|
||||
self.services
|
||||
|
||||
@@ -8,7 +8,7 @@ use axum::{
|
||||
};
|
||||
use conduwuit_service::{oauth::grant::AuthorizationCodeResponse, oidc::SessionCompletionStatus};
|
||||
use futures::FutureExt;
|
||||
use ruma::OwnedServerName;
|
||||
use ruma::{OwnedServerName, UserId};
|
||||
use serde::{Deserialize, de::IgnoredAny};
|
||||
use tower_sessions::Session;
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::{
|
||||
extract::{Expect, PostForm},
|
||||
pages::{
|
||||
GET_POST, Result, TemplateContext,
|
||||
components::UserCard,
|
||||
oidc::{OIDC_SESSION_ID_KEY, OidcSession, OidcSessionState},
|
||||
},
|
||||
response,
|
||||
@@ -30,14 +31,27 @@ pub(crate) fn build() -> Router<crate::State> {
|
||||
|
||||
template! {
|
||||
struct OidcComplete use "oidc_complete.html.j2" {
|
||||
server_name: OwnedServerName,
|
||||
username_error: Option<String>
|
||||
body: OidcCompleteBody
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum OidcCompleteBody {
|
||||
UsernamePrompt {
|
||||
server_name: OwnedServerName,
|
||||
username_error: Option<String>,
|
||||
},
|
||||
PasswordPrompt {
|
||||
username: String,
|
||||
user_card: UserCard,
|
||||
password_error: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LoginForm {
|
||||
username: String,
|
||||
password: Option<String>,
|
||||
}
|
||||
|
||||
async fn route_complete(
|
||||
@@ -89,11 +103,71 @@ async fn route_complete(
|
||||
|
||||
services.oidc.complete_session(&claims, None).await
|
||||
},
|
||||
| OidcSessionState::Authorized { claims } =>
|
||||
| OidcSessionState::Authorized { claims } => {
|
||||
let supplied_user_id = if let Some(form) = form {
|
||||
if let Ok(user_id) = UserId::parse(format!(
|
||||
"@{}:{}",
|
||||
&form.username,
|
||||
services.globals.server_name()
|
||||
)) && services.users.status(&user_id).await.is_active()
|
||||
{
|
||||
let user_card = UserCard::for_local_user(&services, user_id.clone()).await;
|
||||
|
||||
if let Some(password) = form.password {
|
||||
if services
|
||||
.users
|
||||
.check_password(&user_id, &password)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
Some(user_id)
|
||||
} else {
|
||||
return response!(OidcComplete::new(
|
||||
context,
|
||||
OidcCompleteBody::PasswordPrompt {
|
||||
username: form.username,
|
||||
user_card,
|
||||
password_error: true
|
||||
}
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return response!(OidcComplete::new(
|
||||
context,
|
||||
OidcCompleteBody::PasswordPrompt {
|
||||
username: form.username,
|
||||
user_card,
|
||||
password_error: false,
|
||||
}
|
||||
));
|
||||
}
|
||||
} else {
|
||||
match services
|
||||
.users
|
||||
.determine_registration_user_id(Some(form.username), None, None)
|
||||
.await
|
||||
{
|
||||
| Ok(user_id) => Some(user_id),
|
||||
| Err(err) => {
|
||||
return response!(OidcComplete::new(
|
||||
context,
|
||||
OidcCompleteBody::UsernamePrompt {
|
||||
server_name: services.globals.server_name().to_owned(),
|
||||
username_error: Some(err.message()),
|
||||
}
|
||||
));
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
services
|
||||
.oidc
|
||||
.complete_session(&claims, form.map(|form| form.username))
|
||||
.await,
|
||||
.complete_session(&claims, supplied_user_id)
|
||||
.await
|
||||
},
|
||||
}
|
||||
.map_err(|err| WebError::BadRequest(err.to_owned()))?;
|
||||
|
||||
@@ -112,15 +186,11 @@ async fn route_complete(
|
||||
|
||||
response!(Redirect::to(&session.next.target_path()))
|
||||
},
|
||||
| SessionCompletionStatus::NeedsLocalpart => {
|
||||
response!(OidcComplete::new(context, services.globals.server_name().to_owned(), None))
|
||||
},
|
||||
| SessionCompletionStatus::InvalidLocalpart(error) => {
|
||||
response!(OidcComplete::new(
|
||||
context,
|
||||
services.globals.server_name().to_owned(),
|
||||
Some(error)
|
||||
))
|
||||
| SessionCompletionStatus::NeedsUserId => {
|
||||
response!(OidcComplete::new(context, OidcCompleteBody::UsernamePrompt {
|
||||
server_name: services.globals.server_name().to_owned(),
|
||||
username_error: None
|
||||
}))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -17,23 +17,43 @@ Link your account
|
||||
<img class="matrix-icon" alt="Matrix logo" aria-ignore src="{{ crate::ROUTE_PREFIX }}/resources/matrix-icon.svg">
|
||||
</a>
|
||||
</h1>
|
||||
<form method="post">
|
||||
<p>To finish linking your account to Matrix, choose a username.</p>
|
||||
<p>
|
||||
<label for="username">Username</label>
|
||||
<span class="username-input">
|
||||
<span>@</span>
|
||||
<input type="text" id="username" name="username" autocomplete="username" required>
|
||||
<span>:{{ server_name }}</span>
|
||||
</span>
|
||||
{% if let Some(username_error) = username_error %}
|
||||
<small class="error">
|
||||
{{ username_error }}
|
||||
</small>
|
||||
{% endif %}
|
||||
<small>Your username cannot be changed after you create your account.</small>
|
||||
</p>
|
||||
<button type="submit">Continue</button>
|
||||
</form>
|
||||
{% match body %}
|
||||
{% when OidcCompleteBody::UsernamePrompt { server_name, username_error } %}
|
||||
<form method="post">
|
||||
<p>
|
||||
To finish linking your account to Matrix, choose a username.
|
||||
<br>If you have an existing Matrix account, enter its user ID to link it.
|
||||
</p>
|
||||
<p>
|
||||
<label for="username">Username</label>
|
||||
<span class="username-input">
|
||||
<span>@</span>
|
||||
<input type="text" id="username" name="username" autocomplete="username" required>
|
||||
<span>:{{ server_name }}</span>
|
||||
</span>
|
||||
{% if let Some(username_error) = username_error %}
|
||||
<small class="error">
|
||||
{{ username_error }}
|
||||
</small>
|
||||
{% endif %}
|
||||
<small>Your username cannot be changed after you link your account.</small>
|
||||
</p>
|
||||
<button type="submit">Continue</button>
|
||||
</form>
|
||||
{% when OidcCompleteBody::PasswordPrompt { username, user_card, password_error } %}
|
||||
{{ user_card }}
|
||||
<form method="post">
|
||||
<p>To link this legacy account, enter the password you used to use when logging in.</p>
|
||||
<p>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" autocomplete="current-password">
|
||||
{% if password_error %}
|
||||
<small class="error">Incorrect password</small>
|
||||
{% endif %}
|
||||
</p>
|
||||
<input type="hidden" name="username" value="{{ username }}">
|
||||
<button type="submit">Continue</button>
|
||||
</form>
|
||||
{% endmatch %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user