LibreChat/client/src/components/ui/OGDialogTemplate.tsx
Ruben Talstra d99a9db3f6
feat: OAuth for Actions (#5693)
* feat: OAuth for Actions

* WIP: PoC flow state manager

* refactor: Add identifier field to token model from action schema

* chore: fix potential file type issues

* ci: fix type issue with action metadata auth

* fix: ensure FlowManagerOptions has a default ttl value

* WIP: OAUTH actions

* WIP: first pass OAuth Action

* fix: standardize identifier usage in OAuth flow handling

* fix: update token retrieval to include userId in query and use correct identifier

* refacotr: update token retrieval to use userId for OAuth token query

* feat: Tool Call Auth styling

* fix: streamline token creation and add type field to token schema

* refactor: cleanup OAuth flow by encrypting client credentials and ensuring oauth operations only run under condition

* refactor: use encrypted credentials in OAuth callback

* fix: update Token collection indexes to use expiresAt TTL index and not createdAt legacy index

* refactor: enhance Token index cleanup by improving logging and removing redundant index creation logic

* refactor: remove unused OAuth login route and related logic for improved clarity

* refactor: replace fetch with axios for OAuth token exchange and improve error handling

* refactor: better UX after authentication before oauth tool execution

* refactor: implement cleanup handlers for FlowStateManager intervals to enhance resource management

* refactor: encrypt OAuth tokens before storing and decrypt upon retrieval for enhanced security

* refactor: enhance authentication success page with improved styling and countdown feature

* refactor: add response_type parameter to OAuth redirect URI for improved compatibility

* chore: update translation.json new localizations

* chore: remove unused OGDialog import from OGDialogTemplate component

* refactor: Actions Auth using new Dialog styling, use same component with Agents/Assistants

* refactor: update removeNullishValues function to support removal of empty strings and adjust transform usage in schemas

* chore: bump version of librechat-data-provider to 0.7.6991

* refactor: integrate removeNullishValues function to clean metadata before encryption in agent and assistant routes

* refactor: update OAuth input fields to use 'password' type for better security

* refactor: update localization placeholders for sign-in message to use double curly braces

* refactor: add access_type parameter for offline access in createActionTool function

* refactor: implement handleOAuthToken function for token management and encryption

* feat: refresh token support

* refactor: add default expiration for access token and error handling for missing token

* feat: localizations for ActionAuth

* refactor: set refresh token expiration to null to not expire if expiry never given

* fix: prevent crash fromerror within async handleAbortError in AskController, EditController, and AgentController

* feat: Action Callback URL

* 🌍 i18n: Update translation.json with latest translations

* refactor: handle errors in flow state checking to prevent unhandled promise rejections

* fix: improve flow state concurrency to prevent multiple token creation calls

* refactor: RequestExecutor to use separate axios instance

* refactor: improve concurrency flows by keeping completed state until TTL expiry

* refactor: increase TTL for flow state management and adjust monitoring interval

* ci: mock axios instance creation in actions spec

* feat: add Babel and Jest configuration files; implement FlowStateManager tests with concurrency handling

* chore: add disableOAuth prop to ActionsAuth (not implemented for Assistants yet)

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-10 15:56:08 -05:00

109 lines
3.5 KiB
TypeScript

import { forwardRef, ReactNode, Ref } from 'react';
import {
OGDialogTitle,
OGDialogClose,
OGDialogFooter,
OGDialogHeader,
OGDialogContent,
OGDialogDescription,
} from './OriginalDialog';
import { useLocalize } from '~/hooks';
import { Spinner } from '../svg';
import { cn } from '~/utils/';
type SelectionProps = {
selectHandler?: () => void;
selectClasses?: string;
selectText?: string | ReactNode;
isLoading?: boolean;
};
type DialogTemplateProps = {
title: string;
description?: string;
main?: ReactNode;
buttons?: ReactNode;
leftButtons?: ReactNode;
selection?: SelectionProps;
className?: string;
overlayClassName?: string;
headerClassName?: string;
mainClassName?: string;
footerClassName?: string;
showCloseButton?: boolean;
showCancelButton?: boolean;
onClose?: () => void;
};
const OGDialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDivElement>) => {
const localize = useLocalize();
const {
title,
main,
buttons,
selection,
className,
leftButtons,
description = '',
mainClassName,
headerClassName,
footerClassName,
showCloseButton,
overlayClassName,
showCancelButton = true,
} = props;
const { selectHandler, selectClasses, selectText, isLoading } = selection || {};
const Cancel = localize('com_ui_cancel');
const defaultSelect =
'bg-gray-800 text-white transition-colors hover:bg-gray-700 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-200 dark:text-gray-800 dark:hover:bg-gray-200';
return (
<OGDialogContent
overlayClassName={overlayClassName}
showCloseButton={showCloseButton}
ref={ref}
className={cn('w-11/12 border-none bg-background text-foreground', className ?? '')}
onClick={(e) => e.stopPropagation()}
>
<OGDialogHeader className={cn(headerClassName ?? '')}>
<OGDialogTitle>{title}</OGDialogTitle>
{description && (
<OGDialogDescription className="items-center justify-center">
{description}
</OGDialogDescription>
)}
</OGDialogHeader>
<div className={cn('px-0 py-2', mainClassName)}>{main != null ? main : null}</div>
<OGDialogFooter className={footerClassName}>
<div>
{leftButtons != null ? (
<div className="mt-3 flex h-auto gap-3 max-sm:w-full max-sm:flex-col sm:mt-0 sm:flex-row">
{leftButtons}
</div>
) : null}
</div>
<div className="flex h-auto gap-3 max-sm:w-full max-sm:flex-col sm:flex-row">
{buttons != null ? buttons : null}
{showCancelButton && (
<OGDialogClose className="btn btn-neutral border-token-border-light relative justify-center rounded-lg text-sm ring-offset-2 focus:ring-2 focus:ring-black dark:ring-offset-0 max-sm:order-last max-sm:w-full sm:order-first">
{Cancel}
</OGDialogClose>
)}
{selection ? (
<OGDialogClose
onClick={selectHandler}
disabled={isLoading}
className={`${
selectClasses ?? defaultSelect
} flex h-10 items-center justify-center rounded-lg border-none px-4 py-2 text-sm disabled:opacity-80 max-sm:order-first max-sm:w-full sm:order-none`}
>
{isLoading === true ? <Spinner className="size-4 text-white" /> : selectText}
</OGDialogClose>
) : null}
</div>
</OGDialogFooter>
</OGDialogContent>
);
});
export default OGDialogTemplate;