@ory/elements-react 1.0.0-rc.3 → 1.0.0-rc.5

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/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 1.0.0-rc.5 (2025-05-16)
2
+
3
+ ### 🩹 Fixes
4
+
5
+ - properly detect OIDC account linking ([#538](https://github.com/ory/elements/pull/538))
6
+
7
+ ### ❤️ Thank You
8
+
9
+ - Jonas Hungershausen
10
+
11
+ ## 1.0.0-rc.4 (2025-05-15)
12
+
13
+ ### 🩹 Fixes
14
+
15
+ - properly handle missing fields in OIDC registration ([#534](https://github.com/ory/elements/pull/534))
16
+ - incorrect if branching in handle error ([#533](https://github.com/ory/elements/pull/533))
17
+ - re-enable method chooser on mfa method active screens ([#530](https://github.com/ory/elements/pull/530))
18
+
19
+ ### ❤️ Thank You
20
+
21
+ - hackerman @aeneasr
22
+ - Jonas Hungershausen
23
+
1
24
  ## 1.0.0-rc.3 (2025-05-09)
2
25
 
3
26
  ### 🩹 Fixes
package/DEVELOPMENT.md CHANGED
@@ -73,7 +73,8 @@ Usage:
73
73
  ```bash
74
74
  ./scripts/release.sh <project> <tag>
75
75
 
76
- ./scripts/release.sh @ory/elements-react next
76
+ ./scripts/release.sh @ory/elements-react next # next release preview (-next.X)
77
+ ./scripts/release.sh @ory/elements-react rc # release candidate (-rc.X)
77
78
  ```
78
79
 
79
80
  The script asks the user before executing each steps. Please double check
package/dist/index.d.mts CHANGED
@@ -592,6 +592,8 @@ type FormStateAction = {
592
592
  } | {
593
593
  type: "action_select_method";
594
594
  method: UiNodeGroupEnum;
595
+ } | {
596
+ type: "action_clear_active_method";
595
597
  };
596
598
 
597
599
  /**
package/dist/index.d.ts CHANGED
@@ -592,6 +592,8 @@ type FormStateAction = {
592
592
  } | {
593
593
  type: "action_select_method";
594
594
  method: UiNodeGroupEnum;
595
+ } | {
596
+ type: "action_clear_active_method";
595
597
  };
596
598
 
597
599
  /**
package/dist/index.js CHANGED
@@ -322,9 +322,9 @@ function parseStateFromFlow(flow) {
322
322
  return { current: "method_active", method: "code" };
323
323
  } else if (methodWithMessage) {
324
324
  return { current: "method_active", method: methodWithMessage.group };
325
- } else if (flow.flow.active && !["default", "identifier_first", "oidc", "saml"].includes(
326
- flow.flow.active
327
- )) {
325
+ } else if ((_a = flow.flow.ui.messages) == null ? void 0 : _a.some((m) => m.id === 1010016)) {
326
+ return { current: "select_method" };
327
+ } else if (flow.flow.active && !["default", "identifier_first"].includes(flow.flow.active)) {
328
328
  return { current: "method_active", method: flow.flow.active };
329
329
  } else if (isChoosingMethod(flow)) {
330
330
  const authMethods = nodesToAuthMethodGroups(flow.flow.ui.nodes);
@@ -332,8 +332,6 @@ function parseStateFromFlow(flow) {
332
332
  return { current: "method_active", method: authMethods[0] };
333
333
  }
334
334
  return { current: "select_method" };
335
- } else if ((_a = flow.flow.ui.messages) == null ? void 0 : _a.some((m) => m.id === 1010016)) {
336
- return { current: "select_method" };
337
335
  }
338
336
  return { current: "provide_identifier" };
339
337
  }
@@ -362,14 +360,20 @@ function useFormStateReducer(flow) {
362
360
  const formStateReducer = (state, action2) => {
363
361
  switch (action2.type) {
364
362
  case "action_flow_update": {
365
- if (selectedMethod)
363
+ if (selectedMethod) {
366
364
  return { current: "method_active", method: selectedMethod };
365
+ }
367
366
  return parseStateFromFlow(action2.flow);
368
367
  }
369
368
  case "action_select_method": {
370
369
  setSelectedMethod(action2.method);
371
370
  return { current: "method_active", method: action2.method };
372
371
  }
372
+ case "action_clear_active_method": {
373
+ return {
374
+ current: "select_method"
375
+ };
376
+ }
373
377
  }
374
378
  return state;
375
379
  };
@@ -678,6 +682,134 @@ function replaceWindowFlowId(flow) {
678
682
  url.searchParams.set("flow", flow);
679
683
  window.location.href = url.toString();
680
684
  }
685
+ function isGenericErrorResponse(response) {
686
+ return typeof response === "object" && !!response && "error" in response && typeof response.error === "object" && !!response.error && "id" in response.error;
687
+ }
688
+ function isNeedsPrivilegedSessionError(response) {
689
+ return isGenericErrorResponse(response) && response.error.id === "session_refresh_required";
690
+ }
691
+ function isSelfServiceFlowExpiredError(response) {
692
+ return isGenericErrorResponse(response) && response.error.id === "self_service_flow_expired";
693
+ }
694
+ function isBrowserLocationChangeRequired(response) {
695
+ return isGenericErrorResponse(response) && isGenericErrorResponse(response) && response.error.id === "browser_location_change_required";
696
+ }
697
+ function isAddressNotVerified(response) {
698
+ return isGenericErrorResponse(response) && response.error.id === "session_verified_address_required";
699
+ }
700
+ function isCsrfError(response) {
701
+ return isGenericErrorResponse(response) && response.error.id === "security_csrf_violation";
702
+ }
703
+ var isResponseError = (err) => {
704
+ if (err instanceof clientFetch.ResponseError) {
705
+ return true;
706
+ }
707
+ return typeof err === "object" && !!err && "name" in err && err.name === "ResponseError";
708
+ };
709
+ var isFetchError = (err) => {
710
+ return err instanceof clientFetch.FetchError;
711
+ };
712
+
713
+ // src/util/sdk-helpers/urlHelpers.ts
714
+ var verificationUrl = (config) => config.sdk.url + "/self-service/verification/browser";
715
+ var handleFlowError = (opts) => async (err) => {
716
+ var _a;
717
+ if (!isResponseError(err)) {
718
+ if (isFetchError(err)) {
719
+ throw new clientFetch.FetchError(
720
+ err,
721
+ "Unable to call the API endpoint. Ensure that CORS is set up correctly and that you have provided a valid SDK URL to Ory Elements."
722
+ );
723
+ }
724
+ throw err;
725
+ }
726
+ const contentType = err.response.headers.get("content-type") || "";
727
+ if (contentType.includes("application/json")) {
728
+ const body = await toBody(err.response);
729
+ if (isSelfServiceFlowExpiredError(body)) {
730
+ opts.onRestartFlow(body.use_flow_id);
731
+ return;
732
+ } else if (isAddressNotVerified(body)) {
733
+ for (const continueWith of ((_a = body.error.details) == null ? void 0 : _a.continue_with) || []) {
734
+ if (continueWith.action === "show_verification_ui" && continueWith.flow.url) {
735
+ opts.onRedirect(continueWith.flow.url, true);
736
+ return;
737
+ }
738
+ }
739
+ opts.onRedirect(verificationUrl(opts.config), true);
740
+ return;
741
+ } else if (isBrowserLocationChangeRequired(body) && body.redirect_browser_to) {
742
+ opts.onRedirect(body.redirect_browser_to, true);
743
+ return;
744
+ } else if (isNeedsPrivilegedSessionError(body) && body.redirect_browser_to) {
745
+ opts.onRedirect(body.redirect_browser_to, true);
746
+ return;
747
+ } else if (isCsrfError(body)) {
748
+ opts.onRestartFlow();
749
+ return;
750
+ }
751
+ switch (err.response.status) {
752
+ case 404:
753
+ opts.onRestartFlow();
754
+ return;
755
+ case 410:
756
+ opts.onRestartFlow();
757
+ return;
758
+ case 400:
759
+ return opts.onValidationError(
760
+ await err.response.json()
761
+ );
762
+ case 403:
763
+ opts.onRestartFlow();
764
+ return;
765
+ case 422: {
766
+ throw new clientFetch.ResponseError(
767
+ err.response,
768
+ "The API returned an error code indicating a required redirect, but the SDK is outdated and does not know how to handle the action. Received response: " + await err.response.json()
769
+ );
770
+ }
771
+ }
772
+ throw new clientFetch.ResponseError(
773
+ err.response,
774
+ "The Ory API endpoint returned a response code the SDK does not know how to handle. Please check the network tab for more information. Received response: " + await err.response.json()
775
+ );
776
+ } else if (
777
+ // Not a JSON response? If it's a text response we will return an error informing the user that the response is not JSON.
778
+ contentType.includes("text/") || contentType.includes("html") || contentType.includes("xml")
779
+ ) {
780
+ await logResponseError(err.response, true);
781
+ throw new clientFetch.ResponseError(
782
+ err.response,
783
+ `The Ory API endpoint returned an unexpected HTML or text response. Check your console output for details.`
784
+ );
785
+ }
786
+ await logResponseError(err.response, false);
787
+ throw new clientFetch.ResponseError(
788
+ err.response,
789
+ "The Ory API endpoint returned unexpected content type `" + contentType + "`. Check your console output for details."
790
+ );
791
+ };
792
+ async function toBody(response) {
793
+ try {
794
+ return await response.clone().json();
795
+ } catch (e) {
796
+ await logResponseError(response, true, [e]);
797
+ throw new clientFetch.ResponseError(
798
+ response,
799
+ "Unable to decode API response using JSON."
800
+ );
801
+ }
802
+ }
803
+ async function logResponseError(response, printBody, wrap) {
804
+ console.error("Unable to decode API response", {
805
+ response: {
806
+ status: response.status,
807
+ headers: Object.fromEntries(response.headers.entries()),
808
+ body: printBody ? await response.clone().text() : void 0
809
+ },
810
+ errors: wrap
811
+ });
812
+ }
681
813
 
682
814
  // src/util/onSubmitLogin.ts
683
815
  async function onSubmitLogin({ flow }, config, {
@@ -699,7 +831,7 @@ async function onSubmitLogin({ flow }, config, {
699
831
  window.location.href = // eslint-disable-next-line promise/always-return
700
832
  (_a2 = flow.return_to) != null ? _a2 : config.sdk.url + "/self-service/login/browser";
701
833
  }).catch(
702
- clientFetch.handleFlowError({
834
+ handleFlowError({
703
835
  onRestartFlow: (useFlowId) => {
704
836
  if (useFlowId) {
705
837
  replaceWindowFlowId(useFlowId);
@@ -713,7 +845,8 @@ async function onSubmitLogin({ flow }, config, {
713
845
  flowType: clientFetch.FlowType.Login
714
846
  });
715
847
  },
716
- onRedirect
848
+ onRedirect,
849
+ config
717
850
  })
718
851
  );
719
852
  }
@@ -738,7 +871,7 @@ async function onSubmitRecovery({ flow }, config, {
738
871
  flowType: clientFetch.FlowType.Recovery
739
872
  });
740
873
  }).catch(
741
- clientFetch.handleFlowError({
874
+ handleFlowError({
742
875
  onRestartFlow: (useFlowId) => {
743
876
  if (useFlowId) {
744
877
  replaceWindowFlowId(useFlowId);
@@ -757,7 +890,8 @@ async function onSubmitRecovery({ flow }, config, {
757
890
  });
758
891
  }
759
892
  },
760
- onRedirect
893
+ onRedirect,
894
+ config
761
895
  })
762
896
  );
763
897
  }
@@ -792,7 +926,7 @@ async function onSubmitRegistration({ flow }, config, {
792
926
  }
793
927
  onRedirect(clientFetch.registrationUrl(config), true);
794
928
  }).catch(
795
- clientFetch.handleFlowError({
929
+ handleFlowError({
796
930
  onRestartFlow: (useFlowId) => {
797
931
  if (useFlowId) {
798
932
  replaceWindowFlowId(useFlowId);
@@ -806,7 +940,8 @@ async function onSubmitRegistration({ flow }, config, {
806
940
  flowType: clientFetch.FlowType.Registration
807
941
  });
808
942
  },
809
- onRedirect
943
+ onRedirect,
944
+ config
810
945
  })
811
946
  );
812
947
  }
@@ -831,7 +966,7 @@ async function onSubmitSettings({ flow }, config, {
831
966
  flowType: clientFetch.FlowType.Settings
832
967
  });
833
968
  }).catch(
834
- clientFetch.handleFlowError({
969
+ handleFlowError({
835
970
  onRestartFlow: (useFlowId) => {
836
971
  if (useFlowId) {
837
972
  replaceWindowFlowId(useFlowId);
@@ -845,7 +980,8 @@ async function onSubmitSettings({ flow }, config, {
845
980
  flowType: clientFetch.FlowType.Settings
846
981
  });
847
982
  },
848
- onRedirect
983
+ onRedirect,
984
+ config
849
985
  })
850
986
  ).catch((err) => {
851
987
  if (clientFetch.isResponseError(err)) {
@@ -873,7 +1009,7 @@ async function onSubmitVerification({ flow }, config, {
873
1009
  flowType: clientFetch.FlowType.Verification
874
1010
  })
875
1011
  ).catch(
876
- clientFetch.handleFlowError({
1012
+ handleFlowError({
877
1013
  onRestartFlow: (useFlowId) => {
878
1014
  if (useFlowId) {
879
1015
  replaceWindowFlowId(useFlowId);
@@ -887,7 +1023,8 @@ async function onSubmitVerification({ flow }, config, {
887
1023
  flowType: clientFetch.FlowType.Verification
888
1024
  });
889
1025
  },
890
- onRedirect
1026
+ onRedirect,
1027
+ config
891
1028
  })
892
1029
  );
893
1030
  }
@@ -1144,7 +1281,20 @@ var NodeInput = ({
1144
1281
  case clientFetch.UiNodeInputAttributesTypeEnum.Submit:
1145
1282
  case clientFetch.UiNodeInputAttributesTypeEnum.Button:
1146
1283
  if (isSocial) {
1147
- return null;
1284
+ return /* @__PURE__ */ jsxRuntime.jsx(
1285
+ Node2.OidcButton,
1286
+ {
1287
+ node,
1288
+ attributes: attrs,
1289
+ onClick: () => {
1290
+ setValue(
1291
+ "provider",
1292
+ node.attributes.value
1293
+ );
1294
+ setValue("method", node.group);
1295
+ }
1296
+ }
1297
+ );
1148
1298
  }
1149
1299
  if (isResendNode || isScreenSelectionNode) {
1150
1300
  return null;
@@ -1234,6 +1384,37 @@ var Node = ({ node, onClick }) => {
1234
1384
  }
1235
1385
  return null;
1236
1386
  };
1387
+ function OryTwoStepCardStateMethodActive({
1388
+ formState
1389
+ }) {
1390
+ const { Form } = useComponents();
1391
+ const { flow, flowType, dispatchFormState } = useOryFlow();
1392
+ const { ui } = flow;
1393
+ const nodeSorter = useNodeSorter();
1394
+ const sortNodes = (a, b) => nodeSorter(a, b, { flowType });
1395
+ const groupsToShow = useNodeGroupsWithVisibleNodes(ui.nodes);
1396
+ const finalNodes = getFinalNodes(groupsToShow, formState.method);
1397
+ return /* @__PURE__ */ jsxRuntime.jsxs(OryCard, { children: [
1398
+ /* @__PURE__ */ jsxRuntime.jsx(OryCardHeader, {}),
1399
+ /* @__PURE__ */ jsxRuntime.jsxs(OryCardContent, { children: [
1400
+ /* @__PURE__ */ jsxRuntime.jsx(OryCardValidationMessages, {}),
1401
+ /* @__PURE__ */ jsxRuntime.jsx(
1402
+ OryForm,
1403
+ {
1404
+ "data-testid": `ory/form/methods/local`,
1405
+ onAfterSubmit: handleAfterFormSubmit(dispatchFormState),
1406
+ children: /* @__PURE__ */ jsxRuntime.jsxs(Form.Group, { children: [
1407
+ ui.nodes.filter(
1408
+ (n) => clientFetch.isUiNodeScriptAttributes(n.attributes) || n.group === clientFetch.UiNodeGroupEnum.Default || n.group === clientFetch.UiNodeGroupEnum.Profile
1409
+ ).map((node, k) => /* @__PURE__ */ jsxRuntime.jsx(Node, { node }, k)),
1410
+ finalNodes.sort(sortNodes).map((node, k) => /* @__PURE__ */ jsxRuntime.jsx(Node, { node }, k))
1411
+ ] })
1412
+ }
1413
+ )
1414
+ ] }),
1415
+ /* @__PURE__ */ jsxRuntime.jsx(OryCardFooter, {})
1416
+ ] });
1417
+ }
1237
1418
  function OryFormOidcButtons() {
1238
1419
  const {
1239
1420
  flow: { ui }
@@ -1264,8 +1445,10 @@ function OryFormOidcButtons() {
1264
1445
  }
1265
1446
  function OryFormSocialButtonsForm() {
1266
1447
  const {
1267
- flow: { ui }
1448
+ flow: { ui },
1449
+ formState
1268
1450
  } = useOryFlow();
1451
+ console.log(formState);
1269
1452
  const filteredNodes = ui.nodes.filter(
1270
1453
  (node) => node.group === clientFetch.UiNodeGroupEnum.Saml || node.group === clientFetch.UiNodeGroupEnum.Oidc
1271
1454
  );
@@ -1274,39 +1457,6 @@ function OryFormSocialButtonsForm() {
1274
1457
  }
1275
1458
  return /* @__PURE__ */ jsxRuntime.jsx(OryFormProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(OryForm, { "data-testid": `ory/form/methods/oidc-saml`, children: /* @__PURE__ */ jsxRuntime.jsx(OryFormOidcButtons, {}) }) });
1276
1459
  }
1277
- function OryTwoStepCardStateMethodActive({
1278
- formState
1279
- }) {
1280
- const { Form } = useComponents();
1281
- const { flow, flowType, dispatchFormState } = useOryFlow();
1282
- const { ui } = flow;
1283
- const nodeSorter = useNodeSorter();
1284
- const sortNodes = (a, b) => nodeSorter(a, b, { flowType });
1285
- const groupsToShow = useNodeGroupsWithVisibleNodes(ui.nodes);
1286
- const finalNodes = getFinalNodes(groupsToShow, formState.method);
1287
- const selectedMethodIsSocial = formState.method === clientFetch.UiNodeGroupEnum.Oidc || formState.method === clientFetch.UiNodeGroupEnum.Saml;
1288
- return /* @__PURE__ */ jsxRuntime.jsxs(OryCard, { children: [
1289
- /* @__PURE__ */ jsxRuntime.jsx(OryCardHeader, {}),
1290
- /* @__PURE__ */ jsxRuntime.jsxs(OryCardContent, { children: [
1291
- /* @__PURE__ */ jsxRuntime.jsx(OryCardValidationMessages, {}),
1292
- selectedMethodIsSocial && /* @__PURE__ */ jsxRuntime.jsx(OryFormSocialButtonsForm, {}),
1293
- /* @__PURE__ */ jsxRuntime.jsx(
1294
- OryForm,
1295
- {
1296
- "data-testid": `ory/form/methods/local`,
1297
- onAfterSubmit: handleAfterFormSubmit(dispatchFormState),
1298
- children: /* @__PURE__ */ jsxRuntime.jsxs(Form.Group, { children: [
1299
- ui.nodes.filter(
1300
- (n) => clientFetch.isUiNodeScriptAttributes(n.attributes) || n.group === clientFetch.UiNodeGroupEnum.Default || n.group === clientFetch.UiNodeGroupEnum.Profile
1301
- ).map((node, k) => /* @__PURE__ */ jsxRuntime.jsx(Node, { node }, k)),
1302
- finalNodes.sort(sortNodes).map((node, k) => /* @__PURE__ */ jsxRuntime.jsx(Node, { node }, k))
1303
- ] })
1304
- }
1305
- )
1306
- ] }),
1307
- /* @__PURE__ */ jsxRuntime.jsx(OryCardFooter, {})
1308
- ] });
1309
- }
1310
1460
  function OryTwoStepCardStateProvideIdentifier() {
1311
1461
  const { Form, Card } = useComponents();
1312
1462
  const { flowType, flow, dispatchFormState } = useOryFlow();