diff --git a/examples/nextjs-app-router/README.md b/examples/nextjs-app-router/README.md index 1491ba83..45f44200 100644 --- a/examples/nextjs-app-router/README.md +++ b/examples/nextjs-app-router/README.md @@ -13,7 +13,7 @@ router. > [!WARNING] -> For convinience Ory provides a default "playground" project, that +> For convenience Ory provides a default "playground" project, that > can be used to interact with Ory's APIs. It is a public project, that can be > used by anyone and data can be deleted at any time. Make sure to use a > dedicated project. @@ -34,10 +34,20 @@ router. The project files reside in the `app/` directory: - `app/auth` - contains the page files for the user auth flows -- `app/settings` - contaisn the page file for the settings flow +- `app/settings` - contains the page file for the settings flow - `app` - contains the root page file and layout. ## Need help? If you have any issues using this examples, or Ory's products, don't hesitate to reach out via the [Ory Community Slack](https://slack.ory.sh). + +## Run against local Ory Network instance + +This section is relevant to Ory engineers only. When running a local Ory Network +instance, you will need to disable TLS verification and set the +`NEXT_PUBLIC_ORY_SDK_URL` to `https://.projects.oryapis:8080`: + +```sh +NODE_TLS_REJECT_UNAUTHORIZED=0 npm run dev +``` diff --git a/examples/nextjs-pages-router/README.md b/examples/nextjs-pages-router/README.md index 4218b4f9..a6697cfc 100644 --- a/examples/nextjs-pages-router/README.md +++ b/examples/nextjs-pages-router/README.md @@ -23,7 +23,7 @@ router. > [!WARNING] -> For convinience Ory provides a default "playground" project, that +> For convenience Ory provides a default "playground" project, that > can be used to interact with Ory's APIs. It is a public project, that can be > used by anyone and data can be deleted at any time. Make sure to use a > dedicated project. @@ -33,3 +33,13 @@ router. If you have any issues using this examples, or Ory's products, don't hesitate to reach out via the [Ory Community Slack](https://slack.ory.sh). + +## Run against local Ory Network instance + +This section is relevant to Ory engineers only. When running a local Ory Network +instance, you will need to disable TLS verification and set the +`NEXT_PUBLIC_ORY_SDK_URL` to `https://.projects.oryapis:8080`: + +```sh +NODE_TLS_REJECT_UNAUTHORIZED=0 npm run dev +``` diff --git a/packages/elements-react/src/context/form-state.ts b/packages/elements-react/src/context/form-state.ts index aeaea0d7..0a84e9f7 100644 --- a/packages/elements-react/src/context/form-state.ts +++ b/packages/elements-react/src/context/form-state.ts @@ -5,6 +5,7 @@ import { FlowType, UiNode, UiNodeGroupEnum } from "@ory/client-fetch" import { useReducer } from "react" import { isChoosingMethod } from "../components/card/card-two-step.utils" import { OryFlowContainer } from "../util" +import { nodesToAuthMethodGroups } from "../util/ui" export type FormState = | { current: "provide_identifier" } @@ -46,6 +47,12 @@ function parseStateFromFlow(flow: OryFlowContainer): FormState { ) { return { current: "method_active", method: flow.flow.active } } else if (isChoosingMethod(flow.flow.ui.nodes)) { + // Login has a special case where we only have one method. Here, we + // do not want to display the chooser. + const authMethods = nodesToAuthMethodGroups(flow.flow.ui.nodes) + if (authMethods.length === 1) { + return { current: "method_active", method: authMethods[0] } + } return { current: "select_method" } } else if (flow.flow.ui.messages?.some((m) => m.id === 1010016)) { // Account linking edge case diff --git a/packages/elements-react/src/util/ui/index.ts b/packages/elements-react/src/util/ui/index.ts index 9769a0bb..38745bab 100644 --- a/packages/elements-react/src/util/ui/index.ts +++ b/packages/elements-react/src/util/ui/index.ts @@ -4,11 +4,11 @@ import { UiNode } from "@ory/client-fetch" import type { - UiNodeGroupEnum, UiNodeInputAttributesOnclickTriggerEnum, UiNodeInputAttributesOnloadTriggerEnum, UiNodeInputAttributesTypeEnum, } from "@ory/client-fetch" +import { UiNodeGroupEnum } from "@ory/client-fetch" import { useMemo } from "react" import { useGroupSorter } from "../../context/component" @@ -98,7 +98,61 @@ type Entries = { [K in keyof T]: [K, T[K]] }[keyof T][] -export function useNodesGroups(nodes: UiNode[]) { +/** + * Returns a list of auth methods from a list of nodes. For example, + * if Password and Passkey are present, it will return [password, passkey]. + * + * Please note that OIDC is not considered an auth method because it is + * usually shown as a separate auth method + * + * This method the default, identifier_first, and profile groups. + * + * @param nodes The nodes to extract the auth methods from + * @param excludeAuthMethods A list of auth methods to exclude + */ +export function nodesToAuthMethodGroups( + nodes: Array, + excludeAuthMethods = [UiNodeGroupEnum.Oidc], +): UiNodeGroupEnum[] { + const groups: Partial> = {} + + for (const node of nodes) { + if (node.type === "script") { + // We always render all scripts, because the scripts for passkeys are part of the webauthn group, + // which leads to this hook returning a webauthn group on passkey flows (which it should not - webauthn is the "legacy" passkey implementation). + continue + } + const groupNodes = groups[node.group] ?? [] + groupNodes.push(node) + groups[node.group] = groupNodes + } + + return Object.values(UiNodeGroupEnum) + .filter((group) => groups[group]?.length) + .filter( + (group) => + !( + [ + UiNodeGroupEnum.Default, + UiNodeGroupEnum.IdentifierFirst, + UiNodeGroupEnum.Profile, + ...excludeAuthMethods, + ] as UiNodeGroupEnum[] + ).includes(group), + ) +} + +type NodeGroups = { + groups: Partial> + entries: Entries>> +} + +/** + * Groups nodes by their group and returns an object with the groups and entries. + * + * @param nodes + */ +export function useNodesGroups(nodes: UiNode[]): NodeGroups { const groupSorter = useGroupSorter() const groups = useMemo(() => {