UNPKG

@easyyo/ory.elements-react

Version:

Ory Elements React - a collection of React components for authentication UIs.

1,497 lines (1,486 loc) 288 kB
'use strict'; var clientFetch = require('@ory/client-fetch'); var react = require('react'); var jsxRuntime = require('react/jsx-runtime'); var reactIntl = require('react-intl'); var reactHookForm = require('react-hook-form'); var sonner = require('sonner'); // src/context/component.tsx var ComponentContext = react.createContext({ components: null, // fine because we throw an error if it's not provided nodeSorter: () => 0, groupSorter: () => 0 }); function useComponents() { const ctx = react.useContext(ComponentContext); if (!ctx) { throw new Error("useComponents must be used within a ComponentProvider"); } return ctx.components; } function useNodeSorter() { const ctx = react.useContext(ComponentContext); if (!ctx) { throw new Error("useNodeSorter must be used within a ComponentProvider"); } return ctx.nodeSorter; } function useGroupSorter() { const ctx = react.useContext(ComponentContext); if (!ctx) { throw new Error("useGroupSorter must be used within a ComponentProvider"); } return ctx.groupSorter; } var defaultNodeOrder = [ "oidc", "saml", "identifier_first", "default", "profile", "password", // CAPTCHA is below password because otherwise the password input field // would be above the captcha. Somehow, we sort the password sign up button somewhere else to be always at the bottom. "captcha", "passkey", "code", "webauthn" ]; function defaultNodeSorter(a, b) { var _a, _b; const aIsCaptcha = a.group === "captcha"; const bIsCaptcha = b.group === "captcha"; const aIsSubmit = clientFetch.isUiNodeInputAttributes(a.attributes) && a.attributes.type === "submit"; const bIsSubmit = clientFetch.isUiNodeInputAttributes(b.attributes) && b.attributes.type === "submit"; if (aIsCaptcha && bIsSubmit) { return -1; } if (bIsCaptcha && aIsSubmit) { return 1; } const aGroupWeight = (_a = defaultNodeOrder.indexOf(a.group)) != null ? _a : 999; const bGroupWeight = (_b = defaultNodeOrder.indexOf(b.group)) != null ? _b : 999; return aGroupWeight - bGroupWeight; } var defaultGroupOrder = [ clientFetch.UiNodeGroupEnum.Default, clientFetch.UiNodeGroupEnum.Profile, clientFetch.UiNodeGroupEnum.Password, clientFetch.UiNodeGroupEnum.Oidc, clientFetch.UiNodeGroupEnum.Code, clientFetch.UiNodeGroupEnum.LookupSecret, clientFetch.UiNodeGroupEnum.Passkey, clientFetch.UiNodeGroupEnum.Webauthn, clientFetch.UiNodeGroupEnum.Totp ]; function defaultGroupSorter(a, b) { var _a, _b; const aGroupWeight = (_a = defaultGroupOrder.indexOf(a)) != null ? _a : 999; const bGroupWeight = (_b = defaultGroupOrder.indexOf(b)) != null ? _b : 999; return aGroupWeight - bGroupWeight; } function OryComponentProvider({ children, components, nodeSorter = defaultNodeSorter, groupSorter = defaultGroupSorter }) { return /* @__PURE__ */ jsxRuntime.jsx( ComponentContext.Provider, { value: { components, nodeSorter, groupSorter }, children } ); } // src/theme/default/utils/form.ts function isGroupImmediateSubmit(group) { return group === "code"; } function triggerToWindowCall(trigger) { if (!trigger) { return; } const fn = triggerToFunction(trigger); if (fn) { fn(); return; } let i = 0; const ms = 100; const interval = setInterval(() => { i++; if (i > 100) { clearInterval(interval); throw new Error( "Unable to load Ory's WebAuthn script. Is it being blocked or otherwise failing to load? If you are running an old version of Ory Elements, please upgrade. For more information, please check your browser's developer console." ); } const fn2 = triggerToFunction(trigger); if (fn2) { clearInterval(interval); return fn2(); } }, ms); return; } function triggerToFunction(trigger) { if (typeof window === "undefined") { console.debug( "The Ory SDK is missing a required function: window is undefined." ); return void 0; } const typedWindow = window; if (!(trigger in typedWindow) || !typedWindow[trigger]) { console.debug(`The Ory SDK is missing a required function: ${trigger}.`); return void 0; } const triggerFn = typedWindow[trigger]; if (typeof triggerFn !== "function") { console.debug( `The Ory SDK is missing a required function: ${trigger}. It is not a function.` ); return void 0; } return triggerFn; } function nodesToAuthMethodGroups(nodes, excludeAuthMethods = []) { var _a; const groups = {}; for (const node of nodes) { if (node.type === "script") { continue; } const groupNodes = (_a = groups[node.group]) != null ? _a : []; groupNodes.push(node); groups[node.group] = groupNodes; } return Object.values(clientFetch.UiNodeGroupEnum).filter((group) => { var _a2; return (_a2 = groups[group]) == null ? void 0 : _a2.length; }).filter( (group) => ![ clientFetch.UiNodeGroupEnum.Default, clientFetch.UiNodeGroupEnum.IdentifierFirst, clientFetch.UiNodeGroupEnum.Profile, clientFetch.UiNodeGroupEnum.Captcha, ...excludeAuthMethods ].includes(group) ); } function useNodesGroups(nodes, { omit } = {}) { const groupSorter = useGroupSorter(); const groups = react.useMemo(() => { var _a, _b; const groups2 = {}; const groupRetained = {}; for (const node of nodes) { const groupNodes = (_a = groups2[node.group]) != null ? _a : []; groupNodes.push(node); groups2[node.group] = groupNodes; if ((omit == null ? void 0 : omit.includes("script")) && clientFetch.isUiNodeScriptAttributes(node.attributes)) { continue; } if ((omit == null ? void 0 : omit.includes("input_hidden")) && clientFetch.isUiNodeInputAttributes(node.attributes) && node.attributes.type === "hidden") { continue; } groupRetained[node.group] = ((_b = groupRetained[node.group]) != null ? _b : 0) + 1; } const finalGroups = {}; for (const [group, count] of Object.entries(groupRetained)) { if (count > 0) { finalGroups[group] = groups2[group]; } } return finalGroups; }, [nodes, omit]); const entries = react.useMemo( () => Object.entries(groups).sort(([a], [b]) => groupSorter(a, b)), [groups, groupSorter] ); return { groups, entries }; } var findNode = (nodes, opt) => nodes.find((n) => { return n.attributes.node_type === opt.node_type && (opt.group instanceof RegExp ? n.group.match(opt.group) : n.group === opt.group) && (opt.name && n.attributes.node_type === "input" ? opt.name instanceof RegExp ? n.attributes.name.match(opt.name) : n.attributes.name === opt.name : !opt.name) && (opt.type && n.attributes.node_type === "input" ? opt.type instanceof RegExp ? n.attributes.type.match(opt.type) : n.attributes.type === opt.type : !opt.type); }); function useFunctionalNodes(nodes) { return nodes.filter( ({ group }) => [ clientFetch.UiNodeGroupEnum.Default, clientFetch.UiNodeGroupEnum.IdentifierFirst, clientFetch.UiNodeGroupEnum.Profile, clientFetch.UiNodeGroupEnum.Captcha ].includes(group) ); } function isUiNodeGroupEnum(method) { return Object.values(clientFetch.UiNodeGroupEnum).includes(method); } function isSingleSignOnNode(node) { return node.group === clientFetch.UiNodeGroupEnum.Oidc || node.group === clientFetch.UiNodeGroupEnum.Saml; } function hasSingleSignOnNodes(nodes) { return nodes.some(isSingleSignOnNode); } function withoutSingleSignOnNodes(nodes) { return nodes.filter((node) => !isSingleSignOnNode(node)); } function isNodeVisible(node) { if (clientFetch.isUiNodeScriptAttributes(node.attributes)) { return false; } else if (clientFetch.isUiNodeInputAttributes(node.attributes)) { if (node.attributes.type === "hidden") { return false; } } return true; } function useNodeGroupsWithVisibleNodes(nodes) { return react.useMemo(() => { var _a, _b; const groups = {}; const groupRetained = {}; for (const node of nodes) { const groupNodes = (_a = groups[node.group]) != null ? _a : []; const groupCount = (_b = groupRetained[node.group]) != null ? _b : 0; groupNodes.push(node); groups[node.group] = groupNodes; if (!isNodeVisible(node)) { continue; } groupRetained[node.group] = groupCount + 1; } const finalGroups = {}; for (const [group, count] of Object.entries(groupRetained)) { if (count > 0) { finalGroups[group] = groups[group]; } } return finalGroups; }, [nodes]); } // src/components/card/two-step/utils.ts function isScreenSelectionNode(node) { if ("name" in node.attributes && node.attributes.name === "screen" && "value" in node.attributes && node.attributes.value === "previous") { return true; } if (node.group === clientFetch.UiNodeGroupEnum.IdentifierFirst && "name" in node.attributes && node.attributes.name === "identifier" && node.attributes.type === "hidden") { return true; } return false; } function isChoosingMethod(flow) { if (flow.flowType === clientFetch.FlowType.Login) { if (flow.flow.requested_aal === "aal2") { return true; } if (flow.flow.refresh && // TODO: Once https://github.com/ory/kratos/issues/4194 is fixed, this can be removed // Without this, we show the method chooser, and an email input, which looks weird !flow.flow.ui.nodes.some((n) => n.group === "code")) { return true; } } return flow.flow.ui.nodes.some(isScreenSelectionNode); } function getFinalNodes(uniqueGroups, selectedGroup) { var _a, _b, _c, _d; const selectedNodes = selectedGroup ? (_a = uniqueGroups[selectedGroup]) != null ? _a : [] : []; return [ ...(_b = uniqueGroups == null ? void 0 : uniqueGroups.identifier_first) != null ? _b : [], ...(_c = uniqueGroups == null ? void 0 : uniqueGroups.default) != null ? _c : [], ...(_d = uniqueGroups == null ? void 0 : uniqueGroups.captcha) != null ? _d : [] ].flat().filter( (node) => "type" in node.attributes && node.attributes.type === "hidden" ).concat(selectedNodes); } var handleAfterFormSubmit = (dispatchFormState) => (method) => { if (typeof method !== "string" || !isUiNodeGroupEnum(method)) { return; } if (isGroupImmediateSubmit(method)) { dispatchFormState({ type: "action_select_method", method }); } }; // src/context/form-state.ts function findMethodWithMessage(nodes) { var _a; return (_a = nodes == null ? void 0 : nodes.filter((n) => !["default", "identifier_first"].includes(n.group))) == null ? void 0 : _a.find((node) => { var _a2; return ((_a2 = node.messages) == null ? void 0 : _a2.length) > 0; }); } function parseStateFromFlow(flow) { var _a; switch (flow.flowType) { case clientFetch.FlowType.Registration: case clientFetch.FlowType.Login: { const methodWithMessage = findMethodWithMessage(flow.flow.ui.nodes); if (flow.flow.active == "link_recovery") { return { current: "method_active", method: "link" }; } else if (flow.flow.active == "code_recovery") { return { current: "method_active", method: "code" }; } else if (methodWithMessage) { return { current: "method_active", method: methodWithMessage.group }; } else if ((_a = flow.flow.ui.messages) == null ? void 0 : _a.some((m) => m.id === 1010016)) { return { current: "select_method" }; } else if (flow.flow.active && !["default", "identifier_first"].includes(flow.flow.active)) { return { current: "method_active", method: flow.flow.active }; } else if (isChoosingMethod(flow)) { const authMethods = nodesToAuthMethodGroups(flow.flow.ui.nodes); if (authMethods.length === 1 && !["code", "passkey"].includes(authMethods[0])) { return { current: "method_active", method: authMethods[0] }; } return { current: "select_method" }; } return { current: "provide_identifier" }; } case clientFetch.FlowType.Recovery: case clientFetch.FlowType.Verification: if (flow.flow.active === "code" || flow.flow.active === "link") { if (flow.flow.state === "choose_method") { return { current: "provide_identifier" }; } return { current: "method_active", method: flow.flow.active }; } break; case clientFetch.FlowType.Settings: return { current: "settings" }; case clientFetch.FlowType.OAuth2Consent: return { current: "method_active", method: "oauth2_consent" }; } console.warn( `[Ory/Elements React] Encountered an unknown form state on ${flow.flowType} flow with ID ${flow.flow.id}` ); throw new Error("Unknown form state"); } function useFormStateReducer(flow) { const action = parseStateFromFlow(flow); const [selectedMethod, setSelectedMethod] = react.useState(); const formStateReducer = (state, action2) => { switch (action2.type) { case "action_flow_update": { if (selectedMethod) { return { current: "method_active", method: selectedMethod }; } return parseStateFromFlow(action2.flow); } case "action_select_method": { setSelectedMethod(action2.method); return { current: "method_active", method: action2.method }; } case "action_clear_active_method": { return { current: "select_method" }; } } return state; }; return react.useReducer(formStateReducer, action); } function useOryFlow() { const ctx = react.useContext(OryFlowContext); if (!ctx) { throw new Error("useOryFlow must be used within a OryFlowProvider"); } return ctx; } var OryFlowContext = react.createContext(null); function OryFlowProvider({ children, ...container }) { const [flowContainer, setFlowContainer] = react.useState(container); const [formState, dispatchFormState] = useFormStateReducer(container); return /* @__PURE__ */ jsxRuntime.jsx( OryFlowContext.Provider, { value: { ...flowContainer, setFlowContainer: (flowContainer2) => { setFlowContainer(flowContainer2); dispatchFormState({ type: "action_flow_update", flow: flowContainer2 }); }, formState, dispatchFormState }, children } ); } // src/client/config.ts function isProduction() { var _a, _b; return ["production", "prod"].indexOf( (_b = (_a = process.env.VERCEL_ENV) != null ? _a : process.env.NODE_ENV) != null ? _b : "" ) > -1; } function frontendClient(sdkUrl, opts = {}) { const config = new clientFetch.Configuration({ ...opts, basePath: sdkUrl, credentials: "include", headers: { Accept: "application/json", ...opts.headers } }); return new clientFetch.FrontendApi(config); } var defaultProject = { name: "Ory", registration_enabled: true, verification_enabled: true, recovery_enabled: true, recovery_ui_url: "/ui/recovery", registration_ui_url: "/ui/registration", verification_ui_url: "/ui/verification", login_ui_url: "/ui/login", settings_ui_url: "/ui/settings", default_redirect_url: "/ui/welcome", error_ui_url: "/ui/error", default_locale: "en", locale_behavior: "force_default" }; function useOryConfiguration() { const configCtx = react.useContext(OryConfigurationContext); return { sdk: { ...configCtx.sdk, frontend: frontendClient(configCtx.sdk.url, configCtx.sdk.options) }, project: { ...configCtx.project } }; } var OryConfigurationContext = react.createContext({ sdk: computeSdkConfig({}), project: defaultProject }); function OryConfigurationProvider({ children, sdk: initialConfig, project }) { const configRef = react.useRef({ sdk: computeSdkConfig(initialConfig), project: { ...defaultProject, ...project } }); return /* @__PURE__ */ jsxRuntime.jsx(OryConfigurationContext.Provider, { value: configRef.current, children }); } function computeSdkConfig(config) { if ((config == null ? void 0 : config.url) && typeof config.url === "string") { return { url: config.url.replace(/\/$/, ""), options: config.options || {} }; } return { url: getSDKUrl(), options: (config == null ? void 0 : config.options) || {} }; } function getSDKUrl() { var _a; if (typeof process !== "undefined" && process.versions && process.versions.node) { if (isProduction()) { const sdkUrl = (_a = process.env["NEXT_PUBLIC_ORY_SDK_URL"]) != null ? _a : process.env["ORY_SDK_URL"]; if (!sdkUrl) { throw new Error( "Unable to determine SDK URL. Please set NEXT_PUBLIC_ORY_SDK_URL and/or ORY_SDK_URL in production environments." ); } return sdkUrl.replace(/\/$/, ""); } else { if (process.env["__NEXT_PRIVATE_ORIGIN"]) { return process.env["__NEXT_PRIVATE_ORIGIN"].replace(/\/$/, ""); } else if (process.env["VERCEL_URL"]) { return `https://${process.env["VERCEL_URL"]}`.replace(/\/$/, ""); } } } if (typeof window !== "undefined") { return window.location.origin; } throw new Error( "Unable to determine SDK URL. Please set NEXT_PUBLIC_ORY_SDK_URL and/or ORY_SDK_URL or supply the sdk.url parameter in the Ory configuration." ); } function mergeTranslations(customTranslations) { return Object.keys(customTranslations).reduce((acc, key) => { acc[key] = { ...OryLocales[key], ...customTranslations[key] }; return acc; }, OryLocales); } var IntlProvider = ({ children, locale, customTranslations }) => { const existingIntlContext = react.useContext(reactIntl.IntlContext); const messages = mergeTranslations(customTranslations != null ? customTranslations : {}); if (existingIntlContext) { return children; } return /* @__PURE__ */ jsxRuntime.jsx( reactIntl.IntlProvider, { onWarn: () => ({}), defaultRichTextElements: { del: (chunks) => /* @__PURE__ */ jsxRuntime.jsx("del", { children: chunks }) }, locale, messages: messages[locale], defaultLocale: "en", children } ); }; function OryProvider({ children, components: Components, config, ...oryFlowProps }) { var _a, _b, _c; return /* @__PURE__ */ jsxRuntime.jsx(OryConfigurationProvider, { sdk: config.sdk, project: config.project, children: /* @__PURE__ */ jsxRuntime.jsx( IntlProvider, { locale: (_b = (_a = config.intl) == null ? void 0 : _a.locale) != null ? _b : "en", customTranslations: (_c = config.intl) == null ? void 0 : _c.customTranslations, children: /* @__PURE__ */ jsxRuntime.jsx(OryFlowProvider, { ...oryFlowProps, children: /* @__PURE__ */ jsxRuntime.jsx(OryComponentProvider, { components: Components, children }) }) } ) }); } function OryCardHeader() { const { Card } = useComponents(); return /* @__PURE__ */ jsxRuntime.jsx(Card.Header, {}); } function computeDefaultValues(nodes) { return nodes.reduce((acc, node) => { const attrs = node.attributes; if (clientFetch.isUiNodeInputAttributes(attrs)) { if (attrs.type === "checkbox" && typeof attrs.value === "undefined") { attrs.value = false; } if (attrs.name === "method" || attrs.type === "submit" || typeof attrs.value === "undefined") { return acc; } if (attrs.name.startsWith("grant_scope")) { const scope = attrs.value; if (Array.isArray(acc.grant_scope)) { return { ...acc, // We want to have all scopes accepted by default, so that the user has to actively uncheck them. grant_scope: [...acc.grant_scope, scope] }; } else if (!acc.grant_scope) { return { ...acc, grant_scope: [scope] }; } return acc; } return unrollTrait( { name: attrs.name, value: attrs.value }, acc ); } return acc; }, {}); } function unrollTrait(input, output = {}) { const keys = input.name.split("."); let current = output; keys.forEach((key, index) => { if (!key) return; current = current[key] = index === keys.length - 1 ? input.value : current[key] || {}; }); return output; } function isCodeResendRequest(data) { var _a; return (_a = data.email) != null ? _a : data.resend; } function useOryFormResolver() { const flowContainer = useOryFlow(); return (data) => { if (flowContainer.formState.current === "method_active") { if ( // When we submit a code data.method === "code" && // And the code is not present !data.code && // And the flow is not a code resend request !isCodeResendRequest(data) && // And the flow has a code input node flowContainer.flow.ui.nodes.find(({ attributes, group }) => { if (!clientFetch.isUiNodeInputAttributes(attributes)) { return false; } return group === "code" && attributes.name === "code" && attributes.type !== "hidden"; }) ) { return { values: data, errors: { // We know the code node exists, so we can safely hardcode the ID. code: { id: 4000002, context: { property: "code" }, type: "error", text: "Property code is missing" } } }; } } return { values: data, errors: {} }; }; } function OryFormProvider({ children, nodes }) { const flowContainer = useOryFlow(); const defaultNodes = nodes ? flowContainer.flow.ui.nodes.filter((node) => node.group === clientFetch.UiNodeGroupEnum.Default).concat(nodes) : flowContainer.flow.ui.nodes; const methods = reactHookForm.useForm({ // TODO: Generify this, so we have typesafety in the submit handler. defaultValues: computeDefaultValues(defaultNodes), resolver: useOryFormResolver() }); return /* @__PURE__ */ jsxRuntime.jsx(reactHookForm.FormProvider, { ...methods, children }); } function OryCard({ children }) { const { Card } = useComponents(); return /* @__PURE__ */ jsxRuntime.jsx(Card.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(OryFormProvider, { children }) }); } function OryCardFooter() { const { Card } = useComponents(); return /* @__PURE__ */ jsxRuntime.jsx(Card.Footer, {}); } function OryCardContent({ children }) { const { Card } = useComponents(); return /* @__PURE__ */ jsxRuntime.jsx(Card.Content, { children }); } // src/util/internal.ts function replaceWindowFlowId(flow) { const url = new URL(window.location.href); url.searchParams.set("flow", flow); window.location.href = url.toString(); } function isGenericErrorResponse(response) { return typeof response === "object" && !!response && "error" in response && typeof response.error === "object" && !!response.error && "id" in response.error; } function isNeedsPrivilegedSessionError(response) { return isGenericErrorResponse(response) && response.error.id === "session_refresh_required"; } function isSelfServiceFlowExpiredError(response) { return isGenericErrorResponse(response) && response.error.id === "self_service_flow_expired"; } function isBrowserLocationChangeRequired(response) { return isGenericErrorResponse(response) && isGenericErrorResponse(response) && response.error.id === "browser_location_change_required"; } function isAddressNotVerified(response) { return isGenericErrorResponse(response) && response.error.id === "session_verified_address_required"; } function isCsrfError(response) { return isGenericErrorResponse(response) && response.error.id === "security_csrf_violation"; } var isResponseError = (err) => { if (err instanceof clientFetch.ResponseError) { return true; } return typeof err === "object" && !!err && "name" in err && err.name === "ResponseError"; }; var isFetchError = (err) => { return err instanceof clientFetch.FetchError; }; // src/util/sdk-helpers/urlHelpers.ts var verificationUrl = (config) => config.sdk.url + "/self-service/verification/browser"; var handleFlowError = (opts) => async (err) => { var _a; if (!isResponseError(err)) { if (isFetchError(err)) { throw new clientFetch.FetchError( err, "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." ); } throw err; } const contentType = err.response.headers.get("content-type") || ""; if (contentType.includes("application/json")) { const body = await toBody(err.response); if (isSelfServiceFlowExpiredError(body)) { opts.onRestartFlow(body.use_flow_id); return; } else if (isAddressNotVerified(body)) { for (const continueWith of ((_a = body.error.details) == null ? void 0 : _a.continue_with) || []) { if (continueWith.action === "show_verification_ui" && continueWith.flow.url) { opts.onRedirect(continueWith.flow.url, true); return; } } opts.onRedirect(verificationUrl(opts.config), true); return; } else if (isBrowserLocationChangeRequired(body) && body.redirect_browser_to) { opts.onRedirect(body.redirect_browser_to, true); return; } else if (isNeedsPrivilegedSessionError(body) && body.redirect_browser_to) { opts.onRedirect(body.redirect_browser_to, true); return; } else if (isCsrfError(body)) { opts.onRestartFlow(); return; } switch (err.response.status) { case 404: opts.onRestartFlow(); return; case 410: opts.onRestartFlow(); return; case 400: return opts.onValidationError( await err.response.json() ); case 403: opts.onRestartFlow(); return; case 422: { throw new clientFetch.ResponseError( err.response, "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() ); } } throw new clientFetch.ResponseError( err.response, "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() ); } else if ( // Not a JSON response? If it's a text response we will return an error informing the user that the response is not JSON. contentType.includes("text/") || contentType.includes("html") || contentType.includes("xml") ) { await logResponseError(err.response, true); throw new clientFetch.ResponseError( err.response, `The Ory API endpoint returned an unexpected HTML or text response. Check your console output for details.` ); } await logResponseError(err.response, false); throw new clientFetch.ResponseError( err.response, "The Ory API endpoint returned unexpected content type `" + contentType + "`. Check your console output for details." ); }; async function toBody(response) { try { return await response.clone().json(); } catch (e) { await logResponseError(response, true, [e]); throw new clientFetch.ResponseError( response, "Unable to decode API response using JSON." ); } } async function logResponseError(response, printBody, wrap) { console.error("Unable to decode API response", { response: { status: response.status, headers: Object.fromEntries(response.headers.entries()), body: printBody ? await response.clone().text() : void 0 }, errors: wrap }); } // src/util/onSubmitLogin.ts async function onSubmitLogin({ flow }, config, { setFlowContainer, body, onRedirect }) { var _a; if (!config.sdk.url) { throw new Error( `Please supply your Ory Network SDK url to the Ory Elements configuration.` ); } await frontendClient(config.sdk.url, (_a = config.sdk.options) != null ? _a : {}).updateLoginFlowRaw({ flow: flow.id, updateLoginFlowBody: body }).then(() => { var _a2; window.location.href = // eslint-disable-next-line promise/always-return (_a2 = flow.return_to) != null ? _a2 : config.sdk.url + "/self-service/login/browser"; }).catch( handleFlowError({ onRestartFlow: (useFlowId) => { if (useFlowId) { replaceWindowFlowId(useFlowId); } else { onRedirect(clientFetch.loginUrl(config), true); } }, onValidationError: (body2) => { setFlowContainer({ flow: body2, flowType: clientFetch.FlowType.Login }); }, onRedirect, config }) ); } async function onSubmitRecovery({ flow }, config, { setFlowContainer, body, onRedirect }) { await config.sdk.frontend.updateRecoveryFlowRaw({ flow: flow.id, updateRecoveryFlowBody: body }).then(async (res) => { const flow2 = await res.value(); const didContinueWith = clientFetch.handleContinueWith(flow2.continue_with, { onRedirect }); if (didContinueWith) { return; } setFlowContainer({ flow: flow2, flowType: clientFetch.FlowType.Recovery }); }).catch( handleFlowError({ onRestartFlow: (useFlowId) => { if (useFlowId) { replaceWindowFlowId(useFlowId); } else { onRedirect(clientFetch.recoveryUrl(config), true); } }, onValidationError: (body2) => { if ("error" in body2) { handleContinueWithRecoveryUIError(body2.error, config, onRedirect); return; } else { setFlowContainer({ flow: body2, flowType: clientFetch.FlowType.Recovery }); } }, onRedirect, config }) ); } function handleContinueWithRecoveryUIError(error, config, onRedirect) { if ("continue_with" in error.details && Array.isArray(error.details.continue_with)) { const continueWithRecovery = error.details.continue_with.find(clientFetch.instanceOfContinueWithRecoveryUi); if ((continueWithRecovery == null ? void 0 : continueWithRecovery.action) === "show_recovery_ui") { onRedirect( config.project.recovery_ui_url + "?flow=" + (continueWithRecovery == null ? void 0 : continueWithRecovery.flow.id), false ); return; } } onRedirect(clientFetch.recoveryUrl(config), true); } async function onSubmitRegistration({ flow }, config, { setFlowContainer, body, onRedirect }) { await config.sdk.frontend.updateRegistrationFlowRaw({ flow: flow.id, updateRegistrationFlowBody: body }).then(async (res) => { const body2 = await res.value(); const didContinueWith = clientFetch.handleContinueWith(body2.continue_with, { onRedirect }); if (didContinueWith) { return; } onRedirect(clientFetch.registrationUrl(config), true); }).catch( handleFlowError({ onRestartFlow: (useFlowId) => { if (useFlowId) { replaceWindowFlowId(useFlowId); } else { onRedirect(clientFetch.registrationUrl(config), true); } }, onValidationError: (body2) => { setFlowContainer({ flow: body2, flowType: clientFetch.FlowType.Registration }); }, onRedirect, config }) ); } async function onSubmitSettings({ flow }, config, { setFlowContainer, body, onRedirect }) { await config.sdk.frontend.updateSettingsFlowRaw({ flow: flow.id, updateSettingsFlowBody: body }).then(async (res) => { const body2 = await res.value(); const didContinueWith = clientFetch.handleContinueWith(body2.continue_with, { onRedirect }); if (didContinueWith) { return; } setFlowContainer({ flow: body2, flowType: clientFetch.FlowType.Settings }); }).catch( handleFlowError({ onRestartFlow: (useFlowId) => { if (useFlowId) { replaceWindowFlowId(useFlowId); } else { onRedirect(clientFetch.settingsUrl(config), true); } }, onValidationError: (body2) => { setFlowContainer({ flow: body2, flowType: clientFetch.FlowType.Settings }); }, onRedirect, config }) ).catch((err) => { if (clientFetch.isResponseError(err)) { if (err.response.status === 401) { return onRedirect( clientFetch.loginUrl(config) + "?return_to=" + clientFetch.settingsUrl(config), true ); } throw err; } }); } async function onSubmitVerification({ flow }, config, { setFlowContainer, body, onRedirect }) { await config.sdk.frontend.updateVerificationFlowRaw({ flow: flow.id, updateVerificationFlowBody: body }).then( async (res) => setFlowContainer({ flow: await res.value(), flowType: clientFetch.FlowType.Verification }) ).catch( handleFlowError({ onRestartFlow: (useFlowId) => { if (useFlowId) { replaceWindowFlowId(useFlowId); } else { onRedirect(clientFetch.verificationUrl(config), true); } }, onValidationError: (body2) => { setFlowContainer({ flow: body2, flowType: clientFetch.FlowType.Verification }); }, onRedirect, config }) ); } // src/components/form/useOryFormSubmit.ts var supportsSelectAccountPrompt = ["google", "github"]; function useOryFormSubmit(onAfterSubmit) { const flowContainer = useOryFlow(); const methods = reactHookForm.useFormContext(); const config = useOryConfiguration(); const handleSuccess = (flow) => { flowContainer.setFlowContainer(flow); methods.reset(computeDefaultValues(flow.flow.ui.nodes)); }; const onRedirect = (url, _external) => { window.location.assign(url); }; const onSubmit = async (data) => { switch (flowContainer.flowType) { case clientFetch.FlowType.Login: { const submitData = { ...data }; if (submitData.method === "code" && data.code) { submitData.resend = ""; } await onSubmitLogin(flowContainer, config, { onRedirect, setFlowContainer: handleSuccess, body: submitData }); break; } case clientFetch.FlowType.Registration: { const submitData = { ...data }; if (submitData.method === "code" && submitData.code) { submitData.resend = ""; } await onSubmitRegistration(flowContainer, config, { onRedirect, setFlowContainer: handleSuccess, body: submitData }); break; } case clientFetch.FlowType.Verification: await onSubmitVerification(flowContainer, config, { onRedirect, setFlowContainer: handleSuccess, body: data }); break; case clientFetch.FlowType.Recovery: { const submitData = { ...data }; if (data.code) { submitData.email = ""; } await onSubmitRecovery(flowContainer, config, { onRedirect, setFlowContainer: handleSuccess, body: submitData }); break; } case clientFetch.FlowType.Settings: { const submitData = { ...data }; if ("totp_unlink" in submitData) { submitData.method = "totp"; } if ("lookup_secret_confirm" in submitData || "lookup_secret_reveal" in submitData || "lookup_secret_regenerate" in submitData || "lookup_secret_disable" in submitData) { submitData.method = "lookup_secret"; } if (submitData.method === clientFetch.UiNodeGroupEnum.Oidc && submitData.link && supportsSelectAccountPrompt.includes(submitData.link)) { submitData.upstream_parameters = { prompt: "select_account" }; } if ("webauthn_remove" in submitData) { submitData.method = "webauthn"; } if ("passkey_remove" in submitData) { submitData.method = "passkey"; } await onSubmitSettings(flowContainer, config, { onRedirect, setFlowContainer: handleSuccess, body: submitData }); break; } case clientFetch.FlowType.OAuth2Consent: { const response = await fetch(flowContainer.flow.ui.action, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" } }); const oauth2Success = await response.json(); if (oauth2Success.redirect_to && typeof oauth2Success.redirect_to === "string") { onRedirect(oauth2Success.redirect_to); } } } if ("password" in data) { methods.setValue("password", ""); } if ("code" in data) { methods.setValue("code", ""); } if ("totp_code" in data) { methods.setValue("totp_code", ""); } onAfterSubmit == null ? void 0 : onAfterSubmit(data.method); }; return onSubmit; } function OryForm({ children, onAfterSubmit }) { const { Form } = useComponents(); const flowContainer = useOryFlow(); const methods = reactHookForm.useFormContext(); const { Message } = useComponents(); const intl = reactIntl.useIntl(); const onSubmit = useOryFormSubmit(onAfterSubmit); const hasMethods = flowContainer.flow.ui.nodes.some((node) => { if (clientFetch.isUiNodeInputAttributes(node.attributes)) { if (node.attributes.type === "hidden") { return false; } return node.attributes.name !== "csrf_token"; } else if (clientFetch.isUiNodeAnchorAttributes(node.attributes)) { return true; } else if (clientFetch.isUiNodeImageAttributes(node.attributes)) { return true; } else if (clientFetch.isUiNodeScriptAttributes(node.attributes)) { return true; } return false; }); if (!hasMethods) { const m = { id: 5000002, text: intl.formatMessage({ id: `identities.messages.${5000002}`, defaultMessage: "No authentication methods are available for this request. Please contact the site or app owner." }), type: "error" }; return /* @__PURE__ */ jsxRuntime.jsx(Message.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(Message.Content, { message: m }, m.id) }); } if ((flowContainer.flowType === clientFetch.FlowType.Login || flowContainer.flowType === clientFetch.FlowType.Registration) && flowContainer.formState.current === "method_active" && flowContainer.formState.method === "code") { methods.setValue("method", "code"); } return /* @__PURE__ */ jsxRuntime.jsx( Form.Root, { action: flowContainer.flow.ui.action, method: flowContainer.flow.ui.method, onSubmit: (e) => void methods.handleSubmit(onSubmit)(e), children } ); } function OryCardValidationMessages({ hiddenMessageIds = [ 1040009, 1060003, 1080003, 1010004, 1010014, 1040005, 1010016, 1010003 ] }) { var _a; const { flow } = useOryFlow(); const messages = (_a = flow.ui.messages) == null ? void 0 : _a.filter( (m) => !hiddenMessageIds.includes(m.id) ); const { Message } = useComponents(); if (!messages) { return null; } return /* @__PURE__ */ jsxRuntime.jsx(Message.Root, { children: messages == null ? void 0 : messages.map((message) => /* @__PURE__ */ jsxRuntime.jsx(Message.Content, { message }, message.id)) }); } var NodeInput = ({ node, attributes }) => { var _a; const { Node: Node2 } = useComponents(); const { setValue, watch } = reactHookForm.useFormContext(); const { onloadTrigger, onclickTrigger, // These properties do not exist on input fields so we remove them (as we already have handled them). onclick: _ignoredOnclick, onload: _ignoredOnload, // ...attrs } = attributes; const isResendNode = ((_a = node.meta.label) == null ? void 0 : _a.id) === 1070008; const isScreenSelectionNode2 = "name" in node.attributes && node.attributes.name === "screen"; const setFormValue = () => { if (attrs.value && !(isResendNode || isScreenSelectionNode2 || node.group === clientFetch.UiNodeGroupEnum.Oauth2Consent)) { setValue(attrs.name, attrs.value); } }; const hasRun = react.useRef(false); react.useEffect( () => { setFormValue(); if (!hasRun.current && onloadTrigger) { hasRun.current = true; triggerToWindowCall(onloadTrigger); } }, // TODO(jonas): make sure onloadTrigger is stable // eslint-disable-next-line react-hooks/exhaustive-deps -- ignore onloadTrigger for now, until we make sure this is stable [] ); const handleClick = () => { setFormValue(); if (onclickTrigger) { triggerToWindowCall(onclickTrigger); } }; const isSocial = (attrs.name === "provider" || attrs.name === "link") && (node.group === clientFetch.UiNodeGroupEnum.Oidc || node.group === clientFetch.UiNodeGroupEnum.Saml); const isPinCodeInput = attrs.name === "code" && node.group === "code" || attrs.name === "totp_code" && node.group === "totp"; const handleScopeChange = (checked) => { const scopes = watch("grant_scope"); if (Array.isArray(scopes)) { if (checked) { setValue("grant_scope", Array.from(/* @__PURE__ */ new Set([...scopes, attrs.value]))); } else { setValue( "grant_scope", scopes.filter((scope) => scope !== attrs.value) ); } } }; switch (attributes.type) { case clientFetch.UiNodeInputAttributesTypeEnum.Submit: case clientFetch.UiNodeInputAttributesTypeEnum.Button: if (isSocial) { return /* @__PURE__ */ jsxRuntime.jsx( Node2.SsoButton, { node, attributes: attrs, onClick: () => { setValue( "provider", node.attributes.value ); setValue("method", node.group); } } ); } if (isResendNode || isScreenSelectionNode2) { return null; } if (node.group === "oauth2_consent") { return null; } return /* @__PURE__ */ jsxRuntime.jsx( Node2.Label, { attributes: { ...attrs, label: void 0 }, node: { ...node, meta: { ...node.meta, label: void 0 } }, children: /* @__PURE__ */ jsxRuntime.jsx(Node2.Button, { attributes: attrs, node, onClick: handleClick }) } ); case clientFetch.UiNodeInputAttributesTypeEnum.DatetimeLocal: throw new Error("Not implemented"); case clientFetch.UiNodeInputAttributesTypeEnum.Checkbox: if (node.group === "oauth2_consent" && node.attributes.node_type === "input") { switch (node.attributes.name) { case "grant_scope": return /* @__PURE__ */ jsxRuntime.jsx( Node2.ConsentScopeCheckbox, { attributes: attrs, node, onCheckedChange: handleScopeChange } ); default: return null; } } return /* @__PURE__ */ jsxRuntime.jsx( Node2.Label, { attributes: { ...attrs, label: void 0 }, node: { ...node, meta: { ...node.meta, label: void 0 } }, children: /* @__PURE__ */ jsxRuntime.jsx(Node2.Checkbox, { attributes: attrs, node, onClick: handleClick }) } ); case clientFetch.UiNodeInputAttributesTypeEnum.Hidden: return /* @__PURE__ */ jsxRuntime.jsx(Node2.Input, { attributes: attrs, node, onClick: handleClick }); default: if (isPinCodeInput) { return /* @__PURE__ */ jsxRuntime.jsx(Node2.Label, { attributes: attrs, node, children: /* @__PURE__ */ jsxRuntime.jsx( Node2.CodeInput, { attributes: attrs, node, onClick: handleClick } ) }); } return /* @__PURE__ */ jsxRuntime.jsx(Node2.Label, { attributes: attrs, node, children: /* @__PURE__ */ jsxRuntime.jsx(Node2.Input, { attributes: attrs, node, onClick: handleClick }) }); } }; var Node = ({ node, onClick }) => { const { Node: Node2 } = useComponents(); if (node.group === clientFetch.UiNodeGroupEnum.Captcha) { return /* @__PURE__ */ jsxRuntime.jsx(Node2.Captcha, { node }); } if (clientFetch.isUiNodeImageAttributes(node.attributes)) { return /* @__PURE__ */ jsxRuntime.jsx(Node2.Image, { node, attributes: node.attributes }); } else if (clientFetch.isUiNodeTextAttributes(node.attributes)) { const attrs = node.attributes; return /* @__PURE__ */ jsxRuntime.jsx(Node2.Text, { attributes: attrs, node }); } else if (clientFetch.isUiNodeInputAttributes(node.attributes)) { return /* @__PURE__ */ jsxRuntime.jsx(NodeInput, { node, attributes: node.attributes, onClick }); } else if (clientFetch.isUiNodeAnchorAttributes(node.attributes)) { return /* @__PURE__ */ jsxRuntime.jsx(Node2.Anchor, { attributes: node.attributes, node }); } else if (clientFetch.isUiNodeScriptAttributes(node.attributes)) { const { crossorigin, referrerpolicy, node_type: _nodeType, ...attributes } = node.attributes; return /* @__PURE__ */ jsxRuntime.jsx( "script", { crossOrigin: crossorigin, referrerPolicy: referrerpolicy, ...attributes } ); } return null; }; function MethodActiveForm({ formState }) { const { Form } = useComponents(); const { flow, flowType, dispatchFormState } = useOryFlow(); const { ui } = flow; const nodeSorter = useNodeSorter(); const sortNodes = (a, b) => nodeSorter(a, b, { flowType }); const groupsToShow = useNodeGroupsWithVisibleNodes(ui.nodes); const finalNodes = getFinalNodes(groupsToShow, formState.method); return /* @__PURE__ */ jsxRuntime.jsxs(OryCard, { children: [ /* @__PURE__ */ jsxRuntime.jsx(OryCardHeader, {}), /* @__PURE__ */ jsxRuntime.jsxs(OryCardContent, { children: [ /* @__PURE__ */ jsxRuntime.jsx(OryCardValidationMessages, {}), /* @__PURE__ */ jsxRuntime.jsx(OryForm, { onAfterSubmit: handleAfterFormSubmit(dispatchFormState), children: /* @__PURE__ */ jsxRuntime.jsxs(Form.Group, { children: [ ui.nodes.filter( (n) => clientFetch.isUiNodeScriptAttributes(n.attributes) || n.group === clientFetch.UiNodeGroupEnum.Default || n.group === clientFetch.UiNodeGroupEnum.Profile ).map((node, k) => /* @__PURE__ */ jsxRuntime.jsx(Node, { node }, k)), finalNodes.sort(sortNodes).map((node, k) => /* @__PURE__ */ jsxRuntime.jsx(Node, { node }, k)) ] }) }) ] }), /* @__PURE__ */ jsxRuntime.jsx(OryCardFooter, {}) ] }); } function OryFormSsoButtons() { const { flow: { ui } } = useOryFlow(); const { setValue } = reactHookForm.useFormContext(); const filteredNodes = ui.nodes.filter( (node) => node.group === clientFetch.UiNodeGroupEnum.Oidc || node.group === clientFetch.UiNodeGroupEnum.Saml ); const { Form, Node: Node2 } = useComponents(); if (filteredNodes.length === 0) { return null; } return /* @__PURE__ */ jsxRuntime.jsx(Form.SsoRoot, { nodes: filteredNodes, children: filteredNodes.map((node) => /* @__PURE__ */ jsxRuntime.jsx( Node2.SsoButton, { node, attributes: node.attributes, onClick: () => { setValue( "provider", node.attributes.value ); setValue("method", node.group); } }, clientFetch.getNodeId(node) )) }); } function OryFormSsoForm() { const { flow: { ui } } = useOryFlow(); const filteredNodes = ui.nodes.filter( (node) => node.group === clientFetch.UiNodeGroupEnum.Saml || node.group === clientFetch.UiNodeGroupEnum.Oidc ); if (filteredNodes.length === 0) { return null; } return /* @__PURE__ */ jsxRuntime.jsx(OryFormProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(OryForm, { "data-testid": `ory/form/methods/oidc-saml`, children: /* @__PURE__ */ jsxRuntime.jsx(OryFormSsoButtons, {}) }) }); } function ProvideIdentifierForm() { const { Form, Card } = useComponents(); const { flowType, flow, dispatchFormState } = useOryFlow(); const nodeSorter = useNodeSorter(); const sortNodes = (a, b) => nodeSorter(a, b, { flowType }); const nonSsoNodes = withoutSingleSignOnNodes(flow.ui.nodes).sort(sortNodes); const hasSso = flow.ui.nodes.filter(isNodeVisible).some( (node) => node.group === clientFetch.UiNodeGroupEnum.Oidc || node.group === clientFetch.UiNodeGroupEnum.Saml ); const showSsoDivider = hasSso && nonSsoNodes.some(isNodeVisible); return /* @__PURE__ */ jsxRuntime.jsxs(OryCard, { children: [ /* @__PURE__ */ jsxRuntime.jsx(OryCardHeader, {}), /* @__PURE__ */ jsxRuntime.jsxs(OryCardContent, { children: [ /* @__PURE__ */ jsxRuntime.jsx(OryCardValidationMessages, {}), /* @__PURE__ */ jsxRuntime.jsx(OryFormSsoForm, {}), /* @__PURE__ */ jsxRuntime.jsx(OryForm, { onAfterSubmit: handleAfterFormSubmit(dispatchFormState), children: /* @__PURE__ */ jsxRuntime.jsxs(Form.Group, { children: [ showSsoDivider && /* @__PURE__ */ jsxRuntime.jsx(Card.Divider, {}), nonSsoNodes.map((node, k) => /* @__PURE__ */ jsxRuntime.jsx(Node, { node }, k)) ] }) }) ] }), /* @__PURE__ */ jsxRuntime.jsx(OryCardFooter, {}) ] }); } function AuthMethodList({ options, setSelectedGroup }) { const { Card } = useComponents(); const { setValue, getValues } = reactHookForm.useFormContext(); if (Object.entries(options).length === 0) {