diff --git a/.env.example b/.env.example index 0b7fa848e6..9eb117ae68 100644 --- a/.env.example +++ b/.env.example @@ -399,6 +399,13 @@ FIREBASE_STORAGE_BUCKET= FIREBASE_MESSAGING_SENDER_ID= FIREBASE_APP_ID= +#========================# +# Shared Links # +#========================# + +ALLOW_SHARED_LINKS=true +ALLOW_SHARED_LINKS_PUBLIC=true + #===================================================# # UI # #===================================================# diff --git a/api/server/routes/config.js b/api/server/routes/config.js index 07022bc535..de3c0d89c9 100644 --- a/api/server/routes/config.js +++ b/api/server/routes/config.js @@ -8,6 +8,14 @@ const emailLoginEnabled = process.env.ALLOW_EMAIL_LOGIN === undefined || isEnabled(process.env.ALLOW_EMAIL_LOGIN); const passwordResetEnabled = isEnabled(process.env.ALLOW_PASSWORD_RESET); +const sharedLinksEnabled = + process.env.ALLOW_SHARED_LINKS === undefined || isEnabled(process.env.ALLOW_SHARED_LINKS); + +const publicSharedLinksEnabled = + sharedLinksEnabled && + (process.env.ALLOW_SHARED_LINKS_PUBLIC === undefined || + isEnabled(process.env.ALLOW_SHARED_LINKS_PUBLIC)); + router.get('/', async function (req, res) { const isBirthday = () => { const today = new Date(); @@ -52,6 +60,8 @@ router.get('/', async function (req, res) { helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai', interface: req.app.locals.interfaceConfig, modelSpecs: req.app.locals.modelSpecs, + sharedLinksEnabled, + publicSharedLinksEnabled, analyticsGtmId: process.env.ANALYTICS_GTM_ID, }; diff --git a/api/server/routes/share.js b/api/server/routes/share.js index 7fbdae23f1..cc75024224 100644 --- a/api/server/routes/share.js +++ b/api/server/routes/share.js @@ -8,21 +8,33 @@ const { deleteSharedLink, } = require('~/models/Share'); const requireJwtAuth = require('~/server/middleware/requireJwtAuth'); +const { isEnabled } = require('~/server/utils'); const router = express.Router(); /** * Shared messages - * this route does not require authentication */ -router.get('/:shareId', async (req, res) => { - const share = await getSharedMessages(req.params.shareId); +const allowSharedLinks = + process.env.ALLOW_SHARED_LINKS === undefined || isEnabled(process.env.ALLOW_SHARED_LINKS); - if (share) { - res.status(200).json(share); - } else { - res.status(404).end(); - } -}); +if (allowSharedLinks) { + const allowSharedLinksPublic = + process.env.ALLOW_SHARED_LINKS_PUBLIC === undefined || + isEnabled(process.env.ALLOW_SHARED_LINKS_PUBLIC); + router.get( + '/:shareId', + allowSharedLinksPublic ? (req, res, next) => next() : requireJwtAuth, + async (req, res) => { + const share = await getSharedMessages(req.params.shareId); + + if (share) { + res.status(200).json(share); + } else { + res.status(404).end(); + } + }, + ); +} /** * Shared links diff --git a/client/src/components/Chat/ExportAndShareMenu.tsx b/client/src/components/Chat/ExportAndShareMenu.tsx index 4a343ccffe..ba556499ee 100644 --- a/client/src/components/Chat/ExportAndShareMenu.tsx +++ b/client/src/components/Chat/ExportAndShareMenu.tsx @@ -8,7 +8,13 @@ import useLocalize from '~/hooks/useLocalize'; import ExportButton from './ExportButton'; import store from '~/store'; -export default function ExportAndShareMenu({ className = '' }: { className?: string }) { +export default function ExportAndShareMenu({ + isSharedButtonEnabled, + className = '', +}: { + isSharedButtonEnabled: boolean; + className?: string; +}) { const localize = useLocalize(); const conversation = useRecoilValue(store.conversationByIndex(0)); @@ -41,13 +47,15 @@ export default function ExportAndShareMenu({ className = '' }: { className?: str {conversation && conversation.conversationId && ( <> - + {isSharedButtonEnabled && ( + + )} )} diff --git a/client/src/components/Chat/Header.tsx b/client/src/components/Chat/Header.tsx index f3c0dab596..0797cf5212 100644 --- a/client/src/components/Chat/Header.tsx +++ b/client/src/components/Chat/Header.tsx @@ -30,9 +30,16 @@ export default function Header() { {modelSpecs?.length > 0 && } {} {interfaceConfig.presets && } - {isSmallScreen && } + {isSmallScreen && ( + + )} - {!isSmallScreen && } + {!isSmallScreen && ( + + )} {/* Empty div for spacing */}
diff --git a/client/src/components/Conversations/Convo.tsx b/client/src/components/Conversations/Convo.tsx index b9a9a4d3c9..a6eaa7eae8 100644 --- a/client/src/components/Conversations/Convo.tsx +++ b/client/src/components/Conversations/Convo.tsx @@ -1,7 +1,7 @@ import { useRecoilValue } from 'recoil'; import { useParams } from 'react-router-dom'; import { useState, useRef, useMemo } from 'react'; -import { useGetEndpointsQuery } from 'librechat-data-provider/react-query'; +import { useGetEndpointsQuery, useGetStartupConfig } from 'librechat-data-provider/react-query'; import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react'; import { useUpdateConversationMutation } from '~/data-provider'; import EndpointIcon from '~/components/Endpoints/EndpointIcon'; @@ -27,9 +27,9 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa const activeConvos = useRecoilValue(store.allConversationsSelector); const { data: endpointsConfig } = useGetEndpointsQuery(); const { navigateWithLastTools } = useNavigateToConvo(); + const { data: startupConfig } = useGetStartupConfig(); const { refreshConversations } = useConversations(); const { showToast } = useToastContext(); - const { conversationId, title } = conversation; const inputRef = useRef(null); const [titleInput, setTitleInput] = useState(title); @@ -126,13 +126,15 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa setIsPopoverActive={setIsPopoverActive} > - + {startupConfig && startupConfig.sharedLinksEnabled && ( + + )}