From 1816fa4db1bdac4fc6ef7925ab0819b6efa8d68a Mon Sep 17 00:00:00 2001 From: Henning Perl Date: Mon, 11 Dec 2023 10:06:51 +0100 Subject: [PATCH] feat: passwordless strategy --- src/locales/en.json | 2 + src/markup-components/components.ts | 9 ++ src/react-components/ory/helpers/node.tsx | 82 +++++++++++-------- src/react-components/ory/helpers/utils.ts | 27 +++--- src/react-components/ory/index.ts | 1 + .../ory/sections/passkey-settings-section.tsx | 32 ++++++++ .../ory/sections/passwordless-section.tsx | 25 +++++- src/react-components/ory/user-auth-card.tsx | 7 +- .../ory/user-settings-card.tsx | 15 ++++ .../ory/user-settings-screen.tsx | 16 +++- 10 files changed, 159 insertions(+), 57 deletions(-) create mode 100644 src/react-components/ory/sections/passkey-settings-section.tsx diff --git a/src/locales/en.json b/src/locales/en.json index 9c522544f..ee5b6f7ef 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -157,6 +157,7 @@ "settings.navigation-profile": "Profile", "settings.navigation-totp": "Authenticator App", "settings.navigation-webauthn": "Hardware Tokens", + "settings.navigation-passkey": "Passkeys", "settings.subtitle-instructions": "Here you can manage settings related to your account. Keep in mind that certain actions require you to re-authenticate.", "settings.title": "Account Settings", "settings.title-lookup-secret": "Manage 2FA Backup Recovery Codes", @@ -166,6 +167,7 @@ "settings.title-profile": "Profile Settings", "settings.title-totp": "Manage 2FA TOTP Authenticator App", "settings.title-webauthn": "Manage Hardware Tokens", + "settings.title-passkey": "Manage Passkeys", "verification.registration-button": "Sign up", "verification.registration-label": "Don't have an account?", "verification.title": "Verify your account" diff --git a/src/markup-components/components.ts b/src/markup-components/components.ts index cc8ca5b8f..f188adfed 100644 --- a/src/markup-components/components.ts +++ b/src/markup-components/components.ts @@ -58,6 +58,8 @@ import { UserSettingsScreenProps, WebAuthnSettingsProps, WebAuthnSettingsSection as webAuthnSettingsSection, + PasskeySettingsSection as passkeySettingsSection, + PasskeySettingsProps, } from "../react-components" import { ComponentWrapper, Context } from "./component-wrapper" @@ -191,6 +193,13 @@ export const WebAuthnSettingsSection = ( return ComponentWrapper(webAuthnSettingsSection, props, context) } +export const PasskeySettingsSection = ( + props: PasskeySettingsProps, + context: Context = {}, +) => { + return ComponentWrapper(passkeySettingsSection, props, context) +} + export const OIDCSettingsSection = ( props: OIDCSettingsProps, context: Context = {}, diff --git a/src/react-components/ory/helpers/node.tsx b/src/react-components/ory/helpers/node.tsx index 53ade5a7d..8eea8242c 100644 --- a/src/react-components/ory/helpers/node.tsx +++ b/src/react-components/ory/helpers/node.tsx @@ -1,4 +1,4 @@ -import { UiNode, UiText } from "@ory/client" +import {UiNode, UiNodeAttributes, UiText} from "@ory/client" import { isUiNodeAnchorAttributes, isUiNodeImageAttributes, @@ -106,37 +106,37 @@ export const uiTextToFormattedMessage = ( // context might provide an array of objects instead of a single object // for example when looking up a recovery code /* - * - { - "text": { - "id": 1050015, - "text": "3r9noma8, tv14n5tu, ...", - "type": "info", - "context": { - "secrets": [ - { - "context": { - "secret": "3r9noma8" - }, - "id": 1050009, - "text": "3r9noma8", - "type": "info" - }, - { - "context": { - "secret": "tv14n5tu" - }, - "id": 1050009, - "text": "tv14n5tu", - "type": "info" - }, - ] - } - }, - "id": "lookup_secret_codes", - "node_type": "text" - } - */ + * + { + "text": { + "id": 1050015, + "text": "3r9noma8, tv14n5tu, ...", + "type": "info", + "context": { + "secrets": [ + { + "context": { + "secret": "3r9noma8" + }, + "id": 1050009, + "text": "3r9noma8", + "type": "info" + }, + { + "context": { + "secret": "tv14n5tu" + }, + "id": 1050009, + "text": "tv14n5tu", + "type": "info" + }, + ] + } + }, + "id": "lookup_secret_codes", + "node_type": "text" + } + */ if (Array.isArray(value)) { return { ...accumulator, @@ -182,6 +182,18 @@ export const uiTextToFormattedMessage = ( ) } +function dataAttributes(attrs: UiNodeAttributes): Record { + return Object.entries(attrs).reduce( + (accumulator, [key, value]) => { + if (key.startsWith("data-")) { + accumulator[key] = value + } + return accumulator + }, + {} as Record, + ) +} + export const Node = ({ node, className, @@ -205,6 +217,7 @@ export const Node = ({ header={formatMessage(node.meta.label)} width={node.attributes.width} height={node.attributes.height} + {...dataAttributes(node.attributes)} /> ) } else if (isUiNodeTextAttributes(node.attributes)) { @@ -307,6 +320,7 @@ export const Node = ({ disabled={attrs.disabled} {...(buttonSocialOverrideProps && buttonSocialOverrideProps)} {...submit} + {...dataAttributes(attrs)} /> ) : (