Bug 1896772 - verify and display password rules r=backup-reviewers,fluent-reviewers,sthompson
Differential Revision: https://phabricator.services.mozilla.com/D216617
This commit is contained in:
@@ -610,6 +610,7 @@ export class BackupService extends EventTarget {
|
|||||||
/** @type {number?} Number of seconds since UNIX epoch */
|
/** @type {number?} Number of seconds since UNIX epoch */
|
||||||
lastBackupDate: null,
|
lastBackupDate: null,
|
||||||
lastBackupFileName: "",
|
lastBackupFileName: "",
|
||||||
|
supportBaseLink: Services.urlFormatter.formatURLPref("app.support.baseURL"),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.backup-dialog {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
#turn-on-scheduled-backups-dialog {
|
#turn-on-scheduled-backups-dialog {
|
||||||
width: 27.8rem;
|
width: 27.8rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export default class BackupSettings extends MozLitElement {
|
|||||||
scheduledBackupsEnabled: false,
|
scheduledBackupsEnabled: false,
|
||||||
lastBackupDate: null,
|
lastBackupDate: null,
|
||||||
lastBackupFileName: "",
|
lastBackupFileName: "",
|
||||||
|
supportBaseLink: "",
|
||||||
};
|
};
|
||||||
this.recoveryInProgress = false;
|
this.recoveryInProgress = false;
|
||||||
this.recoveryErrorCode = 0;
|
this.recoveryErrorCode = 0;
|
||||||
@@ -352,9 +353,13 @@ export default class BackupSettings extends MozLitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enableBackupEncryptionDialogTemplate() {
|
enableBackupEncryptionDialogTemplate() {
|
||||||
return html`<dialog id="enable-backup-encryption-dialog">
|
return html`<dialog
|
||||||
|
id="enable-backup-encryption-dialog"
|
||||||
|
class="backup-dialog"
|
||||||
|
>
|
||||||
<enable-backup-encryption
|
<enable-backup-encryption
|
||||||
type=${this._enableEncryptionTypeAttr}
|
type=${this._enableEncryptionTypeAttr}
|
||||||
|
.supportBaseLink=${this.backupServiceState.supportBaseLink}
|
||||||
></enable-backup-encryption>
|
></enable-backup-encryption>
|
||||||
</dialog>`;
|
</dialog>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,20 +36,3 @@
|
|||||||
#backup-enable-encryption-button-group {
|
#backup-enable-encryption-button-group {
|
||||||
grid-area: button-group;
|
grid-area: button-group;
|
||||||
}
|
}
|
||||||
|
|
||||||
#passwords {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
row-gap: var(--space-large);
|
|
||||||
|
|
||||||
> #new-password-label,
|
|
||||||
> #repeat-password-label {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
> #new-password-label > input,
|
|
||||||
> #repeat-password-label > input {
|
|
||||||
margin-inline-start: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
|
import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
|
||||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
|
import "chrome://browser/content/backup/password-validation-inputs.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid attributes for the enable-backup-encryption dialog type.
|
* Valid attributes for the enable-backup-encryption dialog type.
|
||||||
*
|
*
|
||||||
@@ -26,8 +29,9 @@ const VALID_L10N_IDS = new Map([
|
|||||||
*/
|
*/
|
||||||
export default class EnableBackupEncryption extends MozLitElement {
|
export default class EnableBackupEncryption extends MozLitElement {
|
||||||
static properties = {
|
static properties = {
|
||||||
passwordsMatch: { type: Boolean, reflect: true },
|
_inputPassValue: { type: String, state: true },
|
||||||
passwordsRequired: { type: Boolean, reflect: true },
|
_passwordsMatch: { type: Boolean, state: true },
|
||||||
|
supportBaseLink: { type: String },
|
||||||
/**
|
/**
|
||||||
* The "type" attribute changes the layout.
|
* The "type" attribute changes the layout.
|
||||||
*
|
*
|
||||||
@@ -40,17 +44,19 @@ export default class EnableBackupEncryption extends MozLitElement {
|
|||||||
return {
|
return {
|
||||||
cancelButtonEl: "#backup-enable-encryption-cancel-button",
|
cancelButtonEl: "#backup-enable-encryption-cancel-button",
|
||||||
confirmButtonEl: "#backup-enable-encryption-confirm-button",
|
confirmButtonEl: "#backup-enable-encryption-confirm-button",
|
||||||
inputNewPasswordEl: "#new-password-input",
|
contentEl: "#backup-enable-encryption-content",
|
||||||
inputRepeatPasswordEl: "#repeat-password-input",
|
textHeaderEl: "#backup-enable-encryption-header",
|
||||||
textDescriptionEl: "#backup-enable-encryption-description",
|
textDescriptionEl: "#backup-enable-encryption-description",
|
||||||
|
passwordInputsEl: "#backup-enable-encryption-password-inputs",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.passwordsMatch = false;
|
this.supportBaseLink = "";
|
||||||
this.passwordsRequired = true;
|
|
||||||
this.type = VALID_TYPES.SET_PASSWORD;
|
this.type = VALID_TYPES.SET_PASSWORD;
|
||||||
|
this._inputPassValue = "";
|
||||||
|
this._passwordsMatch = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,6 +68,20 @@ export default class EnableBackupEncryption extends MozLitElement {
|
|||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("BackupUI:InitWidget", { bubbles: true })
|
new CustomEvent("BackupUI:InitWidget", { bubbles: true })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.addEventListener("ValidPasswordsDetected", this);
|
||||||
|
this.addEventListener("InvalidPasswordsDetected", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(event) {
|
||||||
|
if (event.type == "ValidPasswordsDetected") {
|
||||||
|
let { password } = event.detail;
|
||||||
|
this._passwordsMatch = true;
|
||||||
|
this._inputPassValue = password;
|
||||||
|
} else if (event.type == "InvalidPasswordsDetected") {
|
||||||
|
this._passwordsMatch = false;
|
||||||
|
this._inputPassValue = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCancel() {
|
handleCancel() {
|
||||||
@@ -82,7 +102,7 @@ export default class EnableBackupEncryption extends MozLitElement {
|
|||||||
bubbles: true,
|
bubbles: true,
|
||||||
composed: true,
|
composed: true,
|
||||||
detail: {
|
detail: {
|
||||||
password: this.inputNewPasswordEl.value,
|
password: this._inputPassValue,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -93,7 +113,7 @@ export default class EnableBackupEncryption extends MozLitElement {
|
|||||||
bubbles: true,
|
bubbles: true,
|
||||||
composed: true,
|
composed: true,
|
||||||
detail: {
|
detail: {
|
||||||
password: this.inputNewPasswordEl.value,
|
password: this._inputPassValue,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -102,60 +122,18 @@ export default class EnableBackupEncryption extends MozLitElement {
|
|||||||
this.resetChanges();
|
this.resetChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChangeNewPassword() {
|
|
||||||
this.updatePasswordValidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangeRepeatPassword() {
|
|
||||||
this.updatePasswordValidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePasswordValidity() {
|
|
||||||
// If the "required" attribute was previously removed, add it back
|
|
||||||
// to make password validation work as expected.
|
|
||||||
if (!this.passwordsRequired) {
|
|
||||||
this.passwordsRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let isNewPasswordInputValid = this.inputNewPasswordEl?.checkValidity();
|
|
||||||
let isRepeatPasswordInputValid =
|
|
||||||
this.inputRepeatPasswordEl?.checkValidity();
|
|
||||||
/**
|
|
||||||
* TODO: Before confirmation, verify FxA format rules (bug 1896772).
|
|
||||||
* This step may involve async validation with BackupService. For instance, we have to
|
|
||||||
* check against a list of common passwords (bug 1905140) and display a message if an
|
|
||||||
* issue occurs (bug 1905145).
|
|
||||||
*/
|
|
||||||
this.passwordsMatch =
|
|
||||||
isNewPasswordInputValid &&
|
|
||||||
isRepeatPasswordInputValid &&
|
|
||||||
this.inputNewPasswordEl.value == this.inputRepeatPasswordEl.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetChanges() {
|
resetChanges() {
|
||||||
this.passwordsMatch = false;
|
this._inputPassValue = "";
|
||||||
this.inputNewPasswordEl.value = "";
|
this._passwordsMatch = false;
|
||||||
this.inputRepeatPasswordEl.value = "";
|
|
||||||
// Temporarily remove "required" attribute to remove styles for invalid inputs.
|
this.passwordInputsEl.dispatchEvent(
|
||||||
// The attribute will be added again when we run validation.
|
new CustomEvent("resetInputs", { bubbles: true, composed: true })
|
||||||
this.passwordsRequired = false;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
contentTemplate() {
|
descriptionTemplate() {
|
||||||
return html`
|
return html`
|
||||||
<form
|
<div id="backup-enable-encryption-description">
|
||||||
id="backup-enable-encryption-wrapper"
|
|
||||||
aria-labelledby="backup-enable-encryption-header"
|
|
||||||
aria-describedby="backup-enable-encryption-description"
|
|
||||||
>
|
|
||||||
<h1
|
|
||||||
id="backup-enable-encryption-header"
|
|
||||||
class="heading-medium"
|
|
||||||
data-l10n-id=${ifDefined(VALID_L10N_IDS.get(this.type))}
|
|
||||||
></h1>
|
|
||||||
<main id="backup-enable-encryption-content">
|
|
||||||
${this.type === VALID_TYPES.SET_PASSWORD
|
|
||||||
? html` <div id="backup-enable-encryption-description">
|
|
||||||
<span
|
<span
|
||||||
id="backup-enable-encryption-description-span"
|
id="backup-enable-encryption-description-span"
|
||||||
data-l10n-id="enable-backup-encryption-description"
|
data-l10n-id="enable-backup-encryption-description"
|
||||||
@@ -168,37 +146,12 @@ export default class EnableBackupEncryption extends MozLitElement {
|
|||||||
support-page="todo-backup"
|
support-page="todo-backup"
|
||||||
data-l10n-id="enable-backup-encryption-support-link"
|
data-l10n-id="enable-backup-encryption-support-link"
|
||||||
></a>
|
></a>
|
||||||
</div>`
|
</div>
|
||||||
: null}
|
`;
|
||||||
|
}
|
||||||
<fieldset id="passwords">
|
|
||||||
<label id="new-password-label" for="new-password-input">
|
|
||||||
<span
|
|
||||||
id="new-password-span"
|
|
||||||
data-l10n-id="enable-backup-encryption-create-password-label"
|
|
||||||
></span>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="new-password-input"
|
|
||||||
?required=${this.passwordsRequired}
|
|
||||||
@input=${this.handleChangeNewPassword}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label id="repeat-password-label" for="repeat-password-input">
|
|
||||||
<span
|
|
||||||
id="repeat-password-span"
|
|
||||||
data-l10n-id="enable-backup-encryption-repeat-password-label"
|
|
||||||
></span>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="repeat-password-input"
|
|
||||||
?required=${this.passwordsRequired}
|
|
||||||
@input=${this.handleChangeRepeatPassword}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
|
buttonGroupTemplate() {
|
||||||
|
return html`
|
||||||
<moz-button-group id="backup-enable-encryption-button-group">
|
<moz-button-group id="backup-enable-encryption-button-group">
|
||||||
<moz-button
|
<moz-button
|
||||||
id="backup-enable-encryption-cancel-button"
|
id="backup-enable-encryption-cancel-button"
|
||||||
@@ -210,10 +163,36 @@ export default class EnableBackupEncryption extends MozLitElement {
|
|||||||
@click=${this.handleConfirm}
|
@click=${this.handleConfirm}
|
||||||
type="primary"
|
type="primary"
|
||||||
data-l10n-id="enable-backup-encryption-confirm-button"
|
data-l10n-id="enable-backup-encryption-confirm-button"
|
||||||
?disabled=${!this.passwordsMatch}
|
?disabled=${!this._passwordsMatch}
|
||||||
></moz-button>
|
></moz-button>
|
||||||
</moz-button-group>
|
</moz-button-group>
|
||||||
</form>
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
contentTemplate() {
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
id="backup-enable-encryption-wrapper"
|
||||||
|
aria-labelledby="backup-enable-encryption-header"
|
||||||
|
aria-describedby="backup-enable-encryption-description"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
id="backup-enable-encryption-header"
|
||||||
|
class="heading-medium"
|
||||||
|
data-l10n-id=${ifDefined(VALID_L10N_IDS.get(this.type))}
|
||||||
|
></h1>
|
||||||
|
<div id="backup-enable-encryption-content">
|
||||||
|
${this.type === VALID_TYPES.SET_PASSWORD
|
||||||
|
? this.descriptionTemplate()
|
||||||
|
: null}
|
||||||
|
<password-validation-inputs
|
||||||
|
id="backup-enable-encryption-password-inputs"
|
||||||
|
.supportBaseLink=${this.supportBaseLink}
|
||||||
|
>
|
||||||
|
</password-validation-inputs>
|
||||||
|
</div>
|
||||||
|
${this.buttonGroupTemplate()}
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Template = ({ type }) => html`
|
const Template = ({ type }) => html`
|
||||||
<moz-card style="width: 23.94rem;">
|
<moz-card style="width: 23.94rem; position: relative;">
|
||||||
<enable-backup-encryption type=${type}></enable-backup-encryption>
|
<enable-backup-encryption type=${type}></enable-backup-encryption>
|
||||||
</moz-card>
|
</moz-card>
|
||||||
`;
|
`;
|
||||||
|
|||||||
86
browser/components/backup/content/password-rules-tooltip.css
Normal file
86
browser/components/backup/content/password-rules-tooltip.css
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#password-rules-wrapper {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
padding: var(--space-large) var(--space-xlarge);
|
||||||
|
background-color: var(--background-color-box);
|
||||||
|
border: var(--border-width) solid var(--border-color-interactive);
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
overflow: visible;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-block-start: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Arrow */
|
||||||
|
&::before {
|
||||||
|
width: 0.75rem;
|
||||||
|
height: 0.75rem;
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
background-color: var(--background-color-box);
|
||||||
|
border-block-start: var(--border-width) solid var(--border-color-interactive);
|
||||||
|
border-inline-start: var(--border-width) solid var(--border-color-interactive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default, not zoomed in */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
&::before {
|
||||||
|
inset-inline-start: -0.4rem;
|
||||||
|
inset-block-start: 1.25rem;
|
||||||
|
rotate: -45deg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:dir(rtl)::before {
|
||||||
|
rotate: 45deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zoomed in */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
&::before {
|
||||||
|
inset-inline-start: 1.25rem;
|
||||||
|
inset-block-start: -0.46rem;
|
||||||
|
rotate: 45deg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:dir(rtl)::before {
|
||||||
|
rotate: -45deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#password-rules-header {
|
||||||
|
margin-block-start: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
-moz-context-properties: fill, stroke;
|
||||||
|
fill: currentColor;
|
||||||
|
stroke: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: var(--icon-color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
color: var(--icon-color-warning);
|
||||||
|
}
|
||||||
119
browser/components/backup/content/password-rules-tooltip.mjs
Normal file
119
browser/components/backup/content/password-rules-tooltip.mjs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import { html } from "chrome://global/content/vendor/lit.all.mjs";
|
||||||
|
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The widget for enabling password protection if the backup is not yet
|
||||||
|
* encrypted.
|
||||||
|
*/
|
||||||
|
export default class PasswordRulesTooltip extends MozLitElement {
|
||||||
|
static properties = {
|
||||||
|
hasCommon: { type: Boolean },
|
||||||
|
hasEmail: { type: Boolean },
|
||||||
|
tooShort: { type: Boolean },
|
||||||
|
supportBaseLink: { type: String },
|
||||||
|
};
|
||||||
|
|
||||||
|
static get queries() {
|
||||||
|
return {
|
||||||
|
passwordRulesEl: "#password-rules-wrapper",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.hasCommon = false;
|
||||||
|
this.hasEmail = false;
|
||||||
|
this.tooShort = false;
|
||||||
|
this.supportBaseLink = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
getRuleStateConstants(hasInvalidCondition) {
|
||||||
|
if (hasInvalidCondition) {
|
||||||
|
return {
|
||||||
|
class: "warning",
|
||||||
|
icon: "chrome://global/skin/icons/warning.svg",
|
||||||
|
l10nId: "password-rules-a11y-warning",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
class: "success",
|
||||||
|
icon: "chrome://global/skin/icons/check-filled.svg",
|
||||||
|
l10nId: "password-rules-a11y-success",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let lengthConstants = this.getRuleStateConstants(this.tooShort);
|
||||||
|
let emailConstants = this.getRuleStateConstants(this.hasEmail);
|
||||||
|
// TODO: (bug 1905140) read list of common passwords - default to success state for now
|
||||||
|
let commonConstants = this.getRuleStateConstants(this.hasCommon);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="chrome://browser/content/backup/password-rules-tooltip.css"
|
||||||
|
/>
|
||||||
|
<div id="password-rules-wrapper" aria-live="polite">
|
||||||
|
<h2
|
||||||
|
id="password-rules-header"
|
||||||
|
data-l10n-id="password-rules-header"
|
||||||
|
></h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<img
|
||||||
|
data-l10n-id=${lengthConstants.l10nId}
|
||||||
|
class="icon ${lengthConstants.class}"
|
||||||
|
src=${lengthConstants.icon}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
data-l10n-id="password-rules-length-description"
|
||||||
|
class="rule-description"
|
||||||
|
></span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<img
|
||||||
|
data-l10n-id=${emailConstants.l10nId}
|
||||||
|
class="icon ${emailConstants.class}"
|
||||||
|
src=${emailConstants.icon}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
data-l10n-id="password-rules-email-description"
|
||||||
|
class="rule-description"
|
||||||
|
></span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<img
|
||||||
|
data-l10n-id=${commonConstants.l10nId}
|
||||||
|
class="icon ${commonConstants.class}"
|
||||||
|
src=${commonConstants.icon}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
data-l10n-id="password-rules-common-description"
|
||||||
|
class="rule-description"
|
||||||
|
></span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<img
|
||||||
|
class="icon"
|
||||||
|
src="chrome://browser/skin/preferences/category-privacy-security.svg"
|
||||||
|
/>
|
||||||
|
<span data-l10n-id="password-rules-disclaimer"
|
||||||
|
><a
|
||||||
|
data-l10n-name="password-support-link"
|
||||||
|
target="_blank"
|
||||||
|
href=${`${this.supportBaseLink}password-strength`}
|
||||||
|
></a
|
||||||
|
></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("password-rules-tooltip", PasswordRulesTooltip);
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
@import url("chrome://global/skin/in-content/common.css");
|
||||||
|
@import url("chrome://browser/content/backup/backup-common.css");
|
||||||
|
|
||||||
|
#password-inputs-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: var(--space-large);
|
||||||
|
|
||||||
|
> #repeat-password-label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
> #new-password-label input,
|
||||||
|
> #repeat-password-label input {
|
||||||
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-password-label {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
#new-password-label-wrapper-span-input {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
transition: visibility 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#password-rules {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
/* Default, not zoomed in */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
width: 20rem;
|
||||||
|
inset-inline-start: 100%;
|
||||||
|
transform: translate(-1rem, 1rem);
|
||||||
|
|
||||||
|
&:dir(rtl) {
|
||||||
|
transform: translate(1rem, 1rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zoomed in */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
width: 23rem;
|
||||||
|
/* Shift the tooltip under the password input by a few pixels */
|
||||||
|
transform: translateY(calc(var(--input-text-min-height) + 1.75rem));
|
||||||
|
}
|
||||||
|
}
|
||||||
224
browser/components/backup/content/password-validation-inputs.mjs
Normal file
224
browser/components/backup/content/password-validation-inputs.mjs
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import { html } from "chrome://global/content/vendor/lit.all.mjs";
|
||||||
|
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
|
import "chrome://browser/content/backup/password-rules-tooltip.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The widget for enabling password protection if the backup is not yet
|
||||||
|
* encrypted.
|
||||||
|
*/
|
||||||
|
export default class PasswordValidationInputs extends MozLitElement {
|
||||||
|
static properties = {
|
||||||
|
_hasCommon: { type: Boolean, state: true },
|
||||||
|
_hasEmail: { type: Boolean, state: true },
|
||||||
|
_passwordsMatch: { type: Boolean, state: true },
|
||||||
|
_passwordsValid: { type: Boolean, state: true },
|
||||||
|
_showRules: { type: Boolean, state: true },
|
||||||
|
_tooShort: { type: Boolean, state: true },
|
||||||
|
/**
|
||||||
|
* If, by chance, there is focus on a focusable element in the tooltip,
|
||||||
|
* track the focus state so that we can keep the tooltip open.
|
||||||
|
*/
|
||||||
|
_tooltipFocus: { type: Boolean, state: true },
|
||||||
|
supportBaseLink: { type: String },
|
||||||
|
};
|
||||||
|
|
||||||
|
static get queries() {
|
||||||
|
return {
|
||||||
|
formEl: "#password-inputs-form",
|
||||||
|
inputNewPasswordEl: "#new-password-input",
|
||||||
|
inputRepeatPasswordEl: "#repeat-password-input",
|
||||||
|
passwordRulesEl: "#password-rules",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.supportBaseLink = "";
|
||||||
|
this._tooShort = true;
|
||||||
|
this._hasCommon = false;
|
||||||
|
this._hasEmail = false;
|
||||||
|
this._passwordsMatch = false;
|
||||||
|
this._passwordsValid = false;
|
||||||
|
this._tooltipFocus = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.addEventListener("resetInputs", this.handleReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this.removeEventListener("resetInputs", this.handleReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReset() {
|
||||||
|
this.formEl.reset();
|
||||||
|
this._showRules = false;
|
||||||
|
this._hasCommon = false;
|
||||||
|
this._hasEmail = false;
|
||||||
|
this._tooShort = true;
|
||||||
|
this._passwordsMatch = false;
|
||||||
|
this._passwordsValid = false;
|
||||||
|
this._tooltipFocus = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFocusNewPassword() {
|
||||||
|
this._showRules = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlurNewPassword(event) {
|
||||||
|
this._showRules = !event.target.checkValidity();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeNewPassword() {
|
||||||
|
this.updatePasswordValidity();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeRepeatPassword() {
|
||||||
|
this.updatePasswordValidity();
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePasswordValidity() {
|
||||||
|
const emailRegex = /^[\w!#$%&'*+/=?^`{|}~.-]+@[A-Z0-9-]+\.[A-Z0-9.-]+$/i;
|
||||||
|
this._hasEmail = emailRegex.test(this.inputNewPasswordEl.value);
|
||||||
|
if (this._hasEmail) {
|
||||||
|
// TODO: we need a localized string for this error (bug 1909983)
|
||||||
|
this.inputNewPasswordEl.setCustomValidity("TODO: no emails");
|
||||||
|
} else {
|
||||||
|
this.inputNewPasswordEl.setCustomValidity("");
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPassValidity = this.inputNewPasswordEl.validity;
|
||||||
|
this._tooShort = newPassValidity?.valueMissing || newPassValidity?.tooShort;
|
||||||
|
|
||||||
|
this._passwordsMatch =
|
||||||
|
this.inputNewPasswordEl.value == this.inputRepeatPasswordEl.value;
|
||||||
|
if (!this._passwordsMatch) {
|
||||||
|
// TODO: we need a localized string for this error (bug 1909983)
|
||||||
|
this.inputRepeatPasswordEl.setCustomValidity("TODO: not matching");
|
||||||
|
} else {
|
||||||
|
this.inputRepeatPasswordEl.setCustomValidity("");
|
||||||
|
}
|
||||||
|
|
||||||
|
const repeatPassValidity = this.inputRepeatPasswordEl.validity;
|
||||||
|
this._passwordsValid =
|
||||||
|
newPassValidity?.valid &&
|
||||||
|
repeatPassValidity?.valid &&
|
||||||
|
this._passwordsMatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This step may involve async validation with BackupService. For instance, we have to
|
||||||
|
* check against a list of common passwords (bug 1905140) and display an error message if an
|
||||||
|
* issue occurs (bug 1905145).
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTooltipFocus() {
|
||||||
|
this._tooltipFocus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTooltipBlur() {
|
||||||
|
this._tooltipFocus = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a custom event whenever validity changes.
|
||||||
|
*
|
||||||
|
* @param {Map<string, any>} changedProperties a Map of recently changed properties and their new values
|
||||||
|
*/
|
||||||
|
updated(changedProperties) {
|
||||||
|
if (!changedProperties.has("_passwordsValid")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._passwordsValid) {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("ValidPasswordsDetected", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail: {
|
||||||
|
password: this.inputNewPasswordEl.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("InvalidPasswordsDetected", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentTemplate() {
|
||||||
|
return html`
|
||||||
|
<div id="password-inputs-wrapper" aria-live="polite">
|
||||||
|
<form id="password-inputs-form">
|
||||||
|
<!--TODO: (bug 1909983) change first input field label for the "change-password" dialog-->
|
||||||
|
<label id="new-password-label" for="new-password-input">
|
||||||
|
<div id="new-password-label-wrapper-span-input">
|
||||||
|
<span
|
||||||
|
id="new-password-span"
|
||||||
|
data-l10n-id="enable-backup-encryption-create-password-label"
|
||||||
|
></span>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="new-password-input"
|
||||||
|
minlength="8"
|
||||||
|
required
|
||||||
|
@input=${this.handleChangeNewPassword}
|
||||||
|
@focus=${this.handleFocusNewPassword}
|
||||||
|
@blur=${this.handleBlurNewPassword}
|
||||||
|
/>
|
||||||
|
<!--TODO: (bug 1909984) improve how we read out the first input field for screen readers-->
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<!--TODO: (bug 1909984) look into how the tooltip vs dialog behaves when pressing the ESC key-->
|
||||||
|
<password-rules-tooltip
|
||||||
|
id="password-rules"
|
||||||
|
class=${!this._showRules && !this._tooltipFocus ? "hidden" : ""}
|
||||||
|
.hasCommon=${this._hasCommon}
|
||||||
|
.hasEmail=${this._hasEmail}
|
||||||
|
.tooShort=${this._tooShort}
|
||||||
|
.supportBaseLink=${this.supportBaseLink}
|
||||||
|
@focus=${this.handleTooltipFocus}
|
||||||
|
@blur=${this.handleTooltipBlur}
|
||||||
|
></password-rules-tooltip>
|
||||||
|
<label id="repeat-password-label" for="repeat-password-input">
|
||||||
|
<span
|
||||||
|
id="repeat-password-span"
|
||||||
|
data-l10n-id="enable-backup-encryption-repeat-password-label"
|
||||||
|
></span>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="repeat-password-input"
|
||||||
|
minlength="8"
|
||||||
|
required
|
||||||
|
@input=${this.handleChangeRepeatPassword}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="chrome://browser/content/backup/password-validation-inputs.css"
|
||||||
|
/>
|
||||||
|
${this.contentTemplate()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("password-validation-inputs", PasswordValidationInputs);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-unresolved
|
||||||
|
import { html } from "lit.all.mjs";
|
||||||
|
import "chrome://global/content/elements/moz-card.mjs";
|
||||||
|
import "./password-validation-inputs.mjs";
|
||||||
|
|
||||||
|
window.MozXULElement.insertFTLIfNeeded("locales-preview/backupSettings.ftl");
|
||||||
|
window.MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Domain-specific UI Widgets/Backup/Password Inputs",
|
||||||
|
component: "password-validation-inputs",
|
||||||
|
argTypes: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template = () => html`
|
||||||
|
<moz-card style="position: relative; width: 25rem;">
|
||||||
|
<password-validation-inputs></password-validation-inputs>
|
||||||
|
</moz-card>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
@@ -25,3 +25,7 @@ browser.jar:
|
|||||||
* content/browser/backup/archive.template.html (content/archive.template.html)
|
* content/browser/backup/archive.template.html (content/archive.template.html)
|
||||||
content/browser/backup/archive.css (content/archive.css)
|
content/browser/backup/archive.css (content/archive.css)
|
||||||
* content/browser/backup/archive.js (content/archive.js)
|
* content/browser/backup/archive.js (content/archive.js)
|
||||||
|
content/browser/backup/password-rules-tooltip.css (content/password-rules-tooltip.css)
|
||||||
|
content/browser/backup/password-rules-tooltip.mjs (content/password-rules-tooltip.mjs)
|
||||||
|
content/browser/backup/password-validation-inputs.css (content/password-validation-inputs.css)
|
||||||
|
content/browser/backup/password-validation-inputs.mjs (content/password-validation-inputs.mjs)
|
||||||
|
|||||||
@@ -127,6 +127,21 @@ enable-backup-encryption-confirm-button = Save
|
|||||||
|
|
||||||
change-backup-encryption-header = Change backup password
|
change-backup-encryption-header = Change backup password
|
||||||
|
|
||||||
|
## These strings are displayed in a tooltip showing what requirements are met while creating a password.
|
||||||
|
|
||||||
|
password-rules-header = Password requirements
|
||||||
|
password-rules-length-description = At least 8 characters
|
||||||
|
password-rules-email-description = Not your email address
|
||||||
|
password-rules-common-description = Not a commonly used password
|
||||||
|
password-rules-disclaimer = Stay safe — don’t reuse passwords. See more tips to <a data-l10n-name="password-support-link">create strong passwords</a>.
|
||||||
|
|
||||||
|
## These strings are only used for assistive technologies, like screen readers, in the password requirements tooltip.
|
||||||
|
|
||||||
|
password-rules-a11y-success =
|
||||||
|
.alt = Success
|
||||||
|
password-rules-a11y-warning =
|
||||||
|
.alt = Warning
|
||||||
|
|
||||||
## These strings are displayed in a modal when users want to disable encryption for an existing backup.
|
## These strings are displayed in a modal when users want to disable encryption for an existing backup.
|
||||||
|
|
||||||
disable-backup-encryption-header = Remove password protection
|
disable-backup-encryption-header = Remove password protection
|
||||||
|
|||||||
Reference in New Issue
Block a user