| @@ -23,10 +23,12 @@ const WebSSOForm: FC = () => { | |||
| const redirectUrl = searchParams.get('redirect_url') | |||
| const tokenFromUrl = searchParams.get('web_sso_token') | |||
| const message = searchParams.get('message') | |||
| const code = searchParams.get('code') | |||
| const getSigninUrl = useCallback(() => { | |||
| const params = new URLSearchParams(searchParams) | |||
| params.delete('message') | |||
| params.delete('code') | |||
| return `/webapp-signin?${params.toString()}` | |||
| }, [searchParams]) | |||
| @@ -85,8 +87,8 @@ const WebSSOForm: FC = () => { | |||
| if (message) { | |||
| return <div className='flex h-full flex-col items-center justify-center gap-y-4'> | |||
| <AppUnavailable className='h-auto w-auto' code={t('share.common.appUnavailable')} unknownReason={message} /> | |||
| <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('share.login.backToHome')}</span> | |||
| <AppUnavailable className='h-auto w-auto' code={code || t('share.common.appUnavailable')} unknownReason={message} /> | |||
| <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{code === '403' ? t('common.userProfile.logout') : t('share.login.backToHome')}</span> | |||
| </div> | |||
| } | |||
| if (!redirectUrl) { | |||
| @@ -278,7 +278,7 @@ const AppPublisher = ({ | |||
| onClick={() => { | |||
| setShowAppAccessControl(true) | |||
| }}> | |||
| <div className='flex grow items-center gap-x-1.5 pr-1'> | |||
| <div className='flex grow items-center gap-x-1.5 overflow-hidden pr-1'> | |||
| {appDetail?.access_mode === AccessMode.ORGANIZATION | |||
| && <> | |||
| <RiBuildingLine className='h-4 w-4 shrink-0 text-text-secondary' /> | |||
| @@ -288,7 +288,9 @@ const AppPublisher = ({ | |||
| {appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS | |||
| && <> | |||
| <RiLockLine className='h-4 w-4 shrink-0 text-text-secondary' /> | |||
| <p className='system-sm-medium text-text-secondary'>{t('app.accessControlDialog.accessItems.specific')}</p> | |||
| <div className='grow truncate'> | |||
| <span className='system-sm-medium text-text-secondary'>{t('app.accessControlDialog.accessItems.specific')}</span> | |||
| </div> | |||
| </> | |||
| } | |||
| {appDetail?.access_mode === AccessMode.PUBLIC | |||
| @@ -21,7 +21,7 @@ const AppUnavailable: FC<IAppUnavailableProps> = ({ | |||
| return ( | |||
| <div className={classNames('flex h-screen w-screen items-center justify-center', className)}> | |||
| <h1 className='mr-5 h-[50px] pr-5 text-[24px] font-medium leading-[50px]' | |||
| <h1 className='mr-5 h-[50px] shrink-0 pr-5 text-[24px] font-medium leading-[50px]' | |||
| style={{ | |||
| borderRight: '1px solid rgba(0,0,0,.3)', | |||
| }}>{code}</h1> | |||
| @@ -1,5 +1,7 @@ | |||
| 'use client' | |||
| import type { FC } from 'react' | |||
| import { | |||
| useCallback, | |||
| useEffect, | |||
| useState, | |||
| } from 'react' | |||
| @@ -17,10 +19,12 @@ import ChatWrapper from './chat-wrapper' | |||
| import type { InstalledApp } from '@/models/explore' | |||
| import Loading from '@/app/components/base/loading' | |||
| import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | |||
| import { checkOrSetAccessToken } from '@/app/components/share/utils' | |||
| import { checkOrSetAccessToken, removeAccessToken } from '@/app/components/share/utils' | |||
| import AppUnavailable from '@/app/components/base/app-unavailable' | |||
| import cn from '@/utils/classnames' | |||
| import useDocumentTitle from '@/hooks/use-document-title' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { usePathname, useRouter, useSearchParams } from 'next/navigation' | |||
| type ChatWithHistoryProps = { | |||
| className?: string | |||
| @@ -38,6 +42,7 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({ | |||
| isMobile, | |||
| themeBuilder, | |||
| sidebarCollapseState, | |||
| isInstalledApp, | |||
| } = useChatWithHistoryContext() | |||
| const isSidebarCollapsed = sidebarCollapseState | |||
| const customConfig = appData?.custom_config | |||
| @@ -51,13 +56,34 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({ | |||
| useDocumentTitle(site?.title || 'Chat') | |||
| const { t } = useTranslation() | |||
| const searchParams = useSearchParams() | |||
| const router = useRouter() | |||
| const pathname = usePathname() | |||
| const getSigninUrl = useCallback(() => { | |||
| const params = new URLSearchParams(searchParams) | |||
| params.delete('message') | |||
| params.set('redirect_url', pathname) | |||
| return `/webapp-signin?${params.toString()}` | |||
| }, [searchParams, pathname]) | |||
| const backToHome = useCallback(() => { | |||
| removeAccessToken() | |||
| const url = getSigninUrl() | |||
| router.replace(url) | |||
| }, [getSigninUrl, router]) | |||
| if (appInfoLoading) { | |||
| return ( | |||
| <Loading type='app' /> | |||
| ) | |||
| } | |||
| if (!userCanAccess) | |||
| return <AppUnavailable code={403} unknownReason='no permission.' /> | |||
| if (!userCanAccess) { | |||
| return <div className='flex h-full flex-col items-center justify-center gap-y-2'> | |||
| <AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' /> | |||
| {!isInstalledApp && <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('common.userProfile.logout')}</span>} | |||
| </div> | |||
| } | |||
| if (appInfoError) { | |||
| return ( | |||
| @@ -1,4 +1,6 @@ | |||
| 'use client' | |||
| import { | |||
| useCallback, | |||
| useEffect, | |||
| useState, | |||
| } from 'react' | |||
| @@ -12,7 +14,7 @@ import { useEmbeddedChatbot } from './hooks' | |||
| import { isDify } from './utils' | |||
| import { useThemeContext } from './theme/theme-context' | |||
| import { CssTransform } from './theme/utils' | |||
| import { checkOrSetAccessToken } from '@/app/components/share/utils' | |||
| import { checkOrSetAccessToken, removeAccessToken } from '@/app/components/share/utils' | |||
| import AppUnavailable from '@/app/components/base/app-unavailable' | |||
| import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | |||
| import Loading from '@/app/components/base/loading' | |||
| @@ -23,6 +25,7 @@ import DifyLogo from '@/app/components/base/logo/dify-logo' | |||
| import cn from '@/utils/classnames' | |||
| import useDocumentTitle from '@/hooks/use-document-title' | |||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||
| import { usePathname, useRouter, useSearchParams } from 'next/navigation' | |||
| const Chatbot = () => { | |||
| const { | |||
| @@ -36,6 +39,7 @@ const Chatbot = () => { | |||
| chatShouldReloadKey, | |||
| handleNewConversation, | |||
| themeBuilder, | |||
| isInstalledApp, | |||
| } = useEmbeddedChatbotContext() | |||
| const { t } = useTranslation() | |||
| const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) | |||
| @@ -51,6 +55,22 @@ const Chatbot = () => { | |||
| useDocumentTitle(site?.title || 'Chat') | |||
| const searchParams = useSearchParams() | |||
| const router = useRouter() | |||
| const pathname = usePathname() | |||
| const getSigninUrl = useCallback(() => { | |||
| const params = new URLSearchParams(searchParams) | |||
| params.delete('message') | |||
| params.set('redirect_url', pathname) | |||
| return `/webapp-signin?${params.toString()}` | |||
| }, [searchParams, pathname]) | |||
| const backToHome = useCallback(() => { | |||
| removeAccessToken() | |||
| const url = getSigninUrl() | |||
| router.replace(url) | |||
| }, [getSigninUrl, router]) | |||
| if (appInfoLoading) { | |||
| return ( | |||
| <> | |||
| @@ -66,8 +86,12 @@ const Chatbot = () => { | |||
| ) | |||
| } | |||
| if (!userCanAccess) | |||
| return <AppUnavailable code={403} unknownReason='no permission.' /> | |||
| if (!userCanAccess) { | |||
| return <div className='flex h-full flex-col items-center justify-center gap-y-2'> | |||
| <AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' /> | |||
| {!isInstalledApp && <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('common.userProfile.logout')}</span>} | |||
| </div> | |||
| } | |||
| if (appInfoError) { | |||
| return ( | |||
| @@ -141,7 +165,6 @@ const EmbeddedChatbotWrapper = () => { | |||
| appInfoError, | |||
| appInfoLoading, | |||
| appData, | |||
| accessMode, | |||
| userCanAccess, | |||
| appParams, | |||
| appMeta, | |||
| @@ -176,7 +199,6 @@ const EmbeddedChatbotWrapper = () => { | |||
| return <EmbeddedChatbotContext.Provider value={{ | |||
| userCanAccess, | |||
| accessMode, | |||
| appInfoError, | |||
| appInfoLoading, | |||
| appData, | |||
| @@ -9,7 +9,7 @@ import { | |||
| import { useBoolean } from 'ahooks' | |||
| import { usePathname, useRouter, useSearchParams } from 'next/navigation' | |||
| import TabHeader from '../../base/tab-header' | |||
| import { checkOrSetAccessToken } from '../utils' | |||
| import { checkOrSetAccessToken, removeAccessToken } from '../utils' | |||
| import MenuDropdown from './menu-dropdown' | |||
| import RunBatch from './run-batch' | |||
| import ResDownload from './run-batch/res-download' | |||
| @@ -536,14 +536,31 @@ const TextGeneration: FC<IMainProps> = ({ | |||
| </div> | |||
| ) | |||
| const getSigninUrl = useCallback(() => { | |||
| const params = new URLSearchParams(searchParams) | |||
| params.delete('message') | |||
| params.set('redirect_url', pathname) | |||
| return `/webapp-signin?${params.toString()}` | |||
| }, [searchParams, pathname]) | |||
| const backToHome = useCallback(() => { | |||
| removeAccessToken() | |||
| const url = getSigninUrl() | |||
| router.replace(url) | |||
| }, [getSigninUrl, router]) | |||
| if (!appId || !siteInfo || !promptConfig || (systemFeatures.webapp_auth.enabled && (isGettingAccessMode || isCheckingPermission))) { | |||
| return ( | |||
| <div className='flex h-screen items-center'> | |||
| <Loading type='app' /> | |||
| </div>) | |||
| } | |||
| if (systemFeatures.webapp_auth.enabled && !userCanAccessResult?.result) | |||
| return <AppUnavailable code={403} unknownReason='no permission.' /> | |||
| if (systemFeatures.webapp_auth.enabled && !userCanAccessResult?.result) { | |||
| return <div className='flex h-full flex-col items-center justify-center gap-y-2'> | |||
| <AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' /> | |||
| {!isInstalledApp && <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('common.userProfile.logout')}</span>} | |||
| </div> | |||
| } | |||
| return ( | |||
| <div className={cn( | |||
| @@ -57,22 +57,6 @@ export const setAccessToken = (sharedToken: string, token: string, user_id?: str | |||
| } | |||
| export const removeAccessToken = () => { | |||
| const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0] | |||
| const accessToken = localStorage.getItem('token') || JSON.stringify(getInitialTokenV2()) | |||
| let accessTokenJson = getInitialTokenV2() | |||
| try { | |||
| accessTokenJson = JSON.parse(accessToken) | |||
| if (isTokenV1(accessTokenJson)) | |||
| accessTokenJson = getInitialTokenV2() | |||
| } | |||
| catch { | |||
| } | |||
| localStorage.removeItem(CONVERSATION_ID_INFO) | |||
| localStorage.removeItem('token') | |||
| localStorage.removeItem('webapp_access_token') | |||
| delete accessTokenJson[sharedToken] | |||
| localStorage.setItem('token', JSON.stringify(accessTokenJson)) | |||
| } | |||
| @@ -108,12 +108,13 @@ function unicodeToChar(text: string) { | |||
| }) | |||
| } | |||
| function requiredWebSSOLogin(message?: string) { | |||
| removeAccessToken() | |||
| function requiredWebSSOLogin(message?: string, code?: number) { | |||
| const params = new URLSearchParams() | |||
| params.append('redirect_url', globalThis.location.pathname) | |||
| if (message) | |||
| params.append('message', message) | |||
| if (code) | |||
| params.append('code', String(code)) | |||
| globalThis.location.href = `/webapp-signin?${params.toString()}` | |||
| } | |||
| @@ -403,10 +404,12 @@ export const ssePost = async ( | |||
| res.json().then((data: any) => { | |||
| if (isPublicAPI) { | |||
| if (data.code === 'web_app_access_denied') | |||
| requiredWebSSOLogin(data.message) | |||
| requiredWebSSOLogin(data.message, 403) | |||
| if (data.code === 'web_sso_auth_required') | |||
| if (data.code === 'web_sso_auth_required') { | |||
| removeAccessToken() | |||
| requiredWebSSOLogin() | |||
| } | |||
| if (data.code === 'unauthorized') { | |||
| removeAccessToken() | |||
| @@ -484,10 +487,11 @@ export const request = async<T>(url: string, options = {}, otherOptions?: IOther | |||
| const { code, message } = errRespData | |||
| // webapp sso | |||
| if (code === 'web_app_access_denied') { | |||
| requiredWebSSOLogin(message) | |||
| requiredWebSSOLogin(message, 403) | |||
| return Promise.reject(err) | |||
| } | |||
| if (code === 'web_sso_auth_required') { | |||
| removeAccessToken() | |||
| requiredWebSSOLogin() | |||
| return Promise.reject(err) | |||
| } | |||