diff --git a/clients/client/typescript-fetch/.openapi-generator/FILES b/clients/client/typescript-fetch/.openapi-generator/FILES index 19840b5d59..4ad8b7f83c 100644 --- a/clients/client/typescript-fetch/.openapi-generator/FILES +++ b/clients/client/typescript-fetch/.openapi-generator/FILES @@ -17,6 +17,13 @@ src/apis/RelationshipApi.ts src/apis/WellknownApi.ts src/apis/WorkspaceApi.ts src/apis/index.ts +src/contrib/continueWith.ts +src/contrib/error.ts +src/contrib/flowTypes.ts +src/contrib/index.ts +src/contrib/ui.ts +src/contrib/urlHelpers.ts +src/contrib/utils.ts src/index.ts src/models/AcceptOAuth2ConsentRequest.ts src/models/AcceptOAuth2ConsentRequestSession.ts diff --git a/clients/client/typescript-fetch/src/contrib/continueWith.ts b/clients/client/typescript-fetch/src/contrib/continueWith.ts new file mode 100644 index 0000000000..a4f05234d3 --- /dev/null +++ b/clients/client/typescript-fetch/src/contrib/continueWith.ts @@ -0,0 +1,146 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { + ContinueWith, + ContinueWithRecoveryUi, + ContinueWithSetOrySessionToken, + ContinueWithSettingsUi, + ContinueWithVerificationUi, + ContinueWithRedirectBrowserTo, +} from "../"; + +export type OnRedirectHandler = (url: string, external: boolean) => void; + +// The order in which the actions are defined here is the order in which they are expected to be executed. +const continueWithPriority = [ + "show_settings_ui", + "show_recovery_ui", + "show_verification_ui", + "redirect_browser_to", + "set_ory_session_token", +]; + +export function handleContinueWith( + continueWith: ContinueWith[] | undefined, + { onRedirect }: { onRedirect: OnRedirectHandler } +): boolean { + if (!continueWith || continueWith.length === 0) { + return false; + } + + const action = pickBestContinueWith(continueWith); + if (!action) { + return false; + } + + const redirectFlow = (id: string, flow: string, url?: string) => { + if (url) { + onRedirect(url, true); + return true; + } + + onRedirect("/" + flow + "?flow=" + id, false); + return true; + }; + + if (isSetOrySessionToken(action)) { + throw new Error("Ory Elements does not support API flows yet."); + } else if (isRedirectBrowserTo(action) && action.redirect_browser_to) { + // console.log("Redirecting to", action.redirect_browser_to) + onRedirect(action.redirect_browser_to, true); + return true; + } else if (isShowVerificationUi(action)) { + return redirectFlow(action.flow.id, "verification", action.flow.url); + } else if (isShowRecoveryUi(action)) { + return redirectFlow(action.flow.id, "recovery", action.flow.url); + } else if (isShowSettingsUi(action)) { + // TODO: re-add url + return redirectFlow(action.flow.id, "settings"); + } else { + throw new Error("Unknown action: " + JSON.stringify(action)); + } +} + +/** + * Picks the best continue with action from the list of continue with actions. + * + * @param continueWith - The list of continue with actions. + */ +export function pickBestContinueWith(continueWith: ContinueWith[]) { + if (!continueWith || continueWith.length === 0) { + return; + } + + const sorted = continueWith.sort( + (a, b) => + continueWithPriority.indexOf(a.action) - + continueWithPriority.indexOf(b.action) + ); + return sorted[0]; +} + +/** + * Checks if the continue with action is to set the Ory Session Token. + * + * @param continueWith - The continue with action. + */ +export function isSetOrySessionToken( + continueWith: ContinueWith +): continueWith is ContinueWithSetOrySessionToken & { + action: "set_ory_session_token"; +} { + return continueWith.action === "set_ory_session_token"; +} + +/** + * Checks if the continue with action is to redirect the browser to a different page. + * + * @param continueWith - The continue with action. + */ +export function isRedirectBrowserTo( + continueWith: ContinueWith +): continueWith is ContinueWithRedirectBrowserTo & { + action: "redirect_browser_to"; +} { + return continueWith.action === "redirect_browser_to"; +} + +/** + * Checks if the continue with action is to show the recovery UI. + * + * @param continueWith - The continue with action. + */ +export function isShowRecoveryUi( + continueWith: ContinueWith +): continueWith is ContinueWithRecoveryUi & { + action: "show_recovery_ui"; +} { + return continueWith.action === "show_recovery_ui"; +} + +/** + * Checks if the continue with action is to show the settings UI. + * + * @param continueWith - The continue with action. + */ +export function isShowSettingsUi( + continueWith: ContinueWith +): continueWith is ContinueWithSettingsUi & { + action: "show_settings_ui"; +} { + return continueWith.action === "show_settings_ui"; +} + +/** + * Checks if the continue with action is to show the verification UI. + * + * @param continueWith - The continue with action. + */ +export function isShowVerificationUi( + continueWith: ContinueWith +): continueWith is ContinueWithVerificationUi & { + action: "show_verification_ui"; +} { + return continueWith.action === "show_verification_ui"; +} diff --git a/clients/client/typescript-fetch/src/contrib/error.ts b/clients/client/typescript-fetch/src/contrib/error.ts new file mode 100644 index 0000000000..31b50b1de8 --- /dev/null +++ b/clients/client/typescript-fetch/src/contrib/error.ts @@ -0,0 +1,227 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { + ErrorBrowserLocationChangeRequired, + ErrorFlowReplaced, + GenericError, + NeedsPrivilegedSessionError, + ResponseError, + SelfServiceFlowExpiredError, +} from "../"; + +export function isGenericErrorResponse( + response: unknown +): response is { error: GenericError } { + return ( + typeof response === "object" && + !!response && + "error" in response && + typeof response.error === "object" && + !!response.error && + "id" in response.error + ); +} + +/** + * Checks if the response is a NeedsPrivilegedSessionError. This error is returned when the self-service flow requires + * the user to re-authenticate in order to perform an action that requires elevated privileges. + * + * @param response - The response to check. + */ +export function isNeedsPrivilegedSessionError( + response: unknown +): response is NeedsPrivilegedSessionError { + return ( + isGenericErrorResponse(response) && + response.error.id === "session_refresh_required" + ); +} + +/**1 + * Checks if the response is a SelfServiceFlowExpiredError. This error is returned when the self-service flow is expired. + * + * @param response - The response to check. + */ +export function isSelfServiceFlowExpiredError( + response: unknown +): response is SelfServiceFlowExpiredError { + return ( + isGenericErrorResponse(response) && + response.error.id === "self_service_flow_expired" + ); +} + +/** + * Checks if the response is a GenericError due to the self-service flow being disabled (for example disabled registration). + * + * @param response - The response to check. + */ +export function isSelfServiceFlowDisabled( + response: unknown +): response is GenericError { + return ( + isGenericErrorResponse(response) && + isGenericErrorResponse(response) && + response.error.id === "self_service_flow_disabled" + ); +} + +/** + * Checks if the response is a ErrorBrowserLocationChangeRequired. + * @param response - The response to check. + */ +export function isBrowserLocationChangeRequired( + response: unknown +): response is ErrorBrowserLocationChangeRequired { + return ( + isGenericErrorResponse(response) && + isGenericErrorResponse(response) && + response.error.id === "browser_location_change_required" + ); +} + +/** + * Checks if the response is a ErrorFlowReplaced. + * @param response - The response to check. + */ +export function isSelfServiceFlowReplaced( + response: unknown +): response is ErrorFlowReplaced { + return ( + isGenericErrorResponse(response) && + response.error.id === "self_service_flow_replaced" + ); +} + +/** + * Checks if the response is a GenericError due to the session already being available. + * @param response - The response to check. + */ +export function isSessionAlreadyAvailable( + response: unknown +): response is GenericError { + return ( + isGenericErrorResponse(response) && + response.error.id === "session_already_available" + ); +} + +/** + * Checks if the response is a GenericError due to the session being inactive. + * + * @param response - The response to check. + */ +export function isAddressNotVerified( + response: unknown +): response is GenericError { + return ( + isGenericErrorResponse(response) && + response.error.id === "session_verified_address_required" + ); +} + +/** + * Checks if the response is a GenericError due to the session already having fulfilled the AAL requirement. + * + * @param response - The response to check. + */ +export function isAalAlreadyFulfilled( + response: unknown +): response is GenericError { + return ( + isGenericErrorResponse(response) && + response.error.id === "session_aal_already_fulfilled" + ); +} + +/** + * Checks if the response is a GenericError due to the session requiring a higher AAL. + * + * @param response - The response to check. + */ +export function isSessionAal1Required( + response: unknown +): response is GenericError { + return ( + isGenericErrorResponse(response) && + response.error.id === "session_aal1_required" + ); +} + +/** + * Checks if the response is a GenericError due to the session requiring a higher AAL. + * + * @param response - The response to check. + */ +export function isSessionAal2Required( + response: unknown +): response is GenericError { + return ( + isGenericErrorResponse(response) && + response.error.id === "session_aal2_required" + ); +} + +/** + * Checks if the response is a GenericError due to the session being inactive. + * + * @param response - The response to check. + */ +export function isNoActiveSession(response: unknown): response is GenericError { + return ( + isGenericErrorResponse(response) && response.error.id === "session_inactive" + ); +} +/** + * Checks if the response is a GenericError due to a CSRF violation. + * + * @param response - The response to check. + */ +export function isCsrfError(response: unknown): response is GenericError { + return ( + isGenericErrorResponse(response) && + response.error.id === "security_csrf_violation" + ); +} + +/** + * Checks if the response is a GenericError due to the redirect URL being forbidden. + * + * @param response - The response to check. + */ +export function isRedirectUrlNotAllowed( + response: unknown +): response is GenericError { + return ( + isGenericErrorResponse(response) && + response.error.id === "self_service_flow_return_to_forbidden" + ); +} + +/** + * Checks if the response is a GenericError due to two sessions being active. + * + * @param response - The response to check. + */ +export function isSecurityIdentityMismatch( + response: unknown +): response is GenericError { + return ( + isGenericErrorResponse(response) && + response.error.id === "security_identity_mismatch" + ); +} + +export const isResponseError = (err: unknown): err is ResponseError => { + if (err instanceof ResponseError) { + return true; + } + + return ( + typeof err === "object" && + !!err && + "name" in err && + err.name === "ResponseError" + ); +}; diff --git a/clients/client/typescript-fetch/src/contrib/flowTypes.ts b/clients/client/typescript-fetch/src/contrib/flowTypes.ts new file mode 100644 index 0000000000..29a58448f9 --- /dev/null +++ b/clients/client/typescript-fetch/src/contrib/flowTypes.ts @@ -0,0 +1,11 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +export enum FlowType { + Login = "login", + Registration = "registration", + Recovery = "recovery", + Verification = "verification", + Settings = "settings", + Error = "error", +} diff --git a/clients/client/typescript-fetch/src/contrib/index.ts b/clients/client/typescript-fetch/src/contrib/index.ts new file mode 100644 index 0000000000..dc9fc7f249 --- /dev/null +++ b/clients/client/typescript-fetch/src/contrib/index.ts @@ -0,0 +1,10 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +export * from "./error"; +export * from "./urlHelpers"; +export * from "./flowTypes"; +export * from "./utils"; +export * from "./continueWith"; +export * from "./ui"; +export * from "./client"; diff --git a/clients/client/typescript-fetch/src/contrib/ui.ts b/clients/client/typescript-fetch/src/contrib/ui.ts new file mode 100644 index 0000000000..096a5bc891 --- /dev/null +++ b/clients/client/typescript-fetch/src/contrib/ui.ts @@ -0,0 +1,120 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { + UiNode, + UiNodeAnchorAttributes, + UiNodeImageAttributes, + UiNodeInputAttributes, + UiNodeScriptAttributes, + UiNodeTextAttributes, + UiText, +} from "../"; + +/** + * Returns the node's label. + * + * @param node - the node get the label from + * @returns label of the node + */ +export const getNodeLabel = (node: UiNode): UiText | undefined => { + const attributes = node.attributes; + if (isUiNodeAnchorAttributes(attributes)) { + return attributes.title; + } + + if (isUiNodeImageAttributes(attributes)) { + return node.meta.label; + } + + if (isUiNodeInputAttributes(attributes)) { + if (attributes.label) { + return attributes.label; + } + } + + return node.meta.label; +}; + +type ObjWithNodeType = { + node_type: string; +}; + +/** + * A TypeScript type guard for nodes of the type + * + * @param attrs - the attributes of the node + */ +export function isUiNodeAnchorAttributes( + attrs: ObjWithNodeType +): attrs is UiNodeAnchorAttributes { + return attrs.node_type === "a"; +} + +/** + * A TypeScript type guard for nodes of the type + * + * @param attrs - the attributes of the node + */ +export function isUiNodeImageAttributes( + attrs: ObjWithNodeType +): attrs is UiNodeImageAttributes { + return attrs.node_type === "img"; +} + +/** + * A TypeScript type guard for nodes of the type + * + * @param attrs - the attributes of the node + */ +export function isUiNodeInputAttributes( + attrs: ObjWithNodeType +): attrs is UiNodeInputAttributes { + return attrs.node_type === "input"; +} + +/** + * A TypeScript type guard for nodes of the type `{text}` + * + * @param attrs - the attributes of the node + */ +export function isUiNodeTextAttributes( + attrs: ObjWithNodeType +): attrs is UiNodeTextAttributes { + return attrs.node_type === "text"; +} + +/** + * A TypeScript type guard for nodes of the type