Feat: Add user registration toggle feature (#6327)

### What problem does this PR solve?

Feat: Add user registration toggle feature. Added a user registration
toggle REGISTER_ENABLED in the settings and .env config file. The user
creation interface now checks the state of this toggle to control the
enabling and disabling of the user registration feature.

the front-end implementation is done, the registration button does not
appear if registration is not allowed. I did the actual tests on my
local server and it worked smoothly.
### Type of change

- [x] New Feature (non-breaking change which adds functionality)

---------

Co-authored-by: wenju.li <wenju.li@deepctr.cn>
Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
This commit is contained in:
liwenju0 2025-03-21 09:38:15 +08:00 committed by GitHub
parent 7f80d7304d
commit 1bb990719e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 75 additions and 3 deletions

View File

@ -37,7 +37,6 @@ from timeit import default_timer as timer
from rag.utils.redis_conn import REDIS_CONN from rag.utils.redis_conn import REDIS_CONN
@manager.route("/version", methods=["GET"]) # noqa: F821 @manager.route("/version", methods=["GET"]) # noqa: F821
@login_required @login_required
def version(): def version():
@ -298,3 +297,25 @@ def rm(token):
[APIToken.tenant_id == current_user.id, APIToken.token == token] [APIToken.tenant_id == current_user.id, APIToken.token == token]
) )
return get_json_result(data=True) return get_json_result(data=True)
@manager.route('/config', methods=['GET']) # noqa: F821
def get_config():
"""
Get system configuration.
---
tags:
- System
responses:
200:
description: Return system configuration
schema:
type: object
properties:
registerEnable:
type: integer 0 means disabled, 1 means enabled
description: Whether user registration is enabled
"""
return get_json_result(data={
"registerEnabled": settings.REGISTER_ENABLED
})

View File

@ -562,6 +562,14 @@ def user_add():
schema: schema:
type: object type: object
""" """
if not settings.REGISTER_ENABLED:
return get_json_result(
data=False,
message="User registration is disabled!",
code=settings.RetCode.OPERATING_ERROR,
)
req = request.json req = request.json
email_address = req["email"] email_address = req["email"]

View File

@ -62,9 +62,12 @@ docStoreConn = None
retrievaler = None retrievaler = None
kg_retrievaler = None kg_retrievaler = None
# user registration switch
REGISTER_ENABLED = 1
def init_settings(): def init_settings():
global LLM, LLM_FACTORY, LLM_BASE_URL, LIGHTEN, DATABASE_TYPE, DATABASE, FACTORY_LLM_INFOS global LLM, LLM_FACTORY, LLM_BASE_URL, LIGHTEN, DATABASE_TYPE, DATABASE, FACTORY_LLM_INFOS, REGISTER_ENABLED
LIGHTEN = int(os.environ.get('LIGHTEN', "0")) LIGHTEN = int(os.environ.get('LIGHTEN', "0"))
DATABASE_TYPE = os.getenv("DB_TYPE", 'mysql') DATABASE_TYPE = os.getenv("DB_TYPE", 'mysql')
DATABASE = decrypt_database_config(name=DATABASE_TYPE) DATABASE = decrypt_database_config(name=DATABASE_TYPE)
@ -72,6 +75,10 @@ def init_settings():
LLM_DEFAULT_MODELS = LLM.get("default_models", {}) LLM_DEFAULT_MODELS = LLM.get("default_models", {})
LLM_FACTORY = LLM.get("factory", "Tongyi-Qianwen") LLM_FACTORY = LLM.get("factory", "Tongyi-Qianwen")
LLM_BASE_URL = LLM.get("base_url") LLM_BASE_URL = LLM.get("base_url")
try:
REGISTER_ENABLED = int(os.environ.get("REGISTER_ENABLED", "1"))
except Exception:
pass
try: try:
with open(os.path.join(get_project_base_directory(), "conf", "llm_factories.json"), "r") as f: with open(os.path.join(get_project_base_directory(), "conf", "llm_factories.json"), "r") as f:

View File

@ -146,3 +146,6 @@ TIMEZONE='Asia/Shanghai'
# ENDPOINT=http://oss-cn-hangzhou.aliyuncs.com # ENDPOINT=http://oss-cn-hangzhou.aliyuncs.com
# REGION=cn-hangzhou # REGION=cn-hangzhou
# BUCKET=ragflow65536 # BUCKET=ragflow65536
# user registration switch
REGISTER_ENABLED=1

View File

@ -67,6 +67,8 @@ export const useRegister = () => {
const { data = {} } = await userService.register(params); const { data = {} } = await userService.register(params);
if (data.code === 0) { if (data.code === 0) {
message.success(t('message.registered')); message.success(t('message.registered'));
} else if (data.message && data.message.includes('registration is disabled')) {
message.error(t('message.registerDisabled') || 'User registration is disabled');
} }
return data.code; return data.code;
}, },

View File

@ -0,0 +1,18 @@
import userService from '@/services/user-service';
import { useQuery } from '@tanstack/react-query';
/**
* Hook to fetch system configuration including register enable status
* @returns System configuration with loading status
*/
export const useSystemConfig = () => {
const { data, isLoading } = useQuery({
queryKey: ['systemConfig'],
queryFn: async () => {
const { data = {} } = await userService.getSystemConfig();
return data.data || { registerEnabled: 1 }; // Default to enabling registration
},
});
return { config: data, loading: isLoading };
};

View File

@ -705,6 +705,7 @@ This auto-tag feature enhances retrieval by adding another layer of domain-speci
logout: 'logout', logout: 'logout',
logged: 'logged!', logged: 'logged!',
pleaseSelectChunk: 'Please select chunk!', pleaseSelectChunk: 'Please select chunk!',
registerDisabled: 'User registration is disabled',
modified: 'Modified', modified: 'Modified',
created: 'Created', created: 'Created',
deleted: 'Deleted', deleted: 'Deleted',

View File

@ -1,4 +1,5 @@
import { useLogin, useRegister } from '@/hooks/login-hooks'; import { useLogin, useRegister } from '@/hooks/login-hooks';
import { useSystemConfig } from '@/hooks/system-hooks';
import { rsaPsw } from '@/utils'; import { rsaPsw } from '@/utils';
import { Button, Checkbox, Form, Input } from 'antd'; import { Button, Checkbox, Form, Input } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@ -16,8 +17,13 @@ const Login = () => {
const { register, loading: registerLoading } = useRegister(); const { register, loading: registerLoading } = useRegister();
const { t } = useTranslation('translation', { keyPrefix: 'login' }); const { t } = useTranslation('translation', { keyPrefix: 'login' });
const loading = signLoading || registerLoading; const loading = signLoading || registerLoading;
const { config } = useSystemConfig();
const registerEnabled = config?.registerEnabled !== 0;
const changeTitle = () => { const changeTitle = () => {
if (title === 'login' && !registerEnabled) {
return;
}
setTitle((title) => (title === 'login' ? 'register' : 'login')); setTitle((title) => (title === 'login' ? 'register' : 'login'));
}; };
const [form] = Form.useForm(); const [form] = Form.useForm();
@ -119,7 +125,7 @@ const Login = () => {
</Form.Item> </Form.Item>
)} )}
<div> <div>
{title === 'login' && ( {title === 'login' && registerEnabled && (
<div> <div>
{t('signInTip')} {t('signInTip')}
<Button type="link" onClick={changeTitle}> <Button type="link" onClick={changeTitle}>

View File

@ -22,6 +22,7 @@ const {
getSystemTokenList, getSystemTokenList,
removeSystemToken, removeSystemToken,
createSystemToken, createSystemToken,
getSystemConfig,
} = api; } = api;
const methods = { const methods = {
@ -101,6 +102,10 @@ const methods = {
url: removeSystemToken, url: removeSystemToken,
method: 'delete', method: 'delete',
}, },
getSystemConfig: {
url: getSystemConfig,
method: 'get',
},
} as const; } as const;
const userService = registerServer<keyof typeof methods>(methods, request); const userService = registerServer<keyof typeof methods>(methods, request);

View File

@ -119,6 +119,7 @@ export default {
createSystemToken: `${api_host}/system/new_token`, createSystemToken: `${api_host}/system/new_token`,
listSystemToken: `${api_host}/system/token_list`, listSystemToken: `${api_host}/system/token_list`,
removeSystemToken: `${api_host}/system/token`, removeSystemToken: `${api_host}/system/token`,
getSystemConfig: `${api_host}/system/config`,
// flow // flow
listTemplates: `${api_host}/canvas/templates`, listTemplates: `${api_host}/canvas/templates`,