@descope/web-components-ui 3.9.2 → 3.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@descope/web-components-ui",
3
- "version": "3.9.2",
3
+ "version": "3.10.0",
4
4
  "description": "",
5
5
  "main": "dist/cjs/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -52,10 +52,10 @@
52
52
  "webpack-cli": "^7.0.0",
53
53
  "webpack-dev-server": "^5.0.0",
54
54
  "webpack-subresource-integrity": "5.2.0-rc.1",
55
- "rollup-replace-plugin": "3.9.2",
56
- "test-drivers": "3.9.2",
57
- "webpack-extract-font-loader": "3.9.2",
58
- "webpack-replace-plugin": "3.9.2"
55
+ "rollup-replace-plugin": "3.10.0",
56
+ "test-drivers": "3.10.0",
57
+ "webpack-extract-font-loader": "3.10.0",
58
+ "webpack-replace-plugin": "3.10.0"
59
59
  },
60
60
  "dependencies": {
61
61
  "@vaadin/checkbox": "24.3.4",
@@ -79,36 +79,36 @@
79
79
  "libphonenumber-js": "^1.11.12",
80
80
  "lodash.debounce": "4.0.8",
81
81
  "lodash.merge": "4.6.2",
82
- "@descope-ui/common": "3.9.2",
83
- "@descope-ui/descope-address-field": "3.9.2",
84
- "@descope-ui/descope-country-subdivision-city-field": "3.9.2",
85
- "@descope-ui/descope-apps-list": "3.9.2",
86
- "@descope-ui/descope-autocomplete-field": "3.9.2",
87
- "@descope-ui/descope-avatar": "3.9.2",
88
- "@descope-ui/descope-badge": "3.9.2",
89
- "@descope-ui/descope-button": "3.9.2",
90
- "@descope-ui/descope-collapsible-container": "3.9.2",
91
- "@descope-ui/descope-combo-box": "3.9.2",
92
- "@descope-ui/descope-enriched-text": "3.9.2",
93
- "@descope-ui/descope-icon": "3.9.2",
94
- "@descope-ui/descope-image": "3.9.2",
95
- "@descope-ui/descope-link": "3.9.2",
96
- "@descope-ui/descope-list-item": "3.9.2",
97
- "@descope-ui/descope-list": "3.9.2",
98
- "@descope-ui/descope-multi-line-mappings": "3.9.2",
99
- "@descope-ui/descope-multi-select-combo-box": "3.9.2",
100
- "@descope-ui/descope-outbound-app-button": "3.9.2",
101
- "@descope-ui/descope-recovery-codes": "3.9.2",
102
- "@descope-ui/descope-ponyhot": "3.9.2",
103
- "@descope-ui/descope-password-strength": "3.9.2",
104
- "@descope-ui/descope-outbound-apps": "3.9.2",
105
- "@descope-ui/descope-text": "3.9.2",
106
- "@descope-ui/descope-timer": "3.9.2",
107
- "@descope-ui/descope-timer-button": "3.9.2",
108
- "@descope-ui/descope-tooltip": "3.9.2",
109
- "@descope-ui/descope-trusted-devices": "3.9.2",
110
- "@descope-ui/descope-attachment": "3.9.2",
111
- "@descope-ui/descope-anchored": "3.9.2"
82
+ "@descope-ui/descope-country-subdivision-city-field": "3.10.0",
83
+ "@descope-ui/descope-address-field": "3.10.0",
84
+ "@descope-ui/common": "3.10.0",
85
+ "@descope-ui/descope-autocomplete-field": "3.10.0",
86
+ "@descope-ui/descope-apps-list": "3.10.0",
87
+ "@descope-ui/descope-avatar": "3.10.0",
88
+ "@descope-ui/descope-badge": "3.10.0",
89
+ "@descope-ui/descope-button": "3.10.0",
90
+ "@descope-ui/descope-collapsible-container": "3.10.0",
91
+ "@descope-ui/descope-combo-box": "3.10.0",
92
+ "@descope-ui/descope-enriched-text": "3.10.0",
93
+ "@descope-ui/descope-icon": "3.10.0",
94
+ "@descope-ui/descope-image": "3.10.0",
95
+ "@descope-ui/descope-link": "3.10.0",
96
+ "@descope-ui/descope-list": "3.10.0",
97
+ "@descope-ui/descope-multi-line-mappings": "3.10.0",
98
+ "@descope-ui/descope-list-item": "3.10.0",
99
+ "@descope-ui/descope-outbound-app-button": "3.10.0",
100
+ "@descope-ui/descope-outbound-apps": "3.10.0",
101
+ "@descope-ui/descope-multi-select-combo-box": "3.10.0",
102
+ "@descope-ui/descope-password-strength": "3.10.0",
103
+ "@descope-ui/descope-ponyhot": "3.10.0",
104
+ "@descope-ui/descope-recovery-codes": "3.10.0",
105
+ "@descope-ui/descope-text": "3.10.0",
106
+ "@descope-ui/descope-timer": "3.10.0",
107
+ "@descope-ui/descope-tooltip": "3.10.0",
108
+ "@descope-ui/descope-timer-button": "3.10.0",
109
+ "@descope-ui/descope-trusted-devices": "3.10.0",
110
+ "@descope-ui/descope-attachment": "3.10.0",
111
+ "@descope-ui/descope-anchored": "3.10.0"
112
112
  },
113
113
  "overrides": {
114
114
  "@vaadin/avatar": "24.3.4",
@@ -58,6 +58,8 @@ const customMixin = (superclass) =>
58
58
  'available-policies',
59
59
  'data-password-policy-value-minlength',
60
60
  'data-password-policy-value-passwordstrength',
61
+ 'data-password-policy-value-disallowedchars',
62
+ 'data-password-policy-value-email',
61
63
  'label-type',
62
64
  'manual-visibility-toggle',
63
65
  ],
@@ -18,6 +18,8 @@ const policyPanelAttrs = [
18
18
  'active-policies',
19
19
  'data-password-policy-value-minlength',
20
20
  'data-password-policy-value-passwordstrength',
21
+ 'data-password-policy-value-disallowedchars',
22
+ 'data-password-policy-value-email',
21
23
  'manual-visibility-toggle',
22
24
  ];
23
25
  const commonAttrs = [
@@ -11,6 +11,8 @@ const overrideAttrs = [
11
11
  'data-password-policy-value-minlength',
12
12
  'data-password-policy-value-passwordstrength',
13
13
  'data-password-policy-actual-passwordstrength',
14
+ 'data-password-policy-value-disallowedchars',
15
+ 'data-password-policy-value-email',
14
16
  ];
15
17
  const dataAttrs = ['data', 'active-policies', 'overrides', ...overrideAttrs];
16
18
  const policyAttrs = ['label', 'value', ...dataAttrs];
@@ -129,6 +131,46 @@ class RawPolicyValidation extends createBaseClass({ componentName, baseSelector:
129
131
  };
130
132
  }
131
133
  }
134
+
135
+ // disallowedchars: this stores the configured char list in
136
+ // overrides.disallowedchars.value so the message can interpolate
137
+ // `{{value}}`. The regex pattern itself is built upstream (orchestrator
138
+ // or caller) and shipped on the policy entry — it is not derived from
139
+ // this override. When the attribute is cleared, drop the override so
140
+ // the panel doesn't keep stale data.
141
+ if (attrName === 'data-password-policy-value-disallowedchars') {
142
+ if (newValue) {
143
+ this.#overrides = {
144
+ ...this.#overrides,
145
+ disallowedchars: {
146
+ ...this.#overrides?.disallowedchars,
147
+ value: newValue,
148
+ },
149
+ };
150
+ } else if (this.#overrides?.disallowedchars) {
151
+ const { disallowedchars: _drop, ...rest } = this.#overrides;
152
+ this.#overrides = rest;
153
+ }
154
+ }
155
+
156
+ // disallowemail: stash the user's email so the STR_NEQ_CI comparator
157
+ // has an `expected` to compare against the live password value. Clear
158
+ // the override when the attribute is removed/empty so we don't keep
159
+ // blocking against a previous user's email.
160
+ if (attrName === 'data-password-policy-value-email') {
161
+ if (newValue) {
162
+ this.#overrides = {
163
+ ...this.#overrides,
164
+ disallowemail: {
165
+ ...this.#overrides?.disallowemail,
166
+ expected: newValue,
167
+ },
168
+ };
169
+ } else if (this.#overrides?.disallowemail) {
170
+ const { disallowemail: _drop, ...rest } = this.#overrides;
171
+ this.#overrides = rest;
172
+ }
173
+ }
132
174
  }
133
175
 
134
176
  this.renderItems(this.#availablePolicies, this.#activePolicies, this.#overrides);
@@ -193,6 +235,7 @@ class RawPolicyValidation extends createBaseClass({ componentName, baseSelector:
193
235
  }
194
236
 
195
237
  const { pattern, message, data, compare } = policy;
238
+ const normalizedCompare = typeof compare === 'string' ? compare.toUpperCase() : compare;
196
239
 
197
240
  if ((!pattern && !compare) || !message) {
198
241
  return results;
@@ -206,9 +249,23 @@ class RawPolicyValidation extends createBaseClass({ componentName, baseSelector:
206
249
  if (pattern) {
207
250
  const exp = new RegExp(interpolateString(pattern, data));
208
251
  validationResult.valid = exp.test(this.value);
252
+ } else if (normalizedCompare === 'STR_NEQ_CI') {
253
+ // Compare the live password against the configured string AND its
254
+ // local-part (before '@'), case-insensitively. Used by the
255
+ // disallowemail policy.
256
+ const expected = (data?.expected ?? '').toLowerCase();
257
+ const actual = (this.value ?? '').toLowerCase();
258
+ if (!expected || !actual) {
259
+ // nothing to compare → mark valid so we don't block the flow
260
+ validationResult.valid = true;
261
+ } else {
262
+ const at = expected.indexOf('@');
263
+ const localPart = at > 0 ? expected.slice(0, at) : expected;
264
+ validationResult.valid = actual !== expected && actual !== localPart;
265
+ }
209
266
  } else if (compare) {
210
267
  validationResult.valid = this.compareValues(
211
- compare,
268
+ normalizedCompare,
212
269
  data?.expected ?? -1,
213
270
  data?.actual ?? -1
214
271
  );
@@ -224,13 +281,18 @@ class RawPolicyValidation extends createBaseClass({ componentName, baseSelector:
224
281
  return !this.validate().some(({ valid }) => valid === false);
225
282
  }
226
283
 
227
- getValidationItemTemplate({ valid, message }) {
284
+ buildValidationItem({ valid, message }) {
228
285
  const status = !this.value ? 'none' : valid;
229
- return `
230
- <li class="item" data-valid="${status}">
231
- <span class="message">${message}</span>
232
- </li>
233
- `;
286
+ const li = document.createElement('li');
287
+ li.className = 'item';
288
+ li.dataset.valid = status;
289
+ const span = document.createElement('span');
290
+ span.className = 'message';
291
+ // `textContent` handles any tenant-configured string in `message` safely
292
+ // (e.g. the disallowedchars list) without needing to escape HTML.
293
+ span.textContent = message ?? '';
294
+ li.appendChild(span);
295
+ return li;
234
296
  }
235
297
 
236
298
  renderItems(availablePolicies, activePolicies) {
@@ -238,7 +300,9 @@ class RawPolicyValidation extends createBaseClass({ componentName, baseSelector:
238
300
  return;
239
301
  }
240
302
 
241
- this.list.innerHTML = this.validate().map(this.getValidationItemTemplate.bind(this)).join('');
303
+ this.list.replaceChildren(
304
+ ...this.validate().map(this.buildValidationItem.bind(this)),
305
+ );
242
306
  }
243
307
 
244
308
  updateLabel(val) {
@@ -18,6 +18,16 @@ import {
18
18
  defaultValueMissingControl,
19
19
  } from './commonControls';
20
20
 
21
+ // HTML-attribute-escape user-supplied values (and JSON blobs) so quotes in
22
+ // e.g. the disallowedchars pattern don't break out of attribute quoting.
23
+ const escapeAttr = (v) =>
24
+ String(v ?? '')
25
+ .replace(/&/g, '&amp;')
26
+ .replace(/"/g, '&quot;')
27
+ .replace(/'/g, '&#39;')
28
+ .replace(/</g, '&lt;')
29
+ .replace(/>/g, '&gt;');
30
+
21
31
  const policyData = [
22
32
  {
23
33
  id: 'minlength',
@@ -60,6 +70,18 @@ const policyData = [
60
70
  message: '1 symbol',
61
71
  pattern: '[^a-zA-Z0-9]',
62
72
  },
73
+ {
74
+ id: 'disallowedchars',
75
+ message: 'Does not contain: {{value}}',
76
+ pattern: `^[^'"]*$`,
77
+ data: { value: `'"` },
78
+ },
79
+ {
80
+ id: 'disallowemail',
81
+ message: 'Does not match your email',
82
+ compare: 'STR_NEQ_CI',
83
+ data: { expected: 'user@example.com' },
84
+ },
63
85
  ];
64
86
 
65
87
  const Template = ({
@@ -113,7 +135,7 @@ const Template = ({
113
135
  data-password-policy-value-minlength="${minLengthDataAttr}"
114
136
  data-password-policy-value-passwordstrength="${passwordStrengthDataAttr}"
115
137
  st-host-direction="${direction ?? ''}"
116
- available-policies='${JSON.stringify(availablePolicies) || ''}'
138
+ available-policies="${escapeAttr(JSON.stringify(availablePolicies) || '')}"
117
139
  active-policies="${activePolicies || ''}"
118
140
  policy-label="${policyLabel || ''}"
119
141
  st-policy-preview-background-color="${stPolicyPreviewBgColor || ''}"
@@ -202,6 +224,8 @@ Default.args = {
202
224
  'number',
203
225
  'nonalphanumeric',
204
226
  'passwordstrength',
227
+ 'disallowedchars',
228
+ 'disallowemail',
205
229
  ],
206
230
  'data-password-policy-value-minlength': '8',
207
231
  'data-password-policy-value-passwordstrength': '2',
@@ -35,8 +35,30 @@ const availablePolicies = [
35
35
  message: '1 symbol',
36
36
  pattern: '[^a-zA-Z0-9]',
37
37
  },
38
+ {
39
+ id: 'disallowedchars',
40
+ message: 'Does not contain: {{value}}',
41
+ pattern: `^[^'"]*$`,
42
+ data: { value: `'"` },
43
+ },
44
+ {
45
+ id: 'disallowemail',
46
+ message: 'Does not match your email',
47
+ compare: 'STR_NEQ_CI',
48
+ data: { expected: 'user@example.com' },
49
+ },
38
50
  ];
39
51
 
52
+ // HTML-attribute-escape any user-supplied string so the template still parses
53
+ // when the value itself contains quotes (eg. the default disallowed-chars `'"`).
54
+ const escapeAttr = (v) =>
55
+ String(v ?? '')
56
+ .replace(/&/g, '&amp;')
57
+ .replace(/"/g, '&quot;')
58
+ .replace(/'/g, '&#39;')
59
+ .replace(/</g, '&lt;')
60
+ .replace(/>/g, '&gt;');
61
+
40
62
  const Template = ({
41
63
  value,
42
64
  label,
@@ -48,7 +70,11 @@ const Template = ({
48
70
  useAnyLetter,
49
71
  useSymbol,
50
72
  useMinLength,
73
+ useDisallowedChars,
74
+ useDisallowEmail,
51
75
  minLengthValue,
76
+ disallowedCharsValue,
77
+ emailValue,
52
78
  }) => {
53
79
  const policies = [
54
80
  useMinLength ? 'minlength' : null,
@@ -57,16 +83,20 @@ const Template = ({
57
83
  useAnyLetter ? 'anyletter' : null,
58
84
  useNumber ? 'number' : null,
59
85
  useSymbol ? 'nonalphanumeric' : null,
86
+ useDisallowedChars ? 'disallowedchars' : null,
87
+ useDisallowEmail ? 'disallowemail' : null,
60
88
  ].filter(Boolean);
61
89
 
62
90
  return `
63
91
  <descope-policy-validation
64
- label="${label}"
65
- value="${value}"
66
- data='${JSON.stringify(data) || ''}'
92
+ label="${escapeAttr(label)}"
93
+ value="${escapeAttr(value)}"
94
+ data="${escapeAttr(JSON.stringify(data) || '')}"
67
95
  active-policies="${policies || ''}"
68
- data-password-policy-value-minlength="${minLengthValue || ''}"
69
- st-host-direction="${direction ?? ''}"
96
+ data-password-policy-value-minlength="${escapeAttr(minLengthValue)}"
97
+ data-password-policy-value-disallowedchars="${escapeAttr(disallowedCharsValue)}"
98
+ data-password-policy-value-email="${escapeAttr(emailValue)}"
99
+ st-host-direction="${escapeAttr(direction)}"
70
100
  ></descope-policy-validation>
71
101
  `;
72
102
  };
@@ -96,5 +126,9 @@ Default.args = {
96
126
  useAnyLetter: false,
97
127
  useSymbol: false,
98
128
  useMinLength: false,
129
+ useDisallowedChars: false,
130
+ useDisallowEmail: false,
99
131
  minLengthValue: undefined,
132
+ disallowedCharsValue: `'"`,
133
+ emailValue: 'user@example.com',
100
134
  };