summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/settings/privacy_page/security_keys_pin_field.js
blob: abace08a7e012d7e45ff837ab31dc8d9a00b90b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview 'settings-security-keys-pin-field' is a component for entering
 * a security key PIN.
 */
Polymer({
  is: 'settings-security-keys-pin-field',

  behaviors: [
    I18nBehavior,
  ],

  properties: {
    /** The label of the input field. */
    label: String,

    /** The validation error label of the input field. */
    error: String,

    /** The value of the input field. */
    value: String,

    /**
     * Whether the contents of the input field are visible.
     * @private
     */
    inputVisible_: Boolean,
  },

  /** @override */
  attached: function() {
    this.inputVisible_ = false;
  },

  /** Focuses the PIN input field. */
  focus: function() {
    this.$.pin.focus();
  },

  /**
   * Validates the PIN and sets the validation error if it is not valid.
   * @return {boolean} True iff the PIN is valid.
   */
  validate: function() {
    const error = this.isValidPIN_(this.value);
    if (error != '') {
      this.error = error;
      return false;
    }
    return true;
  },

  /**
   * Sets the validation error to indicate the PIN was incorrect.
   * @param {number} retries The number of retries remaining.
   */
  showIncorrectPINError: function(retries) {
    // Warn the user if the number of retries is getting low.
    let error;
    if (1 < retries && retries <= 3) {
      error =
          this.i18n('securityKeysPINIncorrectRetriesPl', retries.toString());
    } else if (retries == 1) {
      error = this.i18n('securityKeysPINIncorrectRetriesSin');
    } else {
      error = this.i18n('securityKeysPINIncorrect');
    }
    this.error = error;
  },

  /** @private */
  onPINInput_: function() {
    // Typing in the PIN box after an error makes the error message
    // disappear.
    this.error = '';
  },

  /**
   * Polymer helper function to detect when an error string is empty.
   * @param {string} s Arbitrary string
   * @return {boolean} True iff |s| is non-empty.
   * @private
   */
  isNonEmpty_: function(s) {
    return s != '';
  },

  /**
   * @return {string} The PIN-input element type.
   * @private
   */
  inputType_: function() {
    return this.inputVisible_ ? 'text' : 'password';
  },

  /**
   * @return {string} The class (and thus icon) to be displayed.
   * @private
   */
  showButtonClass_: function() {
    return 'icon-visibility' + (this.inputVisible_ ? '-off' : '');
  },

  /**
   * @return {string} The tooltip for the icon.
   * @private
   */
  showButtonTitle_: function() {
    return this.i18n(
        this.inputVisible_ ? 'securityKeysHidePINs' : 'securityKeysShowPINs');
  },

  /**
   * onClick handler for the show/hide icon.
   * @private
   */
  showButtonClick_: function() {
    this.inputVisible_ = !this.inputVisible_;
  },

  /**
   * @param {string} pin A candidate PIN.
   * @return {string} An error string or else '' to indicate validity.
   * @private
   */
  isValidPIN_: function(pin) {
    // The UTF-8 encoding of the PIN must be between 4
    // and 63 bytes, and the final byte cannot be zero.
    const utf8Encoded = new TextEncoder().encode(pin);
    if (utf8Encoded.length < 4) {
      return this.i18n('securityKeysPINTooShort');
    }
    if (utf8Encoded.length > 63 ||
        // If the PIN somehow has a NUL at the end then it's invalid, but this
        // is so obscure that we don't try to message it. Rather we just say
        // that it's too long because trimming the final character is the best
        // response by the user.
        utf8Encoded[utf8Encoded.length - 1] == 0) {
      return this.i18n('securityKeysPINTooLong');
    }

    // A PIN must contain at least four code-points. Javascript strings are
    // UCS-2 and the |length| property counts UCS-2 elements, not code-points.
    // (For example, '\u{1f6b4}'.length == 2, but it's a single code-point.)
    // Therefore, iterate over the string (which does yield codepoints) and
    // check that four or more were seen.
    let length = 0;
    for (const codepoint of pin) {
      length++;
    }

    if (length < 4) {
      return this.i18n('securityKeysPINTooShort');
    }

    return '';
  },
});