patch up rebase

This commit is contained in:
ToxicMushroom 2025-02-13 23:46:35 +01:00
parent 4e4fd8dfa7
commit 91307dc62e
No known key found for this signature in database
GPG key ID: 4F381DAA6E8379CB
4 changed files with 74 additions and 44 deletions

14
Cargo.lock generated
View file

@ -391,6 +391,7 @@ dependencies = [
"multer", "multer",
"pin-project-lite", "pin-project-lite",
"serde", "serde",
"serde_html_form",
"tower 0.5.2", "tower 0.5.2",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@ -5064,6 +5065,19 @@ dependencies = [
"syn 2.0.98", "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]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.138" version = "1.0.138"

View file

@ -9,17 +9,17 @@ pub(crate) enum ProfileMenuItems {
UnixPassword, UnixPassword,
} }
pub(crate) enum UiMessage { // pub(crate) enum UiMessage {
UnlockEdit, // UnlockEdit,
} // }
//
impl std::fmt::Display for UiMessage { // impl std::fmt::Display for UiMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { // match self {
UiMessage::UnlockEdit => write!(f, "Unlock Edit 🔒"), // UiMessage::UnlockEdit => write!(f, "Unlock Edit 🔒"),
} // }
} // }
} // }
pub(crate) enum Urls { pub(crate) enum Urls {
Apps, Apps,

View file

@ -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::errors::WebError;
use crate::https::extractors::{DomainInfo, VerifiedClientInformation}; use crate::https::extractors::{DomainInfo, VerifiedClientInformation};
use crate::https::middleware::KOpId; use crate::https::middleware::KOpId;
use crate::https::ServerState; use crate::https::ServerState;
use askama::Template; use askama::Template;
use askama_axum::IntoResponse;
use axum::extract::State; use axum::extract::State;
use axum::http::Uri;
use axum::response::Response; use axum::response::Response;
use axum::Extension; use axum::Extension;
use axum_extra::extract::cookie::CookieJar; 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 futures_util::TryFutureExt;
use kanidm_proto::attribute::Attribute;
use kanidm_proto::constants::{ATTR_DISPLAYNAME, ATTR_LEGALNAME, ATTR_MAIL};
use kanidm_proto::internal::UserAuthToken; use kanidm_proto::internal::UserAuthToken;
use kanidm_proto::v1::Entry;
use super::constants::{ProfileMenuItems, UiMessage, Urls}; use kanidmd_lib::filter::{f_eq, f_id, Filter};
use super::errors::HtmxError; use kanidmd_lib::prelude::f_and;
use super::login::{LoginDisplayCtx, Reauth, ReauthPurpose}; use kanidmd_lib::prelude::PartialValue;
use super::navbar::NavbarCtx; use kanidmd_lib::prelude::FC;
use serde::Deserialize;
use serde::Serialize;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
#[derive(Template)] #[derive(Template)]
#[template(path = "user_settings.html")] #[template(path = "user_settings.html")]
@ -28,8 +42,7 @@ pub(crate) struct ProfileView {
struct ProfilePartialView { struct ProfilePartialView {
menu_active_item: ProfileMenuItems, menu_active_item: ProfileMenuItems,
can_rw: bool, can_rw: bool,
attrs: ProfileAttributes, attrs: ProfileAttributes
posix_enabled: bool,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@ -45,9 +58,10 @@ pub(crate) struct ProfileAttributes {
#[derive(Template, Clone)] #[derive(Template, Clone)]
#[template(path = "user_settings/profile_changes_partial.html")] #[template(path = "user_settings/profile_changes_partial.html")]
struct ProfileChangesPartialView { struct ProfileChangesPartialView {
menu_active_item: ProfileMenuItems,
can_rw: bool, can_rw: bool,
attrs: ProfileAttributes, attrs: ProfileAttributes,
new_attrs: ProfileAttributes new_attrs: ProfileAttributes,
} }
#[derive(Template, Clone)] #[derive(Template, Clone)]
@ -75,7 +89,7 @@ pub(crate) async fn view_profile_get(
) -> Result<ProfileView, WebError> { ) -> Result<ProfileView, WebError> {
let uat: UserAuthToken = state let uat: UserAuthToken = state
.qe_r_ref .qe_r_ref
.handle_whoami_uat(client_auth_info, kopid.eventid) .handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
.await?; .await?;
let filter = filter_all!(f_and!([f_eq( let filter = filter_all!(f_and!([f_eq(
@ -85,7 +99,6 @@ pub(crate) async fn view_profile_get(
let base: Vec<Entry> = state let base: Vec<Entry> = state
.qe_r_ref .qe_r_ref
.handle_internalsearch(client_auth_info.clone(), filter, None, kopid.eventid) .handle_internalsearch(client_auth_info.clone(), filter, None, kopid.eventid)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.await?; .await?;
let self_entry = base.first().expect("Self no longer exists"); 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(), legal_name: "hardcoded".to_string(),
emails, emails,
primary_email, primary_email,
}, },
},
}) })
} }
pub(crate) async fn view_profile_diff_start_save_post( pub(crate) async fn view_profile_diff_start_save_post(
State(state): State<ServerState>, State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>, Extension(kopid): Extension<KOpId>,
VerifiedClientInformation(client_auth_info): VerifiedClientInformation, 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<ProfileAttributes>, Form(new_attrs): Form<ProfileAttributes>,
) -> axum::response::Result<Response> { ) -> axum::response::Result<Response> {
let uat: UserAuthToken = state let uat: UserAuthToken = state
.qe_r_ref .qe_r_ref
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid) .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?; .await?;
let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0); 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<Entry> = state let base: Vec<Entry> = state
.qe_r_ref .qe_r_ref
.handle_internalsearch(client_auth_info.clone(), filter, None, kopid.eventid) .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?; .await?;
let self_entry = base.first().expect("Self no longer exists"); 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 primary_email = emails.first().cloned();
let profile_view = ProfileChangesPartialView { let profile_view = ProfileChangesPartialView {
menu_active_item: ProfileMenuItems::UserProfile,
can_rw, can_rw,
attrs: ProfileAttributes { attrs: ProfileAttributes {
account_name: uat.name().to_string(), account_name: uat.name().to_string(),
@ -153,13 +168,12 @@ pub(crate) async fn view_profile_diff_start_save_post(
emails, emails,
primary_email, primary_email,
}, },
new_attrs, new_attrs
posix_enabled: true,
}; };
Ok(( Ok((
HxPushUrl(Uri::from_static("/ui/profile/diff")), HxPushUrl(Uri::from_static("/ui/profile/diff")),
HtmlTemplate(profile_view), profile_view,
) )
.into_response()) .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( pub(crate) async fn view_profile_diff_confirm_save_post(
State(state): State<ServerState>, State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>, Extension(kopid): Extension<KOpId>,
HxRequest(hx_request): HxRequest,
VerifiedClientInformation(client_auth_info): VerifiedClientInformation, 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<ProfileAttributes>, Form(new_attrs): Form<ProfileAttributes>,
) -> axum::response::Result<Response> { ) -> axum::response::Result<Response> {
let uat: UserAuthToken = state let uat: UserAuthToken = state
.qe_r_ref .qe_r_ref
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid) .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?; .await?;
dbg!(&new_attrs); dbg!(&new_attrs);
@ -190,7 +205,7 @@ pub(crate) async fn view_profile_diff_confirm_save_post(
filter.clone(), filter.clone(),
kopid.eventid, kopid.eventid,
) )
.map_err(|op_err| HtmxError::new(&kopid, op_err)) .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
.await?; .await?;
state state
@ -203,7 +218,7 @@ pub(crate) async fn view_profile_diff_confirm_save_post(
filter.clone(), filter.clone(),
kopid.eventid, kopid.eventid,
) )
.map_err(|op_err| HtmxError::new(&kopid, op_err)) .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
.await?; .await?;
state state
@ -216,7 +231,7 @@ pub(crate) async fn view_profile_diff_confirm_save_post(
filter.clone(), filter.clone(),
kopid.eventid, kopid.eventid,
) )
.map_err(|op_err| HtmxError::new(&kopid, op_err)) .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
.await?; .await?;
// TODO: These are normally not permitted, user should be prevented from changing non modifiable fields in the UI though // 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?; // .await?;
// TODO: Calling this here returns the old attributes // TODO: Calling this here returns the old attributes
view_profile_get( match view_profile_get(
State(state), State(state),
Extension(kopid), Extension(kopid),
HxRequest(hx_request),
VerifiedClientInformation(client_auth_info), VerifiedClientInformation(client_auth_info),
) DomainInfo(domain_info)
.await ).await {
Ok(pv) => Ok(pv.into_response()),
Err(e) => Ok(e.into_response()),
}
} }
// Sends the user a new email input to fill in :) // 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())]); HxResponseTrigger::after_swap([HxEvent::new("addEmailSwapped".to_string())]);
Ok(( Ok((
passkey_init_trigger, passkey_init_trigger,
HtmlTemplate(FormModEntryModListPartial { FormModEntryModListPartial {
can_rw: true, can_rw: true,
r#type: "email".to_string(), r#type: "email".to_string(),
name: "emails[]".to_string(), name: "emails[]".to_string(),
value: "".to_string(), value: "".to_string(),
invalid_feedback: "Please enter a valid email address.".to_string(), invalid_feedback: "Please enter a valid email address.".to_string(),
}) },
.into_response(),
) )
.into_response()) .into_response())
} }

View file

@ -10,21 +10,21 @@ Profile
<div class="mb-2 row"> <div class="mb-2 row">
<label for="profileUserName" class="col-12 col-md-3 col-lg-2 col-form-label">User name</label> <label for="profileUserName" class="col-12 col-md-3 col-lg-2 col-form-label">User name</label>
<div class="col-12 col-md-6 col-lg-5"> <div class="col-12 col-md-6 col-lg-5">
<input type="text" readonly class="form-control-plaintext" id="profileUserName" value="(( account_name ))"> <input type="text" readonly class="form-control-plaintext" id="profileUserName" value="(( attrs.account_name ))">
</div> </div>
</div> </div>
<div class="mb-2 row"> <div class="mb-2 row">
<label for="profileDisplayName" class="col-12 col-md-3 col-lg-2 col-form-label">Display name</label> <label for="profileDisplayName" class="col-12 col-md-3 col-lg-2 col-form-label">Display name</label>
<div class="col-12 col-md-6 col-lg-5"> <div class="col-12 col-md-6 col-lg-5">
<input type="text" class="form-control-plaintext" id="profileDisplayName" value="(( display_name ))"> <input type="text" class="form-control-plaintext" id="profileDisplayName" value="(( attrs.display_name ))">
</div> </div>
</div> </div>
<div class="mb-2 row"> <div class="mb-2 row">
<label for="profileLegalName" class="col-12 col-md-3 col-lg-2 col-form-label">Legal name</label> <label for="profileLegalName" class="col-12 col-md-3 col-lg-2 col-form-label">Legal name</label>
<div class="col-12 col-md-6 col-lg-5"> <div class="col-12 col-md-6 col-lg-5">
<input type="text" class="form-control-plaintext" id="profileLegalName" value="(( legal_name ))"> <input type="text" class="form-control-plaintext" id="profileLegalName" value="(( attrs.legal_name ))">
</div> </div>
</div> </div>