refactor: centralize authentication handling

- Replaced direct localStorage token access with a centralized `getBearerToken` function across various components and hooks to improve code maintainability and security.
- Updated API calls to use `authenticatedFetch` for consistent authentication handling.
- Enhanced user experience by ensuring proper redirection to login when authentication fails.
- Cleaned up unused imports and improved overall code structure for better readability.
This commit is contained in:
DESKTOP-RTLN3BA\$punk 2025-12-02 01:24:09 -08:00
parent 6cc9e38e1d
commit b2a97b39ce
35 changed files with 396 additions and 497 deletions

View file

@ -22,6 +22,7 @@ import {
type SearchSourceConnector,
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
import { authenticatedFetch } from "@/lib/auth-utils";
export default function AirtableConnectorPage() {
const router = useRouter();
@ -46,14 +47,9 @@ export default function AirtableConnectorPage() {
const handleConnectAirtable = async () => {
setIsConnecting(true);
try {
const response = await fetch(
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/airtable/connector/add/?space_id=${searchSpaceId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
},
}
{ method: "GET" }
);
if (!response.ok) {

View file

@ -40,6 +40,7 @@ import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
// Assuming useSearchSourceConnectors hook exists and works similarly
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
import { authenticatedFetch, redirectToLogin } from "@/lib/auth-utils";
// Define the form schema with Zod for GitHub PAT entry step
const githubPatFormSchema = z.object({
@ -101,19 +102,11 @@ export default function GithubConnectorPage() {
setConnectorName(values.name); // Store the name
setValidatedPat(values.github_pat); // Store the PAT temporarily
try {
const token = localStorage.getItem("surfsense_bearer_token");
if (!token) {
throw new Error("No authentication token found");
}
const response = await fetch(
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/github/repositories`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ github_pat: values.github_pat }),
}
);

View file

@ -24,6 +24,7 @@ import {
type SearchSourceConnector,
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
import { authenticatedFetch } from "@/lib/auth-utils";
export default function GoogleCalendarConnectorPage() {
const router = useRouter();
@ -51,14 +52,9 @@ export default function GoogleCalendarConnectorPage() {
try {
setIsConnecting(true);
// Call backend to initiate authorization flow
const response = await fetch(
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/google/calendar/connector/add/?space_id=${searchSpaceId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
},
}
{ method: "GET" }
);
if (!response.ok) {

View file

@ -24,6 +24,7 @@ import {
type SearchSourceConnector,
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
import { authenticatedFetch } from "@/lib/auth-utils";
export default function GoogleGmailConnectorPage() {
const router = useRouter();
@ -50,14 +51,9 @@ export default function GoogleGmailConnectorPage() {
try {
setIsConnecting(true);
// Call backend to initiate authorization flow
const response = await fetch(
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/google/gmail/connector/add/?space_id=${searchSpaceId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
},
}
{ method: "GET" }
);
if (!response.ok) {

View file

@ -9,6 +9,7 @@ import { BlockNoteEditor } from "@/components/DynamicBlockNoteEditor";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
interface EditorContent {
document_id: number;
@ -29,28 +30,21 @@ export default function EditorPage() {
const [error, setError] = useState<string | null>(null);
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
// Get auth token
const token =
typeof window !== "undefined" ? localStorage.getItem("surfsense_bearer_token") : null;
// Fetch document content - DIRECT CALL TO FASTAPI
useEffect(() => {
async function fetchDocument() {
const token = getBearerToken();
if (!token) {
console.error("No auth token found");
setError("Please login to access the editor");
setLoading(false);
// Redirect to login with current path saved
redirectToLogin();
return;
}
try {
const response = await fetch(
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${params.search_space_id}/documents/${documentId}/editor-content`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
{ method: "GET" }
);
if (!response.ok) {
@ -84,10 +78,10 @@ export default function EditorPage() {
}
}
if (documentId && token) {
if (documentId) {
fetchDocument();
}
}, [documentId, token]);
}, [documentId, params.search_space_id]);
// Track changes to mark as unsaved
useEffect(() => {
@ -100,8 +94,10 @@ export default function EditorPage() {
// Save and exit - DIRECT CALL TO FASTAPI
const handleSave = async () => {
const token = getBearerToken();
if (!token) {
toast.error("Please login to save");
redirectToLogin();
return;
}
@ -113,14 +109,11 @@ export default function EditorPage() {
setSaving(true);
try {
// Save blocknote_document and trigger reindexing in background
const response = await fetch(
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${params.search_space_id}/documents/${documentId}/save`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ blocknote_document: editorContent }),
}
);

View file

@ -13,6 +13,7 @@ import { OnboardLLMSetup } from "@/components/onboard/onboard-llm-setup";
import { OnboardLoading } from "@/components/onboard/onboard-loading";
import { OnboardStats } from "@/components/onboard/onboard-stats";
import { useGlobalLLMConfigs, useLLMConfigs, useLLMPreferences } from "@/hooks/use-llm-configs";
import { getBearerToken, redirectToLogin } from "@/lib/auth-utils";
const OnboardPage = () => {
const t = useTranslations("onboard");
@ -44,12 +45,13 @@ const OnboardPage = () => {
// Check if user is authenticated
useEffect(() => {
const token = localStorage.getItem("surfsense_bearer_token");
const token = getBearerToken();
if (!token) {
router.push("/login");
// Save current path and redirect to login
redirectToLogin();
return;
}
}, [router]);
}, []);
// Capture onboarding state on first load
useEffect(() => {

View file

@ -1,28 +1,28 @@
"use client";
import { Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { AnnouncementBanner } from "@/components/announcement-banner";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { getBearerToken, redirectToLogin } from "@/lib/auth-utils";
interface DashboardLayoutProps {
children: React.ReactNode;
}
export default function DashboardLayout({ children }: DashboardLayoutProps) {
const router = useRouter();
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
useEffect(() => {
// Check if user is authenticated
const token = localStorage.getItem("surfsense_bearer_token");
const token = getBearerToken();
if (!token) {
router.push("/login");
// Save current path and redirect to login
redirectToLogin();
return;
}
setIsCheckingAuth(false);
}, [router]);
}, []);
// Show loading screen while checking authentication
if (isCheckingAuth) {

View file

@ -36,6 +36,7 @@ import { Spotlight } from "@/components/ui/spotlight";
import { Tilt } from "@/components/ui/tilt";
import { useUser } from "@/hooks";
import { useSearchSpaces } from "@/hooks/use-search-spaces";
import { authenticatedFetch } from "@/lib/auth-utils";
/**
* Formats a date string into a readable format
@ -173,14 +174,9 @@ const DashboardPage = () => {
const handleDeleteSearchSpace = async (id: number) => {
// Send DELETE request to the API
try {
const response = await fetch(
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${id}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
},
}
{ method: "DELETE" }
);
if (!response.ok) {

View file

@ -4,18 +4,17 @@ import { motion } from "motion/react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { SearchSpaceForm } from "@/components/search-space-form";
import { authenticatedFetch } from "@/lib/auth-utils";
export default function SearchSpacesPage() {
const router = useRouter();
const handleCreateSearchSpace = async (data: { name: string; description?: string }) => {
try {
const response = await fetch(
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
},
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: data.name,
description: data.description || "",