mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 11:42:29 +08:00
Merge branch 'feat/auth-methods' into deploy/dev
This commit is contained in:
commit
a2cf97fd2c
|
@ -1,10 +1,11 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { SWRConfig } from 'swr'
|
import { SWRConfig } from 'swr'
|
||||||
import { useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { useRouter, useSearchParams } from 'next/navigation'
|
import { useRouter, useSearchParams } from 'next/navigation'
|
||||||
import useRefreshToken from '@/hooks/use-refresh-token'
|
import useRefreshToken from '@/hooks/use-refresh-token'
|
||||||
|
import { fetchSetupStatus } from '@/service/common'
|
||||||
|
|
||||||
type SwrInitorProps = {
|
type SwrInitorProps = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
|
@ -21,27 +22,61 @@ const SwrInitor = ({
|
||||||
const refreshTokenFromLocalStorage = localStorage?.getItem('refresh_token')
|
const refreshTokenFromLocalStorage = localStorage?.getItem('refresh_token')
|
||||||
const [init, setInit] = useState(false)
|
const [init, setInit] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
const isSetupFinished = useCallback(async () => {
|
||||||
if (!(consoleToken || refreshToken || consoleTokenFromLocalStorage || refreshTokenFromLocalStorage)) {
|
try {
|
||||||
router.replace('/signin')
|
if (localStorage.getItem('setup_status') === 'finished')
|
||||||
return
|
return true
|
||||||
|
const setUpStatus = await fetchSetupStatus()
|
||||||
|
if (setUpStatus.step !== 'finished') {
|
||||||
|
localStorage.removeItem('setup_status')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
localStorage.setItem('setup_status', 'finished')
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
if (consoleTokenFromLocalStorage && refreshTokenFromLocalStorage)
|
catch (error) {
|
||||||
getNewAccessToken()
|
console.error(error)
|
||||||
|
return false
|
||||||
if (consoleToken && refreshToken) {
|
|
||||||
localStorage.setItem('console_token', consoleToken)
|
|
||||||
localStorage.setItem('refresh_token', refreshToken)
|
|
||||||
getNewAccessToken().then(() => {
|
|
||||||
router.replace('/apps', { forceOptimisticNavigation: false } as any)
|
|
||||||
}).catch(() => {
|
|
||||||
router.replace('/signin')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setInit(true)
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const setRefreshToken = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
if (!(consoleToken || refreshToken || consoleTokenFromLocalStorage || refreshTokenFromLocalStorage))
|
||||||
|
return Promise.reject(new Error('No token found'))
|
||||||
|
|
||||||
|
if (consoleTokenFromLocalStorage && refreshTokenFromLocalStorage)
|
||||||
|
await getNewAccessToken()
|
||||||
|
|
||||||
|
if (consoleToken && refreshToken) {
|
||||||
|
localStorage.setItem('console_token', consoleToken)
|
||||||
|
localStorage.setItem('refresh_token', refreshToken)
|
||||||
|
await getNewAccessToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
}, [consoleToken, refreshToken, consoleTokenFromLocalStorage, refreshTokenFromLocalStorage, getNewAccessToken])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const isFinished = await isSetupFinished()
|
||||||
|
if (!isFinished) {
|
||||||
|
router.replace('/install')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await setRefreshToken()
|
||||||
|
router.replace('/apps', { forceOptimisticNavigation: false } as any)
|
||||||
|
setInit(true)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
router.replace('/signin')
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
}, [isSetupFinished, setRefreshToken, router])
|
||||||
|
|
||||||
return init
|
return init
|
||||||
? (
|
? (
|
||||||
<SWRConfig value={{
|
<SWRConfig value={{
|
||||||
|
|
|
@ -65,6 +65,7 @@ const InstallForm = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSetupStatus().then((res: SetupStatusResponse) => {
|
fetchSetupStatus().then((res: SetupStatusResponse) => {
|
||||||
if (res.step === 'finished') {
|
if (res.step === 'finished') {
|
||||||
|
localStorage.setItem('setup_status', 'finished')
|
||||||
window.location.href = '/signin'
|
window.location.href = '/signin'
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -153,7 +154,7 @@ const InstallForm = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className="block w-hull mt-2 text-xs text-gray-600">
|
<div className="block w-full mt-2 text-xs text-gray-600">
|
||||||
{t('login.license.tip')}
|
{t('login.license.tip')}
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
|
|
|
@ -44,6 +44,18 @@ export default function CheckCode() {
|
||||||
params.set('email', encodeURIComponent(email))
|
params.set('email', encodeURIComponent(email))
|
||||||
router.push(`/reset-password/check-code?${params.toString()}`)
|
router.push(`/reset-password/check-code?${params.toString()}`)
|
||||||
}
|
}
|
||||||
|
else if (res.message === 'account_not_found') {
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('login.error.registrationNotAllowed'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: res.data,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
|
|
@ -12,11 +12,12 @@ import I18NContext from '@/context/i18n'
|
||||||
|
|
||||||
type MailAndPasswordAuthProps = {
|
type MailAndPasswordAuthProps = {
|
||||||
isInvite: boolean
|
isInvite: boolean
|
||||||
|
allowRegistration: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
||||||
|
|
||||||
export default function MailAndPasswordAuth({ isInvite }: MailAndPasswordAuthProps) {
|
export default function MailAndPasswordAuth({ isInvite, allowRegistration }: MailAndPasswordAuthProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { locale } = useContext(I18NContext)
|
const { locale } = useContext(I18NContext)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
@ -75,10 +76,18 @@ export default function MailAndPasswordAuth({ isInvite }: MailAndPasswordAuthPro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (res.message === 'account_not_found') {
|
else if (res.message === 'account_not_found') {
|
||||||
const params = new URLSearchParams()
|
if (allowRegistration) {
|
||||||
params.append('email', encodeURIComponent(email))
|
const params = new URLSearchParams()
|
||||||
params.append('token', encodeURIComponent(res.data))
|
params.append('email', encodeURIComponent(email))
|
||||||
router.replace(`/reset-password/check-code?${params.toString()}`)
|
params.append('token', encodeURIComponent(res.data))
|
||||||
|
router.replace(`/reset-password/check-code?${params.toString()}`)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('login.error.registrationNotAllowed'),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Toast.notify({
|
Toast.notify({
|
||||||
|
|
|
@ -141,7 +141,7 @@ export default function InviteSettingsPage() {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className="block w-hull mt-2 system-xs-regular">
|
<div className="block w-full mt-2 system-xs-regular">
|
||||||
{t('login.license.tip')}
|
{t('login.license.tip')}
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
|
|
|
@ -2,17 +2,18 @@ import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useRouter, useSearchParams } from 'next/navigation'
|
import { useRouter, useSearchParams } from 'next/navigation'
|
||||||
|
import { RiDoorLockLine } from '@remixicon/react'
|
||||||
import Loading from '../components/base/loading'
|
import Loading from '../components/base/loading'
|
||||||
import MailAndCodeAuth from './components/mail-and-code-auth'
|
import MailAndCodeAuth from './components/mail-and-code-auth'
|
||||||
import MailAndPasswordAuth from './components/mail-and-password-auth'
|
import MailAndPasswordAuth from './components/mail-and-password-auth'
|
||||||
import SocialAuth from './components/social-auth'
|
import SocialAuth from './components/social-auth'
|
||||||
import SSOAuth from './components/sso-auth'
|
import SSOAuth from './components/sso-auth'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { IS_CE_EDITION } from '@/config'
|
|
||||||
import { getSystemFeatures, invitationCheck } from '@/service/common'
|
import { getSystemFeatures, invitationCheck } from '@/service/common'
|
||||||
import { defaultSystemFeatures } from '@/types/feature'
|
import { defaultSystemFeatures } from '@/types/feature'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import useRefreshToken from '@/hooks/use-refresh-token'
|
import useRefreshToken from '@/hooks/use-refresh-token'
|
||||||
|
import { IS_CE_EDITION } from '@/config'
|
||||||
|
|
||||||
const NormalForm = () => {
|
const NormalForm = () => {
|
||||||
const { getNewAccessToken } = useRefreshToken()
|
const { getNewAccessToken } = useRefreshToken()
|
||||||
|
@ -49,10 +50,11 @@ const NormalForm = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const features = await getSystemFeatures()
|
const features = await getSystemFeatures()
|
||||||
setSystemFeatures({ ...defaultSystemFeatures, ...features })
|
const allFeatures = { ...defaultSystemFeatures, ...features }
|
||||||
setAllMethodsAreDisabled(!features.enable_social_oauth_login && !features.enable_email_code_login && !features.enable_email_password_login && !features.sso_enforced_for_signin)
|
setSystemFeatures(allFeatures)
|
||||||
setShowORLine((features.enable_social_oauth_login || features.sso_enforced_for_signin) && (features.enable_email_code_login || features.enable_email_password_login))
|
setAllMethodsAreDisabled(!allFeatures.enable_social_oauth_login && !allFeatures.enable_email_code_login && !allFeatures.enable_email_password_login && !allFeatures.sso_enforced_for_signin)
|
||||||
updateAuthType(features.enable_email_password_login ? 'password' : 'code')
|
setShowORLine((allFeatures.enable_social_oauth_login || allFeatures.sso_enforced_for_signin) && (allFeatures.enable_email_code_login || allFeatures.enable_email_password_login))
|
||||||
|
updateAuthType(allFeatures.enable_email_password_login ? 'password' : 'code')
|
||||||
if (isInviteLink) {
|
if (isInviteLink) {
|
||||||
const checkRes = await invitationCheck({
|
const checkRes = await invitationCheck({
|
||||||
url: '/activate/check',
|
url: '/activate/check',
|
||||||
|
@ -63,7 +65,11 @@ const NormalForm = () => {
|
||||||
setWorkSpaceName(checkRes?.data?.workspace_name || '')
|
setWorkSpaceName(checkRes?.data?.workspace_name || '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) { console.error(error) }
|
catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
setAllMethodsAreDisabled(true)
|
||||||
|
setSystemFeatures(defaultSystemFeatures)
|
||||||
|
}
|
||||||
finally { setIsLoading(false) }
|
finally { setIsLoading(false) }
|
||||||
}, [consoleToken, refreshToken, message, router, invite_token, isInviteLink, getNewAccessToken])
|
}, [consoleToken, refreshToken, message, router, invite_token, isInviteLink, getNewAccessToken])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -118,17 +124,28 @@ const NormalForm = () => {
|
||||||
</div>}
|
</div>}
|
||||||
</>}
|
</>}
|
||||||
{systemFeatures.enable_email_password_login && authType === 'password' && <>
|
{systemFeatures.enable_email_password_login && authType === 'password' && <>
|
||||||
<MailAndPasswordAuth isInvite={isInviteLink} />
|
<MailAndPasswordAuth isInvite={isInviteLink} allowRegistration={systemFeatures.is_allow_register} />
|
||||||
{systemFeatures.enable_email_code_login && <div className='cursor-pointer py-1 text-center' onClick={() => { updateAuthType('code') }}>
|
{systemFeatures.enable_email_code_login && <div className='cursor-pointer py-1 text-center' onClick={() => { updateAuthType('code') }}>
|
||||||
<span className='system-xs-medium text-components-button-secondary-accent-text'>{t('login.useVerificationCode')}</span>
|
<span className='system-xs-medium text-components-button-secondary-accent-text'>{t('login.useVerificationCode')}</span>
|
||||||
</div>}
|
</div>}
|
||||||
</>}
|
</>}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
{allMethodsAreDisabled && <div className="w-hull text-center block system-md-semibold text-text-secondary">
|
{allMethodsAreDisabled && <>
|
||||||
{t('login.noLoginMethod')}
|
<div className="p-4 rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2">
|
||||||
</div>}
|
<div className='flex items-center justify-center w-10 h-10 rounded-xl bg-components-card-bg shadow shadows-shadow-lg mb-2'>
|
||||||
<div className="w-hull text-center block mt-2 system-xs-regular text-text-tertiary">
|
<RiDoorLockLine className='w-5 h-5' />
|
||||||
|
</div>
|
||||||
|
<p className='system-sm-medium text-text-primary'>{t('login.noLoginMethod')}</p>
|
||||||
|
<p className='system-xs-regular text-text-tertiary mt-1'>{t('login.noLoginMethodTip')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="relative my-2 py-2">
|
||||||
|
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||||
|
<div className='bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent h-px w-full'></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
<div className="w-full block mt-2 system-xs-regular text-text-tertiary">
|
||||||
{t('login.tosDesc')}
|
{t('login.tosDesc')}
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
|
@ -143,8 +160,7 @@ const NormalForm = () => {
|
||||||
href='https://dify.ai/privacy'
|
href='https://dify.ai/privacy'
|
||||||
>{t('login.pp')}</Link>
|
>{t('login.pp')}</Link>
|
||||||
</div>
|
</div>
|
||||||
|
{IS_CE_EDITION && <div className="w-hull block mt-2 system-xs-regular text-text-tertiary">
|
||||||
{IS_CE_EDITION && <div className="w-hull text-center block mt-2 system-xs-regular text-text-tertiary">
|
|
||||||
{t('login.goToInit')}
|
{t('login.goToInit')}
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
|
|
|
@ -151,7 +151,7 @@ const OneMoreStep = () => {
|
||||||
{t('login.go')}
|
{t('login.go')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="block w-hull mt-2 system-xs-regular text-text-tertiary">
|
<div className="block w-full mt-2 system-xs-regular text-text-tertiary">
|
||||||
{t('login.license.tip')}
|
{t('login.license.tip')}
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
|
|
|
@ -61,6 +61,7 @@ const translation = {
|
||||||
passwordEmpty: 'Password is required',
|
passwordEmpty: 'Password is required',
|
||||||
passwordLengthInValid: 'Password must be at least 8 characters',
|
passwordLengthInValid: 'Password must be at least 8 characters',
|
||||||
passwordInvalid: 'Password must contain letters and numbers, and the length must be greater than 8',
|
passwordInvalid: 'Password must contain letters and numbers, and the length must be greater than 8',
|
||||||
|
registrationNotAllowed: 'Account not found. Please contact the system admin to register.',
|
||||||
},
|
},
|
||||||
license: {
|
license: {
|
||||||
tip: 'Before starting Dify Community Edition, read the GitHub',
|
tip: 'Before starting Dify Community Edition, read the GitHub',
|
||||||
|
@ -95,7 +96,8 @@ const translation = {
|
||||||
setYourAccount: 'Set Your Account',
|
setYourAccount: 'Set Your Account',
|
||||||
enterYourName: 'Please enter your username',
|
enterYourName: 'Please enter your username',
|
||||||
back: 'Back',
|
back: 'Back',
|
||||||
noLoginMethod: 'Please contact the system admin to add an authentication method.',
|
noLoginMethod: 'Authentication method not configured',
|
||||||
|
noLoginMethodTip: 'Please contact the system admin to add an authentication method.',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|
|
@ -61,6 +61,7 @@ const translation = {
|
||||||
passwordEmpty: '密码不能为空',
|
passwordEmpty: '密码不能为空',
|
||||||
passwordInvalid: '密码必须包含字母和数字,且长度不小于8位',
|
passwordInvalid: '密码必须包含字母和数字,且长度不小于8位',
|
||||||
passwordLengthInValid: '密码必须至少为 8 个字符',
|
passwordLengthInValid: '密码必须至少为 8 个字符',
|
||||||
|
registrationNotAllowed: '账户不存在,请联系系统管理员注册账户',
|
||||||
},
|
},
|
||||||
license: {
|
license: {
|
||||||
tip: '启动 Dify 社区版之前, 请阅读 GitHub 上的',
|
tip: '启动 Dify 社区版之前, 请阅读 GitHub 上的',
|
||||||
|
@ -96,7 +97,8 @@ const translation = {
|
||||||
setYourAccount: '设置您的账户',
|
setYourAccount: '设置您的账户',
|
||||||
enterYourName: '请输入用户名',
|
enterYourName: '请输入用户名',
|
||||||
back: '返回',
|
back: '返回',
|
||||||
noLoginMethod: '请联系管理员添加身份认证方式',
|
noLoginMethod: '未配置身份认证方式',
|
||||||
|
noLoginMethodTip: '请联系系统管理员添加身份认证方式',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|
|
@ -333,7 +333,7 @@ export const emailLoginWithCode = (data: { email: string;code: string;token: str
|
||||||
post<LoginResponse>('/email-code-login/validity', { body: data })
|
post<LoginResponse>('/email-code-login/validity', { body: data })
|
||||||
|
|
||||||
export const sendResetPasswordCode = (email: string, language = 'en-US') =>
|
export const sendResetPasswordCode = (email: string, language = 'en-US') =>
|
||||||
post<CommonResponse & { data: string }>('/forgot-password', { body: { email, language } })
|
post<CommonResponse & { data: string;message?: string }>('/forgot-password', { body: { email, language } })
|
||||||
|
|
||||||
export const verifyResetPasswordCode = (body: { email: string;code: string;token: string }) =>
|
export const verifyResetPasswordCode = (body: { email: string;code: string;token: string }) =>
|
||||||
post<CommonResponse & { is_valid: boolean }>('/forgot-password/validity', { body })
|
post<CommonResponse & { is_valid: boolean }>('/forgot-password/validity', { body })
|
||||||
|
|
|
@ -13,6 +13,8 @@ export type SystemFeatures = {
|
||||||
enable_email_code_login: boolean
|
enable_email_code_login: boolean
|
||||||
enable_email_password_login: boolean
|
enable_email_password_login: boolean
|
||||||
enable_social_oauth_login: boolean
|
enable_social_oauth_login: boolean
|
||||||
|
is_allow_create_workspace: boolean
|
||||||
|
is_allow_register: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultSystemFeatures: SystemFeatures = {
|
export const defaultSystemFeatures: SystemFeatures = {
|
||||||
|
@ -22,6 +24,8 @@ export const defaultSystemFeatures: SystemFeatures = {
|
||||||
sso_enforced_for_web_protocol: '',
|
sso_enforced_for_web_protocol: '',
|
||||||
enable_web_sso_switch_component: false,
|
enable_web_sso_switch_component: false,
|
||||||
enable_email_code_login: false,
|
enable_email_code_login: false,
|
||||||
enable_email_password_login: true,
|
enable_email_password_login: false,
|
||||||
enable_social_oauth_login: false,
|
enable_social_oauth_login: false,
|
||||||
|
is_allow_create_workspace: false,
|
||||||
|
is_allow_register: false,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user