mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-13 12:16:54 +00:00
feat: Speedbump when logging in with OIDC with no next target
This commit is contained in:
5 files changed
+113
-63
No files matched your search
@@ -7,12 +7,14 @@ use axum::{
|
||||
routing::{get, on},
|
||||
};
|
||||
use conduwuit_api::client::handle_login;
|
||||
use openidconnect::core::CoreAuthPrompt;
|
||||
use ruma::{
|
||||
OwnedUserId,
|
||||
api::client::uiaa::{EmailUserIdentifier, MatrixUserIdentifier, UserIdentifier},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tower_sessions::Session;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
ROUTE_PREFIX, WebError,
|
||||
@@ -37,6 +39,7 @@ pub(crate) fn build() -> Router<crate::State> {
|
||||
template! {
|
||||
struct Login use "login.html.j2" {
|
||||
body: LoginBody,
|
||||
login_type: LoginType,
|
||||
login_error: Option<String>
|
||||
}
|
||||
}
|
||||
@@ -45,7 +48,6 @@ template! {
|
||||
enum LoginBody {
|
||||
Unauthenticated {
|
||||
server_name: String,
|
||||
registration_available: bool,
|
||||
next: Option<LoginTarget>,
|
||||
},
|
||||
Authenticated {
|
||||
@@ -53,6 +55,16 @@ enum LoginBody {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LoginType {
|
||||
Interactive {
|
||||
registration_available: bool,
|
||||
},
|
||||
Oidc {
|
||||
redirect_url: Url,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LoginForm {
|
||||
identifier: Option<String>,
|
||||
@@ -69,38 +81,39 @@ async fn route_login(
|
||||
) -> Result {
|
||||
let user_id = user.into_session().map(|session| session.user_id);
|
||||
|
||||
if services.oidc.enabled() {
|
||||
if user_id.is_some() && !reauthenticate {
|
||||
return response!(Redirect::to(&next.unwrap_or_default().target_path()));
|
||||
}
|
||||
|
||||
let (session, redirect_url) = services.oidc.begin_session().await;
|
||||
let login_type = if services.oidc.enabled() {
|
||||
let (session, redirect_url) = services
|
||||
.oidc
|
||||
.begin_session(reauthenticate.then(|| CoreAuthPrompt::Consent))
|
||||
.await;
|
||||
|
||||
session_store
|
||||
.insert(OIDC_SESSION_ID_KEY, OidcSession {
|
||||
next: next.unwrap_or_default(),
|
||||
state: OidcSessionState::CodeExchange { expected_user: user_id, session },
|
||||
next: next.clone().unwrap_or_default(),
|
||||
state: OidcSessionState::CodeExchange { expected_user: user_id.clone(), session },
|
||||
})
|
||||
.await
|
||||
.expect("should be able to serialize OIDC session");
|
||||
|
||||
return response!(Redirect::to(redirect_url.as_str()));
|
||||
}
|
||||
if next.is_some() {
|
||||
return response!(Redirect::to(redirect_url.as_str()));
|
||||
}
|
||||
|
||||
LoginType::Oidc { redirect_url }
|
||||
} else {
|
||||
let (trusted_flow_status, untrusted_flow_status) =
|
||||
registration_flow_status(&services).await;
|
||||
|
||||
let registration_available = matches!(trusted_flow_status, TrustedFlowStatus::Available)
|
||||
|| matches!(untrusted_flow_status, UntrustedFlowStatus::Available { .. });
|
||||
|
||||
LoginType::Interactive { registration_available }
|
||||
};
|
||||
|
||||
let body = match &user_id {
|
||||
| None => {
|
||||
let (trusted_flow_status, untrusted_flow_status) =
|
||||
registration_flow_status(&services).await;
|
||||
|
||||
let registration_available =
|
||||
matches!(trusted_flow_status, TrustedFlowStatus::Available)
|
||||
|| matches!(untrusted_flow_status, UntrustedFlowStatus::Available { .. });
|
||||
|
||||
LoginBody::Unauthenticated {
|
||||
server_name: services.globals.server_name().to_string(),
|
||||
registration_available,
|
||||
next: next.clone(),
|
||||
}
|
||||
| None => LoginBody::Unauthenticated {
|
||||
server_name: services.globals.server_name().to_string(),
|
||||
next: next.clone(),
|
||||
},
|
||||
| Some(user_id) => {
|
||||
if !reauthenticate {
|
||||
@@ -113,7 +126,7 @@ async fn route_login(
|
||||
},
|
||||
};
|
||||
|
||||
let mut template = Login::new(context, body, None);
|
||||
let mut template = Login::new(context, body, login_type, None);
|
||||
|
||||
if let Some(form) = form {
|
||||
let login_result = match (user_id, form.identifier) {
|
||||
|
||||
@@ -4,6 +4,7 @@ use ruma::UserId;
|
||||
use validator::{Validate, ValidationError, ValidationErrors};
|
||||
|
||||
use crate::{
|
||||
WebError,
|
||||
extract::PostForm,
|
||||
form,
|
||||
pages::{
|
||||
@@ -65,6 +66,12 @@ async fn route_change_password(
|
||||
user: User,
|
||||
PostForm(form): PostForm<ChangePasswordForm>,
|
||||
) -> Result {
|
||||
if services.oidc.enabled() {
|
||||
return Err(WebError::BadRequest(
|
||||
"Password changing is not available on this server".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let user_id = user.expect(LoginTarget::ChangePassword)?;
|
||||
let user_card = UserCard::for_local_user(&services, user_id.clone()).await;
|
||||
|
||||
|
||||
@@ -65,6 +65,13 @@ async fn route_reset_password(
|
||||
return response!(ResetPassword::new(context, ResetPasswordBody::Unavailable));
|
||||
}
|
||||
|
||||
// Check if OIDC is enabled
|
||||
if services.oidc.enabled() {
|
||||
return Err(WebError::BadRequest(
|
||||
"Password resets are not available on this server".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let Some(form) = form else {
|
||||
// For GET requests return the reset request form
|
||||
return response!(ResetPassword::new(
|
||||
|
||||
@@ -9,9 +9,14 @@ Log in
|
||||
{%- endblock -%}
|
||||
|
||||
{%- block content -%}
|
||||
{% match login_type %}
|
||||
{% when LoginType::Interactive { .. } %}
|
||||
<div class="panel narrow">
|
||||
{% when LoginType::Oidc { .. } %}
|
||||
<div class="panel narrow middle"/>
|
||||
{% endmatch %}
|
||||
{% match body %}
|
||||
{% when LoginBody::Unauthenticated { server_name, registration_available, next } %}
|
||||
{% when LoginBody::Unauthenticated { server_name, next } %}
|
||||
<h1 class="with-matrix-icon">
|
||||
{% if next.is_some() %}
|
||||
Log in to continue
|
||||
@@ -25,39 +30,49 @@ Log in
|
||||
<p>
|
||||
You're about to log in to your account on <em>{{ server_name }}</em>
|
||||
</p>
|
||||
<hr>
|
||||
<form method="post">
|
||||
<p>
|
||||
<label for="identifier">Username or email address</label>
|
||||
<input type="text" id="identifier" name="identifier" autocomplete="username">
|
||||
</p>
|
||||
<p>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" autocomplete="current-password">
|
||||
</p>
|
||||
<button type="submit">Log in</button>
|
||||
</form>
|
||||
<div class="centered-links">
|
||||
{% if registration_available %}
|
||||
{% let query = next.as_ref().map(serde_urlencoded::to_string).transpose().unwrap().unwrap_or_default() %}
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/account/register/?{{ query }}">Sign up</a>
|
||||
{% endif %}
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/account/password/reset/">Forgot your password?</a>
|
||||
</div>
|
||||
{% match login_type %}
|
||||
{% when LoginType::Interactive { registration_available } %}
|
||||
<hr>
|
||||
<form method="post">
|
||||
<p>
|
||||
<label for="identifier">Username or email address</label>
|
||||
<input type="text" id="identifier" name="identifier" autocomplete="username">
|
||||
</p>
|
||||
<p>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" autocomplete="current-password">
|
||||
</p>
|
||||
<button type="submit">Log in</button>
|
||||
</form>
|
||||
<div class="centered-links">
|
||||
{% if registration_available %}
|
||||
{% let query = next.as_ref().map(serde_urlencoded::to_string).transpose().unwrap().unwrap_or_default() %}
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/account/register/?{{ query }}">Sign up</a>
|
||||
{% endif %}
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/account/password/reset/">Forgot your password?</a>
|
||||
</div>
|
||||
{% when LoginType::Oidc { redirect_url } %}
|
||||
<a class="button" href="{{ redirect_url }}">Continue</a>
|
||||
{% endmatch %}
|
||||
{% when LoginBody::Authenticated { user_card } %}
|
||||
<h1>Confirm your identity</h1>
|
||||
{{ user_card }}
|
||||
<p>Enter your password to continue.</p>
|
||||
<form method="post">
|
||||
<p>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" autocomplete="current-password">
|
||||
</p>
|
||||
<button type="submit">Continue</button>
|
||||
</form>
|
||||
<div class="centered-links">
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/account/password/reset/">Forgot your password?</a>
|
||||
</div>
|
||||
{% match login_type %}
|
||||
{% when LoginType::Interactive { .. } %}
|
||||
<p>Enter your password to continue.</p>
|
||||
<form method="post">
|
||||
<p>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" autocomplete="current-password">
|
||||
</p>
|
||||
<button type="submit">Continue</button>
|
||||
</form>
|
||||
<div class="centered-links">
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/account/password/reset/">Forgot your password?</a>
|
||||
</div>
|
||||
{% when LoginType::Oidc { redirect_url } %}
|
||||
<a class="button" href="{{ redirect_url }}">Continue</a>
|
||||
{% endmatch %}
|
||||
{% endmatch %}
|
||||
{% if let Some(error) = login_error %}
|
||||
<small class="error">{{ error }}</small>
|
||||
|
||||
Reference in new issue
Block a user