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 AuthLayout from "./layouts/AuthLayout";
|
||||
import { RelayEnvironment } from "./RelayEnvironment";
|
||||
import { AuthProvider } from "./contexts/AuthContext";
|
||||
import { ProtectedRoute } from "./components/ProtectedRoute";
|
||||
|
||||
posthog.init(process.env.POSTHOG_KEY!, {
|
||||
api_host: process.env.POSTHOG_HOST,
|
||||
@@ -63,294 +61,290 @@ function App() {
|
||||
<RelayEnvironmentProvider environment={RelayEnvironment}>
|
||||
<HelmetProvider>
|
||||
<BrowserRouter>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
{/* Authentication Routes - Accessible without login */}
|
||||
<Routes>
|
||||
{/* Authentication Routes - Accessible without login */}
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<ErrorBoundaryWithLocation>
|
||||
<AuthLayout />
|
||||
</ErrorBoundaryWithLocation>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path="/login"
|
||||
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="*"
|
||||
index
|
||||
element={
|
||||
<Suspense>
|
||||
<NotFoundPage />
|
||||
<ErrorBoundaryWithLocation>
|
||||
<LoginPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</Route>
|
||||
|
||||
<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>
|
||||
</HelmetProvider>
|
||||
</RelayEnvironmentProvider>
|
||||
|
||||
@@ -6,6 +6,17 @@ import {
|
||||
Store,
|
||||
} from "relay-runtime";
|
||||
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 (
|
||||
request,
|
||||
@@ -66,7 +77,13 @@ const fetchRelay: FetchFunction = async (
|
||||
|
||||
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(
|
||||
`Error fetching GraphQL query '${
|
||||
request.name
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Component, ErrorInfo, ReactNode } from "react";
|
||||
import { ErrorPage } from "@/pages/ErrorPage";
|
||||
import { UnAuthenticatedError } from "@/RelayEnvironment";
|
||||
import { Navigate } from "react-router";
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
@@ -9,16 +11,20 @@ interface ErrorBoundaryProps {
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
isUnAuthenticated: boolean;
|
||||
}
|
||||
|
||||
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
this.state = { hasError: false, isUnAuthenticated: false };
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -27,6 +33,10 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
}
|
||||
|
||||
render(): ReactNode {
|
||||
if (this.state.isUnAuthenticated) {
|
||||
return <Navigate to="/login" replace state={{ authRequired: true }} />;
|
||||
}
|
||||
|
||||
if (this.state.hasError) {
|
||||
if (this.props.fallback) {
|
||||
return this.props.fallback;
|
||||
|
||||
@@ -27,9 +27,9 @@ import {
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { NavUser_viewer$key } from "./__generated__/NavUser_viewer.graphql";
|
||||
import { NavUserSkeleton } from "./NavUserSkeleton";
|
||||
import { buildEndpoint } from "@/utils";
|
||||
|
||||
export const navUserFragment = graphql`
|
||||
fragment NavUser_viewer on User {
|
||||
@@ -42,13 +42,16 @@ export const navUserFragment = graphql`
|
||||
export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
|
||||
const { isMobile } = useSidebar();
|
||||
const currentUser = useFragment(navUserFragment, viewer);
|
||||
const { logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const { organizationId } = useParams();
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
navigate("/login");
|
||||
fetch(buildEndpoint("/auth/logout"), {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}).then(() => {
|
||||
navigate("/login");
|
||||
});
|
||||
};
|
||||
|
||||
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 { Link, useNavigate } from "react-router";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Link, useLocation } from "react-router";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { buildEndpoint } from "@/utils";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
|
||||
export default function LoginPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
const { checkAuth } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
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) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(buildEndpoint("/api/console/v1/auth/login"), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
credentials: "include",
|
||||
});
|
||||
const response = await fetch(
|
||||
buildEndpoint("/api/console/v1/auth/login"),
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
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) {
|
||||
toast({
|
||||
title: "Error",
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { buildEndpoint } from "@/utils";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
|
||||
export default function RegisterPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
@@ -15,7 +14,6 @@ export default function RegisterPage() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
const { checkAuth } = useAuth();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -32,14 +30,17 @@ export default function RegisterPage() {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(buildEndpoint("/api/console/v1/auth/register"), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ email, password, fullName }),
|
||||
});
|
||||
const response = await fetch(
|
||||
buildEndpoint("/api/console/v1/auth/register"),
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ email, password, fullName }),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
@@ -51,10 +52,6 @@ export default function RegisterPage() {
|
||||
description: "Account created successfully",
|
||||
});
|
||||
|
||||
// Check authentication status after registration
|
||||
await checkAuth();
|
||||
|
||||
// Redirect to home page after successful registration
|
||||
navigate("/");
|
||||
} catch (error) {
|
||||
toast({
|
||||
|
||||
@@ -110,7 +110,14 @@ func graphqlHandler(proboSvc *probo.Service, usrmgrSvc *usrmgr.Service, authCfg
|
||||
if user == nil {
|
||||
return func(ctx context.Context) *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