diff --git a/web/app/(shareLayout)/webapp-signin/normalForm.tsx b/web/app/(shareLayout)/webapp-signin/normalForm.tsx
index d6bdf607ba..44006a9f1e 100644
--- a/web/app/(shareLayout)/webapp-signin/normalForm.tsx
+++ b/web/app/(shareLayout)/webapp-signin/normalForm.tsx
@@ -1,3 +1,4 @@
+'use client'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Link from 'next/link'
diff --git a/web/app/(shareLayout)/webapp-signin/page.tsx b/web/app/(shareLayout)/webapp-signin/page.tsx
index 967516c416..1c6209b902 100644
--- a/web/app/(shareLayout)/webapp-signin/page.tsx
+++ b/web/app/(shareLayout)/webapp-signin/page.tsx
@@ -1,36 +1,30 @@
'use client'
import { useRouter, useSearchParams } from 'next/navigation'
import type { FC } from 'react'
-import React, { useCallback, useEffect } from 'react'
+import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
-import Toast from '@/app/components/base/toast'
-import { removeAccessToken, setAccessToken } from '@/app/components/share/utils'
+import { removeAccessToken } from '@/app/components/share/utils'
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 { AccessMode } from '@/models/access-control'
import ExternalMemberSsoAuth from './components/external-member-sso-auth'
-import { fetchAccessToken } from '@/service/share'
+import { useWebAppStore } from '@/context/web-app-context'
const WebSSOForm: FC = () => {
const { t } = useTranslation()
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
- const webAppAccessMode = useGlobalPublicStore(s => s.webAppAccessMode)
+ const webAppAccessMode = useWebAppStore(s => s.webAppAccessMode)
const searchParams = useSearchParams()
const router = useRouter()
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')
+ const params = new URLSearchParams()
+ params.append('redirect_url', redirectUrl || '')
return `/webapp-signin?${params.toString()}`
- }, [searchParams])
+ }, [redirectUrl])
const backToHome = useCallback(() => {
removeAccessToken()
@@ -38,73 +32,12 @@ const WebSSOForm: FC = () => {
router.replace(url)
}, [getSigninUrl, router])
- const showErrorToast = (msg: string) => {
- Toast.notify({
- type: 'error',
- message: msg,
- })
- }
-
- const getAppCodeFromRedirectUrl = useCallback(() => {
- if (!redirectUrl)
- return null
- const url = new URL(`${window.location.origin}${decodeURIComponent(redirectUrl)}`)
- const appCode = url.pathname.split('/').pop()
- if (!appCode)
- return null
-
- return appCode
- }, [redirectUrl])
-
- useEffect(() => {
- (async () => {
- if (message)
- return
-
- const appCode = getAppCodeFromRedirectUrl()
- if (appCode && tokenFromUrl && redirectUrl) {
- localStorage.setItem('webapp_access_token', tokenFromUrl)
- const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: tokenFromUrl })
- await setAccessToken(appCode, tokenResp.access_token)
- router.replace(decodeURIComponent(redirectUrl))
- return
- }
- if (appCode && redirectUrl && localStorage.getItem('webapp_access_token')) {
- const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: localStorage.getItem('webapp_access_token') })
- await setAccessToken(appCode, tokenResp.access_token)
- router.replace(decodeURIComponent(redirectUrl))
- }
- })()
- }, [getAppCodeFromRedirectUrl, redirectUrl, router, tokenFromUrl, message])
-
- useEffect(() => {
- if (webAppAccessMode && webAppAccessMode === AccessMode.PUBLIC && redirectUrl)
- router.replace(decodeURIComponent(redirectUrl))
- }, [webAppAccessMode, router, redirectUrl])
-
- if (tokenFromUrl) {
- return
-
-
- }
-
- if (message) {
- return
-
-
{code === '403' ? t('common.userProfile.logout') : t('share.login.backToHome')}
-
- }
if (!redirectUrl) {
- showErrorToast('redirect url is invalid.')
return
}
- if (webAppAccessMode && webAppAccessMode === AccessMode.PUBLIC) {
- return
-
-
- }
+
if (!systemFeatures.webapp_auth.enabled) {
return
{t('login.webapp.disabled')}
diff --git a/web/app/(shareLayout)/workflow/[token]/page.tsx b/web/app/(shareLayout)/workflow/[token]/page.tsx
index e93bc8c1af..4f5923e91f 100644
--- a/web/app/(shareLayout)/workflow/[token]/page.tsx
+++ b/web/app/(shareLayout)/workflow/[token]/page.tsx
@@ -1,10 +1,13 @@
import React from 'react'
import Main from '@/app/components/share/text-generation'
+import AuthenticatedLayout from '../../components/authenticated-layout'
const Workflow = () => {
return (
-
+
+
+
)
}
diff --git a/web/app/components/share/text-generation/index.tsx b/web/app/components/share/text-generation/index.tsx
index 4be6b18958..6397b57785 100644
--- a/web/app/components/share/text-generation/index.tsx
+++ b/web/app/components/share/text-generation/index.tsx
@@ -9,7 +9,7 @@ import {
import { useBoolean } from 'ahooks'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import TabHeader from '../../base/tab-header'
-import { checkOrSetAccessToken, removeAccessToken } from '../utils'
+import { removeAccessToken } from '../utils'
import MenuDropdown from './menu-dropdown'
import RunBatch from './run-batch'
import ResDownload from './run-batch/res-download'
@@ -376,8 +376,8 @@ const TextGeneration: FC
= ({
}
const fetchInitData = async () => {
- if (!isInstalledApp)
- await checkOrSetAccessToken()
+ // if (!isInstalledApp)
+ // await checkOrSetAccessToken()
return Promise.all([
isInstalledApp
diff --git a/web/app/components/share/text-generation/menu-dropdown.tsx b/web/app/components/share/text-generation/menu-dropdown.tsx
index adb926c7ca..1c1b6adfe8 100644
--- a/web/app/components/share/text-generation/menu-dropdown.tsx
+++ b/web/app/components/share/text-generation/menu-dropdown.tsx
@@ -18,8 +18,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'
+import { useWebAppStore } from '@/context/web-app-context'
type Props = {
data?: SiteInfo
@@ -32,7 +32,7 @@ const MenuDropdown: FC = ({
placement,
hideLogout,
}) => {
- const webAppAccessMode = useGlobalPublicStore(s => s.webAppAccessMode)
+ const webAppAccessMode = useWebAppStore(s => s.webAppAccessMode)
const router = useRouter()
const pathname = usePathname()
const { t } = useTranslation()
diff --git a/web/app/components/share/utils.ts b/web/app/components/share/utils.ts
index 8a897ab59a..0c6457fb0c 100644
--- a/web/app/components/share/utils.ts
+++ b/web/app/components/share/utils.ts
@@ -10,7 +10,7 @@ export const getInitialTokenV2 = (): Record => ({
version: 2,
})
-export const checkOrSetAccessToken = async (appCode?: string) => {
+export const checkOrSetAccessToken = async (appCode?: string | null) => {
const sharedToken = appCode || globalThis.location.pathname.split('/').slice(-1)[0]
const userId = (await getProcessedSystemVariablesFromUrlParams()).user_id
const accessToken = localStorage.getItem('token') || JSON.stringify(getInitialTokenV2())
diff --git a/web/context/global-public-context.tsx b/web/context/global-public-context.tsx
index 26ad84be65..324ac019c8 100644
--- a/web/context/global-public-context.tsx
+++ b/web/context/global-public-context.tsx
@@ -7,15 +7,12 @@ 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 = {
isGlobalPending: boolean
setIsGlobalPending: (isPending: boolean) => void
systemFeatures: SystemFeatures
setSystemFeatures: (systemFeatures: SystemFeatures) => void
- webAppAccessMode: AccessMode,
- setWebAppAccessMode: (webAppAccessMode: AccessMode) => void
}
export const useGlobalPublicStore = create(set => ({
@@ -23,8 +20,6 @@ export const useGlobalPublicStore = create(set => ({
setIsGlobalPending: (isPending: boolean) => set(() => ({ isGlobalPending: isPending })),
systemFeatures: defaultSystemFeatures,
setSystemFeatures: (systemFeatures: SystemFeatures) => set(() => ({ systemFeatures })),
- webAppAccessMode: AccessMode.PUBLIC,
- setWebAppAccessMode: (webAppAccessMode: AccessMode) => set(() => ({ webAppAccessMode })),
}))
const GlobalPublicStoreProvider: FC = ({
diff --git a/web/context/web-app-context.tsx b/web/context/web-app-context.tsx
new file mode 100644
index 0000000000..28a3312b0d
--- /dev/null
+++ b/web/context/web-app-context.tsx
@@ -0,0 +1,74 @@
+'use client'
+
+import Loading from '@/app/components/base/loading'
+import { AccessMode } from '@/models/access-control'
+import { useAppAccessModeByCode } from '@/service/use-share'
+import type { App } from '@/types/app'
+import { usePathname, useSearchParams } from 'next/navigation'
+import type { FC, PropsWithChildren } from 'react'
+import { useEffect } from 'react'
+import { useState } from 'react'
+import { create } from 'zustand'
+
+type WebAppStore = {
+ shareCode: string | null
+ updateShareCode: (shareCode: string | null) => void
+ appInfo: App | null
+ updateAppInfo: (appInfo: App | null) => void
+ webAppAccessMode: AccessMode
+ updateWebAppAccessMode: (accessMode: AccessMode) => void
+}
+
+export const useWebAppStore = create(set => ({
+ shareCode: null,
+ updateShareCode: (shareCode: string | null) => set(() => ({ shareCode })),
+ appInfo: null,
+ updateAppInfo: (appInfo: App | null) => set(() => ({ appInfo })),
+ webAppAccessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
+ updateWebAppAccessMode: (accessMode: AccessMode) => set(() => ({ webAppAccessMode: accessMode })),
+}))
+
+const getShareCodeFromRedirectUrl = (redirectUrl: string | null): string | null => {
+ if (!redirectUrl || redirectUrl.length === 0)
+ return null
+ const url = new URL(`${window.location.origin}${decodeURIComponent(redirectUrl)}`)
+ return url.pathname.split('/').pop() || null
+}
+const getShareCodeFromPathname = (pathname: string): string | null => {
+ const code = pathname.split('/').pop() || null
+ if (code === 'webapp-signin')
+ return null
+ return code
+}
+
+const WebAppStoreProvider: FC = ({ children }) => {
+ const updateWebAppAccessMode = useWebAppStore(state => state.updateWebAppAccessMode)
+ const updateShareCode = useWebAppStore(state => state.updateShareCode)
+ const pathname = usePathname()
+ const searchParams = useSearchParams()
+ const redirectUrlParam = searchParams.get('redirect_url')
+ const [shareCode, setShareCode] = useState(null)
+ useEffect(() => {
+ const shareCodeFromRedirect = getShareCodeFromRedirectUrl(redirectUrlParam)
+ const shareCodeFromPathname = getShareCodeFromPathname(pathname)
+ const newShareCode = shareCodeFromRedirect || shareCodeFromPathname
+ setShareCode(newShareCode)
+ updateShareCode(newShareCode)
+ }, [pathname, redirectUrlParam, updateShareCode])
+ const { isFetching, data: accessModeResult } = useAppAccessModeByCode(shareCode)
+ useEffect(() => {
+ if (accessModeResult?.accessMode)
+ updateWebAppAccessMode(accessModeResult.accessMode)
+ }, [accessModeResult, updateWebAppAccessMode])
+ if (isFetching) {
+ return
+
+
+ }
+ return (
+ <>
+ {children}
+ >
+ )
+}
+export default WebAppStoreProvider
diff --git a/web/i18n/en-US/login.ts b/web/i18n/en-US/login.ts
index 0beb631d24..d47eb7c079 100644
--- a/web/i18n/en-US/login.ts
+++ b/web/i18n/en-US/login.ts
@@ -105,6 +105,7 @@ const translation = {
licenseInactive: 'License Inactive',
licenseInactiveTip: 'The Dify Enterprise license for your workspace is inactive. Please contact your administrator to continue using Dify.',
webapp: {
+ login: 'Login',
noLoginMethod: 'Authentication method not configured for web app',
noLoginMethodTip: 'Please contact the system admin to add an authentication method.',
disabled: 'Webapp authentication is disabled. Please contact the system admin to enable it. You can try to use the app directly.',
diff --git a/web/i18n/ja-JP/login.ts b/web/i18n/ja-JP/login.ts
index 84ab9eecd0..b37700eba2 100644
--- a/web/i18n/ja-JP/login.ts
+++ b/web/i18n/ja-JP/login.ts
@@ -106,6 +106,7 @@ const translation = {
licenseExpired: 'ライセンスの有効期限が切れています',
licenseLostTip: 'Dify ライセンスサーバーへの接続に失敗しました。続けて Dify を使用するために管理者に連絡してください。',
webapp: {
+ login: 'ログイン',
noLoginMethod: 'Web アプリに対して認証方法が構成されていません',
noLoginMethodTip: 'システム管理者に連絡して、認証方法を追加してください。',
disabled: 'Web アプリの認証が無効になっています。システム管理者に連絡して有効にしてください。直接アプリを使用してみてください。',
diff --git a/web/i18n/zh-Hans/login.ts b/web/i18n/zh-Hans/login.ts
index a37fc104eb..b63630e288 100644
--- a/web/i18n/zh-Hans/login.ts
+++ b/web/i18n/zh-Hans/login.ts
@@ -106,6 +106,7 @@ const translation = {
licenseInactive: '许可证未激活',
licenseInactiveTip: '您所在空间的 Dify Enterprise 许可证尚未激活,请联系管理员以继续使用 Dify。',
webapp: {
+ login: '登录',
noLoginMethod: 'Web 应用未配置身份认证方式',
noLoginMethodTip: '请联系系统管理员添加身份认证方式',
disabled: 'Web 应用身份认证已禁用,请联系系统管理员启用。您也可以尝试直接使用应用。',
diff --git a/web/service/base.ts b/web/service/base.ts
index 49377be912..8ffacaa0f1 100644
--- a/web/service/base.ts
+++ b/web/service/base.ts
@@ -413,7 +413,7 @@ export const ssePost = async (
if (data.code === 'unauthorized') {
removeAccessToken()
- globalThis.location.reload()
+ requiredWebSSOLogin()
}
}
})
@@ -507,7 +507,7 @@ export const request = async(url: string, options = {}, otherOptions?: IOther
} = otherOptionsForBaseFetch
if (isPublicAPI && code === 'unauthorized') {
removeAccessToken()
- globalThis.location.reload()
+ requiredWebSSOLogin()
return Promise.reject(err)
}
if (code === 'init_validate_failed' && IS_CE_EDITION && !silent) {
diff --git a/web/service/use-share.ts b/web/service/use-share.ts
index b8f96f6cc5..46181c4878 100644
--- a/web/service/use-share.ts
+++ b/web/service/use-share.ts
@@ -1,14 +1,22 @@
+import { useGlobalPublicStore } from '@/context/global-public-context'
+import { AccessMode } from '@/models/access-control'
import { useQuery } from '@tanstack/react-query'
import { getAppAccessModeByAppCode } from './share'
const NAME_SPACE = 'webapp'
export const useAppAccessModeByCode = (code: string | null) => {
+ const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
return useQuery({
queryKey: [NAME_SPACE, 'appAccessMode', code],
queryFn: () => {
- if (!code)
- return null
+ if (systemFeatures.webapp_auth.enabled === false) {
+ return {
+ accessMode: AccessMode.PUBLIC,
+ }
+ }
+ if (!code || code.length === 0)
+ return Promise.reject(new Error('App code is required to get access mode'))
return getAppAccessModeByAppCode(code)
},