mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-02 22:01:05 +02:00
feat: implement re-authentication flow for connectors and enhance auth expiration handling
- Added a mechanism to mark connectors as 'auth_expired' in the database, allowing the frontend to prompt users for re-authentication. - Updated Linear and Notion connector routes to handle token refresh failures by invoking the new expiration flagging function. - Enhanced UI components to display re-authentication options when a connector's authentication status is expired.
This commit is contained in:
parent
81ddac1f54
commit
7ba5e9c662
9 changed files with 202 additions and 18 deletions
|
|
@ -1,17 +1,26 @@
|
|||
"use client";
|
||||
|
||||
import { ArrowLeft, Plus, Server } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { ArrowLeft, Plus, RefreshCw, Server } from "lucide-react";
|
||||
import { type FC, useCallback, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||
import { formatRelativeDate } from "@/lib/format-date";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useConnectorStatus } from "../hooks/use-connector-status";
|
||||
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
|
||||
|
||||
const REAUTH_ENDPOINTS: Partial<Record<string, string>> = {
|
||||
[EnumConnectorName.LINEAR_CONNECTOR]: "/api/v1/auth/linear/connector/reauth",
|
||||
[EnumConnectorName.NOTION_CONNECTOR]: "/api/v1/auth/notion/connector/reauth",
|
||||
};
|
||||
|
||||
interface ConnectorAccountsListViewProps {
|
||||
connectorType: string;
|
||||
connectorTitle: string;
|
||||
|
|
@ -43,12 +52,46 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
isConnecting = false,
|
||||
addButtonText,
|
||||
}) => {
|
||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
||||
const [reauthingId, setReauthingId] = useState<number | null>(null);
|
||||
|
||||
// Get connector status
|
||||
const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus();
|
||||
|
||||
const isEnabled = isConnectorEnabled(connectorType);
|
||||
const statusMessage = getConnectorStatusMessage(connectorType);
|
||||
|
||||
const reauthEndpoint = REAUTH_ENDPOINTS[connectorType];
|
||||
|
||||
const handleReauth = useCallback(
|
||||
async (connectorId: number) => {
|
||||
if (!searchSpaceId || !reauthEndpoint) return;
|
||||
setReauthingId(connectorId);
|
||||
try {
|
||||
const backendUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";
|
||||
const url = new URL(`${backendUrl}${reauthEndpoint}`);
|
||||
url.searchParams.set("connector_id", String(connectorId));
|
||||
url.searchParams.set("space_id", String(searchSpaceId));
|
||||
url.searchParams.set("return_url", window.location.pathname);
|
||||
const response = await authenticatedFetch(url.toString());
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
toast.error(data.detail ?? "Failed to initiate re-authentication.");
|
||||
return;
|
||||
}
|
||||
const data = await response.json();
|
||||
if (data.auth_url) {
|
||||
window.location.href = data.auth_url;
|
||||
}
|
||||
} catch {
|
||||
toast.error("Failed to initiate re-authentication.");
|
||||
} finally {
|
||||
setReauthingId(null);
|
||||
}
|
||||
},
|
||||
[searchSpaceId, reauthEndpoint]
|
||||
);
|
||||
|
||||
// Filter connectors to only show those of this type
|
||||
const typeConnectors = connectors.filter((c) => c.connector_type === connectorType);
|
||||
|
||||
|
|
@ -149,6 +192,8 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{typeConnectors.map((connector) => {
|
||||
const isIndexing = indexingConnectorIds.has(connector.id);
|
||||
const isAuthExpired =
|
||||
!!reauthEndpoint && connector.config?.auth_expired === true;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -189,14 +234,30 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
|
||||
onClick={() => onManage(connector)}
|
||||
>
|
||||
Manage
|
||||
</Button>
|
||||
{isAuthExpired ? (
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-amber-600 hover:bg-amber-700 text-white border-0 shadow-xs shrink-0"
|
||||
onClick={() => handleReauth(connector.id)}
|
||||
disabled={reauthingId === connector.id}
|
||||
>
|
||||
{reauthingId === connector.id ? (
|
||||
<Spinner size="xs" />
|
||||
) : (
|
||||
<RefreshCw className="size-3.5" />
|
||||
)}
|
||||
Re-authenticate
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
|
||||
onClick={() => onManage(connector)}
|
||||
>
|
||||
Manage
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { makeAssistantToolUI } from "@assistant-ui/react";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { CornerDownLeftIcon, Pen } from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -13,7 +14,6 @@ import {
|
|||
} from "@/components/ui/select";
|
||||
import { PlateEditor } from "@/components/editor/plate-editor";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
|
||||
|
||||
interface LinearLabel {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { makeAssistantToolUI } from "@assistant-ui/react";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { CornerDownLeftIcon, Pen } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -13,7 +14,6 @@ import {
|
|||
} from "@/components/ui/select";
|
||||
import { PlateEditor } from "@/components/editor/plate-editor";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
|
||||
|
||||
interface InterruptResult {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import { makeAssistantToolUI } from "@assistant-ui/react";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { CornerDownLeftIcon, Pen } from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PlateEditor } from "@/components/editor/plate-editor";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
|
||||
|
||||
interface InterruptResult {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue