@iamproperty/components 5.1.0-beta9 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/assets/css/components/component.native.css +1 -1
  2. package/assets/css/components/component.native.css.map +1 -1
  3. package/assets/css/components/component.reset.css +1 -1
  4. package/assets/css/components/component.reset.css.map +1 -1
  5. package/assets/css/core.min.css +1 -1
  6. package/assets/css/core.min.css.map +1 -1
  7. package/assets/css/style.min.css +1 -1
  8. package/assets/css/style.min.css.map +1 -1
  9. package/assets/js/components/accordion/accordion.component.min.js +1 -1
  10. package/assets/js/components/actionbar/actionbar.component.min.js +1 -1
  11. package/assets/js/components/address-lookup/address-lookup.component.js +5 -4
  12. package/assets/js/components/address-lookup/address-lookup.component.min.js +4 -4
  13. package/assets/js/components/address-lookup/address-lookup.component.min.js.map +1 -1
  14. package/assets/js/components/applied-filters/applied-filters.component.min.js +1 -1
  15. package/assets/js/components/card/card.component.min.js +1 -1
  16. package/assets/js/components/collapsible-side/collapsible-side.component.min.js +1 -1
  17. package/assets/js/components/fileupload/fileupload.component.min.js +1 -1
  18. package/assets/js/components/filterlist/filterlist.component.min.js +1 -1
  19. package/assets/js/components/header/header.component.min.js +1 -1
  20. package/assets/js/components/nav/nav.component.min.js +1 -1
  21. package/assets/js/components/notification/notification.component.min.js +1 -1
  22. package/assets/js/components/pagination/pagination.component.min.js +1 -1
  23. package/assets/js/components/search/search.component.js +29 -15
  24. package/assets/js/components/search/search.component.min.js +13 -7
  25. package/assets/js/components/search/search.component.min.js.map +1 -1
  26. package/assets/js/components/table/table.component.js +1 -0
  27. package/assets/js/components/table/table.component.min.js +2 -2
  28. package/assets/js/components/table/table.component.min.js.map +1 -1
  29. package/assets/js/components/tabs/tabs.component.min.js +1 -1
  30. package/assets/js/dynamic.min.js +5 -4
  31. package/assets/js/dynamic.min.js.map +1 -1
  32. package/assets/js/modules/helpers.js +5 -1
  33. package/assets/js/modules/inputs.js +61 -0
  34. package/assets/js/scripts.bundle.js +32 -31
  35. package/assets/js/scripts.bundle.js.map +1 -1
  36. package/assets/js/scripts.bundle.min.js +2 -2
  37. package/assets/js/scripts.bundle.min.js.map +1 -1
  38. package/assets/js/vendor/hibp.js +78 -0
  39. package/assets/sass/_func.scss +1 -0
  40. package/assets/sass/components/component.native.scss +4 -0
  41. package/assets/sass/elements/admin-panel.scss +1 -0
  42. package/assets/sass/elements/dialog.scss +3 -3
  43. package/assets/sass/elements/forms.scss +35 -4
  44. package/assets/sass/helpers/max-height.scss +4 -2
  45. package/assets/ts/components/address-lookup/address-lookup.component.ts +6 -6
  46. package/assets/ts/components/search/search.component.ts +35 -19
  47. package/assets/ts/components/table/table.component.ts +1 -0
  48. package/assets/ts/modules/helpers.ts +6 -1
  49. package/assets/ts/modules/inputs.ts +84 -0
  50. package/assets/ts/vendor/hibp.ts +81 -0
  51. package/dist/components.es.js +13 -13
  52. package/dist/components.umd.js +17 -17
  53. package/package.json +1 -1
@@ -0,0 +1,78 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * hibp.js
4
+ * @version v1
5
+ * @author Mehdi Bounya
6
+ *
7
+ * Report any bugs here: https://github.com/mehdibo/hibp-js
8
+ *
9
+ * The MIT License (http://www.opensource.org/licenses/mit-license.php)
10
+ *
11
+ * Permission is hereby granted, free of charge, to any person
12
+ * obtaining a copy of this software and associated documentation
13
+ * files (the "Software"), to deal in the Software without
14
+ * restriction, including without limitation the rights to use,
15
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the
17
+ * Software is furnished to do so, subject to the following
18
+ * conditions:
19
+ *
20
+ * The above copyright notice and this permission notice shall be
21
+ * included in all copies or substantial portions of the Software.
22
+ *
23
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
25
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
27
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
28
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
30
+ * OTHER DEALINGS IN THE SOFTWARE.
31
+ */
32
+ function sha1(string) {
33
+ var buffer = new TextEncoder("utf-8").encode(string);
34
+ return crypto.subtle.digest("SHA-1", buffer).then(function (buffer) {
35
+ // Get the hex code
36
+ var hexCodes = [];
37
+ var view = new DataView(buffer);
38
+ for (var i = 0; i < view.byteLength; i += 4) {
39
+ // Using getUint32 reduces the number of iterations needed (we process 4 bytes each time)
40
+ var value = view.getUint32(i);
41
+ // toString(16) will give the hex representation of the number without padding
42
+ var stringValue = value.toString(16);
43
+ // We use concatenation and slice for padding
44
+ var padding = '00000000';
45
+ var paddedValue = (padding + stringValue).slice(-padding.length);
46
+ hexCodes.push(paddedValue);
47
+ }
48
+ // Join all the hex strings into one
49
+ return hexCodes.join("");
50
+ });
51
+ }
52
+ const hibpCheck = (pwd, input) => {
53
+ // We hash the pwd first
54
+ sha1(pwd).then(function (hash) {
55
+ // We send the first 5 chars of the hash to hibp's API
56
+ const req = new XMLHttpRequest();
57
+ req.addEventListener("load", function () {
58
+ // When we get back a response from the server
59
+ // We create an array of lines and loop through them
60
+ const resp = this.responseText.split('\n');
61
+ const hashSub = hash.slice(5).toUpperCase();
62
+ var result = false;
63
+ for (let index in resp) {
64
+ // Check if the line matches the rest of the hash
65
+ if (resp[index].substring(0, 35) == hashSub) {
66
+ result = true;
67
+ break; // If found no need to continue the loop
68
+ }
69
+ }
70
+ // Trigger an event with the result
71
+ const event = new CustomEvent('hibpCheck', { detail: result });
72
+ input.dispatchEvent(event);
73
+ });
74
+ req.open('GET', 'https://api.pwnedpasswords.com/range/' + hash.substr(0, 5));
75
+ req.send();
76
+ });
77
+ };
78
+ export default hibpCheck;
@@ -2,6 +2,7 @@ $compatible: 'false'!default;
2
2
  $mobileOnly: 'false'!default;
3
3
  $darkMode: 'true'!default;
4
4
  $layers: 'true'!default;
5
+ $optionalText: 'true'!default;
5
6
 
6
7
  // Declare global vars variable
7
8
  $vars: ()!default;
@@ -72,6 +72,10 @@ iam-carousel {
72
72
  }
73
73
  }
74
74
 
75
+ // Search
76
+ iam-search input:not(.is-invalid):not(:invalid) {
77
+ background: none!important;
78
+ }
75
79
 
76
80
 
77
81
 
@@ -43,6 +43,7 @@
43
43
 
44
44
  font-size: rem(18);
45
45
  line-height: rem(24);
46
+ font-weight: bold;
46
47
  padding: rem(16) var(--padding-x);
47
48
  margin: calc(var(--padding-top) * -1) calc(var(--padding-x) * -1) var(--padding-top) calc(var(--padding-x) * -1);
48
49
  display: block;
@@ -178,7 +178,7 @@ dialog::backdrop {
178
178
  // #endregion
179
179
 
180
180
  // #region Transactional
181
- *:not(.dialog__wrapper) > dialog[open]:not(.dialog--multi):is(:has(:is(button,a):last-child),:has(form:last-child > :is(button,a):last-child)) {
181
+ *:not(.dialog__wrapper) > dialog[open]:not(.dialog--multi):is(:has(:is(button,a):last-child),:has(form:last-child > :is(button,a):last-child)):not(:has(.youtube-embed)) {
182
182
 
183
183
  text-align: center;
184
184
 
@@ -258,8 +258,8 @@ dialog::backdrop {
258
258
 
259
259
  @include media-breakpoint-up(sm) {
260
260
 
261
- button:has( + button:last-child),
262
- button:last-child {
261
+ :is(button, .btn):has(~ :is(button, .btn):last-child),
262
+ :is(button, .btn):last-child {
263
263
  min-width: calc(50% - 1rem);
264
264
  text-align: center;
265
265
  }
@@ -12,11 +12,23 @@ $icon-tick: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' view
12
12
  font-weight: normal;
13
13
  color: var(--colour-heading);
14
14
  display: block;
15
+ }
16
+ // #endregion
17
+
18
+ // #region Optional text
19
+ :is(label,.label) {
15
20
 
16
- &:has(+ input:not(:disabled):not([readonly]):not(:required):not([type='radio']):not([type='checkbox']):not([type='file'])):after {
21
+ @if $optionalText == "true" {
22
+ &:has(+ input:not(:disabled):not([readonly]):not(:required):not([type='radio']):not([type='checkbox']):not([type='file'])):after {
23
+ content: " (Optional)";
24
+ }
25
+ }
26
+
27
+ &:has(input:not(:disabled):not([readonly]):not(:required):not([type='radio']):not([type='checkbox']):not([type='file'])) .optional-text:before {
17
28
  content: " (Optional)";
18
29
  }
19
30
  }
31
+
20
32
  // #endregion
21
33
 
22
34
  // #region input field
@@ -375,7 +387,7 @@ input[maxlength] + span {
375
387
  border-color: var(--colour-complete);
376
388
  }
377
389
 
378
- .was-validated input:is(:valid, .is-valid) {
390
+ .was-validated input:is(:valid, .is-valid):not(:is(:invalid, .is-invalid)) {
379
391
 
380
392
  background-image: escape-svg($icon-tick);
381
393
  background-repeat: no-repeat;
@@ -410,6 +422,25 @@ iam-address-lookup:has([required]){
410
422
  --error-display: block;
411
423
  }
412
424
 
425
+ // Password checker
426
+ .pwd-checker {
427
+
428
+ display: block;
429
+ background-repeat: no-repeat!important;
430
+ background-position: left center;
431
+ background-size: 1em 1em;
432
+ padding-left: 1.5rem!important;
433
+ }
434
+ .pwd-checker.invalid-feedback {
435
+ background-image: escape-svg($icon-error);
436
+ }
437
+
438
+ .pwd-checker:not(.invalid-feedback) {
439
+ background-image: escape-svg($icon-tick)!important;
440
+ }
441
+ label:has(.pwd-checker.invalid-feedback):after {
442
+ display: none!important;
443
+ }
413
444
  // #endregion
414
445
 
415
446
  // #region radio/checkbox field
@@ -868,7 +899,7 @@ iam-fileupload {
868
899
 
869
900
  margin-bottom: 1.5rem;
870
901
 
871
- &:has(:invalid):after {
902
+ &:has(:is(:invalid,.is-invalid)):after {
872
903
  content: "This field is required";
873
904
  color: var(--colour-danger);
874
905
  margin-top: rem(8);
@@ -883,7 +914,7 @@ iam-fileupload {
883
914
  line-height: 1.2;
884
915
  }
885
916
 
886
- &[data-error]:has(:invalid):after {
917
+ &[data-error]:has(:is(:invalid,.is-invalid)):after {
887
918
  content: attr(data-error)!important;
888
919
  }
889
920
 
@@ -1,8 +1,10 @@
1
1
  :is(.mh-sm,.mh-md,.mh-lg,.mh-sm-sm,.mh-sm-md,.mh-sm-lg,.mh-md-sm,.mh-md-md,.mh-md-lg){
2
2
 
3
+ &:not(iam-table):not(.table__wrapper){
3
4
 
4
- padding-inline: var(--mh-padding-inline,0);
5
- margin-inline: calc(var(--mh-padding-inline,0) * -1);
5
+ padding-inline: var(--mh-padding-inline,0);
6
+ margin-inline: calc(var(--mh-padding-inline,0) * -1);
7
+ }
6
8
 
7
9
  &::before {
8
10
  top: calc(100% - 1.5rem);
@@ -84,15 +84,15 @@ class iamAddressLookup extends HTMLElement {
84
84
  preFilledAddress.innerHTML = "";
85
85
 
86
86
  Array.from(component.querySelectorAll('input[required],input[data-required],select[required],select[data-required]')).forEach((input, index) => {
87
+ const value = input.value;
87
88
 
88
- if(!input.value)
89
+ if(!value)
89
90
  preFilled = false;
90
91
  else
91
- preFilledAddress.innerHTML += ', '+input.value;
92
-
92
+ preFilledAddress.innerHTML += value+(/^-?\d+$/.test(value) ? ' ' : ', ');
93
93
  });
94
94
 
95
- preFilledAddress.innerHTML = preFilledAddress.innerHTML.slice(1);
95
+ preFilledAddress.innerHTML = preFilledAddress.innerHTML.slice(0, -2);
96
96
 
97
97
  if(preFilled){
98
98
  preFilledWrapper.classList.remove('js-hide');
@@ -266,9 +266,9 @@ class iamAddressLookup extends HTMLElement {
266
266
  if(key == "address_number_name")
267
267
  itemString += `${value} `;
268
268
  else if(key != "postcode" && key != "address_title")
269
- itemString += `${value}, `;
269
+ itemString += `${value}${(/^-?\d+$/.test(value)?'':',')} `;
270
270
  }
271
-
271
+
272
272
  listString += `<option value="${itemString}${postcode}" data-values='${values}'></option>`;
273
273
  }
274
274
 
@@ -22,7 +22,13 @@ class iamSearch extends HTMLElement {
22
22
  template.innerHTML = `
23
23
  <style>
24
24
  @import "${coreCSS}";
25
-
25
+ input {
26
+ background: red;
27
+ }
28
+ input:not(.is-invalid):not(:invalid) {
29
+ background: none!important;
30
+ }
31
+
26
32
  </style>
27
33
  <link rel="stylesheet" href="https://kit.fontawesome.com/26fdbf0179.css" crossorigin="anonymous" />
28
34
  <slot></slot>
@@ -62,36 +68,43 @@ class iamSearch extends HTMLElement {
62
68
  displayInputField.setAttribute('list',listID);
63
69
  }
64
70
 
65
- // on change update oringinal field with the actual value and use displayed input for the nice display text
66
- displayInputField.addEventListener('change', (event) => {
67
-
68
- let match = datalist.querySelector(`option[value="${displayInputField.value}"]`);
69
-
70
- if(match){
71
- inputField.value = match.getAttribute('data-value');
72
- }
73
- });
74
-
75
71
  // Search the endpoint when 3 characters has been added
76
72
  if(searchWrapper.hasAttribute('data-url')){
77
73
 
78
- displayInputField.addEventListener('keyup', (event) => {
74
+ displayInputField.addEventListener('input', (event) => {
79
75
 
80
76
  if(displayInputField.value.length == 3 && !searched.includes(displayInputField.value)){
81
77
  search(displayInputField.value);
82
78
  searched.push(displayInputField.value);
83
79
  }
84
80
  });
81
+ }
85
82
 
86
- displayInputField.addEventListener('change', (event) => {
83
+ function checkMatch(){
84
+
85
+ let match = datalist.querySelector(`option[value="${displayInputField.value}"]`);
86
+ let subMatch = datalist.querySelector(`option[value*='${displayInputField.value}' i]`);
87
87
 
88
- if(displayInputField.value.length >= 3 && !searched.includes(displayInputField.value.substring(0,3))){
89
- search(displayInputField.value.substring(0,3));
90
- searched.push(displayInputField.value.substring(0,3));
91
- }
92
- });
88
+ if(match){
89
+ inputField.value = match.getAttribute('data-value');
90
+ }
91
+ else if (displayInputField.value.length > 0 && !subMatch){
92
+ displayInputField.classList.add('is-invalid');
93
+ displayInputField.closest('label').setAttribute('data-error','No results returned');
94
+ }
95
+ else {
96
+ displayInputField.classList.remove('is-invalid');
97
+ displayInputField.closest('label').removeAttribute('data-error');
98
+ }
93
99
  }
94
-
100
+
101
+ // on change update oringinal field with the actual value and use displayed input for the nice display text
102
+ displayInputField.addEventListener('input', (event) => {
103
+
104
+ checkMatch();
105
+ });
106
+
107
+
95
108
  const search = async (searchterm) => {
96
109
 
97
110
  let ajaxURL = searchWrapper.getAttribute('data-url');
@@ -154,6 +167,9 @@ class iamSearch extends HTMLElement {
154
167
 
155
168
  datalist.innerHTML += listString;
156
169
 
170
+ displayInputField.closest('form').classList.add('was-validated');
171
+ checkMatch();
172
+
157
173
  return response;
158
174
  });
159
175
 
@@ -65,6 +65,7 @@ class iamTable extends HTMLElement {
65
65
 
66
66
  classList = classList.replace('table--cta','');
67
67
  classList = classList.replace('table--loading','');
68
+ classList = classList.replace('mh-md','');
68
69
  this.shadowRoot.querySelector('.table__wrapper').className += ` ${classList}`;
69
70
 
70
71
  // set actionbar class if needed
@@ -66,7 +66,12 @@ export const addGlobalEvents = (body) => {
66
66
 
67
67
  let form = event.target;
68
68
 
69
- if(form.querySelector(':invalid')){
69
+ // Reset password types
70
+ Array.from(form.querySelectorAll('[data-password-type]')).forEach((input,index) => {
71
+ input.setAttribute('type','password');
72
+ });
73
+
74
+ if(form.querySelector(':invalid') || form.querySelector('.pwd-checker[data-strength="1"]') || form.querySelector('.pwd-checker[data-strength="2"]')){
70
75
 
71
76
  form.classList.add('was-validated');
72
77
  event.preventDefault();
@@ -1,4 +1,6 @@
1
1
  // @ts-nocheck
2
+ import hibpCheck from '../vendor/hibp'
3
+
2
4
  const extendInputs = (body) => {
3
5
 
4
6
  function loadInput(){
@@ -8,6 +10,11 @@ const extendInputs = (body) => {
8
10
  setMaxlengthVars(input,wrapper);
9
11
  });
10
12
 
13
+ Array.from(document.querySelectorAll('label input')).forEach((input,index) => {
14
+ if(!input.closest('label').querySelector('.optional-text') && !input.hasAttribute('required'))
15
+ input.insertAdjacentHTML("beforebegin", `<span class="optional-text"></span>`);
16
+ });
17
+
11
18
  // Date restrictions
12
19
  if(document.querySelector('input[type="date"]')){
13
20
 
@@ -79,6 +86,10 @@ const extendInputs = (body) => {
79
86
 
80
87
  if(input.hasAttribute('maxlength') && input.nextElementSibling)
81
88
  input.nextElementSibling.setAttribute("data-count", input.value.length);
89
+
90
+
91
+ if(input.hasAttribute('data-strength-checker'))
92
+ checkPWDStrength(input);
82
93
  }
83
94
  });
84
95
 
@@ -151,7 +162,80 @@ export const setMaxlengthVars = (input) => {
151
162
 
152
163
  export const changeType = (input,type) => {
153
164
 
165
+ if(input.hasAttribute('type') && input.getAttribute('type') == 'password')
166
+ input.setAttribute('data-password-type',true);
167
+
154
168
  input.setAttribute('type',type);
155
169
  }
156
170
 
171
+ export const checkPWDStrength = (input, check = 'no') => {
172
+
173
+ const pwdChecker = document.getElementById(input.getAttribute('data-strength-checker'))
174
+ const password = input.value;
175
+ const minChars = input.hasAttribute('minlength') ? input.getAttribute('minlength') : 12;
176
+
177
+ let strength = 1;
178
+ let strengthName = ['Very weak', 'Weak', 'Average', 'Strong', 'Very strong'];
179
+ let extraMsg = '';
180
+
181
+ //has number
182
+ if (password.match(/(?=.*[0-9])/))
183
+ strength += 1;
184
+ // has special character
185
+ if (password.match(/(?=.*[!,%,&,#,$,^,*,?,_,~,<,>,])/))
186
+ strength += 1;
187
+ // has lowercase alpha
188
+ if (password.match(/(?=.*[a-z])/))
189
+ strength += 1;
190
+ // has uppercase alpha
191
+ if (password.match(/(?=.*[A-Z])/))
192
+ strength += 1;
193
+
194
+ if (password.length < minChars){
195
+ strength = 1;
196
+ extraMsg = `(must be at least ${minChars} characters.)`;
197
+ }
198
+
199
+
200
+ // if the strength is above weak and above the minimum length do some kind of api call to check if its in a list of passwords
201
+
202
+ if(strength >= 3 && check == 'no'){
203
+ hibpCheck(password,input);
204
+
205
+
206
+ input.addEventListener('hibpCheck', function (event) {
207
+ checkhibpCheck(event, input)
208
+ });
209
+
210
+ function checkhibpCheck(event, input){
211
+
212
+ if(event.detail){ // found
213
+ checkPWDStrength(input,'danger');
214
+ } else { // not found
215
+ checkPWDStrength(input,'success');
216
+ }
217
+
218
+ input.removeEventListener("hibpCheck", checkhibpCheck); // Succeeds
219
+ }
220
+
221
+ }
222
+ else if(strength >= 3 && check == 'danger'){
223
+ strength = 3;
224
+ extraMsg = `(this password is very common)`;
225
+ }
226
+
227
+
228
+ if(pwdChecker){
229
+ if(strength <= 3)
230
+ pwdChecker.classList.add('invalid-feedback');
231
+ else
232
+ pwdChecker.classList.remove('invalid-feedback');
233
+
234
+ pwdChecker.setAttribute('data-strength',strength)
235
+ pwdChecker.innerHTML = `Password strength: ${strengthName[strength-1]} ${extraMsg}`;
236
+ }
237
+
238
+ }
239
+
240
+
157
241
  export default extendInputs;
@@ -0,0 +1,81 @@
1
+ // @ts-nocheck
2
+
3
+ /**
4
+ * hibp.js
5
+ * @version v1
6
+ * @author Mehdi Bounya
7
+ *
8
+ * Report any bugs here: https://github.com/mehdibo/hibp-js
9
+ *
10
+ * The MIT License (http://www.opensource.org/licenses/mit-license.php)
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person
13
+ * obtaining a copy of this software and associated documentation
14
+ * files (the "Software"), to deal in the Software without
15
+ * restriction, including without limitation the rights to use,
16
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ * copies of the Software, and to permit persons to whom the
18
+ * Software is furnished to do so, subject to the following
19
+ * conditions:
20
+ *
21
+ * The above copyright notice and this permission notice shall be
22
+ * included in all copies or substantial portions of the Software.
23
+ *
24
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
26
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
28
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
29
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
30
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
31
+ * OTHER DEALINGS IN THE SOFTWARE.
32
+ */
33
+ function sha1(string){
34
+ var buffer = new TextEncoder("utf-8").encode(string);
35
+ return crypto.subtle.digest("SHA-1", buffer).then(function (buffer) {
36
+ // Get the hex code
37
+ var hexCodes = [];
38
+ var view = new DataView(buffer);
39
+ for (var i = 0; i < view.byteLength; i += 4) {
40
+ // Using getUint32 reduces the number of iterations needed (we process 4 bytes each time)
41
+ var value = view.getUint32(i)
42
+ // toString(16) will give the hex representation of the number without padding
43
+ var stringValue = value.toString(16)
44
+ // We use concatenation and slice for padding
45
+ var padding = '00000000'
46
+ var paddedValue = (padding + stringValue).slice(-padding.length)
47
+ hexCodes.push(paddedValue);
48
+ }
49
+ // Join all the hex strings into one
50
+ return hexCodes.join("");
51
+ });
52
+ }
53
+
54
+ const hibpCheck = (pwd,input) => {
55
+ // We hash the pwd first
56
+ sha1(pwd).then(function(hash){
57
+ // We send the first 5 chars of the hash to hibp's API
58
+ const req = new XMLHttpRequest();
59
+ req.addEventListener("load", function(){
60
+ // When we get back a response from the server
61
+ // We create an array of lines and loop through them
62
+ const resp = this.responseText.split('\n');
63
+ const hashSub = hash.slice(5).toUpperCase();
64
+ var result = false;
65
+ for(let index in resp){
66
+ // Check if the line matches the rest of the hash
67
+ if(resp[index].substring(0, 35) == hashSub){
68
+ result = true;
69
+ break; // If found no need to continue the loop
70
+ }
71
+ }
72
+ // Trigger an event with the result
73
+ const event = new CustomEvent('hibpCheck', {detail: result});
74
+ input.dispatchEvent(event);
75
+ });
76
+ req.open('GET', 'https://api.pwnedpasswords.com/range/'+hash.substr(0, 5));
77
+ req.send();
78
+ });
79
+ }
80
+
81
+ export default hibpCheck;