Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
09acf215
Unverified
Commit
09acf215
authored
Feb 01, 2024
by
Chenhe Gu
Committed by
GitHub
Feb 01, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add option to prompt for a validation password when initializing admin user (#2302)
parent
07dd8b94
Changes
12
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
210 additions
and
12 deletions
+210
-12
error.py
api/controllers/console/error.py
+10
-0
init_validate.py
api/controllers/console/init_validate.py
+47
-0
setup.py
api/controllers/console/setup.py
+10
-3
docker-compose.yaml
docker/docker-compose.yaml
+3
-0
InitPasswordPopup.tsx
web/app/init/InitPasswordPopup.tsx
+82
-0
page.tsx
web/app/init/page.tsx
+22
-0
installForm.tsx
web/app/install/installForm.tsx
+11
-5
login.en.ts
web/i18n/lang/login.en.ts
+5
-3
login.zh.ts
web/i18n/lang/login.zh.ts
+2
-0
common.ts
web/models/common.ts
+4
-0
base.ts
web/service/base.ts
+5
-1
common.ts
web/service/common.ts
+9
-0
No files found.
api/controllers/console/error.py
View file @
09acf215
...
@@ -13,6 +13,16 @@ class NotSetupError(BaseHTTPException):
...
@@ -13,6 +13,16 @@ class NotSetupError(BaseHTTPException):
"Please proceed with the initialization and installation process first."
"Please proceed with the initialization and installation process first."
code
=
401
code
=
401
class
NotInitValidateError
(
BaseHTTPException
):
error_code
=
'not_init_validated'
description
=
"Init validation has not been completed yet. "
\
"Please proceed with the init validation process first."
code
=
401
class
InitValidateFailedError
(
BaseHTTPException
):
error_code
=
'init_validate_failed'
description
=
"Init validation failed. Please check the password and try again."
code
=
401
class
AccountNotLinkTenantError
(
BaseHTTPException
):
class
AccountNotLinkTenantError
(
BaseHTTPException
):
error_code
=
'account_not_link_tenant'
error_code
=
'account_not_link_tenant'
...
...
api/controllers/console/init_validate.py
0 → 100644
View file @
09acf215
import
os
from
flask
import
current_app
,
session
from
flask_restful
import
Resource
,
reqparse
from
libs.helper
import
str_len
from
models.model
import
DifySetup
from
services.account_service
import
TenantService
from
.
import
api
from
.error
import
AlreadySetupError
,
InitValidateFailedError
from
.wraps
import
only_edition_self_hosted
class
InitValidateAPI
(
Resource
):
def
get
(
self
):
init_status
=
get_init_validate_status
()
if
init_status
:
return
{
'status'
:
'finished'
}
return
{
'status'
:
'not_started'
}
@
only_edition_self_hosted
def
post
(
self
):
# is tenant created
tenant_count
=
TenantService
.
get_tenant_count
()
if
tenant_count
>
0
:
raise
AlreadySetupError
()
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'password'
,
type
=
str_len
(
30
),
required
=
True
,
location
=
'json'
)
input_password
=
parser
.
parse_args
()[
'password'
]
if
input_password
!=
os
.
environ
.
get
(
'INIT_PASSWORD'
):
session
[
'is_init_validated'
]
=
False
raise
InitValidateFailedError
()
session
[
'is_init_validated'
]
=
True
return
{
'result'
:
'success'
},
201
def
get_init_validate_status
():
if
current_app
.
config
[
'EDITION'
]
==
'SELF_HOSTED'
:
if
os
.
environ
.
get
(
'INIT_PASSWORD'
):
return
session
.
get
(
'is_init_validated'
)
or
DifySetup
.
query
.
first
()
return
True
api
.
add_resource
(
InitValidateAPI
,
'/init'
)
api/controllers/console/setup.py
View file @
09acf215
...
@@ -10,7 +10,8 @@ from models.model import DifySetup
...
@@ -10,7 +10,8 @@ from models.model import DifySetup
from
services.account_service
import
AccountService
,
RegisterService
,
TenantService
from
services.account_service
import
AccountService
,
RegisterService
,
TenantService
from
.
import
api
from
.
import
api
from
.error
import
AlreadySetupError
,
NotSetupError
from
.error
import
AlreadySetupError
,
NotSetupError
,
NotInitValidateError
from
.init_validate
import
get_init_validate_status
from
.wraps
import
only_edition_self_hosted
from
.wraps
import
only_edition_self_hosted
...
@@ -24,7 +25,7 @@ class SetupApi(Resource):
...
@@ -24,7 +25,7 @@ class SetupApi(Resource):
'step'
:
'finished'
,
'step'
:
'finished'
,
'setup_at'
:
setup_status
.
setup_at
.
isoformat
()
'setup_at'
:
setup_status
.
setup_at
.
isoformat
()
}
}
return
{
'step'
:
'not_start'
}
return
{
'step'
:
'not_start
ed
'
}
return
{
'step'
:
'finished'
}
return
{
'step'
:
'finished'
}
@
only_edition_self_hosted
@
only_edition_self_hosted
...
@@ -38,6 +39,9 @@ class SetupApi(Resource):
...
@@ -38,6 +39,9 @@ class SetupApi(Resource):
if
tenant_count
>
0
:
if
tenant_count
>
0
:
raise
AlreadySetupError
()
raise
AlreadySetupError
()
if
not
get_init_validate_status
():
raise
NotInitValidateError
()
parser
=
reqparse
.
RequestParser
()
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'email'
,
type
=
email
,
parser
.
add_argument
(
'email'
,
type
=
email
,
required
=
True
,
location
=
'json'
)
required
=
True
,
location
=
'json'
)
...
@@ -71,7 +75,10 @@ def setup_required(view):
...
@@ -71,7 +75,10 @@ def setup_required(view):
@
wraps
(
view
)
@
wraps
(
view
)
def
decorated
(
*
args
,
**
kwargs
):
def
decorated
(
*
args
,
**
kwargs
):
# check setup
# check setup
if
not
get_setup_status
():
if
not
get_init_validate_status
():
raise
NotInitValidateError
()
elif
not
get_setup_status
():
raise
NotSetupError
()
raise
NotSetupError
()
return
view
(
*
args
,
**
kwargs
)
return
view
(
*
args
,
**
kwargs
)
...
...
docker/docker-compose.yaml
View file @
09acf215
...
@@ -15,6 +15,9 @@ services:
...
@@ -15,6 +15,9 @@ services:
# different from api or web app domain.
# different from api or web app domain.
# example: http://cloud.dify.ai
# example: http://cloud.dify.ai
CONSOLE_WEB_URL
:
'
'
CONSOLE_WEB_URL
:
'
'
# Password for admin user initialization.
# If left unset, admin user will not be prompted for a password when creating the initial admin account.
INIT_PASSWORD
:
'
'
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is
# different from api or web app domain.
# different from api or web app domain.
# example: http://cloud.dify.ai
# example: http://cloud.dify.ai
...
...
web/app/init/InitPasswordPopup.tsx
0 → 100644
View file @
09acf215
'use client'
import
{
useEffect
,
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useRouter
}
from
'next/navigation'
import
Toast
from
'../components/base/toast'
import
Loading
from
'../components/base/loading'
import
Button
from
'@/app/components/base/button'
import
{
fetchInitValidateStatus
,
initValidate
}
from
'@/service/common'
import
type
{
InitValidateStatusResponse
}
from
'@/models/common'
const
InitPasswordPopup
=
()
=>
{
const
[
password
,
setPassword
]
=
useState
(
''
)
const
[
loading
,
setLoading
]
=
useState
(
true
)
const
[
validated
,
setValidated
]
=
useState
(
false
)
const
router
=
useRouter
()
const
{
t
}
=
useTranslation
()
const
handleValidation
=
async
()
=>
{
setLoading
(
true
)
try
{
const
response
=
await
initValidate
({
body
:
{
password
}
})
if
(
response
.
result
===
'success'
)
{
setValidated
(
true
)
router
.
push
(
'/install'
)
// or render setup form
}
else
{
throw
new
Error
(
'Validation failed'
)
}
}
catch
(
e
:
any
)
{
Toast
.
notify
({
type
:
'error'
,
message
:
e
.
message
,
duration
:
5000
,
})
setLoading
(
false
)
}
}
useEffect
(()
=>
{
fetchInitValidateStatus
().
then
((
res
:
InitValidateStatusResponse
)
=>
{
if
(
res
.
status
===
'finished'
)
window
.
location
.
href
=
'/install'
else
setLoading
(
false
)
})
},
[])
return
(
loading
?
<
Loading
/>
:
<
div
>
{
!
validated
&&
(
<
div
className=
"block mx-12 min-w-28"
>
<
div
className=
"mb-4"
>
<
label
htmlFor=
"password"
className=
"block text-sm font-medium text-gray-700"
>
{
t
(
'login.adminInitPassword'
)
}
</
label
>
<
div
className=
"mt-1 relative rounded-md shadow-sm"
>
<
input
id=
"password"
type=
"password"
value=
{
password
}
onChange=
{
e
=>
setPassword
(
e
.
target
.
value
)
}
className=
"appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
/>
</
div
>
</
div
>
<
div
className=
"flex flex-row flex-wrap justify-stretch p-0"
>
<
Button
type=
"primary"
onClick=
{
handleValidation
}
className=
"basis-full min-w-28"
>
{
t
(
'login.validate'
)
}
</
Button
>
</
div
>
</
div
>
)
}
</
div
>
)
}
export
default
InitPasswordPopup
web/app/init/page.tsx
0 → 100644
View file @
09acf215
import
React
from
'react'
import
classNames
from
'classnames'
import
style
from
'../signin/page.module.css'
import
InitPasswordPopup
from
'./InitPasswordPopup'
const
Install
=
()
=>
{
return
(
<
div
className=
{
classNames
(
style
.
background
,
'flex w-full min-h-screen'
,
'p-4 lg:p-8'
,
'gap-x-20'
,
'justify-center lg:justify-start'
,
)
}
>
<
div
className=
"block m-auto w-96"
>
<
InitPasswordPopup
/>
</
div
>
</
div
>
)
}
export
default
Install
web/app/install/installForm.tsx
View file @
09acf215
...
@@ -9,8 +9,8 @@ import Loading from '../components/base/loading'
...
@@ -9,8 +9,8 @@ import Loading from '../components/base/loading'
import
Button
from
'@/app/components/base/button'
import
Button
from
'@/app/components/base/button'
// import I18n from '@/context/i18n'
// import I18n from '@/context/i18n'
import
{
fetchSetupStatus
,
setup
}
from
'@/service/common'
import
{
fetch
InitValidateStatus
,
fetch
SetupStatus
,
setup
}
from
'@/service/common'
import
type
{
SetupStatusResponse
}
from
'@/models/common'
import
type
{
InitValidateStatusResponse
,
SetupStatusResponse
}
from
'@/models/common'
const
validEmailReg
=
/^
[\w\.
-
]
+@
([\w
-
]
+
\.)
+
[\w
-
]{2,}
$/
const
validEmailReg
=
/^
[\w\.
-
]
+@
([\w
-
]
+
\.)
+
[\w
-
]{2,}
$/
const
validPassword
=
/^
(?=
.*
[
a-zA-Z
])(?=
.*
\d)
.
{8,}
$/
const
validPassword
=
/^
(?=
.*
[
a-zA-Z
])(?=
.*
\d)
.
{8,}
$/
...
@@ -70,9 +70,15 @@ const InstallForm = () => {
...
@@ -70,9 +70,15 @@ const InstallForm = () => {
useEffect
(()
=>
{
useEffect
(()
=>
{
fetchSetupStatus
().
then
((
res
:
SetupStatusResponse
)
=>
{
fetchSetupStatus
().
then
((
res
:
SetupStatusResponse
)
=>
{
if
(
res
.
step
===
'finished'
)
if
(
res
.
step
===
'finished'
)
{
window
.
location
.
href
=
'/signin'
window
.
location
.
href
=
'/signin'
else
}
else
{
fetchInitValidateStatus
().
then
((
res
:
InitValidateStatusResponse
)
=>
{
if
(
res
.
status
===
'not_started'
)
window
.
location
.
href
=
'/init'
})
}
setLoading
(
false
)
setLoading
(
false
)
})
})
},
[])
},
[])
...
...
web/i18n/lang/login.en.ts
View file @
09acf215
...
@@ -9,7 +9,7 @@ const translation = {
...
@@ -9,7 +9,7 @@ const translation = {
namePlaceholder
:
'Your username'
,
namePlaceholder
:
'Your username'
,
forget
:
'Forgot your password?'
,
forget
:
'Forgot your password?'
,
signBtn
:
'Sign in'
,
signBtn
:
'Sign in'
,
installBtn
:
'Set
ting
'
,
installBtn
:
'Set
up
'
,
setAdminAccount
:
'Setting up an admin account'
,
setAdminAccount
:
'Setting up an admin account'
,
setAdminAccountDesc
:
'Maximum privileges for admin account, which can be used to create applications and manage LLM providers, etc.'
,
setAdminAccountDesc
:
'Maximum privileges for admin account, which can be used to create applications and manage LLM providers, etc.'
,
createAndSignIn
:
'Create and sign in'
,
createAndSignIn
:
'Create and sign in'
,
...
@@ -32,7 +32,7 @@ const translation = {
...
@@ -32,7 +32,7 @@ const translation = {
tosDesc
:
'By signing up, you agree to our'
,
tosDesc
:
'By signing up, you agree to our'
,
donthave
:
'Don
\'
t have?'
,
donthave
:
'Don
\'
t have?'
,
invalidInvitationCode
:
'Invalid invitation code'
,
invalidInvitationCode
:
'Invalid invitation code'
,
accountAlreadyInited
:
'Account already inited'
,
accountAlreadyInited
:
'Account already init
ializ
ed'
,
error
:
{
error
:
{
emailEmpty
:
'Email address is required'
,
emailEmpty
:
'Email address is required'
,
emailInValid
:
'Please enter a valid email address'
,
emailInValid
:
'Please enter a valid email address'
,
...
@@ -51,7 +51,9 @@ const translation = {
...
@@ -51,7 +51,9 @@ const translation = {
explore
:
'Explore Dify'
,
explore
:
'Explore Dify'
,
activatedTipStart
:
'You have joined the'
,
activatedTipStart
:
'You have joined the'
,
activatedTipEnd
:
'team'
,
activatedTipEnd
:
'team'
,
activated
:
'Sign In Now'
,
activated
:
'Sign in now'
,
adminInitPassword
:
'Admin initialization password'
,
validate
:
'Validate'
,
}
}
export
default
translation
export
default
translation
web/i18n/lang/login.zh.ts
View file @
09acf215
...
@@ -52,6 +52,8 @@ const translation = {
...
@@ -52,6 +52,8 @@ const translation = {
activatedTipStart
:
'您已加入'
,
activatedTipStart
:
'您已加入'
,
activatedTipEnd
:
'团队'
,
activatedTipEnd
:
'团队'
,
activated
:
'现在登录'
,
activated
:
'现在登录'
,
adminInitPassword
:
'管理员初始化密码'
,
validate
:
'验证'
,
}
}
export
default
translation
export
default
translation
web/models/common.ts
View file @
09acf215
...
@@ -13,6 +13,10 @@ export type SetupStatusResponse = {
...
@@ -13,6 +13,10 @@ export type SetupStatusResponse = {
setup_at
?:
Date
setup_at
?:
Date
}
}
export
type
InitValidateStatusResponse
=
{
status
:
'finished'
|
'not_started'
}
export
type
UserProfileResponse
=
{
export
type
UserProfileResponse
=
{
id
:
string
id
:
string
name
:
string
name
:
string
...
...
web/service/base.ts
View file @
09acf215
...
@@ -256,7 +256,11 @@ const baseFetch = <T>(
...
@@ -256,7 +256,11 @@ const baseFetch = <T>(
}
}
const
loginUrl
=
`
${
globalThis
.
location
.
origin
}
/signin`
const
loginUrl
=
`
${
globalThis
.
location
.
origin
}
/signin`
bodyJson
.
then
((
data
:
ResponseError
)
=>
{
bodyJson
.
then
((
data
:
ResponseError
)
=>
{
if
(
data
.
code
===
'not_setup'
&&
IS_CE_EDITION
)
if
(
data
.
code
===
'init_validate_failed'
&&
IS_CE_EDITION
)
Toast
.
notify
({
type
:
'error'
,
message
:
data
.
message
,
duration
:
4000
})
else
if
(
data
.
code
===
'not_init_validated'
&&
IS_CE_EDITION
)
globalThis
.
location
.
href
=
`
${
globalThis
.
location
.
origin
}
/init`
else
if
(
data
.
code
===
'not_setup'
&&
IS_CE_EDITION
)
globalThis
.
location
.
href
=
`
${
globalThis
.
location
.
origin
}
/install`
globalThis
.
location
.
href
=
`
${
globalThis
.
location
.
origin
}
/install`
else
if
(
location
.
pathname
!==
'/signin'
||
!
IS_CE_EDITION
)
else
if
(
location
.
pathname
!==
'/signin'
||
!
IS_CE_EDITION
)
globalThis
.
location
.
href
=
loginUrl
globalThis
.
location
.
href
=
loginUrl
...
...
web/service/common.ts
View file @
09acf215
...
@@ -9,6 +9,7 @@ import type {
...
@@ -9,6 +9,7 @@ import type {
FileUploadConfigResponse
,
FileUploadConfigResponse
,
ICurrentWorkspace
,
ICurrentWorkspace
,
IWorkspace
,
IWorkspace
,
InitValidateStatusResponse
,
InvitationResponse
,
InvitationResponse
,
LangGeniusVersionResponse
,
LangGeniusVersionResponse
,
Member
,
Member
,
...
@@ -42,6 +43,14 @@ export const setup: Fetcher<CommonResponse, { body: Record<string, any> }> = ({
...
@@ -42,6 +43,14 @@ export const setup: Fetcher<CommonResponse, { body: Record<string, any> }> = ({
return
post
<
CommonResponse
>
(
'/setup'
,
{
body
})
return
post
<
CommonResponse
>
(
'/setup'
,
{
body
})
}
}
export
const
initValidate
:
Fetcher
<
CommonResponse
,
{
body
:
Record
<
string
,
any
>
}
>
=
({
body
})
=>
{
return
post
<
CommonResponse
>
(
'/init'
,
{
body
})
}
export
const
fetchInitValidateStatus
=
()
=>
{
return
get
<
InitValidateStatusResponse
>
(
'/init'
)
}
export
const
fetchSetupStatus
=
()
=>
{
export
const
fetchSetupStatus
=
()
=>
{
return
get
<
SetupStatusResponse
>
(
'/setup'
)
return
get
<
SetupStatusResponse
>
(
'/setup'
)
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment