You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

page.tsx 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. 'use client'
  2. import { useRouter, useSearchParams } from 'next/navigation'
  3. import type { FC } from 'react'
  4. import React, { useCallback, useEffect } from 'react'
  5. import { useTranslation } from 'react-i18next'
  6. import Toast from '@/app/components/base/toast'
  7. import { removeAccessToken, setAccessToken } from '@/app/components/share/utils'
  8. import { useGlobalPublicStore } from '@/context/global-public-context'
  9. import Loading from '@/app/components/base/loading'
  10. import AppUnavailable from '@/app/components/base/app-unavailable'
  11. import NormalForm from './normalForm'
  12. import { AccessMode } from '@/models/access-control'
  13. import ExternalMemberSsoAuth from './components/external-member-sso-auth'
  14. import { fetchAccessToken } from '@/service/share'
  15. const WebSSOForm: FC = () => {
  16. const { t } = useTranslation()
  17. const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
  18. const webAppAccessMode = useGlobalPublicStore(s => s.webAppAccessMode)
  19. const searchParams = useSearchParams()
  20. const router = useRouter()
  21. const redirectUrl = searchParams.get('redirect_url')
  22. const tokenFromUrl = searchParams.get('web_sso_token')
  23. const message = searchParams.get('message')
  24. const code = searchParams.get('code')
  25. const getSigninUrl = useCallback(() => {
  26. const params = new URLSearchParams(searchParams)
  27. params.delete('message')
  28. params.delete('code')
  29. return `/webapp-signin?${params.toString()}`
  30. }, [searchParams])
  31. const backToHome = useCallback(() => {
  32. removeAccessToken()
  33. const url = getSigninUrl()
  34. router.replace(url)
  35. }, [getSigninUrl, router])
  36. const showErrorToast = (msg: string) => {
  37. Toast.notify({
  38. type: 'error',
  39. message: msg,
  40. })
  41. }
  42. const getAppCodeFromRedirectUrl = useCallback(() => {
  43. if (!redirectUrl)
  44. return null
  45. const url = new URL(`${window.location.origin}${decodeURIComponent(redirectUrl)}`)
  46. const appCode = url.pathname.split('/').pop()
  47. if (!appCode)
  48. return null
  49. return appCode
  50. }, [redirectUrl])
  51. useEffect(() => {
  52. (async () => {
  53. if (message)
  54. return
  55. const appCode = getAppCodeFromRedirectUrl()
  56. if (appCode && tokenFromUrl && redirectUrl) {
  57. localStorage.setItem('webapp_access_token', tokenFromUrl)
  58. const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: tokenFromUrl })
  59. await setAccessToken(appCode, tokenResp.access_token)
  60. router.replace(decodeURIComponent(redirectUrl))
  61. return
  62. }
  63. if (appCode && redirectUrl && localStorage.getItem('webapp_access_token')) {
  64. const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: localStorage.getItem('webapp_access_token') })
  65. await setAccessToken(appCode, tokenResp.access_token)
  66. router.replace(decodeURIComponent(redirectUrl))
  67. }
  68. })()
  69. }, [getAppCodeFromRedirectUrl, redirectUrl, router, tokenFromUrl, message])
  70. useEffect(() => {
  71. if (webAppAccessMode && webAppAccessMode === AccessMode.PUBLIC && redirectUrl)
  72. router.replace(decodeURIComponent(redirectUrl))
  73. }, [webAppAccessMode, router, redirectUrl])
  74. if (tokenFromUrl) {
  75. return <div className='flex h-full items-center justify-center'>
  76. <Loading />
  77. </div>
  78. }
  79. if (message) {
  80. return <div className='flex h-full flex-col items-center justify-center gap-y-4'>
  81. <AppUnavailable className='h-auto w-auto' code={code || t('share.common.appUnavailable')} unknownReason={message} />
  82. <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{code === '403' ? t('common.userProfile.logout') : t('share.login.backToHome')}</span>
  83. </div>
  84. }
  85. if (!redirectUrl) {
  86. showErrorToast('redirect url is invalid.')
  87. return <div className='flex h-full items-center justify-center'>
  88. <AppUnavailable code={t('share.common.appUnavailable')} unknownReason='redirect url is invalid.' />
  89. </div>
  90. }
  91. if (webAppAccessMode && webAppAccessMode === AccessMode.PUBLIC) {
  92. return <div className='flex h-full items-center justify-center'>
  93. <Loading />
  94. </div>
  95. }
  96. if (!systemFeatures.webapp_auth.enabled) {
  97. return <div className="flex h-full items-center justify-center">
  98. <p className='system-xs-regular text-text-tertiary'>{t('login.webapp.disabled')}</p>
  99. </div>
  100. }
  101. if (webAppAccessMode && (webAppAccessMode === AccessMode.ORGANIZATION || webAppAccessMode === AccessMode.SPECIFIC_GROUPS_MEMBERS)) {
  102. return <div className='w-full max-w-[400px]'>
  103. <NormalForm />
  104. </div>
  105. }
  106. if (webAppAccessMode && webAppAccessMode === AccessMode.EXTERNAL_MEMBERS)
  107. return <ExternalMemberSsoAuth />
  108. return <div className='flex h-full flex-col items-center justify-center gap-y-4'>
  109. <AppUnavailable className='h-auto w-auto' isUnknownReason={true} />
  110. <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('share.login.backToHome')}</span>
  111. </div>
  112. }
  113. export default React.memo(WebSSOForm)