feat: permission and security fixes (#5266)

This commit is contained in:
Charles Zhou
2024-06-17 03:06:32 -05:00
committed by GitHub
parent a1d8c86ee3
commit cc4a4ec796
13 changed files with 186 additions and 104 deletions

View File

@@ -16,7 +16,7 @@ import Divider from '@/app/components/base/divider'
import Confirm from '@/app/components/base/confirm'
import { useStore as useAppStore } from '@/app/components/app/store'
import { ToastContext } from '@/app/components/base/toast'
import AppsContext from '@/context/app-context'
import AppsContext, { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps'
import DuplicateAppModal from '@/app/components/app/duplicate-modal'
@@ -142,6 +142,8 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
setShowConfirmDelete(false)
}, [appDetail, mutateApps, notify, onPlanInfoChanged, replace, t])
const { isCurrentWorkspaceEditor } = useAppContext()
if (!appDetail)
return null
@@ -154,10 +156,13 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
>
<div className='relative'>
<PortalToFollowElemTrigger
onClick={() => setOpen(v => !v)}
onClick={() => {
if (isCurrentWorkspaceEditor)
setOpen(v => !v)
}}
className='block'
>
<div className={cn('flex cursor-pointer p-1 rounded-lg hover:bg-gray-100', open && 'bg-gray-100')}>
<div className={cn('flex p-1 rounded-lg', open && 'bg-gray-100', isCurrentWorkspaceEditor && 'hover:bg-gray-100 cursor-pointer')}>
<div className='relative shrink-0 mr-2'>
<AppIcon size={expand ? 'large' : 'small'} icon={appDetail.icon} background={appDetail.icon_background} />
<span className={cn(
@@ -185,7 +190,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
<div className="grow w-0">
<div className='flex justify-between items-center text-sm leading-5 font-medium text-gray-900'>
<div className='truncate' title={appDetail.name}>{appDetail.name}</div>
<ChevronDown className='shrink-0 ml-[2px] w-3 h-3 text-gray-500' />
{isCurrentWorkspaceEditor && <ChevronDown className='shrink-0 ml-[2px] w-3 h-3 text-gray-500' />}
</div>
<div className='flex items-center text-[10px] leading-[18px] font-medium text-gray-500 gap-1'>
{appDetail.mode === 'advanced-chat' && (

View File

@@ -5,8 +5,8 @@ import NoData from './no-data'
import Firecrawl from './firecrawl'
import { useModalContext } from '@/context/modal-context'
import type { CrawlOptions, CrawlResultItem } from '@/models/datasets'
import { fetchFirecrawlApiKey } from '@/service/datasets'
import { type DataSourceWebsiteItem, WebsiteProvider } from '@/models/common'
import { fetchDataSources } from '@/service/datasets'
import { type DataSourceItem, DataSourceProvider } from '@/models/common'
type Props = {
onPreview: (payload: CrawlResultItem) => void
@@ -29,9 +29,9 @@ const Website: FC<Props> = ({
const [isLoaded, setIsLoaded] = useState(false)
const [isSetFirecrawlApiKey, setIsSetFirecrawlApiKey] = useState(false)
const checkSetApiKey = useCallback(async () => {
const res = await fetchFirecrawlApiKey() as any
const list = res.settings.filter((item: DataSourceWebsiteItem) => item.provider === WebsiteProvider.fireCrawl && !item.disabled)
setIsSetFirecrawlApiKey(list.length > 0)
const res = await fetchDataSources() as any
const isFirecrawlSet = res.sources.some((item: DataSourceItem) => item.provider === DataSourceProvider.fireCrawl)
setIsSetFirecrawlApiKey(isFirecrawlSet)
}, [])
useEffect(() => {

View File

@@ -58,7 +58,7 @@ const DataSourceNotion: FC<Props> = ({
type={DataSourceType.notion}
isConfigured={connected}
onConfigure={handleConnectNotion}
readonly={!isCurrentWorkspaceManager}
readOnly={!isCurrentWorkspaceManager}
isSupportList
configuredList={workspaces.map(workspace => ({
id: workspace.id,

View File

@@ -11,7 +11,7 @@ import Button from '@/app/components/base/button'
import type { FirecrawlConfig } from '@/models/common'
import Field from '@/app/components/datasets/create/website/firecrawl/base/field'
import Toast from '@/app/components/base/toast'
import { createFirecrawlApiKey } from '@/service/datasets'
import { createDataSourceApiKeyBinding } from '@/service/datasets'
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
type Props = {
onCancel: () => void
@@ -76,7 +76,7 @@ const ConfigFirecrawlModal: FC<Props> = ({
}
try {
setIsSaving(true)
await createFirecrawlApiKey(postData)
await createDataSourceApiKeyBinding(postData)
Toast.notify({
type: 'success',
message: t('common.api.success'),

View File

@@ -7,15 +7,15 @@ import cn from 'classnames'
import Panel from '../panel'
import { DataSourceType } from '../panel/types'
import ConfigFirecrawlModal from './config-firecrawl-modal'
import { fetchFirecrawlApiKey, removeFirecrawlApiKey } from '@/service/datasets'
import { fetchDataSources, removeDataSourceApiKeyBinding } from '@/service/datasets'
import type {
DataSourceWebsiteItem,
DataSourceItem,
} from '@/models/common'
import { useAppContext } from '@/context/app-context'
import {
WebsiteProvider,
DataSourceProvider,
} from '@/models/common'
import Toast from '@/app/components/base/toast'
@@ -24,11 +24,11 @@ type Props = {}
const DataSourceWebsite: FC<Props> = () => {
const { t } = useTranslation()
const { isCurrentWorkspaceManager } = useAppContext()
const [list, setList] = useState<DataSourceWebsiteItem[]>([])
const [sources, setSources] = useState<DataSourceItem[]>([])
const checkSetApiKey = useCallback(async () => {
const res = await fetchFirecrawlApiKey() as any
const list = res.settings.filter((item: DataSourceWebsiteItem) => item.provider === WebsiteProvider.fireCrawl && !item.disabled)
setList(list)
const res = await fetchDataSources() as any
const list = res.sources
setSources(list)
}, [])
useEffect(() => {
@@ -46,23 +46,33 @@ const DataSourceWebsite: FC<Props> = () => {
hideConfig()
}, [checkSetApiKey, hideConfig])
const handleRemove = useCallback(async () => {
await removeFirecrawlApiKey(list[0].id)
setList([])
Toast.notify({
type: 'success',
message: t('common.api.remove'),
})
}, [list, t])
const getIdByProvider = (provider: string): string | undefined => {
const source = sources.find(item => item.provider === provider)
return source?.id
}
const handleRemove = useCallback((provider: string) => {
return async () => {
const dataSourceId = getIdByProvider(provider)
if (dataSourceId) {
await removeDataSourceApiKeyBinding(dataSourceId)
setSources(sources.filter(item => item.provider !== provider))
Toast.notify({
type: 'success',
message: t('common.api.remove'),
})
}
}
}, [sources, t])
return (
<>
<Panel
type={DataSourceType.website}
isConfigured={list.length > 0}
isConfigured={sources.length > 0}
onConfigure={showConfig}
readonly={!isCurrentWorkspaceManager}
configuredList={list.map(item => ({
readOnly={!isCurrentWorkspaceManager}
configuredList={sources.map(item => ({
id: item.id,
logo: ({ className }: { className: string }) => (
<div className={cn(className, 'flex items-center justify-center w-5 h-5 bg-white border border-gray-100 text-xs font-medium text-gray-500 rounded ml-3')}>🔥</div>
@@ -70,7 +80,7 @@ const DataSourceWebsite: FC<Props> = () => {
name: 'FireCrawl',
isActive: true,
}))}
onRemove={handleRemove}
onRemove={handleRemove(DataSourceProvider.fireCrawl)}
/>
{isShowConfig && (
<ConfigFirecrawlModal onSaved={handleAdded} onCancel={hideConfig} />

View File

@@ -26,6 +26,7 @@ type Props = {
notionActions?: {
onChangeAuthorizedPage: () => void
}
readOnly: boolean
}
const ConfigItem: FC<Props> = ({
@@ -33,6 +34,7 @@ const ConfigItem: FC<Props> = ({
payload,
onRemove,
notionActions,
readOnly,
}) => {
const { t } = useTranslation()
const isNotion = type === DataSourceType.notion
@@ -65,7 +67,7 @@ const ConfigItem: FC<Props> = ({
)}
{
isWebsite && (
isWebsite && !readOnly && (
<div className='p-2 text-gray-500 cursor-pointer rounded-md hover:bg-black/5' onClick={onRemove} >
<Trash03 className='w-4 h-4 ' />
</div>

View File

@@ -14,7 +14,7 @@ type Props = {
type: DataSourceType
isConfigured: boolean
onConfigure: () => void
readonly: boolean
readOnly: boolean
isSupportList?: boolean
configuredList: ConfigItemType[]
onRemove: () => void
@@ -27,7 +27,7 @@ const Panel: FC<Props> = ({
type,
isConfigured,
onConfigure,
readonly,
readOnly,
configuredList,
isSupportList,
onRemove,
@@ -67,7 +67,7 @@ const Panel: FC<Props> = ({
className={
`flex items-center ml-3 px-3 h-7 bg-white border border-gray-200
rounded-md text-xs font-medium text-gray-700
${!readonly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
${!readOnly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
}
onClick={onConfigure}
>
@@ -79,7 +79,7 @@ const Panel: FC<Props> = ({
{isSupportList && <div
className={
`flex items-center px-3 py-1 min-h-7 bg-white border-[0.5px] border-gray-200 text-xs font-medium text-primary-600 rounded-md
${!readonly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
${!readOnly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
}
onClick={onConfigure}
>
@@ -96,10 +96,10 @@ const Panel: FC<Props> = ({
<div
className={
`flex items-center ml-3 px-3 h-7 bg-white border border-gray-200
rounded-md text-xs font-medium text-gray-700
${!readonly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
rounded-md text-xs font-medium text-gray-700
${!readOnly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
}
onClick={onConfigure}
onClick={!readOnly ? onConfigure : undefined}
>
{t('common.dataSource.configure')}
</div>
@@ -108,28 +108,28 @@ const Panel: FC<Props> = ({
</div>
{
isConfigured && (
<div className='flex items-center px-3 h-[18px]'>
<div className='text-xs font-medium text-gray-500'>
{isNotion ? t('common.dataSource.notion.connectedWorkspace') : t('common.dataSource.website.configuredCrawlers')}
<>
<div className='flex items-center px-3 h-[18px]'>
<div className='text-xs font-medium text-gray-500'>
{isNotion ? t('common.dataSource.notion.connectedWorkspace') : t('common.dataSource.website.configuredCrawlers')}
</div>
<div className='grow ml-3 border-t border-t-gray-100' />
</div>
<div className='grow ml-3 border-t border-t-gray-100' />
</div>
)
}
{
isConfigured && (
<div className='px-3 pt-2 pb-3'>
{
configuredList.map(item => (
<ConfigItem
key={item.id}
type={type}
payload={item}
onRemove={onRemove}
notionActions={notionActions} />
))
}
</div>
<div className='px-3 pt-2 pb-3'>
{
configuredList.map(item => (
<ConfigItem
key={item.id}
type={type}
payload={item}
onRemove={onRemove}
notionActions={notionActions}
readOnly={readOnly}
/>
))
}
</div>
</>
)
}
</div>