diff --git a/web/app/(shareLayout)/layout.tsx b/web/app/(shareLayout)/layout.tsx
index 83adbd3cae..8db336a17d 100644
--- a/web/app/(shareLayout)/layout.tsx
+++ b/web/app/(shareLayout)/layout.tsx
@@ -1,14 +1,42 @@
-import React from 'react'
+'use client'
+import React, { useEffect, useState } from 'react'
import type { FC } from 'react'
-import type { Metadata } from 'next'
-
-export const metadata: Metadata = {
- icons: 'data:,', // prevent browser from using default favicon
-}
+import { usePathname, useSearchParams } from 'next/navigation'
+import Loading from '../components/base/loading'
+import { useGlobalPublicStore } from '@/context/global-public-context'
+import { AccessMode } from '@/models/access-control'
+import { getAppAccessModeByAppCode } from '@/service/share'
const Layout: FC<{
children: React.ReactNode
}> = ({ children }) => {
+ const isGlobalPending = useGlobalPublicStore(s => s.isGlobalPending)
+ const setWebAppAccessMode = useGlobalPublicStore(s => s.setWebAppAccessMode)
+ const pathname = usePathname()
+ const searchParams = useSearchParams()
+ const redirectUrl = searchParams.get('redirect_url')
+ const [isLoading, setIsLoading] = useState(true)
+ useEffect(() => {
+ (async () => {
+ let appCode: string | null = null
+ if (redirectUrl)
+ appCode = redirectUrl?.split('/').pop() || null
+ else
+ appCode = pathname.split('/').pop() || null
+
+ if (!appCode)
+ return
+ setIsLoading(true)
+ const ret = await getAppAccessModeByAppCode(appCode)
+ setWebAppAccessMode(ret?.accessMode || AccessMode.PUBLIC)
+ setIsLoading(false)
+ })()
+ }, [pathname, redirectUrl, setWebAppAccessMode])
+ if (isLoading || isGlobalPending) {
+ return
+
+
+ }
return (
{children}
diff --git a/web/app/(shareLayout)/webapp-signin/page.tsx b/web/app/(shareLayout)/webapp-signin/page.tsx
index a0855487b4..2e3c82d33d 100644
--- a/web/app/(shareLayout)/webapp-signin/page.tsx
+++ b/web/app/(shareLayout)/webapp-signin/page.tsx
@@ -9,14 +9,13 @@ import { useGlobalPublicStore } from '@/context/global-public-context'
import Loading from '@/app/components/base/loading'
import AppUnavailable from '@/app/components/base/app-unavailable'
import NormalForm from './normalForm'
-import { useAppAccessModeByCode } from '@/service/use-share'
import { AccessMode } from '@/models/access-control'
import ExternalMemberSsoAuth from './components/external-member-sso-auth'
const WebSSOForm: FC = () => {
const { t } = useTranslation()
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
- const isGettingSystemFeatures = useGlobalPublicStore(s => s.isPending)
+ const webAppAccessMode = useGlobalPublicStore(s => s.webAppAccessMode)
const searchParams = useSearchParams()
const router = useRouter()
@@ -39,8 +38,6 @@ const WebSSOForm: FC = () => {
return appCode
}, [redirectUrl])
- const { isLoading, data } = useAppAccessModeByCode(getAppCodeFromRedirectUrl())
-
useEffect(() => {
(async () => {
const appCode = getAppCodeFromRedirectUrl()
@@ -53,11 +50,11 @@ const WebSSOForm: FC = () => {
}, [getAppCodeFromRedirectUrl, redirectUrl, router, tokenFromUrl])
useEffect(() => {
- if (data && data.accessMode === AccessMode.PUBLIC && redirectUrl)
+ if (webAppAccessMode && webAppAccessMode === AccessMode.PUBLIC && redirectUrl)
router.replace(redirectUrl)
- }, [data, router, redirectUrl])
+ }, [webAppAccessMode, router, redirectUrl])
- if (isGettingSystemFeatures || isLoading || tokenFromUrl) {
+ if (tokenFromUrl) {
return
@@ -74,7 +71,7 @@ const WebSSOForm: FC = () => {
}
- if (data && data.accessMode === AccessMode.PUBLIC) {
+ if (webAppAccessMode && webAppAccessMode === AccessMode.PUBLIC) {
return
@@ -84,13 +81,13 @@ const WebSSOForm: FC = () => {
{t('login.webapp.disabled')}
}
- if (data && (data.accessMode === AccessMode.ORGANIZATION || data.accessMode === AccessMode.SPECIFIC_GROUPS_MEMBERS)) {
+ if (webAppAccessMode && (webAppAccessMode === AccessMode.ORGANIZATION || webAppAccessMode === AccessMode.SPECIFIC_GROUPS_MEMBERS)) {
return
}
- if (data && data.accessMode === AccessMode.EXTERNAL_MEMBERS)
+ if (webAppAccessMode && webAppAccessMode === AccessMode.EXTERNAL_MEMBERS)
return
return
diff --git a/web/app/components/base/chat/chat-with-history/context.tsx b/web/app/components/base/chat/chat-with-history/context.tsx
index bb09c95705..5bf1514774 100644
--- a/web/app/components/base/chat/chat-with-history/context.tsx
+++ b/web/app/components/base/chat/chat-with-history/context.tsx
@@ -16,14 +16,12 @@ import type {
ConversationItem,
} from '@/models/share'
import { noop } from 'lodash-es'
-import { AccessMode } from '@/models/access-control'
export type ChatWithHistoryContextValue = {
appInfoError?: any
appInfoLoading?: boolean
appMeta?: AppMeta
appData?: AppData
- accessMode?: AccessMode
userCanAccess?: boolean
appParams?: ChatConfig
appChatListDataLoading?: boolean
@@ -64,7 +62,6 @@ export type ChatWithHistoryContextValue = {
}
export const ChatWithHistoryContext = createContext
({
- accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
userCanAccess: false,
currentConversationId: '',
appPrevChatTree: [],
diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx
index 3694666139..9ec2ebc3d6 100644
--- a/web/app/components/base/chat/chat-with-history/hooks.tsx
+++ b/web/app/components/base/chat/chat-with-history/hooks.tsx
@@ -43,9 +43,8 @@ import { useAppFavicon } from '@/hooks/use-app-favicon'
import { InputVarType } from '@/app/components/workflow/types'
import { TransferMethod } from '@/types/app'
import { noop } from 'lodash-es'
-import { useGetAppAccessMode, useGetUserCanAccessApp } from '@/service/access-control'
+import { useGetUserCanAccessApp } from '@/service/access-control'
import { useGlobalPublicStore } from '@/context/global-public-context'
-import { AccessMode } from '@/models/access-control'
function getFormattedChatList(messages: any[]) {
const newChatList: ChatItem[] = []
@@ -77,11 +76,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo])
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo)
- const { isPending: isGettingAccessMode, data: appAccessMode } = useGetAppAccessMode({
- appId: installedAppInfo?.app.id || appInfo?.app_id,
- isInstalledApp,
- enabled: systemFeatures.webapp_auth.enabled,
- })
const { isPending: isCheckingPermission, data: userCanAccessResult } = useGetUserCanAccessApp({
appId: installedAppInfo?.app.id || appInfo?.app_id,
isInstalledApp,
@@ -469,8 +463,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
return {
appInfoError,
- appInfoLoading: appInfoLoading || (systemFeatures.webapp_auth.enabled && (isGettingAccessMode || isCheckingPermission)),
- accessMode: systemFeatures.webapp_auth.enabled ? appAccessMode?.accessMode : AccessMode.PUBLIC,
+ appInfoLoading: appInfoLoading || (systemFeatures.webapp_auth.enabled && isCheckingPermission),
userCanAccess: systemFeatures.webapp_auth.enabled ? userCanAccessResult?.result : true,
isInstalledApp,
appId,
diff --git a/web/app/components/base/chat/embedded-chatbot/context.tsx b/web/app/components/base/chat/embedded-chatbot/context.tsx
index 5964efd806..d24265ed9e 100644
--- a/web/app/components/base/chat/embedded-chatbot/context.tsx
+++ b/web/app/components/base/chat/embedded-chatbot/context.tsx
@@ -15,10 +15,8 @@ import type {
ConversationItem,
} from '@/models/share'
import { noop } from 'lodash-es'
-import { AccessMode } from '@/models/access-control'
export type EmbeddedChatbotContextValue = {
- accessMode?: AccessMode
userCanAccess?: boolean
appInfoError?: any
appInfoLoading?: boolean
@@ -58,7 +56,6 @@ export type EmbeddedChatbotContextValue = {
export const EmbeddedChatbotContext = createContext({
userCanAccess: false,
- accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
currentConversationId: '',
appPrevChatList: [],
pinnedConversationList: [],
diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx
index 40c56eca7b..0158e8d041 100644
--- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx
+++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx
@@ -36,9 +36,8 @@ import { InputVarType } from '@/app/components/workflow/types'
import { TransferMethod } from '@/types/app'
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
import { noop } from 'lodash-es'
-import { useGetAppAccessMode, useGetUserCanAccessApp } from '@/service/access-control'
+import { useGetUserCanAccessApp } from '@/service/access-control'
import { useGlobalPublicStore } from '@/context/global-public-context'
-import { AccessMode } from '@/models/access-control'
function getFormattedChatList(messages: any[]) {
const newChatList: ChatItem[] = []
@@ -70,11 +69,6 @@ export const useEmbeddedChatbot = () => {
const isInstalledApp = false
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR('appInfo', fetchAppInfo)
- const { isPending: isGettingAccessMode, data: appAccessMode } = useGetAppAccessMode({
- appId: appInfo?.app_id,
- isInstalledApp,
- enabled: systemFeatures.webapp_auth.enabled,
- })
const { isPending: isCheckingPermission, data: userCanAccessResult } = useGetUserCanAccessApp({
appId: appInfo?.app_id,
isInstalledApp,
@@ -385,8 +379,7 @@ export const useEmbeddedChatbot = () => {
return {
appInfoError,
- appInfoLoading: appInfoLoading || (systemFeatures.webapp_auth.enabled && (isGettingAccessMode || isCheckingPermission)),
- accessMode: systemFeatures.webapp_auth.enabled ? appAccessMode?.accessMode : AccessMode.PUBLIC,
+ appInfoLoading: appInfoLoading || (systemFeatures.webapp_auth.enabled && isCheckingPermission),
userCanAccess: systemFeatures.webapp_auth.enabled ? userCanAccessResult?.result : true,
isInstalledApp,
allowResetChat,
diff --git a/web/app/components/share/text-generation/menu-dropdown.tsx b/web/app/components/share/text-generation/menu-dropdown.tsx
index 19b660b083..4682169d44 100644
--- a/web/app/components/share/text-generation/menu-dropdown.tsx
+++ b/web/app/components/share/text-generation/menu-dropdown.tsx
@@ -6,7 +6,7 @@ import type { Placement } from '@floating-ui/react'
import {
RiEqualizer2Line,
} from '@remixicon/react'
-import { useRouter } from 'next/navigation'
+import { usePathname, useRouter } from 'next/navigation'
import Divider from '../../base/divider'
import { removeAccessToken } from '../utils'
import InfoModal from './info-modal'
@@ -19,6 +19,8 @@ import {
import ThemeSwitcher from '@/app/components/base/theme-switcher'
import type { SiteInfo } from '@/models/share'
import cn from '@/utils/classnames'
+import { useGlobalPublicStore } from '@/context/global-public-context'
+import { AccessMode } from '@/models/access-control'
type Props = {
data?: SiteInfo
@@ -31,7 +33,9 @@ const MenuDropdown: FC = ({
placement,
hideLogout,
}) => {
+ const webAppAccessMode = useGlobalPublicStore(s => s.webAppAccessMode)
const router = useRouter()
+ const pathname = usePathname()
const { t } = useTranslation()
const [open, doSetOpen] = useState(false)
const openRef = useRef(open)
@@ -46,8 +50,8 @@ const MenuDropdown: FC = ({
const handleLogout = useCallback(() => {
removeAccessToken()
- router.replace(`/webapp-signin?redirect_url=${window.location.href}`)
- }, [router])
+ router.replace(`/webapp-signin?redirect_url=${pathname}`)
+ }, [router, pathname])
const [show, setShow] = useState(false)
@@ -92,6 +96,16 @@ const MenuDropdown: FC = ({
className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
>{t('common.userProfile.about')}
+ {!(hideLogout || webAppAccessMode === AccessMode.EXTERNAL_MEMBERS || webAppAccessMode === AccessMode.PUBLIC) && (
+
+
+ {t('common.userProfile.logout')}
+
+
+ )}
diff --git a/web/app/components/share/utils.ts b/web/app/components/share/utils.ts
index 741c4e873f..0568aca5c6 100644
--- a/web/app/components/share/utils.ts
+++ b/web/app/components/share/utils.ts
@@ -70,6 +70,7 @@ export const removeAccessToken = () => {
}
localStorage.removeItem(CONVERSATION_ID_INFO)
+ localStorage.removeItem('webAppAccessToken')
delete accessTokenJson[sharedToken]
localStorage.setItem('token', JSON.stringify(accessTokenJson))
diff --git a/web/context/global-public-context.tsx b/web/context/global-public-context.tsx
index 5aa5e7a302..26ad84be65 100644
--- a/web/context/global-public-context.tsx
+++ b/web/context/global-public-context.tsx
@@ -7,19 +7,24 @@ import type { SystemFeatures } from '@/types/feature'
import { defaultSystemFeatures } from '@/types/feature'
import { getSystemFeatures } from '@/service/common'
import Loading from '@/app/components/base/loading'
+import { AccessMode } from '@/models/access-control'
type GlobalPublicStore = {
- isPending: boolean
- setIsPending: (isPending: boolean) => void
+ isGlobalPending: boolean
+ setIsGlobalPending: (isPending: boolean) => void
systemFeatures: SystemFeatures
setSystemFeatures: (systemFeatures: SystemFeatures) => void
+ webAppAccessMode: AccessMode,
+ setWebAppAccessMode: (webAppAccessMode: AccessMode) => void
}
export const useGlobalPublicStore = create(set => ({
- isPending: true,
- setIsPending: (isPending: boolean) => set(() => ({ isPending })),
+ isGlobalPending: true,
+ setIsGlobalPending: (isPending: boolean) => set(() => ({ isGlobalPending: isPending })),
systemFeatures: defaultSystemFeatures,
setSystemFeatures: (systemFeatures: SystemFeatures) => set(() => ({ systemFeatures })),
+ webAppAccessMode: AccessMode.PUBLIC,
+ setWebAppAccessMode: (webAppAccessMode: AccessMode) => set(() => ({ webAppAccessMode })),
}))
const GlobalPublicStoreProvider: FC = ({
@@ -29,7 +34,7 @@ const GlobalPublicStoreProvider: FC = ({
queryKey: ['systemFeatures'],
queryFn: getSystemFeatures,
})
- const { setSystemFeatures, setIsPending } = useGlobalPublicStore()
+ const { setSystemFeatures, setIsGlobalPending: setIsPending } = useGlobalPublicStore()
useEffect(() => {
if (data)
setSystemFeatures({ ...defaultSystemFeatures, ...data })
diff --git a/web/hooks/use-document-title.spec.ts b/web/hooks/use-document-title.spec.ts
index 88239ffbdf..a8d3d56cff 100644
--- a/web/hooks/use-document-title.spec.ts
+++ b/web/hooks/use-document-title.spec.ts
@@ -11,7 +11,7 @@ describe('title should be empty if systemFeatures is pending', () => {
act(() => {
useGlobalPublicStore.setState({
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } },
- isPending: true,
+ isGlobalPending: true,
})
})
it('document title should be empty if set title', () => {
@@ -28,7 +28,7 @@ describe('use default branding', () => {
beforeEach(() => {
act(() => {
useGlobalPublicStore.setState({
- isPending: false,
+ isGlobalPending: false,
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } },
})
})
@@ -48,7 +48,7 @@ describe('use specific branding', () => {
beforeEach(() => {
act(() => {
useGlobalPublicStore.setState({
- isPending: false,
+ isGlobalPending: false,
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: true, application_title: 'Test' } },
})
})
diff --git a/web/hooks/use-document-title.ts b/web/hooks/use-document-title.ts
index 10275a196f..2c848a1f56 100644
--- a/web/hooks/use-document-title.ts
+++ b/web/hooks/use-document-title.ts
@@ -3,7 +3,7 @@ import { useGlobalPublicStore } from '@/context/global-public-context'
import { useFavicon, useTitle } from 'ahooks'
export default function useDocumentTitle(title: string) {
- const isPending = useGlobalPublicStore(s => s.isPending)
+ const isPending = useGlobalPublicStore(s => s.isGlobalPending)
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
const prefix = title ? `${title} - ` : ''
let titleStr = ''