diff --git a/www/app/(app)/AuthGuard.tsx b/www/app/(app)/AuthGuard.tsx
new file mode 100644
index 00000000..19af12e7
--- /dev/null
+++ b/www/app/(app)/AuthGuard.tsx
@@ -0,0 +1,70 @@
+"use client";
+
+import { useEffect } from "react";
+import { useRouter, usePathname } from "next/navigation";
+import { signIn } from "next-auth/react";
+import useSessionStatus from "../lib/useSessionStatus";
+import { Flex, Spinner } from "@chakra-ui/react";
+
+interface AuthGuardProps {
+ children: React.ReactNode;
+ requireAuth?: boolean;
+}
+
+// Routes that should be accessible without authentication
+const PUBLIC_ROUTES = ["/transcripts/new"];
+
+export default function AuthGuard({
+ children,
+ requireAuth = true,
+}: AuthGuardProps) {
+ const { isAuthenticated, isLoading, status } = useSessionStatus();
+ const router = useRouter();
+ const pathname = usePathname();
+
+ // Check if current route is public
+ const isPublicRoute = PUBLIC_ROUTES.some((route) =>
+ pathname.startsWith(route),
+ );
+
+ useEffect(() => {
+ // Don't require auth for public routes
+ if (isPublicRoute) return;
+
+ // Only redirect if we're sure the user is not authenticated and auth is required
+ if (!isLoading && requireAuth && status === "unauthenticated") {
+ // Instead of redirecting to /login, trigger NextAuth signIn
+ signIn("authentik");
+ }
+ }, [isLoading, requireAuth, status, isPublicRoute]);
+
+ // For public routes, always show content
+ if (isPublicRoute) {
+ return <>{children}>;
+ }
+
+ // Show loading spinner while checking authentication
+ if (
+ isLoading ||
+ (requireAuth && !isAuthenticated && status !== "unauthenticated")
+ ) {
+ return (
+
+
+
+ );
+ }
+
+ // If authentication is not required or user is authenticated, show content
+ if (!requireAuth || isAuthenticated) {
+ return <>{children}>;
+ }
+
+ // Don't render anything while redirecting
+ return null;
+}
diff --git a/www/app/(app)/layout.tsx b/www/app/(app)/layout.tsx
index 053df5a7..749ce189 100644
--- a/www/app/(app)/layout.tsx
+++ b/www/app/(app)/layout.tsx
@@ -6,6 +6,7 @@ import About from "../(aboutAndPrivacy)/about";
import Privacy from "../(aboutAndPrivacy)/privacy";
import UserInfo from "../(auth)/userInfo";
import { RECORD_A_MEETING_URL } from "../lib/constants";
+import AuthGuard from "./AuthGuard";
export default async function AppLayout({
children,
@@ -90,7 +91,7 @@ export default async function AppLayout({
- {children}
+ {children}
);
}
diff --git a/www/app/lib/ApiAuthProvider.tsx b/www/app/lib/ApiAuthProvider.tsx
index 8e27f6b7..fd8bdc96 100644
--- a/www/app/lib/ApiAuthProvider.tsx
+++ b/www/app/lib/ApiAuthProvider.tsx
@@ -1,53 +1,13 @@
"use client";
-import { useEffect, useContext, useRef } from "react";
-import { client, configureApiAuth } from "./apiClient";
+import { useEffect } from "react";
+import { configureApiAuth } from "./apiClient";
import useSessionAccessToken from "./useSessionAccessToken";
-import { DomainContext } from "../domainContext";
-// Store the current API URL globally
-let currentApiUrl: string | null = null;
-
-// Set up base URL middleware once
-const baseUrlMiddlewareSetup = () => {
- client.use({
- onRequest({ request }) {
- if (currentApiUrl) {
- // Update the base URL for all requests
- const url = new URL(request.url);
- const apiUrl = new URL(currentApiUrl);
- url.protocol = apiUrl.protocol;
- url.host = apiUrl.host;
- url.port = apiUrl.port;
- return new Request(url.toString(), request);
- }
- return request;
- },
- });
-};
-
-// Initialize base URL middleware once
-if (typeof window !== "undefined") {
- baseUrlMiddlewareSetup();
-}
+// Note: Base URL is now configured directly in apiClient.tsx
export function ApiAuthProvider({ children }: { children: React.ReactNode }) {
const { accessToken } = useSessionAccessToken();
- const { api_url } = useContext(DomainContext);
- const initialized = useRef(false);
-
- // Initialize middleware once on client side
- useEffect(() => {
- if (!initialized.current && typeof window !== "undefined") {
- baseUrlMiddlewareSetup();
- initialized.current = true;
- }
- }, []);
-
- useEffect(() => {
- // Update the global API URL
- currentApiUrl = api_url;
- }, [api_url]);
useEffect(() => {
// Configure authentication
diff --git a/www/app/lib/api-hooks.ts b/www/app/lib/api-hooks.ts
index 02ae66da..caa8e47c 100644
--- a/www/app/lib/api-hooks.ts
+++ b/www/app/lib/api-hooks.ts
@@ -4,16 +4,26 @@ import { $api } from "./apiClient";
import { useError } from "../(errors)/errorContext";
import { useQueryClient } from "@tanstack/react-query";
import type { paths } from "../reflector-api";
+import useSessionStatus from "./useSessionStatus";
// Rooms hooks
export function useRoomsList(page: number = 1) {
const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
- return $api.useQuery("get", "/v1/rooms", {
- params: {
- query: { page },
+ return $api.useQuery(
+ "get",
+ "/v1/rooms",
+ {
+ params: {
+ query: { page },
+ },
},
- });
+ {
+ // Only fetch when authenticated
+ enabled: isAuthenticated && !isLoading,
+ },
+ );
}
// Transcripts hooks
@@ -27,18 +37,27 @@ export function useTranscriptsSearch(
} = {},
) {
const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
- return $api.useQuery("get", "/v1/transcripts/search", {
- params: {
- query: {
- q,
- limit: options.limit,
- offset: options.offset,
- room_id: options.room_id,
- source_kind: options.source_kind as any,
+ return $api.useQuery(
+ "get",
+ "/v1/transcripts/search",
+ {
+ params: {
+ query: {
+ q,
+ limit: options.limit,
+ offset: options.offset,
+ room_id: options.room_id,
+ source_kind: options.source_kind as any,
+ },
},
},
- });
+ {
+ // Only fetch when authenticated
+ enabled: isAuthenticated && !isLoading,
+ },
+ );
}
export function useTranscriptDelete() {
@@ -72,6 +91,7 @@ export function useTranscriptProcess() {
export function useTranscriptGet(transcriptId: string | null) {
const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
return $api.useQuery(
"get",
@@ -84,7 +104,8 @@ export function useTranscriptGet(transcriptId: string | null) {
},
},
{
- enabled: !!transcriptId,
+ // Only fetch when authenticated and transcriptId is provided
+ enabled: !!transcriptId && isAuthenticated && !isLoading,
},
);
}
@@ -141,25 +162,32 @@ export function useRoomDelete() {
// Zulip hooks - NOTE: These endpoints are not in the OpenAPI spec yet
export function useZulipStreams() {
const { setError } = useError();
-
- // @ts-ignore - Zulip endpoint not in OpenAPI spec
- return $api.useQuery("get", "/v1/zulip/get-streams" as any, {});
-}
-
-export function useZulipTopics(streamId: number | null) {
- const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
// @ts-ignore - Zulip endpoint not in OpenAPI spec
return $api.useQuery(
"get",
- "/v1/zulip/get-topics" as any,
+ "/v1/zulip/streams" as any,
+ {},
{
- params: {
- query: { stream_id: streamId || 0 },
- },
+ // Only fetch when authenticated
+ enabled: isAuthenticated && !isLoading,
},
+ );
+}
+
+export function useZulipTopics(streamId: number | null) {
+ const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
+
+ // @ts-ignore - Zulip endpoint not in OpenAPI spec
+ return $api.useQuery(
+ "get",
+ streamId ? (`/v1/zulip/streams/${streamId}/topics` as any) : null,
+ {},
{
- enabled: !!streamId,
+ // Only fetch when authenticated and streamId is provided
+ enabled: !!streamId && isAuthenticated && !isLoading,
},
);
}
@@ -233,6 +261,7 @@ export function useTranscriptUploadAudio() {
// Transcript queries
export function useTranscriptWaveform(transcriptId: string | null) {
const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
return $api.useQuery(
"get",
@@ -243,13 +272,14 @@ export function useTranscriptWaveform(transcriptId: string | null) {
},
},
{
- enabled: !!transcriptId,
+ enabled: !!transcriptId && isAuthenticated && !isLoading,
},
);
}
export function useTranscriptMP3(transcriptId: string | null) {
const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
return $api.useQuery(
"get",
@@ -260,13 +290,14 @@ export function useTranscriptMP3(transcriptId: string | null) {
},
},
{
- enabled: !!transcriptId,
+ enabled: !!transcriptId && isAuthenticated && !isLoading,
},
);
}
export function useTranscriptTopics(transcriptId: string | null) {
const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
return $api.useQuery(
"get",
@@ -277,13 +308,14 @@ export function useTranscriptTopics(transcriptId: string | null) {
},
},
{
- enabled: !!transcriptId,
+ enabled: !!transcriptId && isAuthenticated && !isLoading,
},
);
}
export function useTranscriptTopicsWithWords(transcriptId: string | null) {
const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
return $api.useQuery(
"get",
@@ -294,7 +326,7 @@ export function useTranscriptTopicsWithWords(transcriptId: string | null) {
},
},
{
- enabled: !!transcriptId,
+ enabled: !!transcriptId && isAuthenticated && !isLoading,
},
);
}
@@ -304,6 +336,7 @@ export function useTranscriptTopicsWithWordsPerSpeaker(
topicId: string | null,
) {
const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
return $api.useQuery(
"get",
@@ -317,7 +350,7 @@ export function useTranscriptTopicsWithWordsPerSpeaker(
},
},
{
- enabled: !!transcriptId && !!topicId,
+ enabled: !!transcriptId && !!topicId && isAuthenticated && !isLoading,
},
);
}
@@ -325,6 +358,7 @@ export function useTranscriptTopicsWithWordsPerSpeaker(
// Participant operations
export function useTranscriptParticipants(transcriptId: string | null) {
const { setError } = useError();
+ const { isAuthenticated, isLoading } = useSessionStatus();
return $api.useQuery(
"get",
@@ -335,7 +369,7 @@ export function useTranscriptParticipants(transcriptId: string | null) {
},
},
{
- enabled: !!transcriptId,
+ enabled: !!transcriptId && isAuthenticated && !isLoading,
},
);
}
diff --git a/www/app/lib/apiClient.tsx b/www/app/lib/apiClient.tsx
index 2b320267..0a167f68 100644
--- a/www/app/lib/apiClient.tsx
+++ b/www/app/lib/apiClient.tsx
@@ -10,10 +10,10 @@ import {
} from "@tanstack/react-query";
import createFetchClient from "openapi-react-query";
-// Create the base openapi-fetch client
+// Create the base openapi-fetch client with a default URL
+// The actual URL will be set via middleware in ApiAuthProvider
export const client = createClient({
- // Base URL will be set dynamically via middleware
- baseUrl: "",
+ baseUrl: "http://127.0.0.1:1250",
headers: {
"Content-Type": "application/json",
},