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 = () =>
html`<div style="background: #fff; padding: 4em">
<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>
<ak-password-match-indicator src="#primary-example_repeat"></ak-password-match-indicator>
<p style="margin-top:0.5em">
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>`;

View file

@ -31,10 +31,14 @@ export class PasswordMatchIndicator extends AKElement {
* throw an exception.
*/
@property({ attribute: true })
src = "";
first = "";
sourceInput?: HTMLInputElement;
otherInput?: HTMLInputElement;
@property({ attribute: true })
second = "";
firstElement?: HTMLInputElement;
secondElement?: HTMLInputElement;
@state()
match = false;
@ -46,38 +50,38 @@ export class PasswordMatchIndicator extends AKElement {
connectedCallback() {
super.connectedCallback();
this.input.addEventListener("keyup", this.checkPasswordMatch);
this.other.addEventListener("keyup", this.checkPasswordMatch);
this.firstInput.addEventListener("keyup", this.checkPasswordMatch);
this.secondInput.addEventListener("keyup", this.checkPasswordMatch);
}
disconnectedCallback() {
this.other.removeEventListener("keyup", this.checkPasswordMatch);
this.input.removeEventListener("keyup", this.checkPasswordMatch);
this.secondInput.removeEventListener("keyup", this.checkPasswordMatch);
this.firstInput.removeEventListener("keyup", this.checkPasswordMatch);
super.disconnectedCallback();
}
checkPasswordMatch() {
this.match =
this.input.value.length > 0 &&
this.other.value.length > 0 &&
this.input.value === this.other.value;
this.firstInput.value.length > 0 &&
this.secondInput.value.length > 0 &&
this.firstInput.value === this.secondInput.value;
}
get input() {
if (this.sourceInput) {
return this.sourceInput;
get firstInput() {
if (this.firstElement) {
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() {
if (this.otherInput) {
return this.otherInput;
get secondInput() {
if (this.secondElement) {
return this.secondElement;
}
return (this.otherInput = findInput(
return (this.secondElement = findInput(
this.getRootNode() as 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 { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/password-match-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 { 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) {
return html`<input
@ -20,7 +22,12 @@ export function password(prompt: StagePrompt) {
></ak-password-strength-indicator>`;
}
const REPEAT = /_repeat/;
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">
<input
style="flex:1 0"
@ -31,13 +38,14 @@ export function repeatPassword(prompt: StagePrompt) {
class="pf-c-form-control"
?required=${prompt.required}
/><ak-password-match-indicator
src='input[name="${prompt.fieldKey}"]'
first="${first}"
second="${second}"
></ak-password-match-indicator>
</div>`;
}
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) {
@ -209,7 +217,7 @@ export function renderAkLocale(prompt: StagePrompt) {
?selected=${locale.code === prompt.initialValue}
>
${locale.code.toUpperCase()} - ${locale.label()}
</option> `
</option> `,
);
return html`<select class="pf-c-form-control" name="${prompt.fieldKey}">