diff --git a/apps/console/src/App.tsx b/apps/console/src/App.tsx
index 0e74bd728..432e4bcc1 100644
--- a/apps/console/src/App.tsx
+++ b/apps/console/src/App.tsx
@@ -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() {
-
-
- {/* Authentication Routes - Accessible without login */}
+
+ {/* Authentication Routes - Accessible without login */}
+
+
+
+ }
+ >
-
-
- }
- >
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- >
-
-
-
-
-
- }
- />
-
-
- }>
-
-
-
-
-
- }
- />
-
-
-
-
-
-
- }
- >
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- >
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
- {/* Policy Routes */}
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
+
+
+
}
/>
-
-
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+ {/* Policy Routes */}
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
diff --git a/apps/console/src/RelayEnvironment.ts b/apps/console/src/RelayEnvironment.ts
index c625e7ef7..1995fe1bb 100644
--- a/apps/console/src/RelayEnvironment.ts
+++ b/apps/console/src/RelayEnvironment.ts
@@ -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
diff --git a/apps/console/src/components/ErrorBoundary.tsx b/apps/console/src/components/ErrorBoundary.tsx
index 65a2ce4f6..094f3e378 100644
--- a/apps/console/src/components/ErrorBoundary.tsx
+++ b/apps/console/src/components/ErrorBoundary.tsx
@@ -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 {
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 {
}
render(): ReactNode {
+ if (this.state.isUnAuthenticated) {
+ return ;
+ }
+
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
diff --git a/apps/console/src/components/NavUser.tsx b/apps/console/src/components/NavUser.tsx
index 8e30dd681..41b1ccf8b 100644
--- a/apps/console/src/components/NavUser.tsx
+++ b/apps/console/src/components/NavUser.tsx
@@ -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) {
diff --git a/apps/console/src/components/ProtectedRoute.tsx b/apps/console/src/components/ProtectedRoute.tsx
deleted file mode 100644
index 8d43627f6..000000000
--- a/apps/console/src/components/ProtectedRoute.tsx
+++ /dev/null
@@ -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 ;
- }
-
- // Render the protected content
- return ;
-}
diff --git a/apps/console/src/contexts/AuthContext.tsx b/apps/console/src/contexts/AuthContext.tsx
deleted file mode 100644
index 42b40e109..000000000
--- a/apps/console/src/contexts/AuthContext.tsx
+++ /dev/null
@@ -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;
- logout: () => Promise;
-}
-
-// TODO: should be remove when backend returns proper authenticated error
-
-const AuthContext = createContext(undefined);
-
-export function AuthProvider({ children }: { children: React.ReactNode }) {
- const [isAuthenticated, setIsAuthenticated] = useState(false);
- const [isLoading, setIsLoading] = useState(true);
- const navigate = useNavigate();
-
- const checkAuth = async (): Promise => {
- 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 => {
- try {
- await fetch(buildEndpoint("/console/v1/auth/logout"), {
- method: "POST",
- credentials: "include",
- });
- } finally {
- setIsAuthenticated(false);
- navigate("/login");
- }
- };
-
- useEffect(() => {
- checkAuth();
- }, []);
-
- return (
-
- {children}
-
- );
-}
-
-export function useAuth(): AuthContextType {
- const context = useContext(AuthContext);
- if (context === undefined) {
- throw new Error("useAuth must be used within an AuthProvider");
- }
- return context;
-}
diff --git a/apps/console/src/pages/LoginPage.tsx b/apps/console/src/pages/LoginPage.tsx
index 56b9ff594..1a8d53f09 100644
--- a/apps/console/src/pages/LoginPage.tsx
+++ b/apps/console/src/pages/LoginPage.tsx
@@ -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",
diff --git a/apps/console/src/pages/RegisterPage.tsx b/apps/console/src/pages/RegisterPage.tsx
index b0501c3af..125bbd400 100644
--- a/apps/console/src/pages/RegisterPage.tsx
+++ b/apps/console/src/pages/RegisterPage.tsx
@@ -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({
diff --git a/pkg/server/api/console/v1/resolver.go b/pkg/server/api/console/v1/resolver.go
index 1735a36a8..9e14552b9 100644
--- a/pkg/server/api/console/v1/resolver.go
+++ b/pkg/server/api/console/v1/resolver.go
@@ -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",
+ },
+ },
+ },
}
}
}