199 lines
7.7 KiB
TypeScript
199 lines
7.7 KiB
TypeScript
'use client';
|
|
|
|
import { AxiosError } from 'axios';
|
|
import React, { DependencyList, useEffect, useState } from 'react';
|
|
|
|
import { kratos } from './sdk/kratos';
|
|
import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
|
|
|
|
export const HandleError = (
|
|
getFlow:
|
|
| ((flowId: string) => Promise<void | AxiosError>)
|
|
| undefined = undefined,
|
|
setFlow: React.Dispatch<React.SetStateAction<any>> | undefined = undefined,
|
|
defaultNav: string | undefined = undefined,
|
|
fatalToError = false,
|
|
router: AppRouterInstance,
|
|
) => {
|
|
return async (
|
|
error: AxiosError<any, unknown>,
|
|
): Promise<AxiosError | void> => {
|
|
if (!error.response || error.response?.status === 0) {
|
|
window.location.href = `/flow/error?error=${encodeURIComponent(
|
|
JSON.stringify(error.response),
|
|
)}`;
|
|
return Promise.resolve();
|
|
}
|
|
|
|
const responseData = error.response?.data || {};
|
|
|
|
switch (error.response?.status) {
|
|
case 400: {
|
|
if (responseData.error?.id == 'session_already_available') {
|
|
router.push('/');
|
|
return Promise.resolve();
|
|
}
|
|
|
|
// the request could contain invalid parameters which would set error messages in the flow
|
|
if (setFlow !== undefined) {
|
|
console.warn('sdkError 400: update flow data');
|
|
setFlow(responseData);
|
|
return Promise.resolve();
|
|
}
|
|
break;
|
|
}
|
|
// we have no session or the session is invalid
|
|
case 401: {
|
|
console.warn('handleError hook 401: Navigate to /login');
|
|
router.push('/flow/login');
|
|
return Promise.resolve();
|
|
}
|
|
case 403: {
|
|
// the user might have a session, but would require 2FA (Two-Factor Authentication)
|
|
if (responseData.error?.id === 'session_aal2_required') {
|
|
router.push('/flow/login?aal2=true');
|
|
router.refresh();
|
|
return Promise.resolve();
|
|
}
|
|
|
|
if (
|
|
responseData.error?.id === 'session_refresh_required' &&
|
|
responseData.redirect_browser_to
|
|
) {
|
|
console.warn(
|
|
'sdkError 403: Redirect browser to',
|
|
responseData.redirect_browser_to,
|
|
);
|
|
window.location = responseData.redirect_browser_to;
|
|
return Promise.resolve();
|
|
}
|
|
break;
|
|
}
|
|
case 404: {
|
|
console.warn('sdkError 404: Navigate to Error');
|
|
const errorMsg = {
|
|
data: error.response?.data || error,
|
|
status: error.response?.status,
|
|
statusText: error.response?.statusText,
|
|
url: window.location.href,
|
|
};
|
|
|
|
router.push(
|
|
`/flow/error?error=${encodeURIComponent(JSON.stringify(errorMsg))}`,
|
|
);
|
|
return Promise.resolve();
|
|
}
|
|
// error.id handling
|
|
// "self_service_flow_expired"
|
|
case 410: {
|
|
if (getFlow !== undefined && responseData.use_flow_id !== undefined) {
|
|
console.warn('sdkError 410: Update flow');
|
|
return getFlow(responseData.use_flow_id).catch((error) => {
|
|
// Something went seriously wrong - log and redirect to defaultNav if possible
|
|
console.error(error);
|
|
|
|
if (defaultNav !== undefined) {
|
|
router.push(defaultNav);
|
|
} else {
|
|
// Rethrow error when can't navigate and let caller handle
|
|
throw error;
|
|
}
|
|
});
|
|
} else if (defaultNav !== undefined) {
|
|
console.warn('sdkError 410: Navigate to', defaultNav);
|
|
router.push(defaultNav);
|
|
return Promise.resolve();
|
|
}
|
|
break;
|
|
}
|
|
// we need to parse the response and follow the `redirect_browser_to` URL
|
|
// this could be when the user needs to perform a 2FA challenge
|
|
// or passwordless login
|
|
case 422: {
|
|
if (responseData.redirect_browser_to !== undefined) {
|
|
const currentUrl = new URL(window.location.href);
|
|
const redirect = new URL(responseData.redirect_browser_to);
|
|
|
|
// host name has changed, then change location
|
|
if (currentUrl.host !== redirect.host) {
|
|
console.warn('sdkError 422: Host changed redirect');
|
|
window.location = responseData.redirect_browser_to;
|
|
return Promise.resolve();
|
|
}
|
|
|
|
// Path has changed
|
|
if (currentUrl.pathname !== redirect.pathname) {
|
|
console.warn('sdkError 422: Update path');
|
|
router.push(redirect.pathname + redirect.search);
|
|
return Promise.resolve();
|
|
}
|
|
|
|
// for webauthn we need to reload the flow
|
|
const flowId = redirect.searchParams.get('flow');
|
|
|
|
if (flowId != null && getFlow !== undefined) {
|
|
// get new flow data based on the flow id in the redirect url
|
|
console.warn('sdkError 422: Update flow');
|
|
return getFlow(flowId).catch((error) => {
|
|
// Something went seriously wrong - log and redirect to defaultNav if possible
|
|
console.error(error);
|
|
|
|
if (defaultNav !== undefined) {
|
|
router.push(defaultNav);
|
|
} else {
|
|
// Rethrow error when can't navigate and let caller handle
|
|
throw error;
|
|
}
|
|
});
|
|
} else {
|
|
console.warn('sdkError 422: Redirect browser to');
|
|
window.location = responseData.redirect_browser_to;
|
|
return Promise.resolve();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.error(error);
|
|
|
|
if (fatalToError) {
|
|
console.warn('sdkError: fatal error redirect to /error');
|
|
router.push('/flow/error?id=' + encodeURI(error.response?.data.error?.id));
|
|
return Promise.resolve();
|
|
}
|
|
|
|
throw error;
|
|
};
|
|
};
|
|
|
|
// Returns a function which will log the user out
|
|
export function LogoutLink(deps?: DependencyList) {
|
|
|
|
const [logoutToken, setLogoutToken] = useState<string>('');
|
|
|
|
useEffect(() => {
|
|
kratos
|
|
.createBrowserLogoutFlow()
|
|
.then(({ data }) => {
|
|
setLogoutToken(data.logout_token);
|
|
})
|
|
.catch((err: AxiosError) => {
|
|
switch (err.response?.status) {
|
|
case 401:
|
|
// do nothing, the user is not logged in
|
|
return;
|
|
}
|
|
|
|
// Something else happened!
|
|
return Promise.reject(err);
|
|
});
|
|
}, deps);
|
|
|
|
return () => {
|
|
if (logoutToken) {
|
|
kratos
|
|
.updateLogoutFlow({ token: logoutToken })
|
|
.then(() => window.location.href = '/flow/login');
|
|
}
|
|
};
|
|
}
|