diff --git a/Cargo.lock b/Cargo.lock index 2cd5e62e7..3c810edb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,6 +391,7 @@ dependencies = [ "multer", "pin-project-lite", "serde", + "serde_html_form", "tower 0.5.2", "tower-layer", "tower-service", @@ -5064,6 +5065,19 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "serde_html_form" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4" +dependencies = [ + "form_urlencoded", + "indexmap 2.7.1", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_json" version = "1.0.138" diff --git a/server/core/src/https/views/constants.rs b/server/core/src/https/views/constants.rs index 8e4167941..7276e8c2f 100644 --- a/server/core/src/https/views/constants.rs +++ b/server/core/src/https/views/constants.rs @@ -9,17 +9,17 @@ pub(crate) enum ProfileMenuItems { UnixPassword, } -pub(crate) enum UiMessage { - UnlockEdit, -} - -impl std::fmt::Display for UiMessage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - UiMessage::UnlockEdit => write!(f, "Unlock Edit 🔒"), - } - } -} +// pub(crate) enum UiMessage { +// UnlockEdit, +// } +// +// impl std::fmt::Display for UiMessage { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// match self { +// UiMessage::UnlockEdit => write!(f, "Unlock Edit 🔒"), +// } +// } +// } pub(crate) enum Urls { Apps, diff --git a/server/core/src/https/views/profile.rs b/server/core/src/https/views/profile.rs index 1b8b62fb8..40078c283 100644 --- a/server/core/src/https/views/profile.rs +++ b/server/core/src/https/views/profile.rs @@ -1,20 +1,34 @@ +use super::constants::{ProfileMenuItems, Urls}; +use super::errors::HtmxError; +use super::login::{LoginDisplayCtx, Reauth, ReauthPurpose}; +use super::navbar::NavbarCtx; use crate::https::errors::WebError; use crate::https::extractors::{DomainInfo, VerifiedClientInformation}; use crate::https::middleware::KOpId; use crate::https::ServerState; use askama::Template; +use askama_axum::IntoResponse; use axum::extract::State; +use axum::http::Uri; use axum::response::Response; use axum::Extension; use axum_extra::extract::cookie::CookieJar; -use axum_htmx::{HxPushUrl, HxRequest}; +use axum_extra::extract::Form; +use axum_htmx::{HxEvent, HxPushUrl, HxResponseTrigger}; use futures_util::TryFutureExt; +use kanidm_proto::attribute::Attribute; +use kanidm_proto::constants::{ATTR_DISPLAYNAME, ATTR_LEGALNAME, ATTR_MAIL}; use kanidm_proto::internal::UserAuthToken; - -use super::constants::{ProfileMenuItems, UiMessage, Urls}; -use super::errors::HtmxError; -use super::login::{LoginDisplayCtx, Reauth, ReauthPurpose}; -use super::navbar::NavbarCtx; +use kanidm_proto::v1::Entry; +use kanidmd_lib::filter::{f_eq, f_id, Filter}; +use kanidmd_lib::prelude::f_and; +use kanidmd_lib::prelude::PartialValue; +use kanidmd_lib::prelude::FC; +use serde::Deserialize; +use serde::Serialize; +use std::fmt; +use std::fmt::Display; +use std::fmt::Formatter; #[derive(Template)] #[template(path = "user_settings.html")] @@ -28,8 +42,7 @@ pub(crate) struct ProfileView { struct ProfilePartialView { menu_active_item: ProfileMenuItems, can_rw: bool, - attrs: ProfileAttributes, - posix_enabled: bool, + attrs: ProfileAttributes } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -45,9 +58,10 @@ pub(crate) struct ProfileAttributes { #[derive(Template, Clone)] #[template(path = "user_settings/profile_changes_partial.html")] struct ProfileChangesPartialView { + menu_active_item: ProfileMenuItems, can_rw: bool, attrs: ProfileAttributes, - new_attrs: ProfileAttributes + new_attrs: ProfileAttributes, } #[derive(Template, Clone)] @@ -75,7 +89,7 @@ pub(crate) async fn view_profile_get( ) -> Result { let uat: UserAuthToken = state .qe_r_ref - .handle_whoami_uat(client_auth_info, kopid.eventid) + .handle_whoami_uat(client_auth_info.clone(), kopid.eventid) .await?; let filter = filter_all!(f_and!([f_eq( @@ -85,7 +99,6 @@ pub(crate) async fn view_profile_get( let base: Vec = state .qe_r_ref .handle_internalsearch(client_auth_info.clone(), filter, None, kopid.eventid) - .map_err(|op_err| HtmxError::new(&kopid, op_err)) .await?; let self_entry = base.first().expect("Self no longer exists"); @@ -108,22 +121,23 @@ pub(crate) async fn view_profile_get( legal_name: "hardcoded".to_string(), emails, primary_email, - }, + }, }) } - pub(crate) async fn view_profile_diff_start_save_post( State(state): State, Extension(kopid): Extension, VerifiedClientInformation(client_auth_info): VerifiedClientInformation, + DomainInfo(domain_info): DomainInfo, + // Form must be the last parameter because it consumes the request body Form(new_attrs): Form, ) -> axum::response::Result { let uat: UserAuthToken = state .qe_r_ref .handle_whoami_uat(client_auth_info.clone(), kopid.eventid) - .map_err(|op_err| HtmxError::new(&kopid, op_err)) + .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone())) .await?; let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0); @@ -136,7 +150,7 @@ pub(crate) async fn view_profile_diff_start_save_post( let base: Vec = state .qe_r_ref .handle_internalsearch(client_auth_info.clone(), filter, None, kopid.eventid) - .map_err(|op_err| HtmxError::new(&kopid, op_err)) + .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info)) .await?; let self_entry = base.first().expect("Self no longer exists"); @@ -145,6 +159,7 @@ pub(crate) async fn view_profile_diff_start_save_post( let primary_email = emails.first().cloned(); let profile_view = ProfileChangesPartialView { + menu_active_item: ProfileMenuItems::UserProfile, can_rw, attrs: ProfileAttributes { account_name: uat.name().to_string(), @@ -153,13 +168,12 @@ pub(crate) async fn view_profile_diff_start_save_post( emails, primary_email, }, - new_attrs, - posix_enabled: true, + new_attrs }; Ok(( HxPushUrl(Uri::from_static("/ui/profile/diff")), - HtmlTemplate(profile_view), + profile_view, ) .into_response()) } @@ -167,14 +181,15 @@ pub(crate) async fn view_profile_diff_start_save_post( pub(crate) async fn view_profile_diff_confirm_save_post( State(state): State, Extension(kopid): Extension, - HxRequest(hx_request): HxRequest, VerifiedClientInformation(client_auth_info): VerifiedClientInformation, + DomainInfo(domain_info): DomainInfo, + // Form must be the last parameter because it consumes the request body Form(new_attrs): Form, ) -> axum::response::Result { let uat: UserAuthToken = state .qe_r_ref .handle_whoami_uat(client_auth_info.clone(), kopid.eventid) - .map_err(|op_err| HtmxError::new(&kopid, op_err)) + .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone())) .await?; dbg!(&new_attrs); @@ -190,7 +205,7 @@ pub(crate) async fn view_profile_diff_confirm_save_post( filter.clone(), kopid.eventid, ) - .map_err(|op_err| HtmxError::new(&kopid, op_err)) + .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone())) .await?; state @@ -203,7 +218,7 @@ pub(crate) async fn view_profile_diff_confirm_save_post( filter.clone(), kopid.eventid, ) - .map_err(|op_err| HtmxError::new(&kopid, op_err)) + .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone())) .await?; state @@ -216,7 +231,7 @@ pub(crate) async fn view_profile_diff_confirm_save_post( filter.clone(), kopid.eventid, ) - .map_err(|op_err| HtmxError::new(&kopid, op_err)) + .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone())) .await?; // TODO: These are normally not permitted, user should be prevented from changing non modifiable fields in the UI though @@ -247,13 +262,15 @@ pub(crate) async fn view_profile_diff_confirm_save_post( // .await?; // TODO: Calling this here returns the old attributes - view_profile_get( + match view_profile_get( State(state), Extension(kopid), - HxRequest(hx_request), VerifiedClientInformation(client_auth_info), - ) - .await + DomainInfo(domain_info) + ).await { + Ok(pv) => Ok(pv.into_response()), + Err(e) => Ok(e.into_response()), + } } // Sends the user a new email input to fill in :) @@ -266,14 +283,13 @@ pub(crate) async fn view_new_email_entry_partial( HxResponseTrigger::after_swap([HxEvent::new("addEmailSwapped".to_string())]); Ok(( passkey_init_trigger, - HtmlTemplate(FormModEntryModListPartial { + FormModEntryModListPartial { can_rw: true, r#type: "email".to_string(), name: "emails[]".to_string(), value: "".to_string(), invalid_feedback: "Please enter a valid email address.".to_string(), - }) - .into_response(), + }, ) .into_response()) } diff --git a/server/core/templates/user_settings_profile_partial.html b/server/core/templates/user_settings_profile_partial.html index d4ec7487d..193684c2f 100644 --- a/server/core/templates/user_settings_profile_partial.html +++ b/server/core/templates/user_settings_profile_partial.html @@ -10,21 +10,21 @@ Profile
- +
- +
- +