Commit e64a2b04 by light5980 Committed by GitHub

fix:split register methods from team sync mode (#7230)

parent c8abd09d
......@@ -23,6 +23,10 @@ export type ExternalProviderWorkflowVarType = {
url?: string;
};
export type FastGPTRegisterMethodType = 'email' | 'phone';
export type FastGPTRegisterMethodCompatType = FastGPTRegisterMethodType | 'sync';
export type FastGPTTeamModeType = 'multi' | 'single' | 'sync';
/* fastgpt main */
export type FastGPTConfigFileType = {
feConfigs: FastGPTFeConfigsType;
......@@ -42,10 +46,14 @@ export type FastGPTFeConfigsType = {
show_emptyChat?: boolean;
isPlus?: boolean;
hideChatCopyrightSetting?: boolean;
register_method?: ['email' | 'phone' | 'sync'];
login_method?: ['email' | 'phone']; // Attention: login method is different with oauth
find_password_method?: ['email' | 'phone'];
bind_notification_method?: ['email' | 'phone'];
/**
* 用户自助注册方式。兼容期允许读取旧配置中的 sync,但新配置不再写入 sync。
*/
register_method?: FastGPTRegisterMethodCompatType[];
teamMode?: FastGPTTeamModeType;
login_method?: FastGPTRegisterMethodType[]; // Attention: login method is different with oauth
find_password_method?: FastGPTRegisterMethodType[];
bind_notification_method?: FastGPTRegisterMethodType[];
googleClientVerKey?: string;
/**
* @deprecated MCP SSE 代理地址已迁移到环境变量 SSE_MCP_SERVER_PROXY_ENDPOINT。
......
Subproject commit 5f90c283f1e89ccfb40d17707f9f7603e0a1928e
Subproject commit 11ed3ecf86e55b38e9d829440f9492ec2479dd72
......@@ -39,7 +39,7 @@ import { format } from 'date-fns/format';
import OrgTags from '@/components/support/user/team/OrgTags';
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import { useCallback, useState, useMemo } from 'react';
import { downloadFetch } from '@/web/common/system/utils';
import { downloadFetch, getIsMemberSyncMode } from '@/web/common/system/utils';
import { type TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
import { useToast } from '@fastgpt/web/hooks/useToast';
import MyBox from '@fastgpt/web/components/common/MyBox';
......@@ -59,7 +59,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const { toast } = useToast();
const { userInfo, initUserInfo } = useUserStore();
const { feConfigs } = useSystemStore();
const isSyncMode = feConfigs?.register_method?.includes('sync');
const isSyncMode = getIsMemberSyncMode(feConfigs);
const { myTeams, onSwitchTeam } = useContextSelector(TeamContext, (v) => v);
......
......@@ -36,6 +36,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { delRemoveMember } from '@/web/support/user/team/api';
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
import { getIsMemberSyncMode } from '@/web/common/system/utils';
const OrgInfoModal = dynamic(() => import('./OrgInfoModal'));
const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal'));
......@@ -74,7 +75,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
const { t } = useTranslation();
const { userInfo, isTeamAdmin } = useUserStore();
const { feConfigs } = useSystemStore();
const isSyncMember = feConfigs.register_method?.includes('sync');
const isSyncMember = getIsMemberSyncMode(feConfigs);
const [editOrg, setEditOrg] = useState<OrgFormType>();
const [manageMemberOrg, setManageMemberOrg] = useState<OrgListItemType>();
const [movingOrg, setMovingOrg] = useState<OrgListItemType>();
......
......@@ -14,6 +14,7 @@ import { useMount } from 'ahooks';
import type { LangEnum } from '@fastgpt/global/common/i18n/type';
import type { LoginSuccessResponseType } from '@fastgpt/global/openapi/support/user/account/login/api';
import PolicyTip from './PolicyTip';
import { getRegisterMethods } from '@/web/common/system/utils';
type LoginSuccessHandler = (res: LoginSuccessResponseType) => void | Promise<void>;
......@@ -32,6 +33,9 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
const { feConfigs } = useSystemStore();
const query = useSearchParams();
const router = useRouter();
const registerMethods = getRegisterMethods(feConfigs);
const hasRegisterMethod = registerMethods.length > 0;
const hasFindPasswordMethod = !!feConfigs?.find_password_method?.length;
const {
register,
......@@ -74,7 +78,7 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
}
);
const isCommunityVersion = !!(feConfigs?.register_method && !feConfigs?.isPlus);
const isCommunityVersion = hasRegisterMethod && !feConfigs?.isPlus;
const placeholder = (() => {
if (isCommunityVersion) {
......@@ -158,29 +162,31 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
{t('login:Login')}
</Button>
<Flex
mt={6}
align={'center'}
justifyContent={'center'}
gap={0}
color={'primary.700'}
fontWeight={'medium'}
h={'16px'}
lineHeight={'16px'}
>
{feConfigs?.find_password_method && feConfigs.find_password_method.length > 0 && (
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('forgetPassword')}
fontSize="mini"
>
{t('login:forget_password')}
</Box>
)}
{feConfigs?.register_method && feConfigs.register_method.length > 0 && (
<>
{(hasFindPasswordMethod || hasRegisterMethod) && (
<Flex
mt={6}
align={'center'}
justifyContent={'center'}
gap={0}
color={'primary.700'}
fontWeight={'medium'}
h={'16px'}
lineHeight={'16px'}
>
{hasFindPasswordMethod && (
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('forgetPassword')}
fontSize="mini"
>
{t('login:forget_password')}
</Box>
)}
{hasFindPasswordMethod && hasRegisterMethod && (
<Box display={['block', 'block']} mx={3} h={'12px'} w={'1px'} bg={'myGray.250'}></Box>
)}
{hasRegisterMethod && (
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
......@@ -190,9 +196,9 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
>
{t('login:register')}
</Box>
</>
)}
</Flex>
)}
</Flex>
)}
</Box>
</FormLayout>
);
......
......@@ -18,6 +18,7 @@ import {
import { checkPasswordRule } from '@fastgpt/global/common/string/password';
import type { LoginSuccessResponseType } from '@fastgpt/global/openapi/support/user/account/login/api';
import type { LangEnum } from '@fastgpt/global/common/i18n/type';
import { getRegisterMethods } from '@/web/common/system/utils';
type LoginSuccessHandler = (res: LoginSuccessResponseType) => void | Promise<void>;
......@@ -88,8 +89,8 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
}
};
const placeholder = feConfigs?.register_method
?.map((item) => {
const placeholder = getRegisterMethods(feConfigs)
.map((item) => {
switch (item) {
case 'email':
return t('common:support.user.login.Email');
......
......@@ -47,6 +47,7 @@ import { useUploadAvatar } from '@fastgpt/web/common/file/hooks/useUploadAvatar'
import { getUploadAvatarPresignedUrl } from '@/web/common/file/api';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { i18nT } from '@fastgpt/global/common/i18n/utils';
import { getIsMemberSyncMode } from '@/web/common/system/utils';
const RedeemCouponModal = dynamic(() => import('@/pageComponents/account/info/RedeemCouponModal'), {
ssr: false
......@@ -251,7 +252,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
minW: '52px'
} as const;
const isSyncMember = feConfigs.register_method?.includes('sync');
const isSyncMember = getIsMemberSyncMode(feConfigs);
return (
<Box>
{/* user info */}
......
......@@ -2,9 +2,33 @@ import {
type EmbeddingModelItemType,
type LLMModelItemType
} from '@fastgpt/global/core/ai/model.schema';
import type {
FastGPTFeConfigsType,
FastGPTRegisterMethodType
} from '@fastgpt/global/common/system/types';
import { useSystemStore } from './useSystemStore';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
/**
* 获取真实支持的自助注册方式,兼容过滤旧配置中被混入的 sync 团队模式。
*/
export const getRegisterMethods = (feConfigs?: FastGPTFeConfigsType): FastGPTRegisterMethodType[] =>
feConfigs?.register_method?.filter(
(method): method is FastGPTRegisterMethodType => method === 'email' || method === 'phone'
) ?? [];
/**
* 判断是否为成员同步模式。teamMode 是当前权威字段;旧 register_method: ['sync']
* 仅用于兼容缺少 teamMode 的历史配置,避免新旧字段冲突时前后端模式不一致。
*/
export const getIsMemberSyncMode = (feConfigs?: FastGPTFeConfigsType) => {
if (feConfigs?.teamMode) {
return feConfigs.teamMode === 'sync';
}
return !!feConfigs?.register_method?.includes('sync');
};
export const downloadFetch = async ({
url,
filename,
......
import { describe, expect, it } from 'vitest';
import type { FastGPTFeConfigsType } from '@fastgpt/global/common/system/types';
import { getIsMemberSyncMode, getRegisterMethods } from '@/web/common/system/utils';
const createFeConfigs = (overrides: Partial<FastGPTFeConfigsType>): FastGPTFeConfigsType => ({
uploadFileMaxAmount: 10,
uploadFileMaxSize: 100,
...overrides
});
describe('system fe config utils', () => {
it('filters legacy sync from register methods', () => {
expect(getRegisterMethods(createFeConfigs({ register_method: ['sync'] }))).toEqual([]);
expect(
getRegisterMethods(createFeConfigs({ register_method: ['email', 'sync', 'phone'] }))
).toEqual(['email', 'phone']);
});
it('detects member sync mode from new teamMode and legacy register_method', () => {
expect(getIsMemberSyncMode(createFeConfigs({ teamMode: 'sync', register_method: [] }))).toBe(
true
);
expect(getIsMemberSyncMode(createFeConfigs({ register_method: ['sync'] }))).toBe(true);
expect(
getIsMemberSyncMode(createFeConfigs({ teamMode: 'single', register_method: ['sync'] }))
).toBe(false);
expect(
getIsMemberSyncMode(createFeConfigs({ teamMode: 'multi', register_method: ['sync'] }))
).toBe(false);
expect(
getIsMemberSyncMode(createFeConfigs({ teamMode: 'multi', register_method: ['phone'] }))
).toBe(false);
expect(getIsMemberSyncMode(createFeConfigs({ teamMode: 'single', register_method: [] }))).toBe(
false
);
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment