Credential update tweaks (#2475)

* Make the Credential Update page more user-friendly
This commit is contained in:
illode 2024-02-06 03:36:22 +00:00 committed by GitHub
parent cd27879e7f
commit 8cd62d4d4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 86 additions and 45 deletions

View file

@ -33,8 +33,10 @@
- philipcristiano - philipcristiano
- Jianchen Zhao (bolu61) - Jianchen Zhao (bolu61)
- Allan Zhang (allan2) - Allan Zhang (allan2)
- illode
- Jinna Kiisuo (jinnatar) - Jinna Kiisuo (jinnatar)
## Acknowledgements ## Acknowledgements
- M. Gerstner - M. Gerstner

View file

@ -178,7 +178,7 @@ impl Component for CreateResetCode {
}) })
} }
> >
{ "Update your Authentication Settings on Another Device" } { "Add another device to your account" }
</button> </button>
<div class="modal" tabindex="-1" role="dialog" id={ID_CRED_RESET_CODE}> <div class="modal" tabindex="-1" role="dialog" id={ID_CRED_RESET_CODE}>
<div class="modal-dialog modal-lg" role="document"> <div class="modal-dialog modal-lg" role="document">

View file

@ -227,7 +227,7 @@ impl ProfileApp {
}) })
} }
> >
{ "Password and Authentication Settings" } { "Authentication Settings" }
</button> </button>
</p> </p>
</div> </div>

View file

@ -376,8 +376,8 @@ impl CredentialResetApp {
attested_passkeys_allowed_devices, attested_passkeys_allowed_devices,
} = status; } = status;
let displayname = displayname.clone(); let (username, domain) = spn.split_once('@').unwrap_or(("", &spn));
let spn = spn.clone(); let names = format!("{} ({})", displayname, username);
let cb = self.cb.clone(); let cb = self.cb.clone();
let ext_cred_portal_html = match ext_cred_portal { let ext_cred_portal_html = match ext_cred_portal {
@ -421,7 +421,7 @@ impl CredentialResetApp {
match warning { match warning {
CURegWarning::MfaRequired => html! { CURegWarning::MfaRequired => html! {
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
<p>{ "MFA is required for your account. Add TOTP or remove your password in favour of Passkeys." }</p> <p>{ "Multi-Factor Authentication is required for your account. Either add TOTP or remove your password in favour of passkeys to submit." }</p>
</div> </div>
}, },
CURegWarning::PasskeyRequired => html! { CURegWarning::PasskeyRequired => html! {
@ -464,8 +464,8 @@ impl CredentialResetApp {
<main class="w-100"> <main class="w-100">
<div class="py-3 text-center"> <div class="py-3 text-center">
<h3>{ "Updating Credentials" }</h3> <h3>{ "Updating Credentials" }</h3>
<p>{ displayname }</p> <p>{ names }</p>
<p>{ spn }</p> <p>{ domain }</p>
</div> </div>
<div class="row g-3"> <div class="row g-3">
@ -524,7 +524,7 @@ impl CredentialResetApp {
let cb = self.cb.clone(); let cb = self.cb.clone();
// match on primary, get type_. // match on primary, get type_.
let pw_html_inner = if matches!(primary_state, CUCredState::Modifiable) { let alt_auth_method_inner = if matches!(primary_state, CUCredState::Modifiable) {
match primary { match primary {
Some(CredentialDetail { Some(CredentialDetail {
uuid: _, uuid: _,
@ -532,21 +532,21 @@ impl CredentialResetApp {
}) => { }) => {
html! { html! {
<> <>
<p>{ "Password Set" }</p> <h6> <b>{ "Password" }</b> </h6>
<p> <p>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticPassword"> <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticPassword">
{ "Change Password" } { "Change Password" }
</button> </button>
</p> </p>
<h6> <b>{ "Time-based One Time Password (TOTP)" }</b> </h6>
<p>{ "❌ MFA Disabled" }</p> <p>{ "TOTPs are 6 digit codes generated on-demand as a second authentication factor."}</p>
<p> <p>
<TotpModalApp token={ token.clone() } cb={ cb.clone() }/> <TotpModalApp token={ token.clone() } cb={ cb.clone() }/>
</p> </p>
<br/>
<p> <p>
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#staticDeletePrimaryCred"> <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#staticDeletePrimaryCred">
{ "Delete this Insecure Password" } { "Delete Alternative Credentials" }
</button> </button>
</p> </p>
</> </>
@ -566,15 +566,15 @@ impl CredentialResetApp {
}) => { }) => {
html! { html! {
<> <>
<p>{ "Password Set" }</p> <h6> <b>{ "Password" }</b> </h6>
<p> <p>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticPassword"> <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticPassword">
{ "Change Password" } { "Change Password" }
</button> </button>
</p> </p>
<br/>
<p>{ "✅ MFA Enabled" }</p> <h6> <b>{ "Time-based One Time Password (TOTP)" }</b></h6>
<p>{ "TOTPs are 6 digit codes generated on-demand as a second authentication factor."}</p>
<> <>
{ for totp_set.iter() { for totp_set.iter()
.map(|detail| html! { <TotpRemoveComp token={ token.clone() } label={ detail.clone() } cb={ cb.clone() } /> }) .map(|detail| html! { <TotpRemoveComp token={ token.clone() } label={ detail.clone() } cb={ cb.clone() } /> })
@ -584,10 +584,11 @@ impl CredentialResetApp {
<p> <p>
<TotpModalApp token={ token.clone() } cb={ cb.clone() }/> <TotpModalApp token={ token.clone() } cb={ cb.clone() }/>
</p> </p>
<br/>
<br/>
<p> <p>
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#staticDeletePrimaryCred"> <button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#staticDeletePrimaryCred">
{ "Delete this Legacy MFA Credential" } { "Delete Alternative Credentials" }
</button> </button>
</p> </p>
@ -600,9 +601,10 @@ impl CredentialResetApp {
}) => { }) => {
html! { html! {
<> <>
<p>{ "Generated Password" }</p> <h6> <b>{ "Password" }</b> </h6>
<p>{ "In order to set up alternative authentication methods, you must delete the generated password." }</p>
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#staticDeletePrimaryCred"> <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#staticDeletePrimaryCred">
{ "Delete this Password" } { "Delete Generated Password" }
</button> </button>
</> </>
} }
@ -613,9 +615,9 @@ impl CredentialResetApp {
}) => { }) => {
html! { html! {
<> <>
<p>{ "Webauthn Only - Will migrate to Passkeys in a future update" }</p> <p>{ "Webauthn Only - Will migrate to passkeys in a future update" }</p>
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#staticDeletePrimaryCred"> <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#staticDeletePrimaryCred">
{ "Delete this Credential" } { "Delete Alternative Credentials" }
</button> </button>
</> </>
} }
@ -632,7 +634,7 @@ impl CredentialResetApp {
html! { html! {
<p> <p>
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#staticDeletePrimaryCred"> <button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#staticDeletePrimaryCred">
{ "Delete this Legacy Credential" } { "Delete Legacy Credentials" }
</button> </button>
</p> </p>
} }
@ -640,38 +642,44 @@ impl CredentialResetApp {
html! {<></>} html! {<></>}
}; };
let pw_warn = match primary_state { let alt_auth_method_warning = match primary_state {
CUCredState::Modifiable => { CUCredState::Modifiable => {
html! { html! {
<> <>
<p>{ "Legacy password paired with other authentication factors." }</p> <p>{ "If possible, passkeys should be used instead, as they are phishing and exploit resistant." }</p>
<p>{ "It is recommended you avoid setting these if possible, as these can be phished or exploited." }</p>
</> </>
} }
} }
CUCredState::DeleteOnly => { CUCredState::DeleteOnly => {
html! { html! {
<> <>
<p>{ "Legacy password paired with other authentication factors." }</p> <p>{ "If possible, passkeys should be used instead, as they are phishing and exploit resistant." }</p>
<p>{ "Account policy prevents you modifying this credential, but you may remove it." }</p> <p>{ "Account policy prevents you modifying these credentials, but you may remove them." }</p>
</> </>
} }
} }
CUCredState::AccessDeny => { CUCredState::AccessDeny => {
html! { <><p> { "You do not have access to modify the Password or TOTP tokens of this account" }</p></> } html! {
<>
<p>{ "You do not have access to modify these credentials." }</p>
</>
}
} }
CUCredState::PolicyDeny => { CUCredState::PolicyDeny => {
html! { <><p> { "Account policy prevents you setting the Password or TOTP tokens of this account" }</p></> } html! {
<>
<p>{ "Account policy prevents you from setting these credentials" }</p>
</>
}
} }
}; };
html! { html! {
<> <>
<hr class="my-4" /> <hr class="my-4" />
<h4>{"Alternative Authentication Methods" }</h4>
<h4>{"Password / TOTP"}</h4> { alt_auth_method_warning }
{ pw_warn } { alt_auth_method_inner }
{ pw_html_inner }
<PwModalApp token={ token.clone() } cb={ cb } /> <PwModalApp token={ token.clone() } cb={ cb } />
</> </>
@ -693,13 +701,17 @@ impl CredentialResetApp {
<hr class="my-4" /> <hr class="my-4" />
<h4>{"Passkeys"}</h4> <h4>{"Passkeys"}</h4>
<p>{ "Strong cryptographic authenticators with self contained multi-factor authentication." }</p> <p>{ "Easy to use digital credentials with self-contained multi-factor authentication designed to replace passwords." }</p>
<p>
{ if passkeys.is_empty() { <a target="_blank" href="https://support.microsoft.com/en-us/windows/passkeys-in-windows-301c8944-5ea2-452b-9886-97e4d2ef4422">{ "Windows" }</a>
html! { <p>{ "No Passkeys Registered" }</p> } { ", " }
} else { <a target="_blank" href="https://support.apple.com/guide/mac-help/create-a-passkey-mchl4af65d1a/mac">{ "MacOS" }</a>
html! { <></> } { ", " }
} } <a target="_blank" href="https://support.google.com/android/answer/14124480?hl=en">{ "Android" }</a>
{ ", and " }
<a target="_blank" href="https://support.apple.com/guide/iphone/use-passkeys-to-sign-in-to-apps-and-websites-iphf538ea8d0/ios">{ "iOS" }</a>
{ " have built-in support for passkeys."}
</p>
{ for passkeys.iter() { for passkeys.iter()
.map(|detail| .map(|detail|
@ -744,8 +756,6 @@ impl CredentialResetApp {
<> <>
<hr class="my-4" /> <hr class="my-4" />
<h4>{"Attested Passkeys"}</h4> <h4>{"Attested Passkeys"}</h4>
{ if attested_passkeys.is_empty() { html! { <p> { "No Passkeys Registered" } </p> } } else { html! {<></>} } }
{ for attested_passkeys.iter() { for attested_passkeys.iter()
.map(|detail| .map(|detail|
PasskeyRemoveModalApp::render_button(&detail.tag, detail.uuid) PasskeyRemoveModalApp::render_button(&detail.tag, detail.uuid)

View file

@ -357,6 +357,35 @@ impl Component for TotpModalApp {
</div> </div>
</div> </div>
<p> { "An authentication app is needed to generate TOTP codes"}
<ul>
<li>
<a target="_blank" href="https://support.apple.com/en-in/guide/iphone/ipha6173c19f/ios">{ "Built-in iOS TOTP generator" }</a>
</li>
<li>{ "Google Authenticator" }
{ " (" }
<a target="_blank" href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">{ "Android" }</a>
{ " / " }
<a target="_blank" href="https://apps.apple.com/app/google-authenticator/id388497605">{ "iOS" }</a>
{ ")" }
</li>
<li>{ "Microsoft Authenticator"}
{ " (" }
<a target="_blank" href="https://play.google.com/store/apps/details?id=com.azure.authenticator">{ "Android" }</a>
{ " / " }
<a target="_blank" href="https://apps.apple.com/us/app/microsoft-authenticator/id983156458">{ "iOS" }</a>
{ ")" }
</li>
<li>{ "FreeOTP"}
{ " (" }
<a target="_blank" href="https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp">{ "Android" }</a>
{ " / " }
<a target="_blank" href="https://apps.apple.com/us/app/freeotp-authenticator/id872559395">{ "iOS" }</a>
{ ")" }
</li>
</ul>
</p>
{ {
match &self.secret { match &self.secret {
TotpValue::Secret(secret) => { TotpValue::Secret(secret) => {

View file

@ -225,7 +225,7 @@ impl ProfileApp {
}) })
} }
> >
{ "Password and Authentication Settings" } { "Authentication Settings" }
</button> </button>
</p> </p>
</div> </div>