Skip to content

Commit

Permalink
fix: error handling for preact-spa 422 code (ory#74)
Browse files Browse the repository at this point in the history
* fix: package.json build:preact script

* fix: Error handling for 422 code (ory#53)

* fix: Implement error handling

* refactor: Replicate function flow from react

* style: format

* fix: format

* feat: api call generic error handling

* feat: add basic error page

* fix: change default signupURL to /registration

* fix: change /signup to default kratos /registration

Co-authored-by: Alano Terblanche <[email protected]>

Co-authored-by: Alano Terblanche <[email protected]>
  • Loading branch information
misamu and Benehiko authored Jan 26, 2023
1 parent 4c17171 commit 4339b7c
Show file tree
Hide file tree
Showing 10 changed files with 474 additions and 129 deletions.
53 changes: 34 additions & 19 deletions examples/preact-spa/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "./app.css"
import { Typography } from "@ory/elements-preact"
import sdk from "./sdk"
import { sdk, sdkError } from "./sdk"
import { useEffect, useState } from "preact/hooks"
import { Session } from "@ory/client"
import { useLocation } from "wouter"
Expand All @@ -10,23 +10,9 @@ export const Dashboard = () => {
const [logoutUrl, setLogoutUrl] = useState<string>()
const [location, setLocation] = useLocation()

useEffect(() => {
sdk
.toSession()
.then(({ data: session }) => {
setSession(session)
})
.catch((error) => {
if (error.response?.status === 403) {
if (error.response?.data.error.id === "session_aal2_required") {
return setLocation("/login?aal2=true", { replace: true })
}
}
return setLocation("/login", { replace: true })
})
}, [])
const sdkErrorHandler = sdkError(undefined, undefined, "/login")

useEffect(() => {
const createLogoutFlow = () => {
sdk
.createBrowserLogoutFlow(undefined, {
params: {
Expand All @@ -36,8 +22,37 @@ export const Dashboard = () => {
.then(({ data }) => {
setLogoutUrl(data.logout_url)
})
.catch((data) => {
console.error(data)
.catch(sdkErrorHandler)
}

useEffect(() => {
sdk
.toSession()
.then(({ data: session }) => {
// we set the session data which contains the user Identifier and other traits.
setSession(session)
// Set logout flow
createLogoutFlow()
})
.catch(sdkErrorHandler)
.catch((error) => {
// Handle all other errors like error.message "network error" if Kratos can not be connected etc.
if (error.message) {
return setLocation(
`/error?error=${encodeURIComponent(error.message)}`,
{
replace: true,
},
)
}

// Just stringify error and print all data
setLocation(
`/error?error=${encodeURIComponent(JSON.stringify(error))}`,
{
replace: true,
},
)
})
}, [])

Expand Down
43 changes: 43 additions & 0 deletions examples/preact-spa/src/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect, useState } from "preact/hooks"
import { CodeBox, ButtonLink } from "@ory/elements-preact"
import { getSearchParam } from "./sdk"

export const Error = () => {
const [error, setError] = useState<string>()

useEffect(() => {
const queryError = getSearchParam("error")

if (queryError !== null) {
try {
setError(
JSON.stringify(JSON.parse(decodeURIComponent(queryError)), null, 2),
)
} catch (error) {
setError(queryError)
}
} else {
setError("Undefined error")
}
}, [])

// we check if the flow is set, if not we show a loading indicator
return (
<>
<ButtonLink href="/">Home</ButtonLink>

<p>
An error occurred. Please check the error information below and try
again.
</p>
<CodeBox
style={{
overflow: "auto",
maxWidth: "600px",
}}
>
{error}
</CodeBox>
</>
)
}
88 changes: 49 additions & 39 deletions examples/preact-spa/src/login.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,65 @@
import { LoginFlow, UpdateLoginFlowBody } from "@ory/client"
import { UserAuthCard } from "@ory/elements-preact"
import { useCallback, useEffect, useState } from "preact/hooks"
import sdk from "./sdk"
import { sdk, sdkError, getSearchParam } from "./sdk"
import { useLocation } from "wouter"

export const Login = () => {
const [flow, setFlow] = useState<LoginFlow | null>(null)

const [location, setLocation] = useLocation()

const handleFlow = useCallback(
({ refresh, mfa }: { refresh: boolean; mfa: boolean }) => {
return sdk
.createBrowserLoginFlow({ refresh, aal: mfa ? "aal2" : "aal1" })
.then(({ data: flow }) => flow)
},
// Get the flow based on the flowId in the URL (.e.g redirect to this page after flow initialized)
const getFlow = useCallback(
(flowId: string) =>
sdk
.getLoginFlow({ id: flowId })
.then(({ data: flow }) => setFlow(flow))
.catch(sdkErrorHandler),
[],
)

useEffect(() => {
const aal2 = new URLSearchParams(
new URL(window.location.toString()).search,
).get("aal2")
// initialize the sdkError for generic handling of errors
const sdkErrorHandler = sdkError(getFlow, setFlow, "/login", true)

const createFlow = () => {
const aal2 = getSearchParam("aal2")

sdk
.createBrowserLoginFlow({ refresh: true, aal: aal2 ? "aal2" : "aal1" })
.then(({ data: flow }) => setFlow(flow))
.catch(sdkErrorHandler)
}

const isMFA = aal2 ? true : false
handleFlow({ refresh: true, mfa: isMFA })
.then((flow) => setFlow(flow))
.catch((error) => {
switch (error.response?.status) {
case 400:
setFlow(error.response.data)
break
case 410:
case 404:
return setLocation("/login", { replace: true })
}
// submit the login form data to Ory
const submitFlow = (body: UpdateLoginFlowBody) => {
// something unexpected went wrong and the flow was not set
if (!flow) return setLocation("/login", { replace: true })

sdk
.updateLoginFlow({
flow: flow.id,
updateLoginFlowBody: body as UpdateLoginFlowBody,
})
.then(() => {
// we successfully submitted the login flow, so lets redirect to the dashboard
setLocation("/", { replace: true })
})
.catch(sdkErrorHandler)
}

useEffect(() => {
// we might redirect to this page after the flow is initialized, so we check for the flowId in the URL
const flowId = getSearchParam("flow")

// the flow already exists
if (flowId) {
getFlow(flowId).catch(createFlow) // if for some reason the flow has expired, we need to get a new one
return
}

// we assume there was no flow, so we create a new one
createFlow()
}, [])

return flow ? (
Expand All @@ -44,25 +68,11 @@ export const Login = () => {
flowType={"login"}
additionalProps={{
forgotPasswordURL: "/recovery",
signupURL: "/signup",
signupURL: "/registration",
}}
title={"Login"}
includeScripts={true}
onSubmit={({ body }) => {
sdk
.updateLoginFlow({
flow: flow.id,
updateLoginFlowBody: body as UpdateLoginFlowBody,
})
.then(() => {
// we successfully submitted the login flow, so lets redirect to the dashboard
setLocation("/", { replace: true })
})
.catch((error) => {
console.error({ error })
setFlow(error.response.data)
})
}}
onSubmit={({ body }) => submitFlow(body as UpdateLoginFlowBody)}
/>
) : (
<div>Loading...</div>
Expand Down
4 changes: 3 additions & 1 deletion examples/preact-spa/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Recovery } from "./recovery"
import { Register } from "./register"
import { Settings } from "./settings"
import { Verification } from "./verification"
import { Error } from "./error"

// import Ory elements css
import "@ory/elements-preact/style.css"
Expand All @@ -18,10 +19,11 @@ const Main = () => {
<Router>
<Route path="/" component={Dashboard} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Register} />
<Route path="/registration" component={Register} />
<Route path="/verification" component={Verification} />
<Route path="/recovery" component={Recovery} />
<Route path="/settings" component={Settings} />
<Route path="/error" component={Error} />
</Router>
</ThemeProvider>
)
Expand Down
70 changes: 49 additions & 21 deletions examples/preact-spa/src/recovery.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,64 @@
import { RecoveryFlow, UpdateRecoveryFlowBody } from "@ory/client"
import { UserAuthCard } from "@ory/elements-preact"
import { useEffect, useState } from "preact/hooks"
import { useCallback, useEffect, useState } from "preact/hooks"
import { useLocation } from "wouter"
import sdk from "./sdk"
import { getSearchParam, sdk, sdkError } from "./sdk"

export const Recovery = () => {
const [flow, setFlow] = useState<RecoveryFlow | null>(null)

const [location, setLocation] = useLocation()

useEffect(() => {
const getFlow = useCallback(
(flowId: string) =>
sdk
.getRecoveryFlow({ id: flowId })
.then(({ data: flow }) => setFlow(flow))
.catch(sdkErrorHandler),
[],
)

// initialize the sdkError for generic handling of errors
const sdkErrorHandler = sdkError(getFlow, setFlow, "/recovery")

// create a new recovery flow
const createFlow = () => {
sdk
.createBrowserRecoveryFlow()
.then(({ data: flow }) => {
setFlow(flow)
// flow contains the form fields, error messages and csrf token
.then(({ data: flow }) => setFlow(flow))
// something serious went wrong, so we redirect to the recovery page
.catch(sdkErrorHandler)
}

const submitFlow = (body: UpdateRecoveryFlowBody) => {
// something unexpected went wrong and the flow was not set
if (!flow) return setLocation("/login", { replace: true })

sdk
.updateRecoveryFlow({
flow: flow.id,
updateRecoveryFlowBody: body as UpdateRecoveryFlowBody,
})
.catch((error) => {
console.error(error)
.then(() => {
// we successfully submitted the login flow, so lets redirect to the dashboard
setLocation("/", { replace: true })
})
.catch(sdkErrorHandler)
}

useEffect(() => {
// we might redirect to this page after the flow is initialized, so we check for the flowId in the URL
const flowId = getSearchParam("flow")

// the flow already exists
if (flowId) {
getFlow(flowId).catch(createFlow) // if for some reason the flow has expired, we need to get a new one
return
}

// we assume there was no flow, so we create a new one
createFlow()
}, [])

return flow ? (
Expand All @@ -26,20 +67,7 @@ export const Recovery = () => {
flow={flow}
flowType={"recovery"}
additionalProps={{ loginURL: "/login" }}
onSubmit={({ body }) => {
sdk
.updateRecoveryFlow({
flow: flow.id,
updateRecoveryFlowBody: body as UpdateRecoveryFlowBody,
})
.then(() => {
// we successfully submitted the login flow, so lets redirect to the dashboard
setLocation("/", { replace: true })
})
.catch((error) => {
setFlow(error.response.data)
})
}}
onSubmit={({ body }) => submitFlow(body as UpdateRecoveryFlowBody)}
/>
) : (
<div>Loading...</div>
Expand Down
Loading

0 comments on commit 4339b7c

Please sign in to comment.