@descope/flow-components 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.
@@ -5,7 +5,7 @@
5
5
  "name": "flowComponents",
6
6
  "type": "app",
7
7
  "buildInfo": {
8
- "buildVersion": "3.9.2",
8
+ "buildVersion": "3.10.0",
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.0",
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
  ],
@@ -107117,6 +107183,11 @@ const Loader = React__default.default.forwardRef(({ variant, color, ...restProps
107117
107183
 
107118
107184
  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
107185
 
107186
+ // Escape characters that have a special meaning inside a JS regex character
107187
+ // class so they appear as literals (used to build the disallowedchars regex
107188
+ // from a tenant-configured chars list).
107189
+ const escapeRegexClassChars = (s) => s.replace(/[\\\]\-^]/g, '\\$&');
107190
+ const buildDisallowedCharsPattern = (chars) => chars ? `^[^${escapeRegexClassChars(chars)}]*$` : '';
107120
107191
  const defaultProps = {
107121
107192
  'has-validation': false,
107122
107193
  'policy-label': 'Passwords must have:',
@@ -107134,6 +107205,7 @@ const defaultProps = {
107134
107205
  'data-password-policy-pattern-nonalphanumeric': '[^a-zA-Z0-9]',
107135
107206
  // non-user-editable comparisons
107136
107207
  'data-password-policy-compare-passwordstrength': 'GTE',
107208
+ 'data-password-policy-compare-disallowemail': 'STR_NEQ_CI',
107137
107209
  // props to override with data from policy
107138
107210
  'data-password-policy-value-minlength': '8',
107139
107211
  // translatable props
@@ -107144,30 +107216,50 @@ const defaultProps = {
107144
107216
  'data-password-policy-message-number': '1 number',
107145
107217
  'data-password-policy-message-nonalphanumeric': '1 symbol',
107146
107218
  'data-password-policy-message-passwordstrength': 'Password strength',
107219
+ 'data-password-policy-message-disallowedchars': 'Does not contain: {{value}}',
107220
+ 'data-password-policy-message-disallowemail': 'Does not match your email',
107147
107221
  'st-policy-preview-background-color': 'transparent',
107148
107222
  'st-policy-preview-padding': '0'
107149
107223
  };
107150
107224
  const NewPassword = React__default.default.forwardRef((props, ref) => {
107151
107225
  const mergedProps = { ...defaultProps, ...props };
107226
+ // Dynamic key lookups (`data-password-policy-${kind}-${id}`) below sit
107227
+ // outside the typed Props surface, so we read them through a narrow
107228
+ // Record alias rather than widening the whole mergedProps to `any`.
107229
+ const mergedPropsByKey = mergedProps;
107152
107230
  /* 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 = [
107231
+ * make sure to update both places when changing it
107232
+ */
107233
+ const availablePolicies = JSON.stringify([
107156
107234
  'minlength',
107157
107235
  'lowercase',
107158
107236
  'uppercase',
107159
107237
  'anyletter',
107160
107238
  'number',
107161
107239
  '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}`]
107240
+ 'passwordstrength',
107241
+ 'disallowedchars',
107242
+ 'disallowemail'
107243
+ ].map((id) => {
107244
+ const entry = {
107245
+ id,
107246
+ message: mergedPropsByKey[`data-password-policy-message-${id}`],
107247
+ pattern: mergedPropsByKey[`data-password-policy-pattern-${id}`],
107248
+ compare: mergedPropsByKey[`data-password-policy-compare-${id}`],
107249
+ data: mergedPropsByKey[`data-password-policy-data-${id}`]
107250
+ };
107251
+ // disallowedchars: the pattern is `^[^<escaped chars>]*$`, built from
107252
+ // the configured chars list. Done here (rather than as a static
107253
+ // pattern with a `{{value}}` placeholder) so chars like `]`, `^`, `-`,
107254
+ // `\` are escaped instead of breaking the character class.
107255
+ if (id === 'disallowedchars') {
107256
+ const chars = mergedPropsByKey['data-password-policy-value-disallowedchars'] || '';
107257
+ entry.pattern = buildDisallowedCharsPattern(chars);
107258
+ entry.data = { value: chars };
107259
+ }
107260
+ return entry;
107169
107261
  }));
107170
- return (React__default.default.createElement("descope-new-password", { ...mergedProps, ref: ref, "available-policies": JSON.stringify(availablePolicies || []) }));
107262
+ return (React__default.default.createElement("descope-new-password", { ...mergedProps, ref: ref, "available-policies": availablePolicies }));
107171
107263
  });
107172
107264
 
107173
107265
  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.0",
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.0"
101
101
  },
102
102
  "peerDependencies": {
103
103
  "react": ">= 18"