diff --git a/package-lock.json b/package-lock.json index 78f21e57b..5396100c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,6 +92,7 @@ "react-dom": "18.3.1", "react-intl": "6.8.1", "storybook": "8.3.0", + "storybook-mock-date-decorator": "2.0.6", "tailwindcss": "3.4.17", "ts-jest": "29.2.4", "ts-node": "10.9.2", @@ -26225,6 +26226,13 @@ "node": ">=0.4.0" } }, + "node_modules/mockdate": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz", + "integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==", + "dev": true, + "license": "MIT" + }, "node_modules/modern-ahocorasick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modern-ahocorasick/-/modern-ahocorasick-1.0.1.tgz", @@ -29993,6 +30001,19 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/storybook-mock-date-decorator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/storybook-mock-date-decorator/-/storybook-mock-date-decorator-2.0.6.tgz", + "integrity": "sha512-5y9daZ5mhQnAQ0OZ7jIicAgMQjWvTmBwVuQYJB8uVue0HUpVL2goNLXSPAtn7vWyYebhYD2s29300t/rmaXigg==", + "dev": true, + "license": "ISC", + "dependencies": { + "mockdate": "^3.0.5" + }, + "peerDependencies": { + "@storybook/addons": ">=6" + } + }, "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", diff --git a/package.json b/package.json index 92aba1faf..de6e274e5 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "react-dom": "18.3.1", "react-intl": "6.8.1", "storybook": "8.3.0", + "storybook-mock-date-decorator": "2.0.6", "tailwindcss": "3.4.17", "ts-jest": "29.2.4", "ts-node": "10.9.2", diff --git a/packages/elements-react/.storybook/preview.tsx b/packages/elements-react/.storybook/preview.tsx index 3bc559d06..7d00e7bb6 100644 --- a/packages/elements-react/.storybook/preview.tsx +++ b/packages/elements-react/.storybook/preview.tsx @@ -4,6 +4,7 @@ import type { Decorator, Preview } from "@storybook/react" import { merge } from "lodash" import "./global.css" +import { mockDateDecorator } from "storybook-mock-date-decorator" const preview: Preview = { parameters: { @@ -45,7 +46,7 @@ const withI18next: Decorator = (Story, context) => { } // export decorators for storybook to wrap your stories in -export const decorators = [withI18next] +export const decorators = [withI18next, mockDateDecorator] export const globalTypes = { locale: { diff --git a/packages/elements-react/api-report/elements-react-theme.api.json b/packages/elements-react/api-report/elements-react-theme.api.json index f2f4778dc..7e81f1267 100644 --- a/packages/elements-react/api-report/elements-react-theme.api.json +++ b/packages/elements-react/api-report/elements-react-theme.api.json @@ -752,7 +752,16 @@ "excerptTokens": [ { "kind": "Content", - "text": "declare function Error({ error, children, }: " + "text": "declare function Error({ error, " + }, + { + "kind": "Reference", + "text": "components", + "canonicalReference": "@ory/elements-react!~__type#components" + }, + { + "kind": "Content", + "text": ": Components, config, session, }: " }, { "kind": "Reference", @@ -788,17 +797,17 @@ ], "fileUrlPath": "dist/theme/default/index.d.ts", "returnTypeTokenRange": { - "startIndex": 6, - "endIndex": 7 + "startIndex": 8, + "endIndex": 9 }, "releaseTag": "Public", "overloadIndex": 1, "parameters": [ { - "parameterName": "{ error, children, }", + "parameterName": "{ error, components: Components, config, session, }", "parameterTypeTokenRange": { - "startIndex": 1, - "endIndex": 5 + "startIndex": 3, + "endIndex": 7 }, "isOptional": false } @@ -820,8 +829,8 @@ }, { "kind": "Reference", - "text": "FlowError", - "canonicalReference": "@ory/client-fetch!FlowError:interface" + "text": "OryError", + "canonicalReference": "@ory/elements-react!OryError:type" }, { "kind": "Content", @@ -841,6 +850,15 @@ "text": "OryClientConfiguration", "canonicalReference": "@ory/elements-react!~OryClientConfiguration:type" }, + { + "kind": "Content", + "text": ";\n session?: " + }, + { + "kind": "Reference", + "text": "Session", + "canonicalReference": "@ory/client-fetch!Session:interface" + }, { "kind": "Content", "text": ";\n}" @@ -855,7 +873,7 @@ "name": "ErrorFlowContextProps", "typeTokenRange": { "startIndex": 1, - "endIndex": 8 + "endIndex": 10 } }, { @@ -1027,6 +1045,81 @@ "endIndex": 8 } }, + { + "kind": "TypeAlias", + "canonicalReference": "@ory/elements-react!OAuth2Error:type", + "docComment": "/**\n * An OAuth2 error response.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "type OAuth2Error = " + }, + { + "kind": "Content", + "text": "{\n error: string;\n error_description: string;\n}" + }, + { + "kind": "Content", + "text": ";" + } + ], + "fileUrlPath": "dist/theme/default/index.d.ts", + "releaseTag": "Public", + "name": "OAuth2Error", + "typeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "kind": "TypeAlias", + "canonicalReference": "@ory/elements-react!OryError:type", + "docComment": "/**\n * A union type of all possible errors that can be returned by the Ory SDK.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "type OryError = " + }, + { + "kind": "Reference", + "text": "FlowError", + "canonicalReference": "@ory/client-fetch!FlowError:interface" + }, + { + "kind": "Content", + "text": " | " + }, + { + "kind": "Reference", + "text": "OAuth2Error", + "canonicalReference": "@ory/elements-react!OAuth2Error:type" + }, + { + "kind": "Content", + "text": " | {\n error: " + }, + { + "kind": "Reference", + "text": "GenericError", + "canonicalReference": "@ory/client-fetch!GenericError:interface" + }, + { + "kind": "Content", + "text": ";\n}" + }, + { + "kind": "Content", + "text": ";" + } + ], + "fileUrlPath": "dist/theme/default/index.d.ts", + "releaseTag": "Public", + "name": "OryError", + "typeTokenRange": { + "startIndex": 1, + "endIndex": 7 + } + }, { "kind": "Function", "canonicalReference": "@ory/elements-react!Recovery:function(1)", diff --git a/packages/elements-react/api-report/elements-react-theme.api.md b/packages/elements-react/api-report/elements-react-theme.api.md index a4d4eb3cd..9436dbc12 100644 --- a/packages/elements-react/api-report/elements-react-theme.api.md +++ b/packages/elements-react/api-report/elements-react-theme.api.md @@ -11,6 +11,7 @@ import { DetailedHTMLProps } from 'react'; import { ElementType } from 'react'; import { FlowError } from '@ory/client-fetch'; import { FormEventHandler } from 'react'; +import { GenericError } from '@ory/client-fetch'; import { HTMLAttributes } from 'react'; import { LoginFlow } from '@ory/client-fetch'; import { MouseEventHandler } from 'react'; @@ -19,6 +20,7 @@ import * as react from 'react'; import * as react_jsx_runtime from 'react/jsx-runtime'; import { RecoveryFlow } from '@ory/client-fetch'; import { RegistrationFlow } from '@ory/client-fetch'; +import { Session } from '@ory/client-fetch'; import { SettingsFlow } from '@ory/client-fetch'; import { UiNode } from '@ory/client-fetch'; import { UiNodeAnchorAttributes } from '@ory/client-fetch'; @@ -79,14 +81,15 @@ export function DefaultMessage({ message }: OryMessageContentProps): react_jsx_r export function DefaultMessageContainer({ children }: PropsWithChildren): react_jsx_runtime.JSX.Element | null; // @public (undocumented) -function Error_2({ error, children, }: PropsWithChildren): react_jsx_runtime.JSX.Element; +function Error_2({ error, components: Components, config, session, }: PropsWithChildren): react_jsx_runtime.JSX.Element; export { Error_2 as Error } // @public (undocumented) export type ErrorFlowContextProps = { - error: FlowError; + error: OryError; components?: OryFlowComponentOverrides; config: OryClientConfiguration; + session?: Session; }; // Warning: (ae-forgotten-export) The symbol "OryFlowComponents" needs to be exported by the entry point index.d.ts @@ -104,6 +107,17 @@ export type LoginFlowContextProps = { config: OryClientConfiguration; }; +// @public +export type OAuth2Error = { + error: string; + error_description: string; +}; + +// @public +export type OryError = FlowError | OAuth2Error | { + error: GenericError; +}; + // @public (undocumented) export function Recovery({ flow, config, children, components: flowOverrideComponents, }: PropsWithChildren): react_jsx_runtime.JSX.Element; @@ -141,8 +155,8 @@ export type VerificationFlowContextProps = { // Warnings were encountered during analysis: // -// dist/theme/default/index.d.ts:38:5 - (ae-forgotten-export) The symbol "OryFlowComponentOverrides" needs to be exported by the entry point index.d.ts -// dist/theme/default/index.d.ts:39:5 - (ae-forgotten-export) The symbol "OryClientConfiguration" needs to be exported by the entry point index.d.ts +// dist/theme/default/index.d.ts:51:5 - (ae-forgotten-export) The symbol "OryFlowComponentOverrides" needs to be exported by the entry point index.d.ts +// dist/theme/default/index.d.ts:52:5 - (ae-forgotten-export) The symbol "OryClientConfiguration" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/elements-react/api-report/elements-react.api.json b/packages/elements-react/api-report/elements-react.api.json index 509c9000f..cd892306e 100644 --- a/packages/elements-react/api-report/elements-react.api.json +++ b/packages/elements-react/api-report/elements-react.api.json @@ -1181,7 +1181,7 @@ }, { "kind": "Content", - "text": ">;\n };\n project: {\n registration_enabled: boolean;\n verification_enabled: boolean;\n recovery_enabled: boolean;\n recovery_ui_url: string;\n registration_ui_url: string;\n verification_ui_url: string;\n login_ui_url: string;\n };\n intl?: " + "text": ">;\n };\n project: {\n registration_enabled: boolean;\n verification_enabled: boolean;\n recovery_enabled: boolean;\n recovery_ui_url: string;\n registration_ui_url: string;\n verification_ui_url: string;\n login_ui_url: string;\n default_redirect_url?: string;\n };\n intl?: " }, { "kind": "Reference", diff --git a/packages/elements-react/api-report/elements-react.api.md b/packages/elements-react/api-report/elements-react.api.md index 02cb6b977..f4ce54dab 100644 --- a/packages/elements-react/api-report/elements-react.api.md +++ b/packages/elements-react/api-report/elements-react.api.md @@ -170,6 +170,7 @@ export type OryClientConfiguration = { registration_ui_url: string; verification_ui_url: string; login_ui_url: string; + default_redirect_url?: string; }; intl?: IntlConfig; }; diff --git a/packages/elements-react/src/locales/de.json b/packages/elements-react/src/locales/de.json index 9a28fe9d9..54b9577a7 100644 --- a/packages/elements-react/src/locales/de.json +++ b/packages/elements-react/src/locales/de.json @@ -259,5 +259,11 @@ "property.password": "Passwort", "property.phone": "Telefon", "property.code": "Code", - "property.username": "Benutzername" + "property.username": "Benutzername", + "error.title.what-happened": "Was ist passiert?", + "error.footer.copy": "Kopieren", + "error.footer.text": "Bitte fügen Sie bei der Meldung dieses Fehlers die folgenden Informationen hinzu:", + "error.instructions": "Bitte versuchen Sie es in wenigen Minuten erneut oder wenden Sie sich an den Website-Betreiber.", + "error.title.what-can-i-do": "Was kann ich tun?", + "error.action.go-back": "Zurück" } diff --git a/packages/elements-react/src/locales/en.json b/packages/elements-react/src/locales/en.json index 83f552cba..967fba28e 100644 --- a/packages/elements-react/src/locales/en.json +++ b/packages/elements-react/src/locales/en.json @@ -259,5 +259,11 @@ "property.phone": "phone", "property.username": "username", "property.identifier": "identifier", - "property.code": "code" + "property.code": "code", + "error.title.what-happened": "What happened?", + "error.title.what-can-i-do": "What can I do?", + "error.instructions": "Please try again in a few minutes or contact the website operator.", + "error.footer.text": "When reporting this error, please include the following information:", + "error.footer.copy": "Copy", + "error.action.go-back": "Go back" } diff --git a/packages/elements-react/src/locales/es.json b/packages/elements-react/src/locales/es.json index 333c73cfa..e77ba9b34 100644 --- a/packages/elements-react/src/locales/es.json +++ b/packages/elements-react/src/locales/es.json @@ -10,7 +10,7 @@ "error.back-button": "Regresar", "error.description": "Ocurrió un error con el siguiente mensaje:", "error.support-email-link": "Si el problema persiste, por favor contacte a {contactSupportEmail}", - "error.title": "Ocurrió un error", + "error.title": "", "error.title-internal-server-error": "Error Interno del Servidor", "error.title-not-found": "404 - Página no encontrada", "identities.messages.1010001": "Iniciar sesión", @@ -259,5 +259,11 @@ "property.identifier": "", "property.password": "", "property.phone": "", - "property.username": "" + "property.username": "", + "error.action.go-back": "", + "error.footer.copy": "", + "error.footer.text": "", + "error.instructions": "", + "error.title.what-can-i-do": "", + "error.title.what-happened": "" } diff --git a/packages/elements-react/src/locales/fr.json b/packages/elements-react/src/locales/fr.json index 5871ef8e3..c28b87d4d 100644 --- a/packages/elements-react/src/locales/fr.json +++ b/packages/elements-react/src/locales/fr.json @@ -259,5 +259,11 @@ "property.identifier": "", "property.password": "", "property.phone": "", - "property.username": "" + "property.username": "", + "error.action.go-back": "", + "error.footer.copy": "", + "error.footer.text": "", + "error.title.what-can-i-do": "", + "error.title.what-happened": "", + "error.instructions": "" } diff --git a/packages/elements-react/src/locales/nl.json b/packages/elements-react/src/locales/nl.json index 16e4a956d..0479fa11f 100644 --- a/packages/elements-react/src/locales/nl.json +++ b/packages/elements-react/src/locales/nl.json @@ -259,5 +259,11 @@ "property.identifier": "", "property.password": "", "property.phone": "", - "property.username": "" + "property.username": "", + "error.action.go-back": "", + "error.footer.copy": "", + "error.footer.text": "", + "error.title.what-can-i-do": "", + "error.title.what-happened": "", + "error.instructions": "" } diff --git a/packages/elements-react/src/locales/pl.json b/packages/elements-react/src/locales/pl.json index 0a7789b45..2fa6d0b83 100644 --- a/packages/elements-react/src/locales/pl.json +++ b/packages/elements-react/src/locales/pl.json @@ -259,5 +259,11 @@ "property.password": "", "property.phone": "", "property.username": "", - "property.identifier": "" + "property.identifier": "", + "error.action.go-back": "", + "error.footer.copy": "", + "error.footer.text": "", + "error.title.what-can-i-do": "", + "error.title.what-happened": "", + "error.instructions": "" } diff --git a/packages/elements-react/src/locales/pt.json b/packages/elements-react/src/locales/pt.json index 14c0088d2..acdf2cd82 100644 --- a/packages/elements-react/src/locales/pt.json +++ b/packages/elements-react/src/locales/pt.json @@ -259,5 +259,11 @@ "property.identifier": "", "property.password": "", "property.phone": "", - "property.username": "" + "property.username": "", + "error.action.go-back": "", + "error.footer.copy": "", + "error.footer.text": "", + "error.title.what-can-i-do": "", + "error.title.what-happened": "", + "error.instructions": "" } diff --git a/packages/elements-react/src/locales/sv.json b/packages/elements-react/src/locales/sv.json index c1e2ca96c..c98ba7655 100644 --- a/packages/elements-react/src/locales/sv.json +++ b/packages/elements-react/src/locales/sv.json @@ -259,5 +259,11 @@ "property.phone": "telefon", "property.username": "användarnamn", "property.identifier": "identifier", - "property.code": "kod" + "property.code": "kod", + "error.action.go-back": "", + "error.footer.copy": "", + "error.footer.text": "", + "error.title.what-can-i-do": "", + "error.title.what-happened": "", + "error.instructions": "" } diff --git a/packages/elements-react/src/theme/default/components/ui/user-menu.tsx b/packages/elements-react/src/theme/default/components/ui/user-menu.tsx index 71e261db1..48ad85b7c 100644 --- a/packages/elements-react/src/theme/default/components/ui/user-menu.tsx +++ b/packages/elements-react/src/theme/default/components/ui/user-menu.tsx @@ -2,12 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import { LogoutFlow, Session } from "@ory/client-fetch" -import { DropdownMenuLabel } from "@radix-ui/react-dropdown-menu" -import { useCallback, useEffect, useState } from "react" import { useOryFlow } from "@ory/elements-react" -import { frontendClient } from "../../../../util/client" +import { DropdownMenuLabel } from "@radix-ui/react-dropdown-menu" import IconLogout from "../../assets/icons/logout.svg" import IconSettings from "../../assets/icons/settings.svg" +import { useClientLogout } from "../../utils/logout" import { getUserInitials } from "../../utils/user" import { DropdownMenu, @@ -25,16 +24,7 @@ type UserMenuProps = { export const UserMenu = ({ session }: UserMenuProps) => { const { config } = useOryFlow() const initials = getUserInitials(session) - const [logoutFlow, setLogoutFlow] = useState() - - const fetchLogoutFlow = useCallback(async () => { - const flow = await frontendClient(config.sdk.url).createBrowserLogoutFlow() - setLogoutFlow(flow) - }, [config.sdk.url]) - - useEffect(() => { - void fetchLogoutFlow() - }, [fetchLogoutFlow]) + const logoutFlow = useClientLogout(config) return ( diff --git a/packages/elements-react/src/theme/default/flows/error.tsx b/packages/elements-react/src/theme/default/flows/error.tsx index 9c03168e7..6d5c329b0 100644 --- a/packages/elements-react/src/theme/default/flows/error.tsx +++ b/packages/elements-react/src/theme/default/flows/error.tsx @@ -2,26 +2,230 @@ // SPDX-License-Identifier: Apache-2.0 "use client" -import { FlowError } from "@ory/client-fetch" +import { + FlowError, + GenericError, + instanceOfFlowError, + instanceOfGenericError, + Session, +} from "@ory/client-fetch" import { OryClientConfiguration, OryFlowComponentOverrides, } from "@ory/elements-react" -import { PropsWithChildren } from "react" +import { PropsWithChildren, useMemo } from "react" +import { DefaultCard } from "../components" +import { DefaultHorizontalDivider } from "../components/form/horizontal-divider" +import { useClientLogout } from "../utils/logout" +import { IntlProvider } from "../../../context/intl-context" +import { FormattedMessage } from "react-intl" + +/** + * A union type of all possible errors that can be returned by the Ory SDK. + */ +export type OryError = FlowError | OAuth2Error | { error: GenericError } + +/** + * An OAuth2 error response. + */ +export type OAuth2Error = { + error: string + error_description: string +} + +function isOAuth2Error(error: unknown): error is OAuth2Error { + return ( + !!error && + typeof error === "object" && + "error" in error && + "error_description" in error + ) +} export type ErrorFlowContextProps = { - error: FlowError + error: OryError components?: OryFlowComponentOverrides config: OryClientConfiguration + session?: Session +} + +const errorDescriptions: Record = { + 4: "The server could not handle your request, because it was malformed", + 5: "The server encountered an error and could not complete your request", +} + +type InternalStandardizedError = { + code: number + message?: string + status?: string + reason?: string + id?: string + timestamp?: Date +} + +function useStandardize(error: OryError): InternalStandardizedError { + // Memoize the error to keep the timestamp consistent + return useMemo(() => { + if (isOAuth2Error(error)) { + return { + code: 400, + message: error.error_description, + status: error.error, + timestamp: new Date(), + } + } + if (instanceOfFlowError(error)) { + const parsed = error.error as InternalStandardizedError + return { + ...parsed, + id: error.id, + timestamp: error.created_at, + } + } else if (error.error && instanceOfGenericError(error.error)) { + return { + code: error.error.code ?? 500, + message: error.error.message, + status: error.error.status, + reason: error.error.reason, + timestamp: new Date(), + } + } + return { + code: 500, + message: "An error occurred", + status: "error", + } + }, [error]) } export function Error({ error, - children, + components: Components, + config, + session, }: PropsWithChildren) { + const Card = Components?.Card?.Root ?? DefaultCard + const Divider = Components?.Card?.Divider ?? DefaultHorizontalDivider + const parsed = useStandardize(error) + + const description = errorDescriptions[Math.floor(parsed.code / 100)] + + return ( + + +
+
+ +
+

+ +

+

+ {parsed.message ?? description} +

+ {parsed.reason && ( +

+ {parsed.reason} +

+ )} +
+
+ + +
+

+ +

+

+ +

+
+ {session ? ( + + ) : ( + + )} +
+
+ + +
+ + + + + {parsed.id && ( +

+ ID: {parsed.id} +

+ )} +

+ Time: {parsed.timestamp?.toUTCString()} +

+

+ Message: {parsed.reason} +

+ +
+ +
+
+
+
+
+ ) +} + +function LoggedInActions({ config }: { config: OryClientConfiguration }) { + const logoutFlow = useClientLogout(config) + + return ( + + Logout + + ) +} + +function GoBackButton({ config }: { config: OryClientConfiguration }) { + if ("default_redirect_url" in config.project) { + return ( + + + + ) + } + + return null +} + +function ErrorLogo({ config }: { config: OryClientConfiguration }) { + if (config.logoUrl) { + return Logo + } + return ( -
- {JSON.stringify(error) || children} -
+

+ {config.name} +

) } diff --git a/packages/elements-react/src/theme/default/utils/logout.ts b/packages/elements-react/src/theme/default/utils/logout.ts new file mode 100644 index 000000000..ad8781546 --- /dev/null +++ b/packages/elements-react/src/theme/default/utils/logout.ts @@ -0,0 +1,21 @@ +// Copyright © 2025 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { LogoutFlow } from "@ory/client-fetch" +import { useCallback, useEffect, useState } from "react" +import { frontendClient } from "../../../util/client" + +export function useClientLogout(config: { sdk: { url: string } }) { + const [logoutFlow, setLogoutFlow] = useState() + + const fetchLogoutFlow = useCallback(async () => { + const flow = await frontendClient(config.sdk.url).createBrowserLogoutFlow() + setLogoutFlow(flow) + }, [config.sdk.url]) + + useEffect(() => { + void fetchLogoutFlow() + }, [fetchLogoutFlow]) + + return logoutFlow +} diff --git a/packages/elements-react/src/util/clientConfiguration.ts b/packages/elements-react/src/util/clientConfiguration.ts index d2b358018..0d36cc5c8 100644 --- a/packages/elements-react/src/util/clientConfiguration.ts +++ b/packages/elements-react/src/util/clientConfiguration.ts @@ -35,6 +35,7 @@ export type OryClientConfiguration = { registration_ui_url: string verification_ui_url: string login_ui_url: string + default_redirect_url?: string } intl?: IntlConfig } diff --git a/packages/elements-react/stories/components/error/error.stories.tsx b/packages/elements-react/stories/components/error/error.stories.tsx new file mode 100644 index 000000000..42255cef1 --- /dev/null +++ b/packages/elements-react/stories/components/error/error.stories.tsx @@ -0,0 +1,67 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { FlowErrorFromJSON } from "@ory/client-fetch" +import type { Meta, StoryObj } from "@storybook/react" +import { Error, OryError } from "../../../src/theme/default" +import { config } from "../../utils" + +const errors: Record = { + oidc_continuity: FlowErrorFromJSON(require("./errors/oidc_continuity.json")), + oauth2: { + error: "invalid_request", + error_description: + "The server could not handle your request, because it was malformed", + }, +} + +const meta = { + title: "Ory Elements/Error", + component: Error, + parameters: { + layout: "centered", + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const GenericError: Story = { + parameters: { + date: new Date(Date.UTC(2025, 1, 1)), + }, + args: { + error: errors.oidc_continuity, + config, + }, + + argTypes: { + error: { + options: Object.keys(errors), + control: { type: "select" }, + mapping: errors, + }, + }, +} + +export const GenericErrorWithSession: Story = { + parameters: { + date: new Date(Date.UTC(2025, 1, 1)), + }, + args: { + error: errors.oidc_continuity, + session: { + id: "session-id", + }, + config, + }, + + argTypes: { + error: { + options: Object.keys(errors), + control: { type: "select" }, + mapping: errors, + }, + }, +} diff --git a/packages/elements-react/stories/components/error/errors/oidc_continuity.json b/packages/elements-react/stories/components/error/errors/oidc_continuity.json new file mode 100644 index 000000000..6c425a713 --- /dev/null +++ b/packages/elements-react/stories/components/error/errors/oidc_continuity.json @@ -0,0 +1,12 @@ +{ + "id": "754e5ca5-80fa-4839-b199-b153d2b5a76e", + "error": { + "code": 400, + "debug": "key ory_kratos_oidc_auth_code_session does not exist in cookie: ory_kratos_continuity\ngithub.com/ory/kratos/x.SessionGetString.func1\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/x/cookie.go:31\ngithub.com/ory/kratos/x.SessionGetString\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/x/cookie.go:50\ngithub.com/ory/kratos/continuity.(*ManagerCookie).sessionID\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/continuity/manager_cookie.go:119\ngithub.com/ory/kratos/continuity.(*ManagerCookie).container\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/continuity/manager_cookie.go:135\ngithub.com/ory/kratos/continuity.(*ManagerCookie).Continue\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/continuity/manager_cookie.go:77\ngithub.com/ory/kratos/selfservice/strategy/oidc.(*Strategy).ValidateCallback\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/selfservice/strategy/oidc/strategy.go:362\ngithub.com/ory/kratos/selfservice/strategy/oidc.(*Strategy).HandleCallback\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/selfservice/strategy/oidc/strategy.go:443\ngithub.com/ory/kratos/selfservice/strategy.disabledWriter\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/selfservice/strategy/handler.go:28\ngithub.com/ory/kratos/selfservice/strategy/oidc.(*Strategy).setRoutes.IsDisabled.func1\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/selfservice/strategy/handler.go:33\ngithub.com/ory/kratos/x.(*RouterPublic).GET.NoCacheHandle.func1\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/x/nocache.go:21\ngithub.com/ory/kratos/x.(*RouterPublic).Handle.NoCacheHandle.func1\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/x/nocache.go:21\ngithub.com/julienschmidt/httprouter.(*Router).ServeHTTP\n\tgithub.com/julienschmidt/httprouter@v1.3.0/router.go:387\ngithub.com/ory/nosurf.(*CSRFHandler).handleSuccess\n\tgithub.com/ory/nosurf@v1.2.7/handler.go:212\ngithub.com/ory/nosurf.(*CSRFHandler).ServeHTTP\n\tgithub.com/ory/nosurf@v1.2.7/handler.go:163\ngithub.com/ory/kratos/cmd/daemon.servePublic.MaxBytesHandler.func4\n\tnet/http/server.go:4055\nnet/http.HandlerFunc.ServeHTTP\n\tnet/http/server.go:2294\ngithub.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1\n\tgithub.com/urfave/negroni@v1.0.0/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\tgithub.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\tgithub.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/kratos/x.init.func1\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/x/clean_url.go:15\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\tgithub.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\tgithub.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/kratos/cmd/daemon.servePublic.func1\n\tgithub.com/ory/kratos@v1.3.1-0.20250306093537-37793ea3e5c0/cmd/daemon/serve.go:111\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\tgithub.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\tgithub.com/urfave/negroni@v1.0.0/negroni.go:38\nnet/http.HandlerFunc.ServeHTTP\n\tnet/http/server.go:2294\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerResponseSize.func1\n\tgithub.com/prometheus/client_golang@v1.16.0/prometheus/promhttp/instrument_server.go:296\nnet/http.HandlerFunc.ServeHTTP\n\tnet/http/server.go:2294\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerCounter.func1\n\tgithub.com/prometheus/client_golang@v1.16.0/prometheus/promhttp/instrument_server.go:147\nnet/http.HandlerFunc.ServeHTTP\n\tnet/http/server.go:2294\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func1\n\tgithub.com/prometheus/client_golang@v1.16.0/prometheus/promhttp/instrument_server.go:97\nnet/http.HandlerFunc.ServeHTTP\n\tnet/http/server.go:2294", + "message": "no resumable session found", + "reason": "The browser does not contain the necessary cookie to resume the session. This is a security violation and was blocked. Please clear your browser's cookies and cache and try again!", + "status": "Bad Request" + }, + "created_at": "2025-03-12T08:41:01.98727Z", + "updated_at": "2025-03-12T08:41:01.98727Z" +} diff --git a/packages/elements-react/stories/pages/error.tsx b/packages/elements-react/stories/pages/error.tsx deleted file mode 100644 index 76ac16c7d..000000000 --- a/packages/elements-react/stories/pages/error.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2024 Ory Corp -// SPDX-License-Identifier: Apache-2.0 - -"use client" -import { Configuration, LoginFlow } from "@ory/client-fetch" -import { OryFlowComponents } from "@ory/elements-react" - -export type FlowContextProps = { - flow: LoginFlow - components?: Partial - config: Configuration -} - -function _Error() { - throw new Error("not implemented yet") - // return ( - // - // {children || } - // - // ) -} - -// export const Error = _Error diff --git a/packages/elements-react/stories/utils.ts b/packages/elements-react/stories/utils.ts index af1fdc383..12b66eb33 100644 --- a/packages/elements-react/stories/utils.ts +++ b/packages/elements-react/stories/utils.ts @@ -16,5 +16,126 @@ export const config = { recovery_ui_url: "", registration_ui_url: "", verification_ui_url: "", + default_redirect_url: "https://ory.sh", }, } satisfies OryClientConfiguration + +// TODO(jonas): we neeed to harmonize the error types across the SDKs +// At the moment, the error types are not consistent across the SDKs. +// This is a temporary solution to make the SDKs work with the new error types. + +/** Error parsing polyfils */ +/* eslint-disable */ +/** + * The standard Ory JSON API error format. + * @export + * @interface ErrorGeneric + */ +export interface ErrorGeneric { + /** + * + * @type {GenericError} + * @memberof ErrorGeneric + */ + error: GenericError +} + +export function ErrorGenericFromJSON(json: any): ErrorGeneric { + return ErrorGenericFromJSONTyped(json, false) +} + +export function ErrorGenericFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): ErrorGeneric { + if (json == null) { + return json + } + return { + error: GenericErrorFromJSON(json["error"]), + } +} + +export function GenericErrorFromJSON(json: any): GenericError { + return GenericErrorFromJSONTyped(json, false) +} + +export function GenericErrorFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): GenericError { + if (json == null) { + return json + } + return { + code: json["code"] == null ? undefined : json["code"], + debug: json["debug"] == null ? undefined : json["debug"], + details: json["details"] == null ? undefined : json["details"], + id: json["id"] == null ? undefined : json["id"], + message: json["message"], + reason: json["reason"] == null ? undefined : json["reason"], + request: json["request"] == null ? undefined : json["request"], + status: json["status"] == null ? undefined : json["status"], + } +} + +export interface GenericError { + /** + * The status code + * @type {number} + * @memberof GenericError + */ + code?: number + /** + * Debug information + * + * This field is often not exposed to protect against leaking + * sensitive information. + * @type {string} + * @memberof GenericError + */ + debug?: string + /** + * Further error details + * @type {object} + * @memberof GenericError + */ + details?: object + /** + * The error ID + * + * Useful when trying to identify various errors in application logic. + * @type {string} + * @memberof GenericError + */ + id?: string + /** + * Error message + * + * The error's message. + * @type {string} + * @memberof GenericError + */ + message: string + /** + * A human-readable reason for the error + * @type {string} + * @memberof GenericError + */ + reason?: string + /** + * The request ID + * + * The request ID is often exposed internally in order to trace + * errors across service architectures. This is often a UUID. + * @type {string} + * @memberof GenericError + */ + request?: string + /** + * The status description + * @type {string} + * @memberof GenericError + */ + status?: string +}