mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-06 08:06:05 +08:00
fix: web app support logout
This commit is contained in:
parent
35fea50077
commit
ce635d7e24
@ -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 <div className='flex h-full w-full items-center justify-center'>
|
||||
<Loading />
|
||||
</div>
|
||||
}
|
||||
return (
|
||||
<div className="h-full min-w-[300px] pb-[env(safe-area-inset-bottom)]">
|
||||
{children}
|
||||
|
@ -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 <div className='flex h-full items-center justify-center'>
|
||||
<Loading />
|
||||
</div>
|
||||
@ -74,7 +71,7 @@ const WebSSOForm: FC = () => {
|
||||
<AppUnavailable code={'App Unavailable'} unknownReason='redirect url is invalid.' />
|
||||
</div>
|
||||
}
|
||||
if (data && data.accessMode === AccessMode.PUBLIC) {
|
||||
if (webAppAccessMode && webAppAccessMode === AccessMode.PUBLIC) {
|
||||
return <div className='flex h-full items-center justify-center'>
|
||||
<Loading />
|
||||
</div>
|
||||
@ -84,13 +81,13 @@ const WebSSOForm: FC = () => {
|
||||
<p className='system-xs-regular text-text-tertiary'>{t('login.webapp.disabled')}</p>
|
||||
</div>
|
||||
}
|
||||
if (data && (data.accessMode === AccessMode.ORGANIZATION || data.accessMode === AccessMode.SPECIFIC_GROUPS_MEMBERS)) {
|
||||
if (webAppAccessMode && (webAppAccessMode === AccessMode.ORGANIZATION || webAppAccessMode === AccessMode.SPECIFIC_GROUPS_MEMBERS)) {
|
||||
return <div className='w-[400px]'>
|
||||
<NormalForm />
|
||||
</div>
|
||||
}
|
||||
|
||||
if (data && data.accessMode === AccessMode.EXTERNAL_MEMBERS)
|
||||
if (webAppAccessMode && webAppAccessMode === AccessMode.EXTERNAL_MEMBERS)
|
||||
return <ExternalMemberSsoAuth />
|
||||
|
||||
return <div className='flex h-full items-center justify-center'>
|
||||
|
@ -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<ChatWithHistoryContextValue>({
|
||||
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
|
||||
userCanAccess: false,
|
||||
currentConversationId: '',
|
||||
appPrevChatTree: [],
|
||||
|
@ -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,
|
||||
|
@ -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<EmbeddedChatbotContextValue>({
|
||||
userCanAccess: false,
|
||||
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
|
||||
currentConversationId: '',
|
||||
appPrevChatList: [],
|
||||
pinnedConversationList: [],
|
||||
|
@ -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,
|
||||
|
@ -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<Props> = ({
|
||||
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<Props> = ({
|
||||
|
||||
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<Props> = ({
|
||||
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')}</div>
|
||||
</div>
|
||||
{!(hideLogout || webAppAccessMode === AccessMode.EXTERNAL_MEMBERS || webAppAccessMode === AccessMode.PUBLIC) && (
|
||||
<div className='p-1'>
|
||||
<div
|
||||
onClick={handleLogout}
|
||||
className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
|
||||
>
|
||||
{t('common.userProfile.logout')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
|
@ -70,6 +70,7 @@ export const removeAccessToken = () => {
|
||||
}
|
||||
|
||||
localStorage.removeItem(CONVERSATION_ID_INFO)
|
||||
localStorage.removeItem('webAppAccessToken')
|
||||
|
||||
delete accessTokenJson[sharedToken]
|
||||
localStorage.setItem('token', JSON.stringify(accessTokenJson))
|
||||
|
@ -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<GlobalPublicStore>(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<PropsWithChildren> = ({
|
||||
@ -29,7 +34,7 @@ const GlobalPublicStoreProvider: FC<PropsWithChildren> = ({
|
||||
queryKey: ['systemFeatures'],
|
||||
queryFn: getSystemFeatures,
|
||||
})
|
||||
const { setSystemFeatures, setIsPending } = useGlobalPublicStore()
|
||||
const { setSystemFeatures, setIsGlobalPending: setIsPending } = useGlobalPublicStore()
|
||||
useEffect(() => {
|
||||
if (data)
|
||||
setSystemFeatures({ ...defaultSystemFeatures, ...data })
|
||||
|
@ -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' } },
|
||||
})
|
||||
})
|
||||
|
@ -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 = ''
|
||||
|
Loading…
x
Reference in New Issue
Block a user