Account: <brave-account-password-strength-meter> (#34055)

This commit is contained in:
Szilard Szaloki
2026-02-19 19:04:45 -07:00
committed by GitHub
parent 0e9a048dc9
commit 749b60cba6
7 changed files with 188 additions and 116 deletions
@@ -32,6 +32,8 @@ build_webui("build") {
"brave_account_forgot_password_dialog.ts",
"brave_account_password_icons.html.ts",
"brave_account_password_icons.ts",
"brave_account_password_strength_meter.html.ts",
"brave_account_password_strength_meter.ts",
"brave_account_sign_in_dialog.html.ts",
"brave_account_sign_in_dialog.ts",
]
@@ -44,6 +46,7 @@ build_webui("build") {
"brave_account_entry_dialog.css",
"brave_account_forgot_password_dialog.css",
"brave_account_password_icons.css",
"brave_account_password_strength_meter.css",
"brave_account_sign_in_dialog.css",
]
@@ -7,9 +7,11 @@ import { html } from '//resources/lit/v3_0/lit.rollup.js'
import './brave_account_email_input.js'
import './brave_account_password_icons.js'
import './brave_account_password_strength_meter.js'
import { BraveAccountCreateDialogElement } from './brave_account_create_dialog.js'
import type { EmailInputEventDetail } from './brave_account_email_input.js'
import { onToggleVisibility } from './brave_account_common.js'
import type { PasswordStrengthChangedEventDetail } from './brave_account_password_strength_meter.js'
export function getHtml(this: BraveAccountCreateDialogElement) {
return html`<!--_html_template_start_-->
@@ -47,11 +49,18 @@ export function getHtml(this: BraveAccountCreateDialogElement) {
</brave-account-password-icons>
<div
slot="errors"
class="dropdown ${this.passwordStrength !== 0 ? 'visible' : ''}"
class="dropdown ${this.password.length !== 0 ? 'visible' : ''}"
>
<div class="dropdown-content">
<password-strength-meter strength=${this.passwordStrength}>
</password-strength-meter>
<brave-account-password-strength-meter
password=${this.password}
@password-strength-changed=${(
e: CustomEvent<PasswordStrengthChangedEventDetail>,
) => {
this.isPasswordStrongEnough = e.detail.isStrongEnough
}}
>
</brave-account-password-strength-meter>
</div>
</div>
</leo-input>
@@ -101,7 +110,7 @@ export function getHtml(this: BraveAccountCreateDialogElement) {
<leo-button
slot="buttons"
?isDisabled=${!this.isEmailValid
|| this.passwordStrength !== 100
|| !this.isPasswordStrongEnough
|| this.passwordConfirmation !== this.password}
@click=${this.onCreateAccountButtonClicked}
>
@@ -3,14 +3,12 @@
* 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 { CrLitElement, css, html } from '//resources/lit/v3_0/lit.rollup.js'
import { loadTimeData } from '//resources/js/load_time_data.js'
import { CrLitElement } from '//resources/lit/v3_0/lit.rollup.js'
import {
BraveAccountBrowserProxy,
BraveAccountBrowserProxyImpl,
} from './brave_account_browser_proxy.js'
import { BraveAccountStrings } from './brave_components_webui_strings.js'
import { getCss } from './brave_account_create_dialog.css.js'
import { getHtml } from './brave_account_create_dialog.html.js'
import { Error, makeFocusHandler } from './brave_account_common.js'
@@ -22,106 +20,6 @@ import {
// @ts-expect-error
import { Registration } from 'chrome://resources/brave/opaque_ke.bundle.js'
class PasswordStrengthMeter extends CrLitElement {
static get is() {
return 'password-strength-meter'
}
static override get styles() {
return css`
:host {
align-items: center;
display: flex;
gap: var(--leo-spacing-m);
justify-content: space-between;
width: 100%;
}
:host([category='Weak']) {
--primary-color: var(--leo-color-systemfeedback-error-icon);
--secondary-color: var(--leo-color-systemfeedback-error-background);
}
:host([category='Medium']) {
--primary-color: var(--leo-color-systemfeedback-warning-icon);
--secondary-color: var(--leo-color-systemfeedback-warning-background);
}
:host([category='Strong']) {
--primary-color: var(--leo-color-systemfeedback-success-icon);
--secondary-color: var(--leo-color-systemfeedback-success-background);
}
.bar {
background-color: var(--secondary-color);
border-radius: var(--leo-radius-m);
flex-grow: 1;
height: 4px;
transition: 750ms;
}
.strength {
background-color: var(--primary-color);
border-radius: var(--leo-radius-m);
height: 100%;
transition: 750ms;
width: calc(1% * var(--strength));
}
.text {
color: var(--primary-color);
font: var(--leo-font-small-regular);
transition: 750ms;
}
`
}
override render() {
return html`
<div class="bar">
<div
class="strength"
style="--strength: ${this.strength}"
></div>
</div>
<div class="text">
${loadTimeData.getString(
this.category === 'Weak'
? BraveAccountStrings.BRAVE_ACCOUNT_PASSWORD_STRENGTH_METER_WEAK
: this.category === 'Medium'
? BraveAccountStrings.BRAVE_ACCOUNT_PASSWORD_STRENGTH_METER_MEDIUM
: BraveAccountStrings.BRAVE_ACCOUNT_PASSWORD_STRENGTH_METER_STRONG,
)}
</div>
`
}
static override get properties() {
return {
category: { type: String, reflect: true },
strength: { type: Number },
}
}
override updated(changedProperties: Map<PropertyKey, unknown>) {
if (changedProperties.has('strength')) {
this.category =
this.strength < 60 ? 'Weak' : this.strength < 100 ? 'Medium' : 'Strong'
}
}
protected accessor category: 'Weak' | 'Medium' | 'Strong' = 'Weak'
protected accessor strength: number = 0
}
declare global {
interface HTMLElementTagNameMap {
'password-strength-meter': PasswordStrengthMeter
}
}
customElements.define(PasswordStrengthMeter.is, PasswordStrengthMeter)
export class BraveAccountCreateDialogElement extends CrLitElement {
static get is() {
return 'brave-account-create-dialog'
@@ -142,20 +40,14 @@ export class BraveAccountCreateDialogElement extends CrLitElement {
isEmailValid: { type: Boolean },
isPasswordInputFocused: { type: Boolean },
isPasswordConfirmationInputFocused: { type: Boolean },
isPasswordStrongEnough: { type: Boolean },
password: { type: String },
passwordConfirmation: { type: String },
passwordStrength: { type: Number },
}
}
protected onPasswordInput(detail: { value: string }) {
this.password = detail.value
this.browserProxy.password_strength_meter
.getPasswordStrength(this.password)
.then(
(value: { strength: number }) =>
(this.passwordStrength = value.strength),
)
}
protected onPasswordConfirmationInput(detail: { value: string }) {
@@ -232,9 +124,9 @@ export class BraveAccountCreateDialogElement extends CrLitElement {
protected accessor isEmailValid: boolean = false
protected accessor isPasswordInputFocused: boolean = false
protected accessor isPasswordConfirmationInputFocused: boolean = false
protected accessor isPasswordStrongEnough: boolean = false
protected accessor password: string = ''
protected accessor passwordConfirmation: string = ''
protected accessor passwordStrength: number = 0
protected registration = new Registration()
protected readonly passwordFocusHandler = makeFocusHandler(
@@ -0,0 +1,53 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* 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/. */
/* #css_wrapper_metadata_start
* #type=style-lit
* #css_wrapper_metadata_end */
:host {
align-items: center;
display: flex;
gap: var(--leo-spacing-m);
justify-content: space-between;
width: 100%;
}
:host([category='Weak']) {
--primary-color: var(--leo-color-systemfeedback-error-icon);
--secondary-color: var(--leo-color-systemfeedback-error-background);
}
:host([category='Medium']) {
--primary-color: var(--leo-color-systemfeedback-warning-icon);
--secondary-color: var(--leo-color-systemfeedback-warning-background);
}
:host([category='Strong']) {
--primary-color: var(--leo-color-systemfeedback-success-icon);
--secondary-color: var(--leo-color-systemfeedback-success-background);
}
.bar {
background-color: var(--secondary-color);
border-radius: var(--leo-radius-m);
flex-grow: 1;
height: 4px;
transition: 750ms;
}
.strength {
background-color: var(--primary-color);
border-radius: var(--leo-radius-m);
height: 100%;
transition: 750ms;
width: calc(1% * var(--strength));
}
.text {
color: var(--primary-color);
font: var(--leo-font-small-regular);
transition: 750ms;
}
@@ -0,0 +1,26 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* 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 { html } from '//resources/lit/v3_0/lit.rollup.js'
import { BraveAccountPasswordStrengthMeter } from './brave_account_password_strength_meter.js'
export function getHtml(this: BraveAccountPasswordStrengthMeter) {
return html`<!--_html_template_start_-->
<div class="bar">
<div
class="strength"
style="--strength: ${this.strength}"
></div>
</div>
<div class="text">
${this.category === 'Weak'
? '$i18n{BRAVE_ACCOUNT_PASSWORD_STRENGTH_METER_WEAK}'
: this.category === 'Medium'
? '$i18n{BRAVE_ACCOUNT_PASSWORD_STRENGTH_METER_MEDIUM}'
: '$i18n{BRAVE_ACCOUNT_PASSWORD_STRENGTH_METER_STRONG}'}
</div>
<!--_html_template_end_-->`
}
@@ -0,0 +1,89 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* 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 {
CrLitElement,
PropertyValues,
} from '//resources/lit/v3_0/lit.rollup.js'
import {
BraveAccountBrowserProxy,
BraveAccountBrowserProxyImpl,
} from './brave_account_browser_proxy.js'
import { getCss } from './brave_account_password_strength_meter.css.js'
import { getHtml } from './brave_account_password_strength_meter.html.js'
export type PasswordStrengthChangedEventDetail = { isStrongEnough: boolean }
export class BraveAccountPasswordStrengthMeter extends CrLitElement {
static get is() {
return 'brave-account-password-strength-meter'
}
static override get styles() {
return getCss()
}
override render() {
return getHtml.bind(this)()
}
static override get properties() {
return {
category: { type: String, reflect: true },
strength: { type: Number, state: true },
password: { type: String },
}
}
override async updated(changedProperties: PropertyValues<this>) {
super.updated(changedProperties)
if ((changedProperties as Map<PropertyKey, unknown>).has('password')) {
const password = this.password
try {
const { strength } =
await this.browserProxy.password_strength_meter.getPasswordStrength(
password,
)
// Ignore stale response if password changed while we were waiting.
if (this.password !== password) {
return
}
this.strength = strength
} catch (error) {
console.error('Failed to calculate password strength:', error)
this.strength = 0
}
this.category =
this.strength < 60 ? 'Weak' : this.strength < 100 ? 'Medium' : 'Strong'
this.fire('password-strength-changed', {
isStrongEnough: this.strength === 100,
})
}
}
private browserProxy: BraveAccountBrowserProxy =
BraveAccountBrowserProxyImpl.getInstance()
protected accessor category: 'Weak' | 'Medium' | 'Strong' = 'Weak'
protected accessor strength = 0
private accessor password = ''
}
declare global {
interface HTMLElementTagNameMap {
'brave-account-password-strength-meter': BraveAccountPasswordStrengthMeter
}
}
customElements.define(
BraveAccountPasswordStrengthMeter.is,
BraveAccountPasswordStrengthMeter,
)
+1 -1
View File
@@ -215,7 +215,7 @@
"includes": [53980],
},
"<(SHARED_INTERMEDIATE_DIR)/brave/components/brave_account/resources/resources.grd": {
"META": {"sizes": {"includes": [35]}},
"META": {"sizes": {"includes": [40]}},
"includes": [54000],
},
"brave/ios/web/test/test_resources.grd": {