use askama::Template;
use axum::extract::{Query, State};
use axum::http::{StatusCode, Uri};
use axum::response::{ErrorResponse, IntoResponse, Redirect, Response};
use axum::{Extension, Form};
use axum_extra::extract::cookie::SameSite;
use axum_extra::extract::CookieJar;
use axum_htmx::{
HxEvent, HxLocation, HxPushUrl, HxRequest, HxReselect, HxResponseTrigger, HxReswap, HxRetarget,
SwapOption,
};
use futures_util::TryFutureExt;
use qrcode::render::svg;
use qrcode::QrCode;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use uuid::Uuid;
use kanidm_proto::internal::{
CUCredState, CUExtPortal, CURegState, CURegWarning, CURequest, CUSessionToken, CUStatus,
CredentialDetail, OperationError, PasskeyDetail, PasswordFeedback, TotpAlgo, UserAuthToken,
COOKIE_CU_SESSION_TOKEN,
};
use super::constants::Urls;
use super::navbar::NavbarCtx;
use crate::https::extractors::{DomainInfo, DomainInfoRead, VerifiedClientInformation};
use crate::https::middleware::KOpId;
use crate::https::views::constants::ProfileMenuItems;
use crate::https::views::cookies;
use crate::https::views::errors::HtmxError;
use crate::https::views::login::{LoginDisplayCtx, Reauth, ReauthPurpose};
use crate::https::ServerState;
use super::UnrecoverableErrorView;
#[derive(Template)]
#[template(path = "user_settings.html")]
struct ProfileView {
navbar_ctx: NavbarCtx,
profile_partial: CredStatusView,
}
#[derive(Template)]
#[template(path = "credentials_reset_form.html")]
struct ResetCredFormView {
domain_info: DomainInfoRead,
wrong_code: bool,
}
#[derive(Template)]
#[template(path = "credentials_reset.html")]
struct CredResetView {
domain_info: DomainInfoRead,
names: String,
credentials_update_partial: CredResetPartialView,
}
#[derive(Template)]
#[template(path = "credentials_status.html")]
struct CredStatusView {
domain_info: DomainInfoRead,
menu_active_item: ProfileMenuItems,
names: String,
credentials_update_partial: CredResetPartialView,
}
#[derive(Template)]
#[template(path = "credentials_update_partial.html")]
struct CredResetPartialView {
ext_cred_portal: CUExtPortal,
warnings: Vec<CURegWarning>,
attested_passkeys_state: CUCredState,
passkeys_state: CUCredState,
primary_state: CUCredState,
attested_passkeys: Vec<PasskeyDetail>,
passkeys: Vec<PasskeyDetail>,
primary: Option<CredentialDetail>,
unixcred_state: CUCredState,
unixcred: Option<CredentialDetail>,
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct ResetTokenParam {
token: Option<String>,
}
#[derive(Template)]
#[template(path = "credential_update_add_password_partial.html")]
struct AddPasswordPartial {
check_res: PwdCheckResult,
}
#[derive(Template)]
#[template(path = "credential_update_set_unixcred_partial.html")]
struct SetUnixCredPartial {
check_res: PwdCheckResult,
}
#[derive(Serialize, Deserialize, Debug)]
enum PwdCheckResult {
Success,
Init,
Failure {
pwd_equal: bool,
warnings: Vec<PasswordFeedback>,
},
}
#[derive(Deserialize, Debug)]
pub(crate) struct NewPassword {
new_password: String,
new_password_check: String,
}
#[derive(Deserialize, Debug)]
pub(crate) struct NewTotp {
name: String,
#[serde(rename = "checkTOTPCode")]
check_totpcode: String,
#[serde(rename = "ignoreBrokenApp")]
ignore_broken_app: bool,
}
#[derive(Template)]
#[template(path = "credential_update_add_passkey_partial.html")]
struct AddPasskeyPartial {
challenge: String,
class: PasskeyClass,
}
#[derive(Deserialize, Debug)]
struct PasskeyCreateResponse {}
#[derive(Deserialize, Debug)]
struct PasskeyCreateExtensions {}
#[derive(Deserialize, Debug)]
pub(crate) struct PasskeyInitForm {
class: PasskeyClass,
}
#[derive(Deserialize, Debug)]
pub(crate) struct PasskeyCreateForm {
name: String,
class: PasskeyClass,
#[serde(rename = "creationData")]
creation_data: String,
}
#[derive(Deserialize, Debug)]
pub(crate) struct PasskeyRemoveData {
uuid: Uuid,
}
#[derive(Deserialize, Debug)]
pub(crate) struct TOTPRemoveData {
name: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct TotpInit {
secret: String,
qr_code_svg: String,
steps: u64,
digits: u8,
algo: TotpAlgo,
uri: String,
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub(crate) struct TotpCheck {
wrong_code: bool,
broken_app: bool,
}
#[derive(Template)]
#[template(path = "credential_update_add_totp_partial.html")]
struct AddTotpPartial {
totp_init: Option<TotpInit>,
totp_name: String,
totp_value: String,
check: TotpCheck,
}
#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub enum PasskeyClass {
Any,
Attested,
}
impl Display for PasskeyClass {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
PasskeyClass::Any => write!(f, "Any"),
PasskeyClass::Attested => write!(f, "Attested"),
}
}
}
pub(crate) async fn commit(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
state
.qe_w_ref
.handle_idmcredentialupdatecommit(cu_session_token, kopid.eventid)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.await?;
let jar = cookies::destroy(jar, COOKIE_CU_SESSION_TOKEN, &state);
Ok((jar, HxLocation::from(Uri::from_static("/ui")), "").into_response())
}
pub(crate) async fn cancel_cred_update(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
state
.qe_w_ref
.handle_idmcredentialupdatecancel(cu_session_token, kopid.eventid)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.await?;
let jar = cookies::destroy(jar, COOKIE_CU_SESSION_TOKEN, &state);
Ok((
jar,
HxLocation::from(Uri::from_static(Urls::Profile.as_ref())),
"",
)
.into_response())
}
pub(crate) async fn cancel_mfareg(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
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))
.await?;
Ok(get_cu_partial_response(cu_status))
}
pub(crate) async fn remove_alt_creds(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
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))
.await?;
Ok(get_cu_partial_response(cu_status))
}
pub(crate) async fn remove_unixcred(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
let cu_status = state
.qe_r_ref
.handle_idmcredentialupdate(
cu_session_token,
CURequest::UnixPasswordRemove,
kopid.eventid,
)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.await?;
Ok(get_cu_partial_response(cu_status))
}
pub(crate) async fn remove_totp(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
Form(totp): Form<TOTPRemoveData>,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
let cu_status = state
.qe_r_ref
.handle_idmcredentialupdate(
cu_session_token,
CURequest::TotpRemove(totp.name),
kopid.eventid,
)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.await?;
Ok(get_cu_partial_response(cu_status))
}
pub(crate) async fn remove_passkey(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
Form(passkey): Form<PasskeyRemoveData>,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
let cu_status = state
.qe_r_ref
.handle_idmcredentialupdate(
cu_session_token,
CURequest::PasskeyRemove(passkey.uuid),
kopid.eventid,
)
.map_err(|op_err| HtmxError::new(&kopid, op_err))
.await?;
Ok(get_cu_partial_response(cu_status))
}
pub(crate) async fn finish_passkey(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
Form(passkey_create): Form<PasskeyCreateForm>,
) -> axum::response::Result<Response> {
let cu_session_token = get_cu_session(&jar).await?;
match serde_json::from_str(passkey_create.creation_data.as_str()) {
Ok(creation_data) => {
let cu_request = match passkey_create.class {
PasskeyClass::Any => CURequest::PasskeyFinish(passkey_create.name, creation_data),
PasskeyClass::Attested => {
CURequest::AttestedPasskeyFinish(passkey_create.name, creation_data)
}
};
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))
.await?;
Ok(get_cu_partial_response(cu_status))
}
Err(e) => {
error!("Bad request for passkey creation: {e}");
Ok((
StatusCode::UNPROCESSABLE_ENTITY,
HtmxError::new(&kopid, OperationError::Backend).into_response(),
)
.into_response())
}
}
}
pub(crate) async fn view_new_passkey(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
Form(init_form): Form<PasskeyInitForm>,
) -> axum::response::Result<Response> {
let cu_session_token = get_cu_session(&jar).await?;
let cu_req = match init_form.class {
PasskeyClass::Any => CURequest::PasskeyInit,
PasskeyClass::Attested => CURequest::AttestedPasskeyInit,
};
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))
.await?;
let response = match cu_status.mfaregstate {
CURegState::Passkey(chal) | CURegState::AttestedPasskey(chal) => {
if let Ok(challenge) = serde_json::to_string(&chal) {
AddPasskeyPartial {
challenge,
class: init_form.class,
}
.into_response()
} else {
UnrecoverableErrorView {
err_code: OperationError::UI0001ChallengeSerialisation,
operation_id: kopid.eventid,
}
.into_response()
}
}
_ => UnrecoverableErrorView {
err_code: OperationError::UI0002InvalidState,
operation_id: kopid.eventid,
}
.into_response(),
};
let passkey_init_trigger =
HxResponseTrigger::after_swap([HxEvent::new("addPasskeySwapped".to_string())]);
Ok((
passkey_init_trigger,
HxPushUrl(Uri::from_static("/ui/reset/add_passkey")),
response,
)
.into_response())
}
pub(crate) async fn view_new_totp(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
) -> axum::response::Result<Response> {
let cu_session_token = get_cu_session(&jar).await?;
let push_url = HxPushUrl(Uri::from_static("/ui/reset/add_totp"));
let cu_status = state
.qe_r_ref
.handle_idmcredentialupdate(cu_session_token, CURequest::TotpGenerate, kopid.eventid)
.await
.map_err(|op_err| HtmxError::new(&kopid, op_err))?;
let partial = if let CURegState::TotpCheck(secret) = cu_status.mfaregstate {
let uri = secret.to_uri();
let svg = match QrCode::new(uri.as_str()) {
Ok(qr) => qr.render::<svg::Color>().build(),
Err(qr_err) => {
error!("Failed to create TOTP QR code: {qr_err}");
"QR Code Generation Failed".to_string()
}
};
AddTotpPartial {
totp_init: Some(TotpInit {
secret: secret.get_secret(),
qr_code_svg: svg,
steps: secret.step,
digits: secret.digits,
algo: secret.algo,
uri,
}),
totp_name: Default::default(),
totp_value: Default::default(),
check: TotpCheck::default(),
}
} else {
return Err(ErrorResponse::from(HtmxError::new(
&kopid,
OperationError::CannotStartMFADuringOngoingMFASession,
)));
};
Ok((push_url, partial).into_response())
}
pub(crate) async fn add_totp(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
new_totp_form: Form<NewTotp>,
) -> axum::response::Result<Response> {
let cu_session_token = get_cu_session(&jar).await?;
let check_totpcode = u32::from_str(&new_totp_form.check_totpcode).unwrap_or_default();
let cu_status = if new_totp_form.ignore_broken_app {
state.qe_r_ref.handle_idmcredentialupdate(
cu_session_token,
CURequest::TotpAcceptSha1,
kopid.eventid,
)
} else {
state.qe_r_ref.handle_idmcredentialupdate(
cu_session_token,
CURequest::TotpVerify(check_totpcode, new_totp_form.name.clone()),
kopid.eventid,
)
}
.await
.map_err(|op_err| HtmxError::new(&kopid, op_err))?;
let check = match &cu_status.mfaregstate {
CURegState::None => return Ok(get_cu_partial_response(cu_status)),
CURegState::TotpTryAgain => TotpCheck {
wrong_code: true,
..Default::default()
},
CURegState::TotpInvalidSha1 => TotpCheck {
broken_app: true,
..Default::default()
},
CURegState::TotpCheck(_)
| CURegState::BackupCodes(_)
| CURegState::Passkey(_)
| CURegState::AttestedPasskey(_) => {
return Err(ErrorResponse::from(HtmxError::new(
&kopid,
OperationError::InvalidState,
)))
}
};
let check_totpcode = if check.wrong_code {
String::default()
} else {
new_totp_form.check_totpcode.clone()
};
let swapped_handler_trigger =
HxResponseTrigger::after_swap([HxEvent::new("addTotpSwapped".to_string())]);
Ok((
swapped_handler_trigger,
AddTotpPartial {
totp_init: None,
totp_name: new_totp_form.name.clone(),
totp_value: check_totpcode,
check,
},
)
.into_response())
}
pub(crate) async fn view_new_pwd(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
opt_form: Option<Form<NewPassword>>,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
let swapped_handler_trigger =
HxResponseTrigger::after_swap([HxEvent::new("addPasswordSwapped".to_string())]);
let new_passwords = match opt_form {
None => {
return Ok((
swapped_handler_trigger,
AddPasswordPartial {
check_res: PwdCheckResult::Init,
},
)
.into_response());
}
Some(Form(new_passwords)) => new_passwords,
};
let pwd_equal = new_passwords.new_password == new_passwords.new_password_check;
let (warnings, status) = if pwd_equal {
let res = state
.qe_r_ref
.handle_idmcredentialupdate(
cu_session_token,
CURequest::Password(new_passwords.new_password),
kopid.eventid,
)
.await;
match res {
Ok(cu_status) => return Ok(get_cu_partial_response(cu_status)),
Err(OperationError::PasswordQuality(password_feedback)) => {
(password_feedback, StatusCode::UNPROCESSABLE_ENTITY)
}
Err(operr) => return Err(ErrorResponse::from(HtmxError::new(&kopid, operr))),
}
} else {
(vec![], StatusCode::UNPROCESSABLE_ENTITY)
};
let check_res = PwdCheckResult::Failure {
pwd_equal,
warnings,
};
Ok((
status,
swapped_handler_trigger,
HxPushUrl(Uri::from_static("/ui/reset/change_password")),
AddPasswordPartial { check_res },
)
.into_response())
}
pub(crate) async fn view_self_reset_get(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
mut jar: CookieJar,
) -> axum::response::Result<Response> {
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))
.await?;
let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
let can_rw = uat.purpose_readwrite_active(time);
if can_rw {
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))
.await?;
let cu_resp = get_cu_response(domain_info, cu_status, true);
jar = add_cu_cookie(jar, &state, cu_session_token);
Ok((jar, cu_resp).into_response())
} else {
let display_ctx = LoginDisplayCtx {
domain_info,
oauth2: None,
reauth: Some(Reauth {
username: uat.spn,
purpose: ReauthPurpose::ProfileSettings,
}),
error: None,
};
Ok(super::login::view_reauth_get(
state,
client_auth_info,
kopid,
jar,
Urls::UpdateCredentials.as_ref(),
display_ctx,
)
.await)
}
}
fn add_cu_cookie(
jar: CookieJar,
state: &ServerState,
cu_session_token: CUSessionToken,
) -> CookieJar {
let mut token_cookie =
cookies::make_unsigned(state, COOKIE_CU_SESSION_TOKEN, cu_session_token.token);
token_cookie.set_same_site(SameSite::Strict);
jar.add(token_cookie)
}
pub(crate) async fn view_set_unixcred(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
jar: CookieJar,
opt_form: Option<Form<NewPassword>>,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
let swapped_handler_trigger =
HxResponseTrigger::after_swap([HxEvent::new("addPasswordSwapped".to_string())]);
let new_passwords = match opt_form {
None => {
return Ok((
swapped_handler_trigger,
SetUnixCredPartial {
check_res: PwdCheckResult::Init,
},
)
.into_response());
}
Some(Form(new_passwords)) => new_passwords,
};
let pwd_equal = new_passwords.new_password == new_passwords.new_password_check;
let (warnings, status) = if pwd_equal {
let res = state
.qe_r_ref
.handle_idmcredentialupdate(
cu_session_token,
CURequest::UnixPassword(new_passwords.new_password),
kopid.eventid,
)
.await;
match res {
Ok(cu_status) => return Ok(get_cu_partial_response(cu_status)),
Err(OperationError::PasswordQuality(password_feedback)) => {
(password_feedback, StatusCode::UNPROCESSABLE_ENTITY)
}
Err(operr) => return Err(ErrorResponse::from(HtmxError::new(&kopid, operr))),
}
} else {
(vec![], StatusCode::UNPROCESSABLE_ENTITY)
};
let check_res = PwdCheckResult::Failure {
pwd_equal,
warnings,
};
Ok((
status,
swapped_handler_trigger,
HxPushUrl(Uri::from_static("/ui/reset/set_unixcred")),
AddPasswordPartial { check_res },
)
.into_response())
}
pub(crate) async fn view_reset_get(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
HxRequest(_hx_request): HxRequest,
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
Query(params): Query<ResetTokenParam>,
mut jar: CookieJar,
) -> axum::response::Result<Response> {
let push_url = HxPushUrl(Uri::from_static(Urls::CredReset.as_ref()));
let cookie = jar.get(COOKIE_CU_SESSION_TOKEN);
let is_logged_in = state
.qe_r_ref
.handle_auth_valid(_client_auth_info.clone(), kopid.eventid)
.await
.is_ok();
if let Some(cookie) = cookie {
let cu_session_token = cookie.value();
let cu_session_token = CUSessionToken {
token: cu_session_token.into(),
};
let cu_status = match state
.qe_r_ref
.handle_idmcredentialupdatestatus(cu_session_token, kopid.eventid)
.await
{
Ok(cu_status) => cu_status,
Err(
OperationError::SessionExpired
| OperationError::InvalidSessionState
| OperationError::InvalidState,
) => {
jar = cookies::destroy(jar, COOKIE_CU_SESSION_TOKEN, &state);
if let Some(token) = params.token {
let token_uri_string = format!("{}?token={}", Urls::CredReset, token);
return Ok((jar, Redirect::to(&token_uri_string)).into_response());
}
return Ok((jar, Redirect::to(Urls::CredReset.as_ref())).into_response());
}
Err(op_err) => return Ok(HtmxError::new(&kopid, op_err).into_response()),
};
let cu_resp = get_cu_response(domain_info, cu_status, is_logged_in);
Ok(cu_resp)
} else if let Some(token) = params.token {
match state
.qe_w_ref
.handle_idmcredentialexchangeintent(token, kopid.eventid)
.await
{
Ok((cu_session_token, cu_status)) => {
let cu_resp = get_cu_response(domain_info, cu_status, is_logged_in);
jar = add_cu_cookie(jar, &state, cu_session_token);
Ok((jar, cu_resp).into_response())
}
Err(OperationError::SessionExpired) | Err(OperationError::Wait(_)) => {
Ok((
push_url,
ResetCredFormView {
domain_info,
wrong_code: true,
},
)
.into_response())
}
Err(op_err) => Err(ErrorResponse::from(
HtmxError::new(&kopid, op_err).into_response(),
)),
}
} else {
Ok((
push_url,
ResetCredFormView {
domain_info,
wrong_code: false,
},
)
.into_response())
}
}
fn get_cu_partial(cu_status: CUStatus) -> CredResetPartialView {
let CUStatus {
ext_cred_portal,
warnings,
passkeys_state,
attested_passkeys_state,
attested_passkeys,
passkeys,
primary_state,
primary,
unixcred_state,
unixcred,
..
} = cu_status;
CredResetPartialView {
ext_cred_portal,
warnings,
attested_passkeys_state,
passkeys_state,
attested_passkeys,
passkeys,
primary_state,
primary,
unixcred_state,
unixcred,
}
}
fn get_cu_partial_response(cu_status: CUStatus) -> Response {
let credentials_update_partial = get_cu_partial(cu_status);
(
HxPushUrl(Uri::from_static(Urls::CredReset.as_ref())),
HxRetarget("#credentialUpdateDynamicSection".to_string()),
HxReselect("#credentialUpdateDynamicSection".to_string()),
HxReswap(SwapOption::OuterHtml),
credentials_update_partial,
)
.into_response()
}
fn get_cu_response(
domain_info: DomainInfoRead,
cu_status: CUStatus,
is_logged_in: bool,
) -> Response {
let spn = cu_status.spn.clone();
let displayname = cu_status.displayname.clone();
let (username, _domain) = spn.split_once('@').unwrap_or(("", &spn));
let names = format!("{} ({})", displayname, username);
let credentials_update_partial = get_cu_partial(cu_status);
if is_logged_in {
let cred_status_view = CredStatusView {
menu_active_item: ProfileMenuItems::Credentials,
domain_info: domain_info.clone(),
names,
credentials_update_partial,
};
(
HxPushUrl(Uri::from_static(Urls::UpdateCredentials.as_ref())),
ProfileView {
navbar_ctx: NavbarCtx { domain_info },
profile_partial: cred_status_view,
},
)
.into_response()
} else {
(
HxPushUrl(Uri::from_static(Urls::CredReset.as_ref())),
CredResetView {
domain_info,
names,
credentials_update_partial,
},
)
.into_response()
}
}
async fn get_cu_session(jar: &CookieJar) -> Result<CUSessionToken, Response> {
let cookie = jar.get(COOKIE_CU_SESSION_TOKEN);
if let Some(cookie) = cookie {
let cu_session_token = cookie.value();
let cu_session_token = CUSessionToken {
token: cu_session_token.into(),
};
Ok(cu_session_token)
} else {
Err((
StatusCode::FORBIDDEN,
Redirect::to(Urls::CredReset.as_ref()),
)
.into_response())
}
}