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

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

/**
 * @fileoverview
 * 'pin-autosubmit-confirmation-dialog' is a confirmation dialog that pops up
 * when the user chooses to enable PIN auto submit. The user is prompted to
 * enter their current PIN and if it matches the feature is enabled.
 *
 */

import 'chrome://resources/ash/common/quick_unlock/pin_keyboard.js';
import 'chrome://resources/ash/common/quick_unlock/setup_pin_keyboard.js';
import 'chrome://resources/js/assert.js';
import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import '../settings_shared.css.js';

import {PinKeyboardElement} from 'chrome://resources/ash/common/quick_unlock/pin_keyboard.js';
import {fireAuthTokenInvalidEvent} from 'chrome://resources/ash/common/quick_unlock/utils.js';
import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {getTemplate} from './pin_autosubmit_dialog.html.js';

// Maximum length supported by auto submit
const AutosubmitMaxLength = 12;

// Possible errors that might occur with the respective i18n string.
const AutoSubmitErrorStringsName = {
  PinIncorrect: 'pinAutoSubmitPinIncorrect',
  PinTooLong: 'pinAutoSubmitLongPinError',
};

const SettingsPinAutosubmitDialogElementBase = I18nMixin(PolymerElement);

interface SettingsPinAutosubmitDialogElement {
  $: {
    dialog: CrDialogElement,
    pinKeyboard: PinKeyboardElement,
  };
}

class SettingsPinAutosubmitDialogElement extends
    SettingsPinAutosubmitDialogElementBase {
  static get is() {
    return 'settings-pin-autosubmit-dialog' as const;
  }

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

  static get properties() {
    return {
      /**
       * The current PIN keyboard value.
       */
      pinValue_: {
        type: String,
      },

      /**
       * Possible errors that might occur. Null when there are no errors to
       * show.
       */
      error_: {
        type: String,
        value: null,
      },

      /**
       * Whether there is a request in process already. Disables the
       * buttons, but leaves the cancel button actionable.
       */
      requestInProcess_: {
        type: Boolean,
        value: false,
      },

      /**
       * Whether the confirm button should be disabled.
       */
      confirmButtonDisabled_: {
        type: Boolean,
        value: true,
      },

      /**
       * Authentication token provided by lock-screen-password-prompt-dialog.
       */
      authToken: {
        type: String,
        notify: true,
      },

      /**
       * Interface for chrome.quickUnlockPrivate calls. May be overridden by
       * tests.
       */
      quickUnlockPrivate: {type: Object, value: chrome.quickUnlockPrivate},
    };
  }

  authToken: string|undefined;
  private error_: string|null;
  private confirmButtonDisabled_: boolean;
  private pinValue_: string;
  private quickUnlockPrivate: typeof chrome.quickUnlockPrivate;
  private requestInProcess_: boolean;

  static get observers() {
    return [
      'updateButtonState_(error_, requestInProcess_, pinValue_)',
    ];
  }

  override connectedCallback(): void {
    super.connectedCallback();

    this.resetState();
    this.$.dialog.showModal();
    this.$.pinKeyboard.focusInput();
  }

  close(): void {
    if (this.$.dialog.open) {
      this.$.dialog.close();
    }
    this.resetState();
  }

  resetState(): void {
    this.requestInProcess_ = false;
    this.pinValue_ = '';
    this.error_ = null;
  }

  private onCancelClick_(): void {
    this.close();
  }

  /**
   * Update error notice when more digits are inserted.
   * e: Custom event containing the new pin
   */
  private onPinChange_(e: CustomEvent<{pin: string}>): void {
    if (e && e.detail && e.detail.pin) {
      this.pinValue_ = e.detail.pin;
    }

    if (this.pinValue_ && this.pinValue_.length > AutosubmitMaxLength) {
      this.error_ = AutoSubmitErrorStringsName['PinTooLong'];
      return;
    }

    this.error_ = null;
  }

  /**
   * Make a request to the quick unlock API to enable PIN auto-submit.
   */
  private onPinSubmit_(): void {
    // Prevent submission through 'ENTER' if the 'Submit' button is disabled
    this.updateButtonState_();
    if (this.confirmButtonDisabled_) {
      return;
    }

    if (typeof this.authToken !== 'string') {
      fireAuthTokenInvalidEvent(this);
      return;
    }

    // Make a request to enable pin autosubmit.
    this.requestInProcess_ = true;

    this.quickUnlockPrivate.setPinAutosubmitEnabled(
        this.authToken, this.pinValue_ /* PIN */, true /*enabled*/,
        this.onPinSubmitResponse_.bind(this));
  }

  /**
   * Response from the quick unlock API.
   *
   * If the call is not successful because the PIN is incorrect,
   * it will check if its still possible to authenticate with PIN.
   * Submitting an invalid PIN will either show an error to the user,
   * or close the dialog and trigger a password re-prompt.
   */
  private onPinSubmitResponse_(success: boolean): void {
    if (success) {
      this.close();
      return;
    }
    // Check if it is still possible to authenticate with pin.
    this.quickUnlockPrivate.canAuthenticatePin(
        this.onCanAuthenticateResponse_.bind(this));
  }

  /**
   * Response from the quick unlock API on whether PIN authentication
   * is currently possible.
   */
  private onCanAuthenticateResponse_(canAuthenticate: boolean): void {
    if (!canAuthenticate) {
      const event = new CustomEvent(
          'invalidate-auth-token-requested', {bubbles: true, composed: true});
      this.dispatchEvent(event);
      this.close();
      return;
    }
    // The entered PIN was incorrect.
    this.pinValue_ = '';
    this.requestInProcess_ = false;
    this.error_ = AutoSubmitErrorStringsName.PinIncorrect;
    this.$.pinKeyboard.focusInput();
  }

  private updateButtonState_(): void {
    this.confirmButtonDisabled_ =
        this.requestInProcess_ || !!this.error_ || !this.pinValue_;
  }

  /**
   * Error message to be shown on the dialog when the PIN is
   * incorrect, or if its too long to activate auto submit.
   * error: i18n String
   */
  private getErrorMessageString_(error: string|null): string {
    return error ? this.i18n(error) : '';
  }
}

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

customElements.define(
    SettingsPinAutosubmitDialogElement.is, SettingsPinAutosubmitDialogElement);