Skip to content

Commit

Permalink
feat: do not show two-step selector if only one method exists
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed Dec 18, 2024
1 parent 81aa35d commit 6409dec
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 5 deletions.
14 changes: 12 additions & 2 deletions examples/nextjs-app-router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ router.

<!-- prettier-ignore-start -->
> [!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.
Expand All @@ -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://<slug>.projects.oryapis:8080`:

```sh
NODE_TLS_REJECT_UNAUTHORIZED=0 npm run dev
```
12 changes: 11 additions & 1 deletion examples/nextjs-pages-router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ router.

<!-- prettier-ignore-start -->
> [!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.
Expand All @@ -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://<slug>.projects.oryapis:8080`:

```sh
NODE_TLS_REJECT_UNAUTHORIZED=0 npm run dev
```
7 changes: 7 additions & 0 deletions packages/elements-react/src/context/form-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down Expand Up @@ -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
Expand Down
58 changes: 56 additions & 2 deletions packages/elements-react/src/util/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -98,7 +98,61 @@ type Entries<T> = {
[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<UiNode>,
excludeAuthMethods = [UiNodeGroupEnum.Oidc],
): UiNodeGroupEnum[] {
const groups: Partial<Record<UiNodeGroupEnum, UiNode[]>> = {}

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<Record<UiNodeGroupEnum, UiNode[]>>
entries: Entries<Partial<Record<UiNodeGroupEnum, UiNode[]>>>
}

/**
* 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(() => {
Expand Down

0 comments on commit 6409dec

Please sign in to comment.