@descope/flow-components 3.9.2 → 3.10.1

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.
@@ -5,7 +5,7 @@
5
5
  "name": "flowComponents",
6
6
  "type": "app",
7
7
  "buildInfo": {
8
- "buildVersion": "3.9.2",
8
+ "buildVersion": "3.10.1",
9
9
  "buildName": "@descope/flow-components"
10
10
  },
11
11
  "remoteEntry": {
@@ -5,7 +5,7 @@
5
5
  "name": "flowComponents",
6
6
  "type": "app",
7
7
  "buildInfo": {
8
- "buildVersion": "3.9.2",
8
+ "buildVersion": "3.10.1",
9
9
  "buildName": "@descope/flow-components"
10
10
  },
11
11
  "remoteEntry": {
package/dist/index.cjs.js CHANGED
@@ -97739,6 +97739,8 @@ descope-enriched-text {
97739
97739
  'data-password-policy-value-minlength',
97740
97740
  'data-password-policy-value-passwordstrength',
97741
97741
  'data-password-policy-actual-passwordstrength',
97742
+ 'data-password-policy-value-disallowedchars',
97743
+ 'data-password-policy-value-email',
97742
97744
  ];
97743
97745
  const dataAttrs = ['data', 'active-policies', 'overrides', ...overrideAttrs];
97744
97746
  const policyAttrs = ['label', 'value', ...dataAttrs];
@@ -97857,6 +97859,46 @@ descope-enriched-text {
97857
97859
  };
97858
97860
  }
97859
97861
  }
97862
+
97863
+ // disallowedchars: this stores the configured char list in
97864
+ // overrides.disallowedchars.value so the message can interpolate
97865
+ // `{{value}}`. The regex pattern itself is built upstream (orchestrator
97866
+ // or caller) and shipped on the policy entry — it is not derived from
97867
+ // this override. When the attribute is cleared, drop the override so
97868
+ // the panel doesn't keep stale data.
97869
+ if (attrName === 'data-password-policy-value-disallowedchars') {
97870
+ if (newValue) {
97871
+ this.#overrides = {
97872
+ ...this.#overrides,
97873
+ disallowedchars: {
97874
+ ...this.#overrides?.disallowedchars,
97875
+ value: newValue,
97876
+ },
97877
+ };
97878
+ } else if (this.#overrides?.disallowedchars) {
97879
+ const { disallowedchars: _drop, ...rest } = this.#overrides;
97880
+ this.#overrides = rest;
97881
+ }
97882
+ }
97883
+
97884
+ // disallowemail: stash the user's email so the STR_NEQ_CI comparator
97885
+ // has an `expected` to compare against the live password value. Clear
97886
+ // the override when the attribute is removed/empty so we don't keep
97887
+ // blocking against a previous user's email.
97888
+ if (attrName === 'data-password-policy-value-email') {
97889
+ if (newValue) {
97890
+ this.#overrides = {
97891
+ ...this.#overrides,
97892
+ disallowemail: {
97893
+ ...this.#overrides?.disallowemail,
97894
+ expected: newValue,
97895
+ },
97896
+ };
97897
+ } else if (this.#overrides?.disallowemail) {
97898
+ const { disallowemail: _drop, ...rest } = this.#overrides;
97899
+ this.#overrides = rest;
97900
+ }
97901
+ }
97860
97902
  }
97861
97903
 
97862
97904
  this.renderItems(this.#availablePolicies, this.#activePolicies, this.#overrides);
@@ -97921,6 +97963,7 @@ descope-enriched-text {
97921
97963
  }
97922
97964
 
97923
97965
  const { pattern, message, data, compare } = policy;
97966
+ const normalizedCompare = typeof compare === 'string' ? compare.toUpperCase() : compare;
97924
97967
 
97925
97968
  if ((!pattern && !compare) || !message) {
97926
97969
  return results;
@@ -97934,9 +97977,23 @@ descope-enriched-text {
97934
97977
  if (pattern) {
97935
97978
  const exp = new RegExp(interpolateString(pattern, data));
97936
97979
  validationResult.valid = exp.test(this.value);
97980
+ } else if (normalizedCompare === 'STR_NEQ_CI') {
97981
+ // Compare the live password against the configured string AND its
97982
+ // local-part (before '@'), case-insensitively. Used by the
97983
+ // disallowemail policy.
97984
+ const expected = (data?.expected ?? '').toLowerCase();
97985
+ const actual = (this.value ?? '').toLowerCase();
97986
+ if (!expected || !actual) {
97987
+ // nothing to compare → mark valid so we don't block the flow
97988
+ validationResult.valid = true;
97989
+ } else {
97990
+ const at = expected.indexOf('@');
97991
+ const localPart = at > 0 ? expected.slice(0, at) : expected;
97992
+ validationResult.valid = actual !== expected && actual !== localPart;
97993
+ }
97937
97994
  } else if (compare) {
97938
97995
  validationResult.valid = this.compareValues(
97939
- compare,
97996
+ normalizedCompare,
97940
97997
  data?.expected ?? -1,
97941
97998
  data?.actual ?? -1
97942
97999
  );
@@ -97952,13 +98009,18 @@ descope-enriched-text {
97952
98009
  return !this.validate().some(({ valid }) => valid === false);
97953
98010
  }
97954
98011
 
97955
- getValidationItemTemplate({ valid, message }) {
98012
+ buildValidationItem({ valid, message }) {
97956
98013
  const status = !this.value ? 'none' : valid;
97957
- return `
97958
- <li class="item" data-valid="${status}">
97959
- <span class="message">${message}</span>
97960
- </li>
97961
- `;
98014
+ const li = document.createElement('li');
98015
+ li.className = 'item';
98016
+ li.dataset.valid = status;
98017
+ const span = document.createElement('span');
98018
+ span.className = 'message';
98019
+ // `textContent` handles any tenant-configured string in `message` safely
98020
+ // (e.g. the disallowedchars list) without needing to escape HTML.
98021
+ span.textContent = message ?? '';
98022
+ li.appendChild(span);
98023
+ return li;
97962
98024
  }
97963
98025
 
97964
98026
  renderItems(availablePolicies, activePolicies) {
@@ -97966,7 +98028,9 @@ descope-enriched-text {
97966
98028
  return;
97967
98029
  }
97968
98030
 
97969
- this.list.innerHTML = this.validate().map(this.getValidationItemTemplate.bind(this)).join('');
98031
+ this.list.replaceChildren(
98032
+ ...this.validate().map(this.buildValidationItem.bind(this)),
98033
+ );
97970
98034
  }
97971
98035
 
97972
98036
  updateLabel(val) {
@@ -98067,6 +98131,8 @@ descope-enriched-text {
98067
98131
  'available-policies',
98068
98132
  'data-password-policy-value-minlength',
98069
98133
  'data-password-policy-value-passwordstrength',
98134
+ 'data-password-policy-value-disallowedchars',
98135
+ 'data-password-policy-value-email',
98070
98136
  'label-type',
98071
98137
  'manual-visibility-toggle',
98072
98138
  ],
@@ -106481,8 +106547,6 @@ descope-enriched-text {
106481
106547
  `;
106482
106548
 
106483
106549
  this.defaultSlot = this.shadowRoot.querySelector('slot:not([name])');
106484
-
106485
- this.#syncComponentState();
106486
106550
  }
106487
106551
 
106488
106552
  init() {
@@ -106540,7 +106604,7 @@ descope-enriched-text {
106540
106604
  // To support conditional components in flow, we need to sync the 'hidden' className to the root of the component.
106541
106605
  // Ideally, this would happen in the SDK, but we resolved to this patch to fix the issue without forcing users to update SDKs.
106542
106606
  #syncComponentState() {
106543
- const hasHidden = this.#anchor.classList.contains('hidden');
106607
+ const hasHidden = this.#anchor?.classList?.contains('hidden');
106544
106608
  this.classList.toggle('hidden', hasHidden);
106545
106609
  }
106546
106610
 
@@ -106605,6 +106669,7 @@ descope-enriched-text {
106605
106669
  // empty host rather than reserving its layout box.
106606
106670
  #onAnchorChanged() {
106607
106671
  this.toggleAttribute('has-anchor', !!this.#anchor);
106672
+ this.#syncComponentState();
106608
106673
  }
106609
106674
  }
106610
106675
 
@@ -107117,6 +107182,11 @@ const Loader = React__default.default.forwardRef(({ variant, color, ...restProps
107117
107182
 
107118
107183
  const Logo = React__default.default.forwardRef(({ width, height, ...props }, ref) => (React__default.default.createElement("descope-logo", { "st-width": width, "st-height": height, ref: ref, ...props })));
107119
107184
 
107185
+ // Escape characters that have a special meaning inside a JS regex character
107186
+ // class so they appear as literals (used to build the disallowedchars regex
107187
+ // from a tenant-configured chars list).
107188
+ const escapeRegexClassChars = (s) => s.replace(/[\\\]\-^]/g, '\\$&');
107189
+ const buildDisallowedCharsPattern = (chars) => chars ? `^[^${escapeRegexClassChars(chars)}]*$` : '';
107120
107190
  const defaultProps = {
107121
107191
  'has-validation': false,
107122
107192
  'policy-label': 'Passwords must have:',
@@ -107134,6 +107204,7 @@ const defaultProps = {
107134
107204
  'data-password-policy-pattern-nonalphanumeric': '[^a-zA-Z0-9]',
107135
107205
  // non-user-editable comparisons
107136
107206
  'data-password-policy-compare-passwordstrength': 'GTE',
107207
+ 'data-password-policy-compare-disallowemail': 'STR_NEQ_CI',
107137
107208
  // props to override with data from policy
107138
107209
  'data-password-policy-value-minlength': '8',
107139
107210
  // translatable props
@@ -107144,30 +107215,50 @@ const defaultProps = {
107144
107215
  'data-password-policy-message-number': '1 number',
107145
107216
  'data-password-policy-message-nonalphanumeric': '1 symbol',
107146
107217
  'data-password-policy-message-passwordstrength': 'Password strength',
107218
+ 'data-password-policy-message-disallowedchars': 'Does not contain: {{value}}',
107219
+ 'data-password-policy-message-disallowemail': 'Does not match your email',
107147
107220
  'st-policy-preview-background-color': 'transparent',
107148
107221
  'st-policy-preview-padding': '0'
107149
107222
  };
107150
107223
  const NewPassword = React__default.default.forwardRef((props, ref) => {
107151
107224
  const mergedProps = { ...defaultProps, ...props };
107225
+ // Dynamic key lookups (`data-password-policy-${kind}-${id}`) below sit
107226
+ // outside the typed Props surface, so we read them through a narrow
107227
+ // Record alias rather than widening the whole mergedProps to `any`.
107228
+ const mergedPropsByKey = mergedProps;
107152
107229
  /* this logic is cloned in the orchestration service (context.go file)
107153
- * make sure to update both places when changing it
107154
- */
107155
- const availablePolicies = [
107230
+ * make sure to update both places when changing it
107231
+ */
107232
+ const availablePolicies = JSON.stringify([
107156
107233
  'minlength',
107157
107234
  'lowercase',
107158
107235
  'uppercase',
107159
107236
  'anyletter',
107160
107237
  'number',
107161
107238
  'nonalphanumeric',
107162
- 'passwordstrength'
107163
- ].map((id) => ({
107164
- id,
107165
- message: mergedProps[`data-password-policy-message-${id}`],
107166
- pattern: mergedProps[`data-password-policy-pattern-${id}`],
107167
- compare: mergedProps[`data-password-policy-compare-${id}`],
107168
- data: mergedProps[`data-password-policy-data-${id}`]
107239
+ 'passwordstrength',
107240
+ 'disallowedchars',
107241
+ 'disallowemail'
107242
+ ].map((id) => {
107243
+ const entry = {
107244
+ id,
107245
+ message: mergedPropsByKey[`data-password-policy-message-${id}`],
107246
+ pattern: mergedPropsByKey[`data-password-policy-pattern-${id}`],
107247
+ compare: mergedPropsByKey[`data-password-policy-compare-${id}`],
107248
+ data: mergedPropsByKey[`data-password-policy-data-${id}`]
107249
+ };
107250
+ // disallowedchars: the pattern is `^[^<escaped chars>]*$`, built from
107251
+ // the configured chars list. Done here (rather than as a static
107252
+ // pattern with a `{{value}}` placeholder) so chars like `]`, `^`, `-`,
107253
+ // `\` are escaped instead of breaking the character class.
107254
+ if (id === 'disallowedchars') {
107255
+ const chars = mergedPropsByKey['data-password-policy-value-disallowedchars'] || '';
107256
+ entry.pattern = buildDisallowedCharsPattern(chars);
107257
+ entry.data = { value: chars };
107258
+ }
107259
+ return entry;
107169
107260
  }));
107170
- return (React__default.default.createElement("descope-new-password", { ...mergedProps, ref: ref, "available-policies": JSON.stringify(availablePolicies || []) }));
107261
+ return (React__default.default.createElement("descope-new-password", { ...mergedProps, ref: ref, "available-policies": availablePolicies }));
107171
107262
  });
107172
107263
 
107173
107264
  const PhoneCountrySelection = React__default.default.forwardRef((props, ref) => React__default.default.createElement("descope-phone-field", { ...props, ref: ref }));
package/dist/index.d.ts CHANGED
@@ -355,7 +355,11 @@ type Props$H = {
355
355
  'data-password-policy-message-number'?: string;
356
356
  'data-password-policy-message-nonalphanumeric'?: string;
357
357
  'data-password-policy-message-passwordstrength'?: string;
358
+ 'data-password-policy-message-disallowedchars'?: string;
359
+ 'data-password-policy-message-disallowemail'?: string;
358
360
  'data-password-policy-value-minlength'?: string;
361
+ 'data-password-policy-value-disallowedchars'?: string;
362
+ 'data-password-policy-value-email'?: string;
359
363
  'st-policy-preview-background-color'?: string;
360
364
  'st-policy-preview-padding'?: string;
361
365
  };
package/dist/index.esm.js CHANGED
@@ -283,6 +283,11 @@ const Loader = React.forwardRef(({ variant, color, ...restProps }, ref) => {
283
283
 
284
284
  const Logo = React.forwardRef(({ width, height, ...props }, ref) => (React.createElement("descope-logo", { "st-width": width, "st-height": height, ref: ref, ...props })));
285
285
 
286
+ // Escape characters that have a special meaning inside a JS regex character
287
+ // class so they appear as literals (used to build the disallowedchars regex
288
+ // from a tenant-configured chars list).
289
+ const escapeRegexClassChars = (s) => s.replace(/[\\\]\-^]/g, '\\$&');
290
+ const buildDisallowedCharsPattern = (chars) => chars ? `^[^${escapeRegexClassChars(chars)}]*$` : '';
286
291
  const defaultProps = {
287
292
  'has-validation': false,
288
293
  'policy-label': 'Passwords must have:',
@@ -300,6 +305,7 @@ const defaultProps = {
300
305
  'data-password-policy-pattern-nonalphanumeric': '[^a-zA-Z0-9]',
301
306
  // non-user-editable comparisons
302
307
  'data-password-policy-compare-passwordstrength': 'GTE',
308
+ 'data-password-policy-compare-disallowemail': 'STR_NEQ_CI',
303
309
  // props to override with data from policy
304
310
  'data-password-policy-value-minlength': '8',
305
311
  // translatable props
@@ -310,30 +316,50 @@ const defaultProps = {
310
316
  'data-password-policy-message-number': '1 number',
311
317
  'data-password-policy-message-nonalphanumeric': '1 symbol',
312
318
  'data-password-policy-message-passwordstrength': 'Password strength',
319
+ 'data-password-policy-message-disallowedchars': 'Does not contain: {{value}}',
320
+ 'data-password-policy-message-disallowemail': 'Does not match your email',
313
321
  'st-policy-preview-background-color': 'transparent',
314
322
  'st-policy-preview-padding': '0'
315
323
  };
316
324
  const NewPassword = React.forwardRef((props, ref) => {
317
325
  const mergedProps = { ...defaultProps, ...props };
326
+ // Dynamic key lookups (`data-password-policy-${kind}-${id}`) below sit
327
+ // outside the typed Props surface, so we read them through a narrow
328
+ // Record alias rather than widening the whole mergedProps to `any`.
329
+ const mergedPropsByKey = mergedProps;
318
330
  /* this logic is cloned in the orchestration service (context.go file)
319
- * make sure to update both places when changing it
320
- */
321
- const availablePolicies = [
331
+ * make sure to update both places when changing it
332
+ */
333
+ const availablePolicies = JSON.stringify([
322
334
  'minlength',
323
335
  'lowercase',
324
336
  'uppercase',
325
337
  'anyletter',
326
338
  'number',
327
339
  'nonalphanumeric',
328
- 'passwordstrength'
329
- ].map((id) => ({
330
- id,
331
- message: mergedProps[`data-password-policy-message-${id}`],
332
- pattern: mergedProps[`data-password-policy-pattern-${id}`],
333
- compare: mergedProps[`data-password-policy-compare-${id}`],
334
- data: mergedProps[`data-password-policy-data-${id}`]
340
+ 'passwordstrength',
341
+ 'disallowedchars',
342
+ 'disallowemail'
343
+ ].map((id) => {
344
+ const entry = {
345
+ id,
346
+ message: mergedPropsByKey[`data-password-policy-message-${id}`],
347
+ pattern: mergedPropsByKey[`data-password-policy-pattern-${id}`],
348
+ compare: mergedPropsByKey[`data-password-policy-compare-${id}`],
349
+ data: mergedPropsByKey[`data-password-policy-data-${id}`]
350
+ };
351
+ // disallowedchars: the pattern is `^[^<escaped chars>]*$`, built from
352
+ // the configured chars list. Done here (rather than as a static
353
+ // pattern with a `{{value}}` placeholder) so chars like `]`, `^`, `-`,
354
+ // `\` are escaped instead of breaking the character class.
355
+ if (id === 'disallowedchars') {
356
+ const chars = mergedPropsByKey['data-password-policy-value-disallowedchars'] || '';
357
+ entry.pattern = buildDisallowedCharsPattern(chars);
358
+ entry.data = { value: chars };
359
+ }
360
+ return entry;
335
361
  }));
336
- return (React.createElement("descope-new-password", { ...mergedProps, ref: ref, "available-policies": JSON.stringify(availablePolicies || []) }));
362
+ return (React.createElement("descope-new-password", { ...mergedProps, ref: ref, "available-policies": availablePolicies }));
337
363
  });
338
364
 
339
365
  const PhoneCountrySelection = React.forwardRef((props, ref) => React.createElement("descope-phone-field", { ...props, ref: ref }));
@@ -22,7 +22,11 @@ export type Props = {
22
22
  'data-password-policy-message-number'?: string;
23
23
  'data-password-policy-message-nonalphanumeric'?: string;
24
24
  'data-password-policy-message-passwordstrength'?: string;
25
+ 'data-password-policy-message-disallowedchars'?: string;
26
+ 'data-password-policy-message-disallowemail'?: string;
25
27
  'data-password-policy-value-minlength'?: string;
28
+ 'data-password-policy-value-disallowedchars'?: string;
29
+ 'data-password-policy-value-email'?: string;
26
30
  'st-policy-preview-background-color'?: string;
27
31
  'st-policy-preview-padding'?: string;
28
32
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@descope/flow-components",
3
- "version": "3.9.2",
3
+ "version": "3.10.1",
4
4
  "description": "",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -97,7 +97,7 @@
97
97
  "webpack-subresource-integrity": "5.2.0-rc.1"
98
98
  },
99
99
  "dependencies": {
100
- "@descope/web-components-ui": "3.9.2"
100
+ "@descope/web-components-ui": "3.10.1"
101
101
  },
102
102
  "peerDependencies": {
103
103
  "react": ">= 18"