web: password quality indicators

Resolves issue 5165

This commit updates the password match indicator so that the user, and not
the component, makes decisions about the names of the initial and confirmation
inputs.
This commit is contained in:
Ken Sternberg 2023-06-08 11:25:13 -07:00
parent 1c85dc512f
commit 0d94373f10
3 changed files with 45 additions and 28 deletions

View file

@ -9,6 +9,11 @@ export default {
export const Primary = () => export const Primary = () =>
html`<div style="background: #fff; padding: 4em"> html`<div style="background: #fff; padding: 4em">
<p>Type some text: <input id="primary-example" style="color:#000" /></p> <p>Type some text: <input id="primary-example" style="color:#000" /></p>
<p>Type some other text: <input id="primary-example_repeat" style="color:#000" /></p> <p style="margin-top:0.5em">
<ak-password-match-indicator src="#primary-example_repeat"></ak-password-match-indicator> Type some other text: <input id="primary-example_repeat" style="color:#000" />
<ak-password-match-indicator
first="#primary-example"
second="#primary-example_repeat"
></ak-password-match-indicator>
</p>
</div>`; </div>`;

View file

@ -31,10 +31,14 @@ export class PasswordMatchIndicator extends AKElement {
* throw an exception. * throw an exception.
*/ */
@property({ attribute: true }) @property({ attribute: true })
src = ""; first = "";
sourceInput?: HTMLInputElement; @property({ attribute: true })
otherInput?: HTMLInputElement; second = "";
firstElement?: HTMLInputElement;
secondElement?: HTMLInputElement;
@state() @state()
match = false; match = false;
@ -46,38 +50,38 @@ export class PasswordMatchIndicator extends AKElement {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this.input.addEventListener("keyup", this.checkPasswordMatch); this.firstInput.addEventListener("keyup", this.checkPasswordMatch);
this.other.addEventListener("keyup", this.checkPasswordMatch); this.secondInput.addEventListener("keyup", this.checkPasswordMatch);
} }
disconnectedCallback() { disconnectedCallback() {
this.other.removeEventListener("keyup", this.checkPasswordMatch); this.secondInput.removeEventListener("keyup", this.checkPasswordMatch);
this.input.removeEventListener("keyup", this.checkPasswordMatch); this.firstInput.removeEventListener("keyup", this.checkPasswordMatch);
super.disconnectedCallback(); super.disconnectedCallback();
} }
checkPasswordMatch() { checkPasswordMatch() {
this.match = this.match =
this.input.value.length > 0 && this.firstInput.value.length > 0 &&
this.other.value.length > 0 && this.secondInput.value.length > 0 &&
this.input.value === this.other.value; this.firstInput.value === this.secondInput.value;
} }
get input() { get firstInput() {
if (this.sourceInput) { if (this.firstElement) {
return this.sourceInput; return this.firstElement;
} }
return (this.sourceInput = findInput(this.getRootNode() as Element, ELEMENT, this.src)); return (this.firstElement = findInput(this.getRootNode() as Element, ELEMENT, this.first));
} }
get other() { get secondInput() {
if (this.otherInput) { if (this.secondElement) {
return this.otherInput; return this.secondElement;
} }
return (this.otherInput = findInput( return (this.secondElement = findInput(
this.getRootNode() as Element, this.getRootNode() as Element,
ELEMENT, ELEMENT,
this.src.replace(/_repeat/, ""), this.second,
)); ));
} }

View file

@ -1,11 +1,13 @@
import { rootInterface } from "@goauthentik/elements/Base";
import { LOCALES } from "@goauthentik/common/ui/locale"; import { LOCALES } from "@goauthentik/common/ui/locale";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/password-match-indicator"; import "@goauthentik/elements/password-match-indicator";
import "@goauthentik/elements/password-strength-indicator"; import "@goauthentik/elements/password-strength-indicator";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { TemplateResult, html } from "lit";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { StagePrompt, CapabilitiesEnum, PromptTypeEnum } from "@goauthentik/api"; import { TemplateResult, html } from "lit";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { CapabilitiesEnum, PromptTypeEnum, StagePrompt } from "@goauthentik/api";
export function password(prompt: StagePrompt) { export function password(prompt: StagePrompt) {
return html`<input return html`<input
@ -20,7 +22,12 @@ export function password(prompt: StagePrompt) {
></ak-password-strength-indicator>`; ></ak-password-strength-indicator>`;
} }
const REPEAT = /_repeat/;
export function repeatPassword(prompt: StagePrompt) { export function repeatPassword(prompt: StagePrompt) {
const first = `input[name="${prompt.fieldKey}"]`;
const second = `input[name="${prompt.fieldKey.replace(REPEAT, "")}"]`;
return html` <div style="display:flex; flex-direction:row; gap: 0.5em; align-content: center"> return html` <div style="display:flex; flex-direction:row; gap: 0.5em; align-content: center">
<input <input
style="flex:1 0" style="flex:1 0"
@ -31,13 +38,14 @@ export function repeatPassword(prompt: StagePrompt) {
class="pf-c-form-control" class="pf-c-form-control"
?required=${prompt.required} ?required=${prompt.required}
/><ak-password-match-indicator /><ak-password-match-indicator
src='input[name="${prompt.fieldKey}"]' first="${first}"
second="${second}"
></ak-password-match-indicator> ></ak-password-match-indicator>
</div>`; </div>`;
} }
export function renderPassword(prompt: StagePrompt) { export function renderPassword(prompt: StagePrompt) {
return /_repeat$/.test(prompt.fieldKey) ? repeatPassword(prompt) : password(prompt); return REPEAT.test(prompt.fieldKey) ? repeatPassword(prompt) : password(prompt);
} }
export function renderText(prompt: StagePrompt) { export function renderText(prompt: StagePrompt) {
@ -209,7 +217,7 @@ export function renderAkLocale(prompt: StagePrompt) {
?selected=${locale.code === prompt.initialValue} ?selected=${locale.code === prompt.initialValue}
> >
${locale.code.toUpperCase()} - ${locale.label()} ${locale.code.toUpperCase()} - ${locale.label()}
</option> ` </option> `,
); );
return html`<select class="pf-c-form-control" name="${prompt.fieldKey}"> return html`<select class="pf-c-form-control" name="${prompt.fieldKey}">