From 271de96cce05d94b6c9ab082ef3c87735b008c1d Mon Sep 17 00:00:00 2001
From: CREDO23
Date: Mon, 26 Jan 2026 20:10:03 +0200
Subject: [PATCH] fix: public chat copy link button and podcast access
---
.../app/routes/podcasts_routes.py | 29 ++++++++++++-------
surfsense_backend/app/schemas/new_chat.py | 1 +
.../app/services/public_chat_service.py | 17 ++++++++---
.../new-chat/[[...chat_id]]/page.tsx | 2 +-
.../components/new-chat/chat-share-button.tsx | 15 +++++++---
surfsense_web/lib/apis/base-api.service.ts | 2 +-
surfsense_web/lib/chat/thread-persistence.ts | 1 +
7 files changed, 47 insertions(+), 20 deletions(-)
diff --git a/surfsense_backend/app/routes/podcasts_routes.py b/surfsense_backend/app/routes/podcasts_routes.py
index 467ef8d23..27970b707 100644
--- a/surfsense_backend/app/routes/podcasts_routes.py
+++ b/surfsense_backend/app/routes/podcasts_routes.py
@@ -84,12 +84,17 @@ async def read_podcasts(
async def read_podcast(
podcast_id: int,
session: AsyncSession = Depends(get_async_session),
- user: User = Depends(current_active_user),
+ user: User | None = Depends(current_optional_user),
):
"""
Get a specific podcast by ID.
- Requires PODCASTS_READ permission for the search space.
+
+ Access is allowed if:
+ - User is authenticated with PODCASTS_READ permission, OR
+ - Podcast belongs to a publicly shared thread
"""
+ from app.services.public_chat_service import is_podcast_publicly_accessible
+
try:
result = await session.execute(select(Podcast).filter(Podcast.id == podcast_id))
podcast = result.scalars().first()
@@ -100,14 +105,18 @@ async def read_podcast(
detail="Podcast not found",
)
- # Check permission for the search space
- await check_permission(
- session,
- user,
- podcast.search_space_id,
- Permission.PODCASTS_READ.value,
- "You don't have permission to read podcasts in this search space",
- )
+ is_public = await is_podcast_publicly_accessible(session, podcast_id)
+
+ if not is_public:
+ if not user:
+ raise HTTPException(status_code=401, detail="Authentication required")
+ await check_permission(
+ session,
+ user,
+ podcast.search_space_id,
+ Permission.PODCASTS_READ.value,
+ "You don't have permission to read podcasts in this search space",
+ )
return podcast
except HTTPException as he:
diff --git a/surfsense_backend/app/schemas/new_chat.py b/surfsense_backend/app/schemas/new_chat.py
index ef2868495..5e9d44beb 100644
--- a/surfsense_backend/app/schemas/new_chat.py
+++ b/surfsense_backend/app/schemas/new_chat.py
@@ -96,6 +96,7 @@ class NewChatThreadRead(NewChatThreadBase, IDModel):
visibility: ChatVisibility
created_by_id: UUID | None = None
public_share_enabled: bool = False
+ public_share_token: str | None = None
created_at: datetime
updated_at: datetime
diff --git a/surfsense_backend/app/services/public_chat_service.py b/surfsense_backend/app/services/public_chat_service.py
index 42a26c403..62fd4f923 100644
--- a/surfsense_backend/app/services/public_chat_service.py
+++ b/surfsense_backend/app/services/public_chat_service.py
@@ -27,8 +27,8 @@ def strip_citations(text: str) -> str:
Remove [citation:X] and [citation:doc-X] patterns from text.
Preserves newlines to maintain markdown formatting.
"""
- # Remove citation patterns (including Chinese brackets 【】)
- text = re.sub(r"[\[【]citation:(doc-)?\d+[\]】]", "", text)
+ # Remove citation patterns
+ text = re.sub(r"[\[【]\u200B?citation:(doc-)?\d+\u200B?[\]】]", "", text)
# Collapse multiple spaces/tabs (but NOT newlines) into single space
text = re.sub(r"[^\S\n]+", " ", text)
# Normalize excessive blank lines (3+ newlines → 2)
@@ -63,8 +63,17 @@ def sanitize_content_for_public(content: list | str | None) -> list:
sanitized.append({"type": "text", "text": clean_text})
elif part_type == "tool-call":
- if part.get("toolName") in UI_TOOLS:
- sanitized.append(part)
+ tool_name = part.get("toolName")
+ if tool_name not in UI_TOOLS:
+ continue
+
+ # Skip podcasts that are still processing (would cause auth errors)
+ if tool_name == "generate_podcast":
+ result = part.get("result", {})
+ if result.get("status") in ("processing", "already_generating"):
+ continue
+
+ sanitized.append(part)
return sanitized
diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx
index 2af50f8e2..9b45d4d62 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx
@@ -355,7 +355,7 @@ export default function NewChatPage() {
hasComments: currentThread?.has_comments ?? false,
addingCommentToMessageId: null,
publicShareEnabled: currentThread?.public_share_enabled ?? false,
- publicShareToken: null,
+ publicShareToken: currentThread?.public_share_token ?? null,
});
}, [currentThread, setCurrentThreadState]);
diff --git a/surfsense_web/components/new-chat/chat-share-button.tsx b/surfsense_web/components/new-chat/chat-share-button.tsx
index 4e811779f..2df363203 100644
--- a/surfsense_web/components/new-chat/chat-share-button.tsx
+++ b/surfsense_web/components/new-chat/chat-share-button.tsx
@@ -245,17 +245,24 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS
{isPublicEnabled && publicShareToken && (
-
+
)}
diff --git a/surfsense_web/lib/apis/base-api.service.ts b/surfsense_web/lib/apis/base-api.service.ts
index a87d4deaf..b14818ac1 100644
--- a/surfsense_web/lib/apis/base-api.service.ts
+++ b/surfsense_web/lib/apis/base-api.service.ts
@@ -26,7 +26,7 @@ class BaseApiService {
noAuthEndpoints: string[] = ["/auth/jwt/login", "/auth/register", "/auth/refresh"];
// Prefixes that don't require auth (checked with startsWith)
- noAuthPrefixes: string[] = ["/api/v1/public/"];
+ noAuthPrefixes: string[] = ["/api/v1/public/", "/api/v1/podcasts/"];
// Use a getter to always read fresh token from localStorage
// This ensures the token is always up-to-date after login/logout
diff --git a/surfsense_web/lib/chat/thread-persistence.ts b/surfsense_web/lib/chat/thread-persistence.ts
index 6990ff582..2188d9cec 100644
--- a/surfsense_web/lib/chat/thread-persistence.ts
+++ b/surfsense_web/lib/chat/thread-persistence.ts
@@ -25,6 +25,7 @@ export interface ThreadRecord {
updated_at: string;
has_comments?: boolean;
public_share_enabled?: boolean;
+ public_share_token?: string | null;
}
export interface MessageRecord {