chromium/chrome/browser/resources/ash/settings/os_people_page/password_settings.ts

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {assert} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {AuthFactor, AuthFactorConfig, FactorObserverReceiver} from 'chrome://resources/mojo/chromeos/ash/services/auth_factor_config/public/mojom/auth_factor_config.mojom-webui.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {getTemplate} from './password_settings.html.js';
import {SettingsSetLocalPasswordDialogElement} from './set_local_password_dialog.js';

enum PasswordType {
  LOCAL = 'local',
  GAIA = 'gaia',
}

export class SettingsPasswordSettingsElement extends PolymerElement {
  static get is() {
    return 'settings-password-settings' as const;
  }

  static get template() {
    return getTemplate();
  }

  static get properties() {
    return {
      authToken: {
        type: String,
        value: null,
        observer: 'updatePasswordState_',
      },

      hasGaiaPassword_: {
        type: Boolean,
        value: false,
      },

      hasLocalPassword_: {
        type: Boolean,
        value: false,
      },

      changePasswordFactorSetupEnabled_: {
        type: Boolean,
        value() {
          return loadTimeData.getBoolean('changePasswordFactorSetupEnabled');
        },
        readOnly: true,
      },
    };
  }

  authToken: string|null;
  private hasGaiaPassword_: boolean;
  private hasLocalPassword_: boolean;
  private changePasswordFactorSetupEnabled_: boolean;

  override ready(): void {
    super.ready();
    // Register observer for auth factor updates.
    // TODO(crbug.com/40223898): Are we leaking |this| here because we never remove
    // the observer? We could close the pipe with |$.close()|, but not clear
    // whether that removes all references to |receiver| and then eventually to
    // |this|.
    const receiver = new FactorObserverReceiver(this);
    const remote = receiver.$.bindNewPipeAndPassRemote();
    AuthFactorConfig.getRemote().observeFactorChanges(remote);
  }

  onFactorChanged(factor: AuthFactor): void {
    switch (factor) {
      case AuthFactor.kGaiaPassword:
      case AuthFactor.kLocalPassword:
        this.updatePasswordState_();
        break;
      default:
        return;
    }
  }

  /**
   * Fetches the state of the password factor and updates the corresponding
   * property.
   */
  private async updatePasswordState_(): Promise<void> {
    if (!this.authToken) {
      return;
    }
    const authToken = this.authToken;

    const afc = AuthFactorConfig.getRemote();
    const [{configured: hasGaiaPassword}, {configured: hasLocalPassword}] =
        await Promise.all([
          afc.isConfigured(authToken, AuthFactor.kGaiaPassword),
          afc.isConfigured(authToken, AuthFactor.kLocalPassword),
        ]);

    this.hasGaiaPassword_ = hasGaiaPassword;
    this.hasLocalPassword_ = hasLocalPassword;
  }

  private hasPassword_(): boolean {
    return this.hasGaiaPassword_ || this.hasLocalPassword_;
  }

  private hasNoPassword_(): boolean {
    return !this.hasPassword_();
  }

  /**
   * Computes the current |PasswordType| based on the values of
   * hasGaiaPassword_ and hasLocalPassword_.
   */
  private passwordType_(): PasswordType|null {
    // This control works only when there is at most one password.
    assert(!(this.hasGaiaPassword_ && this.hasLocalPassword_));

    if (this.hasGaiaPassword_) {
      return PasswordType.GAIA;
    }

    if (this.hasLocalPassword_) {
      return PasswordType.LOCAL;
    }

    return null;
  }

  private setLocalPasswordDialog(): SettingsSetLocalPasswordDialogElement {
    const el = this.shadowRoot!.getElementById('setLocalPasswordDialog');
    assert(el instanceof SettingsSetLocalPasswordDialogElement);
    return el;
  }

  private openSetLocalPasswordDialog_(): void {
    this.setLocalPasswordDialog().showModal();
  }

  private canSwitchLocalPassword_(): boolean {
    return this.hasGaiaPassword_ && this.changePasswordFactorSetupEnabled_;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    [SettingsPasswordSettingsElement.is]: SettingsPasswordSettingsElement;
  }
}

customElements.define(
    SettingsPasswordSettingsElement.is, SettingsPasswordSettingsElement);