From 5e555a8f9a36eba414d35d3883f8c68430f8f17c Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Sat, 31 Jan 2026 16:24:43 +0530 Subject: [PATCH 01/15] fix: improve notification for token expiration and revocation errors for multiple connectors --- .../connectors/google_drive/credentials.py | 9 +++++++ .../routes/airtable_add_connector_route.py | 13 +++++++++ .../app/routes/clickup_add_connector_route.py | 11 ++++++++ .../routes/confluence_add_connector_route.py | 13 +++++++++ .../app/routes/jira_add_connector_route.py | 13 +++++++++ .../app/routes/linear_add_connector_route.py | 13 +++++++++ .../app/routes/notion_add_connector_route.py | 13 +++++++++ .../app/routes/slack_add_connector_route.py | 27 +++++++++++++++++++ .../app/routes/teams_add_connector_route.py | 13 +++++++++ 9 files changed, 125 insertions(+) diff --git a/surfsense_backend/app/connectors/google_drive/credentials.py b/surfsense_backend/app/connectors/google_drive/credentials.py index 7e6335f6d..5b1900ab2 100644 --- a/surfsense_backend/app/connectors/google_drive/credentials.py +++ b/surfsense_backend/app/connectors/google_drive/credentials.py @@ -132,6 +132,15 @@ async def get_valid_credentials( await session.commit() except Exception as e: + error_str = str(e) + # Check if this is an invalid_grant error (token expired/revoked) + if ( + "invalid_grant" in error_str.lower() + or "token has been expired or revoked" in error_str.lower() + ): + raise Exception( + "Google Drive authentication failed. Please re-authenticate." + ) from e raise Exception(f"Failed to refresh Google OAuth credentials: {e!s}") from e return credentials diff --git a/surfsense_backend/app/routes/airtable_add_connector_route.py b/surfsense_backend/app/routes/airtable_add_connector_route.py index 64fa104d8..423d61fb2 100644 --- a/surfsense_backend/app/routes/airtable_add_connector_route.py +++ b/surfsense_backend/app/routes/airtable_add_connector_route.py @@ -442,11 +442,24 @@ async def refresh_airtable_token( if token_response.status_code != 200: error_detail = token_response.text + error_code = "" try: error_json = token_response.json() error_detail = error_json.get("error_description", error_detail) + error_code = error_json.get("error", "") except Exception: pass + # Check if this is a token expiration/revocation error + error_lower = (error_detail + error_code).lower() + if ( + "invalid_grant" in error_lower + or "expired" in error_lower + or "revoked" in error_lower + ): + raise HTTPException( + status_code=401, + detail="Airtable authentication failed. Please re-authenticate.", + ) raise HTTPException( status_code=400, detail=f"Token refresh failed: {error_detail}" ) diff --git a/surfsense_backend/app/routes/clickup_add_connector_route.py b/surfsense_backend/app/routes/clickup_add_connector_route.py index f962f65fb..1b2e6795d 100644 --- a/surfsense_backend/app/routes/clickup_add_connector_route.py +++ b/surfsense_backend/app/routes/clickup_add_connector_route.py @@ -417,6 +417,17 @@ async def refresh_clickup_token( error_detail = error_json.get("error", error_detail) except Exception: pass + # Check if this is a token expiration/revocation error + error_lower = error_detail.lower() + if ( + "invalid_grant" in error_lower + or "expired" in error_lower + or "revoked" in error_lower + ): + raise HTTPException( + status_code=401, + detail="ClickUp authentication failed. Please re-authenticate.", + ) raise HTTPException( status_code=400, detail=f"Token refresh failed: {error_detail}" ) diff --git a/surfsense_backend/app/routes/confluence_add_connector_route.py b/surfsense_backend/app/routes/confluence_add_connector_route.py index 6c5830b17..24e0f858a 100644 --- a/surfsense_backend/app/routes/confluence_add_connector_route.py +++ b/surfsense_backend/app/routes/confluence_add_connector_route.py @@ -428,13 +428,26 @@ async def refresh_confluence_token( if token_response.status_code != 200: error_detail = token_response.text + error_code = "" try: error_json = token_response.json() error_detail = error_json.get( "error_description", error_json.get("error", error_detail) ) + error_code = error_json.get("error", "") except Exception: pass + # Check if this is a token expiration/revocation error + error_lower = (error_detail + error_code).lower() + if ( + "invalid_grant" in error_lower + or "expired" in error_lower + or "revoked" in error_lower + ): + raise HTTPException( + status_code=401, + detail="Confluence authentication failed. Please re-authenticate.", + ) raise HTTPException( status_code=400, detail=f"Token refresh failed: {error_detail}" ) diff --git a/surfsense_backend/app/routes/jira_add_connector_route.py b/surfsense_backend/app/routes/jira_add_connector_route.py index fb66f4da7..58903606a 100644 --- a/surfsense_backend/app/routes/jira_add_connector_route.py +++ b/surfsense_backend/app/routes/jira_add_connector_route.py @@ -446,13 +446,26 @@ async def refresh_jira_token( if token_response.status_code != 200: error_detail = token_response.text + error_code = "" try: error_json = token_response.json() error_detail = error_json.get( "error_description", error_json.get("error", error_detail) ) + error_code = error_json.get("error", "") except Exception: pass + # Check if this is a token expiration/revocation error + error_lower = (error_detail + error_code).lower() + if ( + "invalid_grant" in error_lower + or "expired" in error_lower + or "revoked" in error_lower + ): + raise HTTPException( + status_code=401, + detail="Jira authentication failed. Please re-authenticate.", + ) raise HTTPException( status_code=400, detail=f"Token refresh failed: {error_detail}" ) diff --git a/surfsense_backend/app/routes/linear_add_connector_route.py b/surfsense_backend/app/routes/linear_add_connector_route.py index fc9501bfb..dd5f7443c 100644 --- a/surfsense_backend/app/routes/linear_add_connector_route.py +++ b/surfsense_backend/app/routes/linear_add_connector_route.py @@ -403,11 +403,24 @@ async def refresh_linear_token( if token_response.status_code != 200: error_detail = token_response.text + error_code = "" try: error_json = token_response.json() error_detail = error_json.get("error_description", error_detail) + error_code = error_json.get("error", "") except Exception: pass + # Check if this is a token expiration/revocation error + error_lower = (error_detail + error_code).lower() + if ( + "invalid_grant" in error_lower + or "expired" in error_lower + or "revoked" in error_lower + ): + raise HTTPException( + status_code=401, + detail="Linear authentication failed. Please re-authenticate.", + ) raise HTTPException( status_code=400, detail=f"Token refresh failed: {error_detail}" ) diff --git a/surfsense_backend/app/routes/notion_add_connector_route.py b/surfsense_backend/app/routes/notion_add_connector_route.py index aac821793..81017af50 100644 --- a/surfsense_backend/app/routes/notion_add_connector_route.py +++ b/surfsense_backend/app/routes/notion_add_connector_route.py @@ -407,11 +407,24 @@ async def refresh_notion_token( if token_response.status_code != 200: error_detail = token_response.text + error_code = "" try: error_json = token_response.json() error_detail = error_json.get("error_description", error_detail) + error_code = error_json.get("error", "") except Exception: pass + # Check if this is a token expiration/revocation error + error_lower = (error_detail + error_code).lower() + if ( + "invalid_grant" in error_lower + or "expired" in error_lower + or "revoked" in error_lower + ): + raise HTTPException( + status_code=401, + detail="Notion authentication failed. Please re-authenticate.", + ) raise HTTPException( status_code=400, detail=f"Token refresh failed: {error_detail}" ) diff --git a/surfsense_backend/app/routes/slack_add_connector_route.py b/surfsense_backend/app/routes/slack_add_connector_route.py index 62d2ccaaa..e7f19e8b0 100644 --- a/surfsense_backend/app/routes/slack_add_connector_route.py +++ b/surfsense_backend/app/routes/slack_add_connector_route.py @@ -418,6 +418,19 @@ async def refresh_slack_token( error_detail = error_json.get("error", error_detail) except Exception: pass + # Check if this is a token expiration/revocation error + error_lower = error_detail.lower() + if ( + "invalid_grant" in error_lower + or "invalid_auth" in error_lower + or "token_revoked" in error_lower + or "expired" in error_lower + or "revoked" in error_lower + ): + raise HTTPException( + status_code=401, + detail="Slack authentication failed. Please re-authenticate.", + ) raise HTTPException( status_code=400, detail=f"Token refresh failed: {error_detail}" ) @@ -427,6 +440,20 @@ async def refresh_slack_token( # Slack OAuth v2 returns success status in the JSON if not token_json.get("ok", False): error_msg = token_json.get("error", "Unknown error") + # Check if this is a token expiration/revocation error + error_lower = error_msg.lower() + if ( + "invalid_grant" in error_lower + or "invalid_auth" in error_lower + or "invalid_refresh_token" in error_lower + or "token_revoked" in error_lower + or "expired" in error_lower + or "revoked" in error_lower + ): + raise HTTPException( + status_code=401, + detail="Slack authentication failed. Please re-authenticate.", + ) raise HTTPException( status_code=400, detail=f"Slack OAuth refresh error: {error_msg}" ) diff --git a/surfsense_backend/app/routes/teams_add_connector_route.py b/surfsense_backend/app/routes/teams_add_connector_route.py index 9ce84e171..77ce4965e 100644 --- a/surfsense_backend/app/routes/teams_add_connector_route.py +++ b/surfsense_backend/app/routes/teams_add_connector_route.py @@ -420,11 +420,24 @@ async def refresh_teams_token( if token_response.status_code != 200: error_detail = token_response.text + error_code = "" try: error_json = token_response.json() error_detail = error_json.get("error_description", error_detail) + error_code = error_json.get("error", "") except Exception: pass + # Check if this is a token expiration/revocation error + error_lower = (error_detail + error_code).lower() + if ( + "invalid_grant" in error_lower + or "expired" in error_lower + or "revoked" in error_lower + ): + raise HTTPException( + status_code=401, + detail="Microsoft Teams authentication failed. Please re-authenticate.", + ) raise HTTPException( status_code=400, detail=f"Token refresh failed: {error_detail}" ) From eaf0a454b1bb048d430a243f3cde0d94eb2c7628 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Sat, 31 Jan 2026 17:07:57 +0530 Subject: [PATCH 02/15] refactor: remove chat button from collapsed sidebar for cleaner UI --- .../components/layout/ui/sidebar/Sidebar.tsx | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index 4a587cd58..070462341 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -1,6 +1,6 @@ "use client"; -import { FolderOpen, MessageSquare, PenSquare } from "lucide-react"; +import { FolderOpen, PenSquare } from "lucide-react"; import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; @@ -122,26 +122,7 @@ export function Sidebar({ {/* Chat sections - fills available space */} {isCollapsed ? ( -
{isIndexableConnector(connector.connector_type) ? connector.last_indexed_at - ? `Last indexed: ${formatLastIndexedDate(connector.last_indexed_at)}` + ? `Last indexed: ${formatRelativeDate(connector.last_indexed_at)}` : "Never indexed" : "Active"}
diff --git a/surfsense_web/lib/format-date.ts b/surfsense_web/lib/format-date.ts new file mode 100644 index 000000000..c7d8ca85e --- /dev/null +++ b/surfsense_web/lib/format-date.ts @@ -0,0 +1,25 @@ +import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns"; + +/** + * Format a date string as a human-readable relative time + * - < 1 min: "Just now" + * - < 60 min: "15m ago" + * - Today: "Today, 2:30 PM" + * - Yesterday: "Yesterday, 2:30 PM" + * - < 7 days: "3d ago" + * - Older: "Jan 15, 2026" + */ +export function formatRelativeDate(dateString: string): string { + const date = new Date(dateString); + const now = new Date(); + const minutesAgo = differenceInMinutes(now, date); + const daysAgo = differenceInDays(now, date); + + if (minutesAgo < 1) return "Just now"; + if (minutesAgo < 60) return `${minutesAgo}m ago`; + if (isToday(date)) return `Today, ${format(date, "h:mm a")}`; + if (isYesterday(date)) return `Yesterday, ${format(date, "h:mm a")}`; + if (daysAgo < 7) return `${daysAgo}d ago`; + return format(date, "MMM d, yyyy"); +} + From e5f7e87f42bb1cb1a1f7157130fcee8be4910aa1 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Sat, 31 Jan 2026 23:33:20 +0530 Subject: [PATCH 05/15] refactor: update sidebar layout for shared and private chats to optimize space usage --- .../components/layout/ui/sidebar/Sidebar.tsx | 11 ++++++----- .../layout/ui/sidebar/SidebarSection.tsx | 18 +++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index 070462341..db04bf6dc 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -125,11 +125,12 @@ export function Sidebar({ ) : (Add Bot to Servers
- Before indexing, make sure the Discord bot has been added to the servers (guilds) you - want to index. The bot can only access messages from servers it's been added to. Use the - OAuth authorization flow to add the bot to your servers. + The bot needs "Read Message History" permission to index channels. + Ask a server admin to grant this permission for channels shown below.
- The bot needs "Read Message History" permission to index channels. - Ask a server admin to grant this permission for channels shown below. + The bot needs "Read Message History" permission to index channels. Ask a + server admin to grant this permission for channels shown below.