fix: unrecoverable error page doesn't include logo or domain name ()

This commit is contained in:
James Hodgkinson 2025-01-14 13:49:20 +10:00 committed by GitHub
parent 2d439c508f
commit 419c4a1827
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 152 additions and 42 deletions

View file

@ -88,6 +88,7 @@ webauthn-rs = { workspace = true, features = [
[dev-dependencies]
walkdir = { workspace = true }
tempfile = { workspace = true }
kanidmd_lib = { workspace = true, features = ["test"] }
[build-dependencies]
kanidm_build_profiles = { workspace = true }

View file

@ -43,7 +43,7 @@ pub(crate) async fn view_apps_get(
.qe_r_ref
.handle_list_applinks(client_auth_info, kopid.eventid)
.await
.map_err(|old| HtmxError::new(&kopid, old))?;
.map_err(|old| HtmxError::new(&kopid, old, domain_info.clone()))?;
Ok({
(

View file

@ -50,7 +50,7 @@ pub(crate) async fn view_enrol_get(
.qe_r_ref
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
.await
.map_err(|op_err| HtmxError::new(&kopid, op_err))?;
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
let can_rw = uat.purpose_readwrite_active(time);
@ -87,7 +87,7 @@ pub(crate) async fn view_enrol_get(
kopid.eventid,
)
.await
.map_err(|op_err| HtmxError::new(&kopid, op_err))?;
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
let secret = cu_intent.token;

View file

@ -1,6 +1,7 @@
use axum::http::StatusCode;
use axum::response::{IntoResponse, Redirect, Response};
use axum_htmx::{HxReswap, HxRetarget, SwapOption};
use kanidmd_lib::idm::server::DomainInfoRead;
use utoipa::ToSchema;
use uuid::Uuid;
@ -21,19 +22,19 @@ use crate::https::views::UnrecoverableErrorView;
#[derive(Debug, ToSchema)]
pub(crate) enum HtmxError {
/// Something went wrong when doing things.
OperationError(Uuid, OperationError),
OperationError(Uuid, OperationError, DomainInfoRead),
}
impl HtmxError {
pub(crate) fn new(kopid: &KOpId, operr: OperationError) -> Self {
HtmxError::OperationError(kopid.eventid, operr)
pub(crate) fn new(kopid: &KOpId, operr: OperationError, domain_info: DomainInfoRead) -> Self {
HtmxError::OperationError(kopid.eventid, operr, domain_info)
}
}
impl IntoResponse for HtmxError {
fn into_response(self) -> Response {
match self {
HtmxError::OperationError(kopid, inner) => {
HtmxError::OperationError(kopid, inner, domain_info) => {
let body = serde_json::to_string(&inner).unwrap_or(inner.to_string());
match &inner {
OperationError::NotAuthenticated
@ -58,6 +59,7 @@ impl IntoResponse for HtmxError {
UnrecoverableErrorView {
err_code: inner,
operation_id: kopid,
domain_info,
},
)
.into_response(),

View file

@ -48,6 +48,7 @@ struct SessionContext {
after_auth_loc: Option<String>,
}
#[derive(Clone)]
pub enum ReauthPurpose {
ProfileSettings,
}
@ -59,7 +60,7 @@ impl fmt::Display for ReauthPurpose {
}
}
}
#[derive(Clone)]
pub enum LoginError {
InvalidUsername,
}
@ -71,16 +72,17 @@ impl fmt::Display for LoginError {
}
}
}
#[derive(Clone)]
pub struct Reauth {
pub username: String,
pub purpose: ReauthPurpose,
}
#[derive(Clone)]
pub struct Oauth2Ctx {
pub client_name: String,
}
#[derive(Clone)]
pub struct LoginDisplayCtx {
pub domain_info: DomainInfoRead,
// We only need this on the first re-auth screen to indicate what we are doing
@ -160,6 +162,7 @@ pub async fn view_logout_get(
State(state): State<ServerState>,
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
Extension(kopid): Extension<KOpId>,
DomainInfo(domain_info): DomainInfo,
mut jar: CookieJar,
) -> Response {
let response = if let Err(err_code) = state
@ -170,6 +173,7 @@ pub async fn view_logout_get(
UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info,
}
.into_response()
} else {
@ -232,7 +236,7 @@ pub async fn view_reauth_get(
ar,
client_auth_info,
session_context,
display_ctx,
display_ctx.clone(),
)
.await
{
@ -241,6 +245,7 @@ pub async fn view_reauth_get(
Err(err_code) => UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info: display_ctx.clone().domain_info,
}
.into_response(),
}
@ -249,6 +254,7 @@ pub async fn view_reauth_get(
Err(err_code) => UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info: display_ctx.domain_info,
}
.into_response(),
}
@ -275,6 +281,7 @@ pub async fn view_reauth_get(
Err(err_code) => UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info: display_ctx.domain_info,
}
.into_response(),
}
@ -358,6 +365,7 @@ pub async fn view_index_get(
Err(err_code) => UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info,
}
.into_response(),
}
@ -420,7 +428,7 @@ pub async fn view_login_begin_post(
};
let mut display_ctx = LoginDisplayCtx {
domain_info,
domain_info: domain_info.clone(),
oauth2: None,
reauth: None,
error: None,
@ -445,6 +453,7 @@ pub async fn view_login_begin_post(
Err(err_code) => UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info,
}
.into_response(),
}
@ -463,6 +472,7 @@ pub async fn view_login_begin_post(
_ => UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info,
}
.into_response(),
},
@ -503,7 +513,7 @@ pub async fn view_login_mech_choose_post(
.await;
let display_ctx = LoginDisplayCtx {
domain_info,
domain_info: domain_info.clone(),
oauth2: None,
reauth: None,
error: None,
@ -528,6 +538,7 @@ pub async fn view_login_mech_choose_post(
Err(err_code) => UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info,
}
.into_response(),
}
@ -536,6 +547,7 @@ pub async fn view_login_mech_choose_post(
Err(err_code) => UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info,
}
.into_response(),
}
@ -662,7 +674,7 @@ pub async fn view_login_passkey_post(
}
Err(e) => {
error!(err = ?e, "Unable to deserialize credential submission");
HtmxError::new(&kopid, OperationError::SerdeJsonError).into_response()
HtmxError::new(&kopid, OperationError::SerdeJsonError, domain_info).into_response()
}
}
}
@ -692,7 +704,7 @@ async fn credential_step(
.unwrap_or_default();
let display_ctx = LoginDisplayCtx {
domain_info,
domain_info: domain_info.clone(),
oauth2: None,
reauth: None,
error: None,
@ -720,7 +732,7 @@ async fn credential_step(
ar,
client_auth_info,
session_context,
display_ctx,
display_ctx.clone(),
)
.await
{
@ -729,6 +741,7 @@ async fn credential_step(
Err(err_code) => UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info: display_ctx.domain_info,
}
.into_response(),
}
@ -737,6 +750,7 @@ async fn credential_step(
Err(err_code) => UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info,
}
.into_response(),
}
@ -785,6 +799,7 @@ async fn view_login_step(
UnrecoverableErrorView {
err_code: OperationError::InvalidState,
operation_id: kopid.eventid,
domain_info: display_ctx.domain_info,
}
.into_response()
}
@ -845,6 +860,7 @@ async fn view_login_step(
UnrecoverableErrorView {
err_code: OperationError::InvalidState,
operation_id: kopid.eventid,
domain_info: display_ctx.domain_info,
}
.into_response()
}

View file

@ -9,7 +9,10 @@ use axum::{
use axum_htmx::HxRequestGuardLayer;
use constants::Urls;
use kanidmd_lib::prelude::{OperationError, Uuid};
use kanidmd_lib::{
idm::server::DomainInfoRead,
prelude::{OperationError, Uuid},
};
use crate::https::ServerState;
@ -29,6 +32,8 @@ mod reset;
struct UnrecoverableErrorView {
err_code: OperationError,
operation_id: Uuid,
// This is an option because it's not always present in an "unrecoverable" situation
domain_info: DomainInfoRead,
}
pub fn view_router() -> Router<ServerState> {
@ -130,3 +135,29 @@ where
.map(Some),
}
}
#[cfg(test)]
mod tests {
use askama_axum::IntoResponse;
use super::*;
#[tokio::test]
async fn test_unrecoverableerrorview() {
let domain_info = kanidmd_lib::server::DomainInfo::new_test();
let view = UnrecoverableErrorView {
err_code: OperationError::InvalidState,
operation_id: Uuid::new_v4(),
domain_info: domain_info.read(),
};
let error_html = view.render().expect("Failed to render");
assert!(error_html.contains(domain_info.read().display_name()));
let response = view.into_response();
// TODO: this really should be an error code :(
assert_eq!(response.status(), 200);
}
}

View file

@ -104,6 +104,7 @@ async fn oauth2_auth_req(
UnrecoverableErrorView {
err_code: OperationError::UI0003InvalidOauth2Resume,
operation_id: kopid.eventid,
domain_info,
},
)
.into_response();
@ -184,6 +185,7 @@ async fn oauth2_auth_req(
UnrecoverableErrorView {
err_code,
operation_id: kopid.eventid,
domain_info,
},
)
.into_response(),
@ -221,6 +223,7 @@ async fn oauth2_auth_req(
UnrecoverableErrorView {
err_code: OperationError::InvalidState,
operation_id: kopid.eventid,
domain_info,
},
)
.into_response()
@ -240,6 +243,7 @@ pub async fn view_consent_post(
State(server_state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
Form(consent_form): Form<ConsentForm>,
) -> Result<Response, UnrecoverableErrorView> {
@ -291,6 +295,7 @@ pub async fn view_consent_post(
Err(UnrecoverableErrorView {
err_code: OperationError::InvalidState,
operation_id: kopid.eventid,
domain_info,
})
}
}

View file

@ -69,7 +69,7 @@ pub(crate) async fn view_profile_unlock_get(
.qe_r_ref
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
.await
.map_err(|op_err| HtmxError::new(&kopid, op_err))?;
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
let display_ctx = LoginDisplayCtx {
domain_info,

View file

@ -209,6 +209,7 @@ pub(crate) async fn commit(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
@ -216,7 +217,7 @@ pub(crate) async fn commit(
state
.qe_w_ref
.handle_idmcredentialupdatecommit(cu_session_token, kopid.eventid)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info))
.await?;
// No longer need the cookie jar.
@ -230,6 +231,7 @@ pub(crate) async fn cancel_cred_update(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
@ -237,7 +239,7 @@ pub(crate) async fn cancel_cred_update(
state
.qe_w_ref
.handle_idmcredentialupdatecancel(cu_session_token, kopid.eventid)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info))
.await?;
// No longer need the cookie jar.
@ -256,6 +258,7 @@ pub(crate) async fn cancel_mfareg(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
@ -263,7 +266,7 @@ pub(crate) async fn cancel_mfareg(
let cu_status = state
.qe_r_ref
.handle_idmcredentialupdate(cu_session_token, CURequest::CancelMFAReg, kopid.eventid)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
.await?;
Ok(get_cu_partial_response(cu_status))
@ -274,6 +277,7 @@ pub(crate) async fn remove_alt_creds(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
@ -281,7 +285,7 @@ pub(crate) async fn remove_alt_creds(
let cu_status = state
.qe_r_ref
.handle_idmcredentialupdate(cu_session_token, CURequest::PrimaryRemove, kopid.eventid)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
.await?;
Ok(get_cu_partial_response(cu_status))
@ -292,6 +296,7 @@ pub(crate) async fn remove_unixcred(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
@ -303,7 +308,7 @@ pub(crate) async fn remove_unixcred(
CURequest::UnixPasswordRemove,
kopid.eventid,
)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
.await?;
Ok(get_cu_partial_response(cu_status))
@ -314,6 +319,7 @@ pub(crate) async fn remove_totp(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
Form(totp): Form<TOTPRemoveData>,
) -> axum::response::Result<Response> {
@ -326,7 +332,7 @@ pub(crate) async fn remove_totp(
CURequest::TotpRemove(totp.name),
kopid.eventid,
)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
.await?;
Ok(get_cu_partial_response(cu_status))
@ -337,6 +343,7 @@ pub(crate) async fn remove_passkey(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
Form(passkey): Form<PasskeyRemoveData>,
) -> axum::response::Result<Response> {
@ -349,7 +356,7 @@ pub(crate) async fn remove_passkey(
CURequest::PasskeyRemove(passkey.uuid),
kopid.eventid,
)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
.await?;
Ok(get_cu_partial_response(cu_status))
@ -358,8 +365,7 @@ pub(crate) async fn remove_passkey(
pub(crate) async fn finish_passkey(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
Form(passkey_create): Form<PasskeyCreateForm>,
) -> axum::response::Result<Response> {
@ -377,7 +383,7 @@ pub(crate) async fn finish_passkey(
let cu_status = state
.qe_r_ref
.handle_idmcredentialupdate(cu_session_token, cu_request, kopid.eventid)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
.await?;
Ok(get_cu_partial_response(cu_status))
@ -386,7 +392,7 @@ pub(crate) async fn finish_passkey(
error!("Bad request for passkey creation: {e}");
Ok((
StatusCode::UNPROCESSABLE_ENTITY,
HtmxError::new(&kopid, OperationError::Backend).into_response(),
HtmxError::new(&kopid, OperationError::Backend, domain_info).into_response(),
)
.into_response())
}
@ -398,6 +404,7 @@ pub(crate) async fn view_new_passkey(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
Form(init_form): Form<PasskeyInitForm>,
) -> axum::response::Result<Response> {
@ -410,7 +417,7 @@ pub(crate) async fn view_new_passkey(
let cu_status: CUStatus = state
.qe_r_ref
.handle_idmcredentialupdate(cu_session_token, cu_req, 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 response = match cu_status.mfaregstate {
@ -425,6 +432,7 @@ pub(crate) async fn view_new_passkey(
UnrecoverableErrorView {
err_code: OperationError::UI0001ChallengeSerialisation,
operation_id: kopid.eventid,
domain_info,
}
.into_response()
}
@ -432,6 +440,7 @@ pub(crate) async fn view_new_passkey(
_ => UnrecoverableErrorView {
err_code: OperationError::UI0002InvalidState,
operation_id: kopid.eventid,
domain_info,
}
.into_response(),
};
@ -449,8 +458,7 @@ pub(crate) async fn view_new_passkey(
pub(crate) async fn view_new_totp(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token = get_cu_session(&jar).await?;
@ -462,7 +470,7 @@ pub(crate) async fn view_new_totp(
.await
// TODO: better handling for invalid mfaregstate state, can be invalid if certain mfa flows were interrupted
// TODO: We should maybe automatically cancel the other MFA reg
.map_err(|op_err| HtmxError::new(&kopid, op_err))?;
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
let partial = if let CURegState::TotpCheck(secret) = cu_status.mfaregstate {
let uri = secret.to_uri();
@ -491,6 +499,7 @@ pub(crate) async fn view_new_totp(
return Err(ErrorResponse::from(HtmxError::new(
&kopid,
OperationError::CannotStartMFADuringOngoingMFASession,
domain_info,
)));
};
@ -502,6 +511,7 @@ pub(crate) async fn add_totp(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
new_totp_form: Form<NewTotp>,
) -> axum::response::Result<Response> {
@ -525,7 +535,7 @@ pub(crate) async fn add_totp(
)
}
.await
.map_err(|op_err| HtmxError::new(&kopid, op_err))?;
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
let check = match &cu_status.mfaregstate {
CURegState::None => return Ok(get_cu_partial_response(cu_status)),
@ -544,6 +554,7 @@ pub(crate) async fn add_totp(
return Err(ErrorResponse::from(HtmxError::new(
&kopid,
OperationError::InvalidState,
domain_info,
)))
}
};
@ -574,6 +585,7 @@ pub(crate) async fn view_new_pwd(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
opt_form: Option<Form<NewPassword>>,
) -> axum::response::Result<Response> {
@ -609,7 +621,13 @@ pub(crate) async fn view_new_pwd(
Err(OperationError::PasswordQuality(password_feedback)) => {
(password_feedback, StatusCode::UNPROCESSABLE_ENTITY)
}
Err(operr) => return Err(ErrorResponse::from(HtmxError::new(&kopid, operr))),
Err(operr) => {
return Err(ErrorResponse::from(HtmxError::new(
&kopid,
operr,
domain_info,
)))
}
}
} else {
(vec![], StatusCode::UNPROCESSABLE_ENTITY)
@ -641,7 +659,7 @@ pub(crate) async fn view_self_reset_get(
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);
@ -651,7 +669,7 @@ pub(crate) async fn view_self_reset_get(
let (cu_session_token, cu_status) = state
.qe_w_ref
.handle_idmcredentialupdate(client_auth_info, uat.uuid.to_string(), 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 cu_resp = get_cu_response(domain_info, cu_status, true);
@ -698,6 +716,7 @@ pub(crate) async fn view_set_unixcred(
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
opt_form: Option<Form<NewPassword>>,
) -> axum::response::Result<Response> {
@ -733,7 +752,13 @@ pub(crate) async fn view_set_unixcred(
Err(OperationError::PasswordQuality(password_feedback)) => {
(password_feedback, StatusCode::UNPROCESSABLE_ENTITY)
}
Err(operr) => return Err(ErrorResponse::from(HtmxError::new(&kopid, operr))),
Err(operr) => {
return Err(ErrorResponse::from(HtmxError::new(
&kopid,
operr,
domain_info,
)))
}
}
} else {
(vec![], StatusCode::UNPROCESSABLE_ENTITY)
@ -796,7 +821,9 @@ pub(crate) async fn view_reset_get(
}
return Ok((jar, Redirect::to(Urls::CredReset.as_ref())).into_response());
}
Err(op_err) => return Ok(HtmxError::new(&kopid, op_err).into_response()),
Err(op_err) => {
return Ok(HtmxError::new(&kopid, op_err, domain_info.clone()).into_response())
}
};
// CU Session cookie is okay
@ -827,7 +854,7 @@ pub(crate) async fn view_reset_get(
.into_response())
}
Err(op_err) => Err(ErrorResponse::from(
HtmxError::new(&kopid, op_err).into_response(),
HtmxError::new(&kopid, op_err, domain_info).into_response(),
)),
}
} else {

View file

@ -6,12 +6,24 @@
(% endblock %)
(% block body %)
<main id="main" class="m-auto align-items-center d-flex flex-column">
(% if domain_info.image().is_some() %)
<img src="/ui/images/domain"
alt="(( domain_info.display_name() ))" class="kanidm_logo" />
(% else %)
<img
src="/pkg/img/logo-square.svg?v=((crate::https::cache_buster::get_cache_buster_key()))"
alt="(( domain_info.display_name() ))" class="kanidm_logo" />
(% endif %)
<h3>(( domain_info.display_name() ))</h3>
<h2>Error</h2>
<main id="main">
<p>An unrecoverable error occurred. Please contact your administrator with the details below.</p>
<p>Operation ID: (( operation_id ))</p>
<p>Error Code: (( err_code ))</p>
<a href=((Urls::Ui))>Return</a>
</main>
(% endblock %)
(% endblock %)

View file

@ -22,6 +22,7 @@ default = []
dhat-heap = ["dep:dhat"]
dhat-ad-hoc = ["dep:dhat"]
dev-oauth2-device-flow = [] # still-in-development oauth2 device flow support
test = [] # Enable this for cross-package test features.
[dependencies]
base64 = { workspace = true }

View file

@ -108,6 +108,21 @@ impl DomainInfo {
pub fn allow_easter_eggs(&self) -> bool {
self.d_allow_easter_eggs
}
#[cfg(feature = "test")]
pub fn new_test() -> CowCell<Self> {
concread::cowcell::CowCell::new(Self {
d_uuid: Uuid::new_v4(),
d_name: "test domain".to_string(),
d_display: "Test Domain".to_string(),
d_vers: 1,
d_patch_level: 0,
d_devel_taint: false,
d_ldap_allow_unix_pw_bind: false,
d_allow_easter_eggs: false,
d_image: None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]