Fix unauthenticate user not redirect to login page
fix #31 Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
@@ -11,8 +11,6 @@ import ErrorBoundary from "./components/ErrorBoundary";
|
|||||||
import ConsoleLayout from "./layouts/ConsoleLayout";
|
import ConsoleLayout from "./layouts/ConsoleLayout";
|
||||||
import AuthLayout from "./layouts/AuthLayout";
|
import AuthLayout from "./layouts/AuthLayout";
|
||||||
import { RelayEnvironment } from "./RelayEnvironment";
|
import { RelayEnvironment } from "./RelayEnvironment";
|
||||||
import { AuthProvider } from "./contexts/AuthContext";
|
|
||||||
import { ProtectedRoute } from "./components/ProtectedRoute";
|
|
||||||
|
|
||||||
posthog.init(process.env.POSTHOG_KEY!, {
|
posthog.init(process.env.POSTHOG_KEY!, {
|
||||||
api_host: process.env.POSTHOG_HOST,
|
api_host: process.env.POSTHOG_HOST,
|
||||||
@@ -63,294 +61,290 @@ function App() {
|
|||||||
<RelayEnvironmentProvider environment={RelayEnvironment}>
|
<RelayEnvironmentProvider environment={RelayEnvironment}>
|
||||||
<HelmetProvider>
|
<HelmetProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<AuthProvider>
|
<Routes>
|
||||||
<Routes>
|
{/* Authentication Routes - Accessible without login */}
|
||||||
{/* Authentication Routes - Accessible without login */}
|
<Route
|
||||||
|
path="/login"
|
||||||
|
element={
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<AuthLayout />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Route
|
<Route
|
||||||
path="/login"
|
index
|
||||||
element={
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<AuthLayout />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Route
|
|
||||||
index
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<LoginPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/register"
|
|
||||||
element={
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<AuthLayout />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Route
|
|
||||||
index
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<RegisterPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route element={<ProtectedRoute />}>
|
|
||||||
<Route
|
|
||||||
path="/"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<OrganizationSelectionPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/organizations/create"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<ConsoleLayout />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Route
|
|
||||||
index
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<CreateOrganizationPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/organizations/:organizationId/*"
|
|
||||||
element={
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<ConsoleLayout />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Route
|
|
||||||
index
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<HomePage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="create"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<CreateOrganizationPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="peoples"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<PeopleListPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="peoples/create"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<CreatePeoplePage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="peoples/:peopleId"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<PeopleOverviewPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="vendors"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<VendorListPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="frameworks"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<FrameworkListPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="frameworks/create"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<CreateFrameworkPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="frameworks/:frameworkId"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<FrameworkOverviewPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="frameworks/:frameworkId/update"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<UpdateFrameworkPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="frameworks/:frameworkId/controls/create"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<CreateControlPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="frameworks/:frameworkId/controls/:controlId"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<ControlOverviewPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="frameworks/:frameworkId/controls/:controlId/update"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<UpdateControlPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="vendors/:vendorId"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<VendorOverviewPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{/* Policy Routes */}
|
|
||||||
<Route
|
|
||||||
path="policies"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<PolicyListPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="policies/create"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<CreatePolicyPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="policies/:policyId"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<PolicyOverviewPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="policies/:policyId/update"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<UpdatePolicyPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="settings"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<SettingsPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
</Route>
|
|
||||||
<Route
|
|
||||||
path="*"
|
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<NotFoundPage />
|
<ErrorBoundaryWithLocation>
|
||||||
|
<LoginPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Route>
|
||||||
</AuthProvider>
|
|
||||||
|
<Route
|
||||||
|
path="/register"
|
||||||
|
element={
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<AuthLayout />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route
|
||||||
|
index
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<RegisterPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<OrganizationSelectionPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/organizations/create"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<ConsoleLayout />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route
|
||||||
|
index
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<CreateOrganizationPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/organizations/:organizationId/*"
|
||||||
|
element={
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<ConsoleLayout />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route
|
||||||
|
index
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<HomePage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="create"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<CreateOrganizationPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="peoples"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<PeopleListPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="peoples/create"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<CreatePeoplePage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="peoples/:peopleId"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<PeopleOverviewPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="vendors"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<VendorListPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="frameworks"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<FrameworkListPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="frameworks/create"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<CreateFrameworkPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="frameworks/:frameworkId"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<FrameworkOverviewPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="frameworks/:frameworkId/update"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<UpdateFrameworkPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="frameworks/:frameworkId/controls/create"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<CreateControlPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="frameworks/:frameworkId/controls/:controlId"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<ControlOverviewPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="frameworks/:frameworkId/controls/:controlId/update"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<UpdateControlPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="vendors/:vendorId"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<VendorOverviewPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{/* Policy Routes */}
|
||||||
|
<Route
|
||||||
|
path="policies"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<PolicyListPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="policies/create"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<CreatePolicyPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="policies/:policyId"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<PolicyOverviewPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="policies/:policyId/update"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<UpdatePolicyPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="settings"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<SettingsPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route
|
||||||
|
path="*"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<NotFoundPage />
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</HelmetProvider>
|
</HelmetProvider>
|
||||||
</RelayEnvironmentProvider>
|
</RelayEnvironmentProvider>
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ import {
|
|||||||
Store,
|
Store,
|
||||||
} from "relay-runtime";
|
} from "relay-runtime";
|
||||||
import { buildEndpoint } from "./utils";
|
import { buildEndpoint } from "./utils";
|
||||||
|
import { GraphQLError } from "graphql";
|
||||||
|
|
||||||
|
export class UnAuthenticatedError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("UNAUTHENTICATED");
|
||||||
|
this.name = "UnAuthenticatedError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasUnauthenticatedError = (error: GraphQLError) =>
|
||||||
|
error.extensions?.code == "UNAUTHENTICATED";
|
||||||
|
|
||||||
const fetchRelay: FetchFunction = async (
|
const fetchRelay: FetchFunction = async (
|
||||||
request,
|
request,
|
||||||
@@ -66,7 +77,13 @@ const fetchRelay: FetchFunction = async (
|
|||||||
|
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|
||||||
if (Array.isArray(json.errors)) {
|
if (json.errors) {
|
||||||
|
const errors = json.errors as GraphQLError[];
|
||||||
|
|
||||||
|
if (errors.find(hasUnauthenticatedError)) {
|
||||||
|
throw new UnAuthenticatedError();
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Error fetching GraphQL query '${
|
`Error fetching GraphQL query '${
|
||||||
request.name
|
request.name
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Component, ErrorInfo, ReactNode } from "react";
|
import { Component, ErrorInfo, ReactNode } from "react";
|
||||||
import { ErrorPage } from "@/pages/ErrorPage";
|
import { ErrorPage } from "@/pages/ErrorPage";
|
||||||
|
import { UnAuthenticatedError } from "@/RelayEnvironment";
|
||||||
|
import { Navigate } from "react-router";
|
||||||
|
|
||||||
interface ErrorBoundaryProps {
|
interface ErrorBoundaryProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -9,16 +11,20 @@ interface ErrorBoundaryProps {
|
|||||||
interface ErrorBoundaryState {
|
interface ErrorBoundaryState {
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
error?: Error;
|
error?: Error;
|
||||||
|
isUnAuthenticated: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||||
constructor(props: ErrorBoundaryProps) {
|
constructor(props: ErrorBoundaryProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { hasError: false };
|
this.state = { hasError: false, isUnAuthenticated: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||||
return { hasError: true, error };
|
if (error instanceof UnAuthenticatedError) {
|
||||||
|
return { hasError: true, error, isUnAuthenticated: true };
|
||||||
|
}
|
||||||
|
return { hasError: true, error, isUnAuthenticated: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error: Error, info: ErrorInfo): void {
|
componentDidCatch(error: Error, info: ErrorInfo): void {
|
||||||
@@ -27,6 +33,10 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
|
if (this.state.isUnAuthenticated) {
|
||||||
|
return <Navigate to="/login" replace state={{ authRequired: true }} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
if (this.props.fallback) {
|
if (this.props.fallback) {
|
||||||
return this.props.fallback;
|
return this.props.fallback;
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ import {
|
|||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
|
||||||
import { NavUser_viewer$key } from "./__generated__/NavUser_viewer.graphql";
|
import { NavUser_viewer$key } from "./__generated__/NavUser_viewer.graphql";
|
||||||
import { NavUserSkeleton } from "./NavUserSkeleton";
|
import { NavUserSkeleton } from "./NavUserSkeleton";
|
||||||
|
import { buildEndpoint } from "@/utils";
|
||||||
|
|
||||||
export const navUserFragment = graphql`
|
export const navUserFragment = graphql`
|
||||||
fragment NavUser_viewer on User {
|
fragment NavUser_viewer on User {
|
||||||
@@ -42,13 +42,16 @@ export const navUserFragment = graphql`
|
|||||||
export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
|
export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
|
||||||
const { isMobile } = useSidebar();
|
const { isMobile } = useSidebar();
|
||||||
const currentUser = useFragment(navUserFragment, viewer);
|
const currentUser = useFragment(navUserFragment, viewer);
|
||||||
const { logout } = useAuth();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { organizationId } = useParams();
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
await logout();
|
fetch(buildEndpoint("/auth/logout"), {
|
||||||
navigate("/login");
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
}).then(() => {
|
||||||
|
navigate("/login");
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!organizationId) {
|
if (!organizationId) {
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Navigate, Outlet, useLocation } from "react-router";
|
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
|
||||||
|
|
||||||
export function ProtectedRoute() {
|
|
||||||
const { isAuthenticated, isLoading } = useAuth();
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
// Show nothing while checking authentication
|
|
||||||
if (isLoading) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to login if not authenticated
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
return <Navigate to="/login" replace state={{ from: location }} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the protected content
|
|
||||||
return <Outlet />;
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
import { buildEndpoint } from "@/utils";
|
|
||||||
|
|
||||||
interface AuthContextType {
|
|
||||||
isAuthenticated: boolean;
|
|
||||||
isLoading: boolean;
|
|
||||||
checkAuth: () => Promise<boolean>;
|
|
||||||
logout: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should be remove when backend returns proper authenticated error
|
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const checkAuth = async (): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
// Make a request to an endpoint that requires authentication
|
|
||||||
const response = await fetch(buildEndpoint("/api/console/v1/query"), {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
operationName: "CheckAuth",
|
|
||||||
query: `query CheckAuth { viewer { id } }`,
|
|
||||||
variables: {},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const authenticated =
|
|
||||||
response.ok && !response.headers.get("WWW-Authenticate");
|
|
||||||
setIsAuthenticated(authenticated);
|
|
||||||
return authenticated;
|
|
||||||
} catch {
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const logout = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
await fetch(buildEndpoint("/console/v1/auth/logout"), {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "include",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
navigate("/login");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
checkAuth();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthContext.Provider
|
|
||||||
value={{ isAuthenticated, isLoading, checkAuth, logout }}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</AuthContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAuth(): AuthContextType {
|
|
||||||
const context = useContext(AuthContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error("useAuth must be used within an AuthProvider");
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
@@ -1,50 +1,49 @@
|
|||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Link, useNavigate } from "react-router";
|
import { Link, useLocation } from "react-router";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { buildEndpoint } from "@/utils";
|
import { buildEndpoint } from "@/utils";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const navigate = useNavigate();
|
const location = useLocation();
|
||||||
const { checkAuth } = useAuth();
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (location.state?.authRequired) {
|
||||||
|
toast({
|
||||||
|
title: "Authentication Required",
|
||||||
|
description: "You need to log in to access this resource",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [location, toast]);
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(buildEndpoint("/api/console/v1/auth/login"), {
|
const response = await fetch(
|
||||||
method: "POST",
|
buildEndpoint("/api/console/v1/auth/login"),
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "POST",
|
||||||
},
|
headers: {
|
||||||
body: JSON.stringify({ email, password }),
|
"Content-Type": "application/json",
|
||||||
credentials: "include",
|
},
|
||||||
});
|
body: JSON.stringify({ email, password }),
|
||||||
|
credentials: "include",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
throw new Error(error.message || "Failed to login");
|
throw new Error(error.message || "Failed to login");
|
||||||
}
|
}
|
||||||
|
|
||||||
const authenticated = await checkAuth();
|
|
||||||
if (authenticated) {
|
|
||||||
toast({
|
|
||||||
title: "Success",
|
|
||||||
description: "Logged in successfully",
|
|
||||||
variant: "default",
|
|
||||||
});
|
|
||||||
navigate("/");
|
|
||||||
} else {
|
|
||||||
throw new Error("Authentication failed");
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { buildEndpoint } from "@/utils";
|
import { buildEndpoint } from "@/utils";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
@@ -15,7 +14,6 @@ export default function RegisterPage() {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { checkAuth } = useAuth();
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -32,14 +30,17 @@ export default function RegisterPage() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(buildEndpoint("/api/console/v1/auth/register"), {
|
const response = await fetch(
|
||||||
method: "POST",
|
buildEndpoint("/api/console/v1/auth/register"),
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "POST",
|
||||||
},
|
headers: {
|
||||||
credentials: "include",
|
"Content-Type": "application/json",
|
||||||
body: JSON.stringify({ email, password, fullName }),
|
},
|
||||||
});
|
credentials: "include",
|
||||||
|
body: JSON.stringify({ email, password, fullName }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
@@ -51,10 +52,6 @@ export default function RegisterPage() {
|
|||||||
description: "Account created successfully",
|
description: "Account created successfully",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check authentication status after registration
|
|
||||||
await checkAuth();
|
|
||||||
|
|
||||||
// Redirect to home page after successful registration
|
|
||||||
navigate("/");
|
navigate("/");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -110,7 +110,14 @@ func graphqlHandler(proboSvc *probo.Service, usrmgrSvc *usrmgr.Service, authCfg
|
|||||||
if user == nil {
|
if user == nil {
|
||||||
return func(ctx context.Context) *graphql.Response {
|
return func(ctx context.Context) *graphql.Response {
|
||||||
return &graphql.Response{
|
return &graphql.Response{
|
||||||
Errors: gqlerror.List{gqlerror.Errorf("authentication required")},
|
Errors: gqlerror.List{
|
||||||
|
&gqlerror.Error{
|
||||||
|
Message: "authentication required",
|
||||||
|
Extensions: map[string]any{
|
||||||
|
"code": "UNAUTHENTICATED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user