From 64233f91d21491d0eaf8d2a0cbcc6450e8fb8741 Mon Sep 17 00:00:00 2001
From: Lennart <18233294+lennart-k@users.noreply.github.com>
Date: Sun, 13 Apr 2025 19:55:48 +0200
Subject: [PATCH] frontend: Janky code to make redirects after login work
---
Cargo.lock | 1 +
crates/frontend/Cargo.toml | 1 +
.../public/templates/pages/login.html | 13 ++++++-
crates/frontend/src/lib.rs | 19 ++++++----
crates/frontend/src/nextcloud_login.rs | 9 ++++-
crates/frontend/src/oidc/mod.rs | 31 +++++++++++-----
crates/frontend/src/routes/login.rs | 36 ++++++++++++++++---
7 files changed, 89 insertions(+), 21 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index f7b79fd..57d11cd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3116,6 +3116,7 @@ dependencies = [
"serde",
"thiserror 2.0.12",
"tokio",
+ "url",
"uuid",
]
diff --git a/crates/frontend/Cargo.toml b/crates/frontend/Cargo.toml
index 37f580e..6bd9d84 100644
--- a/crates/frontend/Cargo.toml
+++ b/crates/frontend/Cargo.toml
@@ -24,3 +24,4 @@ reqwest.workspace = true
rand.workspace = true
chrono.workspace = true
uuid.workspace = true
+url.workspace = true
diff --git a/crates/frontend/public/templates/pages/login.html b/crates/frontend/public/templates/pages/login.html
index 044a71e..77ef407 100644
--- a/crates/frontend/public/templates/pages/login.html
+++ b/crates/frontend/public/templates/pages/login.html
@@ -4,6 +4,9 @@
Login
+ {% if let Some(redirect_uri) = redirect_uri %}
+
and redirect to {{redirect_uri}}
+ {% endif %}
{% if let Some(OidcProviderData {name, redirect_url}) = oidc_data %}
-
Login with {{ name }}
+
{% endif %}
diff --git a/crates/frontend/src/lib.rs b/crates/frontend/src/lib.rs
index b72e6b5..d4fafb3 100644
--- a/crates/frontend/src/lib.rs
+++ b/crates/frontend/src/lib.rs
@@ -12,7 +12,7 @@ use actix_web::{
use askama::Template;
use askama_web::WebTemplate;
use assets::{Assets, EmbedService};
-use oidc::{route_get_oidc, route_get_oidc_callback};
+use oidc::{route_get_oidc_callback, route_post_oidc};
use routes::{
addressbook::{route_addressbook, route_addressbook_restore},
calendar::{route_calendar, route_calendar_restore},
@@ -44,7 +44,7 @@ struct UserPage {
async fn route_user(user: User, req: HttpRequest) -> Redirect {
Redirect::to(
- req.url_for("frontend_user_named", &[&user.id])
+ req.url_for("frontend_user_named", &[user.id])
.unwrap()
.to_string(),
)
@@ -105,15 +105,22 @@ async fn route_root(user: Option, req: HttpRequest) -> impl Responder {
web::Redirect::to(redirect_url.to_string()).permanent()
}
-fn unauthorized_handler(res: ServiceResponse) -> actix_web::Result> {
+pub(crate) fn unauthorized_handler(
+ res: ServiceResponse,
+) -> actix_web::Result> {
let (req, _) = res.into_parts();
- let login_url = req.url_for_static("frontend_login").unwrap().to_string();
+ let redirect_uri = req.uri().to_string();
+ let mut login_url = req.url_for_static("frontend_login").unwrap();
+ login_url
+ .query_pairs_mut()
+ .append_pair("redirect_uri", &redirect_uri);
+ let login_url = login_url.to_string();
let response = HttpResponse::Unauthorized().body(format!(
r#"
-
+
Unauthorized, redirecting to login page
@@ -196,7 +203,7 @@ pub fn configure_frontend(
) {
cfg.service(
web::scope("/index.php/login/v2")
+ .wrap(ErrorHandlers::new().handler(StatusCode::UNAUTHORIZED, unauthorized_handler))
.wrap(AuthenticationMiddleware::new(auth_provider.clone()))
.app_data(Data::from(nextcloud_flows_state))
.app_data(Data::from(auth_provider.clone()))
diff --git a/crates/frontend/src/oidc/mod.rs b/crates/frontend/src/oidc/mod.rs
index a1014f8..1cfff7e 100644
--- a/crates/frontend/src/oidc/mod.rs
+++ b/crates/frontend/src/oidc/mod.rs
@@ -5,7 +5,7 @@ use actix_web::{
body::BoxBody,
error::UrlGenerationError,
http::StatusCode,
- web::{Data, Query, Redirect},
+ web::{Data, Form, Query, Redirect},
};
use openidconnect::{
AuthenticationFlow, AuthorizationCode, ClaimsVerificationError, ConfigurationError, CsrfToken,
@@ -67,6 +67,7 @@ struct OidcState {
state: CsrfToken,
nonce: Nonce,
pkce_verifier: PkceCodeVerifier,
+ redirect_uri: Option,
}
fn get_http_client() -> reqwest::Client {
@@ -109,9 +110,15 @@ async fn get_oidc_client(
.set_redirect_uri(redirect_uri))
}
+#[derive(Debug, Deserialize)]
+pub struct GetOidcForm {
+ redirect_uri: Option,
+}
+
/// Endpoint that redirects to the authorize endpoint of the OIDC service
-pub async fn route_get_oidc(
+pub async fn route_post_oidc(
req: HttpRequest,
+ Form(GetOidcForm { redirect_uri }): Form,
config: Data,
session: Session,
) -> Result {
@@ -146,6 +153,7 @@ pub async fn route_get_oidc(
state: csrf_token,
nonce,
pkce_verifier,
+ redirect_uri,
},
)?;
@@ -225,16 +233,23 @@ pub async fn route_get_oidc_callback(
user = Some(new_user);
}
+ let default_redirect = req.url_for_static("frontend_user")?.to_string();
+ let redirect_uri = oidc_state.redirect_uri.unwrap_or(default_redirect.clone());
+ let redirect_uri = req
+ .full_url()
+ .join(&redirect_uri)
+ .ok()
+ .and_then(|uri| req.full_url().make_relative(&uri))
+ .unwrap_or(default_redirect);
+
// Complete login flow
if let Some(user) = user {
session.insert("user", user.id.clone())?;
- Ok(
- Redirect::to(req.url_for_static("frontend_user")?.to_string())
- .temporary()
- .respond_to(&req)
- .map_into_boxed_body(),
- )
+ Ok(Redirect::to(redirect_uri)
+ .temporary()
+ .respond_to(&req)
+ .map_into_boxed_body())
} else {
// Add user provisioning
Ok(HttpResponse::build(StatusCode::UNAUTHORIZED).body("User does not exist"))
diff --git a/crates/frontend/src/routes/login.rs b/crates/frontend/src/routes/login.rs
index 1442075..80d1dea 100644
--- a/crates/frontend/src/routes/login.rs
+++ b/crates/frontend/src/routes/login.rs
@@ -3,7 +3,7 @@ use actix_session::Session;
use actix_web::{
HttpRequest, HttpResponse, Responder,
error::ErrorUnauthorized,
- web::{Data, Form, Redirect},
+ web::{Data, Form, Query, Redirect},
};
use askama::Template;
use askama_web::WebTemplate;
@@ -13,11 +13,22 @@ use serde::Deserialize;
#[derive(Template, WebTemplate)]
#[template(path = "pages/login.html")]
struct LoginPage<'a> {
+ redirect_uri: Option,
oidc_data: Option>,
}
-pub async fn route_get_login(req: HttpRequest, config: Data) -> impl Responder {
+#[derive(Debug, Deserialize)]
+pub struct GetLoginQuery {
+ redirect_uri: Option,
+}
+
+pub async fn route_get_login(
+ Query(GetLoginQuery { redirect_uri }): Query,
+ req: HttpRequest,
+ config: Data,
+) -> impl Responder {
LoginPage {
+ redirect_uri,
oidc_data: config.oidc.as_ref().map(|oidc| OidcProviderData {
name: &oidc.name,
redirect_url: req
@@ -33,20 +44,35 @@ pub async fn route_get_login(req: HttpRequest, config: Data) ->
pub struct PostLoginForm {
username: String,
password: String,
+ redirect_uri: Option,
}
pub async fn route_post_login(
req: HttpRequest,
- form: Form,
+ Form(PostLoginForm {
+ username,
+ password,
+ redirect_uri,
+ }): Form,
session: Session,
auth_provider: Data,
) -> HttpResponse {
+ // Ensure that redirect_uri never goes cross-origin
+ let default_redirect = "/frontend/user".to_string();
+ let redirect_uri = redirect_uri.unwrap_or(default_redirect.clone());
+ let redirect_uri = req
+ .full_url()
+ .join(&redirect_uri)
+ .ok()
+ .and_then(|uri| req.full_url().make_relative(&uri))
+ .unwrap_or(default_redirect);
+
if let Ok(Some(user)) = auth_provider
- .validate_user_token(&form.username, &form.password)
+ .validate_user_token(&username, &password)
.await
{
session.insert("user", user.id).unwrap();
- Redirect::to(format!("/frontend/user/{}", &form.username))
+ Redirect::to(redirect_uri)
.see_other()
.respond_to(&req)
.map_into_boxed_body()