feat: add login page ip detect (#2819)

* feat: add login page ip detect

* code perf
This commit is contained in:
papapatrick
2024-09-27 13:39:10 +08:00
committed by GitHub
parent efcb53cd6d
commit 691476c821
11 changed files with 255 additions and 45 deletions

View File

@@ -39,6 +39,8 @@ export const iconPaths = {
'common/inviteLight': () => import('./icons/common/inviteLight.svg'),
'common/language/en': () => import('./icons/common/language/en.svg'),
'common/language/zh': () => import('./icons/common/language/zh.svg'),
'common/language/China': () => import('./icons/common/language/China.svg'),
'common/language/America': () => import('./icons/common/language/America.svg'),
'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'),
'common/line': () => import('./icons/common/line.svg'),
'common/lineChange': () => import('./icons/common/lineChange.svg'),

View File

@@ -0,0 +1,56 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10" fill="none">
<path d="M14 0H0V9.31222H14V0Z" fill="#B31942"/>
<path d="M0 1.07422H14H0ZM14 2.50687H0H14ZM0 3.93952H14H0ZM14 5.37217H0H14ZM0 6.80481H14H0ZM14 8.23746H0H14Z" fill="#000008"/>
<path d="M0 1.07422H14M14 2.50687H0M0 3.93952H14M14 5.37217H0M0 6.80481H14M14 8.23746H0" stroke="white" stroke-width="0.716325"/>
<path d="M7.07729 0H0V5.01427H7.07729V0Z" fill="#0A3161"/>
<path d="M0.589645 0.214844L0.758063 0.733181L0.317139 0.412831H0.862151L0.421227 0.733181L0.589645 0.214844Z" fill="white"/>
<path d="M0.589645 1.21777L0.758063 1.73611L0.317139 1.41576H0.862151L0.421227 1.73611L0.589645 1.21777Z" fill="white"/>
<path d="M0.589645 2.2207L0.758063 2.73904L0.317139 2.41869H0.862151L0.421227 2.73904L0.589645 2.2207Z" fill="white"/>
<path d="M0.589645 3.22363L0.758063 3.74197L0.317139 3.42162H0.862151L0.421227 3.74197L0.589645 3.22363Z" fill="white"/>
<path d="M0.589645 4.22607L0.758063 4.74441L0.317139 4.42406H0.862151L0.421227 4.74441L0.589645 4.22607Z" fill="white"/>
<path d="M1.17924 0.71582L1.34766 1.23416L0.906738 0.913808H1.45175L1.01083 1.23416L1.17924 0.71582Z" fill="white"/>
<path d="M1.17924 1.71875L1.34766 2.23709L0.906738 1.91674H1.45175L1.01083 2.23709L1.17924 1.71875Z" fill="white"/>
<path d="M1.17924 2.72168L1.34766 3.24002L0.906738 2.91967H1.45175L1.01083 3.24002L1.17924 2.72168Z" fill="white"/>
<path d="M1.17924 3.72461L1.34766 4.24295L0.906738 3.9226H1.45175L1.01083 4.24295L1.17924 3.72461Z" fill="white"/>
<path d="M1.76811 0.215332L1.93653 0.733669L1.49561 0.413319H2.04062L1.59969 0.733669L1.76811 0.215332Z" fill="white"/>
<path d="M1.76811 1.21826L1.93653 1.7366L1.49561 1.41625H2.04062L1.59969 1.7366L1.76811 1.21826Z" fill="white"/>
<path d="M1.76811 2.22119L1.93653 2.73953L1.49561 2.41918H2.04062L1.59969 2.73953L1.76811 2.22119Z" fill="white"/>
<path d="M1.76811 3.22412L1.93653 3.74246L1.49561 3.42211H2.04062L1.59969 3.74246L1.76811 3.22412Z" fill="white"/>
<path d="M1.76836 4.22656L1.93677 4.7449L1.49585 4.42455H2.04086L1.59994 4.7449L1.76836 4.22656Z" fill="white"/>
<path d="M2.3582 0.716309L2.52662 1.23465L2.08569 0.914296H2.63071L2.18978 1.23465L2.3582 0.716309Z" fill="white"/>
<path d="M2.3582 1.71924L2.52662 2.23758L2.08569 1.91723H2.63071L2.18978 2.23758L2.3582 1.71924Z" fill="white"/>
<path d="M2.3582 2.72217L2.52662 3.24051L2.08569 2.92016H2.63071L2.18978 3.24051L2.3582 2.72217Z" fill="white"/>
<path d="M2.3582 3.7251L2.52662 4.24343L2.08569 3.92308H2.63071L2.18978 4.24343L2.3582 3.7251Z" fill="white"/>
<path d="M2.94682 0.21582L3.11524 0.734158L2.67432 0.413808H3.21933L2.7784 0.734158L2.94682 0.21582Z" fill="white"/>
<path d="M2.94682 1.21875L3.11524 1.73709L2.67432 1.41674H3.21933L2.7784 1.73709L2.94682 1.21875Z" fill="white"/>
<path d="M2.94682 2.22168L3.11524 2.74002L2.67432 2.41967H3.21933L2.7784 2.74002L2.94682 2.22168Z" fill="white"/>
<path d="M2.94682 3.22461L3.11524 3.74295L2.67432 3.4226H3.21933L2.7784 3.74295L2.94682 3.22461Z" fill="white"/>
<path d="M2.94731 4.22705L3.11573 4.74539L2.6748 4.42504H3.21982L2.77889 4.74539L2.94731 4.22705Z" fill="white"/>
<path d="M3.53715 0.716797L3.70557 1.23513L3.26465 0.914784H3.80966L3.36874 1.23513L3.53715 0.716797Z" fill="white"/>
<path d="M3.53715 1.71973L3.70557 2.23806L3.26465 1.91771H3.80966L3.36874 2.23806L3.53715 1.71973Z" fill="white"/>
<path d="M3.53715 2.72266L3.70557 3.24099L3.26465 2.92064H3.80966L3.36874 3.24099L3.53715 2.72266Z" fill="white"/>
<path d="M3.53715 3.72559L3.70557 4.24392L3.26465 3.92357H3.80966L3.36874 4.24392L3.53715 3.72559Z" fill="white"/>
<path d="M4.12895 0.216309L4.29737 0.734646L3.85645 0.414296H4.40146L3.96053 0.734646L4.12895 0.216309Z" fill="white"/>
<path d="M4.12895 1.21924L4.29737 1.73758L3.85645 1.41723H4.40146L3.96053 1.73758L4.12895 1.21924Z" fill="white"/>
<path d="M4.12895 2.22217L4.29737 2.74051L3.85645 2.42016H4.40146L3.96053 2.74051L4.12895 2.22217Z" fill="white"/>
<path d="M4.12895 3.2251L4.29737 3.74343L3.85645 3.42308H4.40146L3.96053 3.74343L4.12895 3.2251Z" fill="white"/>
<path d="M4.12846 4.22754L4.29688 4.74588L3.85596 4.42553H4.40097L3.96005 4.74588L4.12846 4.22754Z" fill="white"/>
<path d="M4.71831 0.716797L4.88673 1.23513L4.4458 0.914784H4.99081L4.54989 1.23513L4.71831 0.716797Z" fill="white"/>
<path d="M4.71831 1.71973L4.88673 2.23806L4.4458 1.91771H4.99081L4.54989 2.23806L4.71831 1.71973Z" fill="white"/>
<path d="M4.71831 2.72266L4.88673 3.24099L4.4458 2.92064H4.99081L4.54989 3.24099L4.71831 2.72266Z" fill="white"/>
<path d="M4.71831 3.72559L4.88673 4.24392L4.4458 3.92357H4.99081L4.54989 4.24392L4.71831 3.72559Z" fill="white"/>
<path d="M5.30839 0.21582L5.47681 0.734158L5.03589 0.413808H5.5809L5.13998 0.734158L5.30839 0.21582Z" fill="white"/>
<path d="M5.30839 1.21875L5.47681 1.73709L5.03589 1.41674H5.5809L5.13998 1.73709L5.30839 1.21875Z" fill="white"/>
<path d="M5.30839 2.22168L5.47681 2.74002L5.03589 2.41967H5.5809L5.13998 2.74002L5.30839 2.22168Z" fill="white"/>
<path d="M5.30839 3.22461L5.47681 3.74295L5.03589 3.4226H5.5809L5.13998 3.74295L5.30839 3.22461Z" fill="white"/>
<path d="M5.30815 4.22705L5.47657 4.74539L5.03564 4.42504H5.58066L5.13973 4.74539L5.30815 4.22705Z" fill="white"/>
<path d="M5.89799 0.716309L6.06641 1.23465L5.62549 0.914296H6.1705L5.72958 1.23465L5.89799 0.716309Z" fill="white"/>
<path d="M5.89799 1.71924L6.06641 2.23758L5.62549 1.91723H6.1705L5.72958 2.23758L5.89799 1.71924Z" fill="white"/>
<path d="M5.89799 2.72217L6.06641 3.24051L5.62549 2.92016H6.1705L5.72958 3.24051L5.89799 2.72217Z" fill="white"/>
<path d="M5.89799 3.72461L6.06641 4.24295L5.62549 3.9226H6.1705L5.72958 4.24295L5.89799 3.72461Z" fill="white"/>
<path d="M6.48759 0.215332L6.65601 0.733669L6.21509 0.413319H6.7601L6.31918 0.733669L6.48759 0.215332Z" fill="white"/>
<path d="M6.48735 1.21826L6.65577 1.7366L6.21484 1.41625H6.75986L6.31893 1.7366L6.48735 1.21826Z" fill="white"/>
<path d="M6.48735 2.22119L6.65577 2.73953L6.21484 2.41918H6.75986L6.31893 2.73953L6.48735 2.22119Z" fill="white"/>
<path d="M6.48735 3.22412L6.65577 3.74246L6.21484 3.42211H6.75986L6.31893 3.74246L6.48735 3.22412Z" fill="white"/>
<path d="M6.48735 4.22656L6.65577 4.7449L6.21484 4.42455H6.75986L6.31893 4.7449L6.48735 4.22656Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10" fill="none">
<path d="M0 0H14V9.31222H0V0Z" fill="#EE1C25"/>
<path d="M1.28535 3.71454L2.16821 1.06592L3.05108 3.71454L0.696777 2.09594H3.63965L1.28535 3.71454Z" fill="#FFFF00"/>
<path d="M5.11473 1.11669L4.20615 1.31803L4.8122 0.611803L4.75276 1.5623L4.24856 0.720824L5.11473 1.11669Z" fill="#FFFF00"/>
<path d="M6.03166 2.27839L5.11611 2.11159L5.94863 1.69569L5.5252 2.54873L5.38682 1.57757L6.03166 2.27839Z" fill="#FFFF00"/>
<path d="M5.89789 3.90911L5.12998 3.3834L6.05973 3.34321L5.32522 3.94939L5.59495 3.00623L5.89789 3.90911Z" fill="#FFFF00"/>
<path d="M4.74297 4.97429L4.23769 4.19279L5.11081 4.51482L4.19918 4.79026L4.81225 4.02447L4.74297 4.97429Z" fill="#FFFF00"/>
</svg>

After

Width:  |  Height:  |  Size: 763 B

View File

@@ -1,13 +1,16 @@
{
"Chinese_ip_tip": "It is detected that you are a mainland Chinese IP, click to jump to visit the mainland China version.",
"Login": "Login",
"forget_password": "Find password",
"login_failed": "Login failed",
"login_success": "Login successful",
"no_remind": "Don't remind again",
"password_condition": "Password maximum 60 characters",
"policy_tip": "By useing, you agree to our",
"privacy": "Privacy policy",
"redirect": "Jump",
"register": "Register",
"root_password_placeholder": "The root user password is the value of the environment variable DEFAULT_ROOT_PSW",
"terms": "Terms",
"use_root_login": "Log in as root user"
}
}

View File

@@ -9,5 +9,8 @@
"register": "注册账号",
"root_password_placeholder": "root 用户密码为环境变量 DEFAULT_ROOT_PSW 的值",
"terms": "服务协议",
"use_root_login": "使用 root 用户登录"
}
"use_root_login": "使用 root 用户登录",
"redirect": "跳转",
"no_remind": "不再提醒",
"Chinese_ip_tip": "检测到您是中国大陆 IP点击跳转访问中国大陆版。"
}

View File

@@ -0,0 +1,40 @@
import { langMap } from '@/web/common/utils/i18n';
import { Avatar, Box, Flex } from '@chakra-ui/react';
import MySelect from '@fastgpt/web/components/common/MySelect';
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import { useTranslation } from 'next-i18next';
import { useMemo } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
const I18nLngSelector = () => {
const { i18n } = useTranslation();
const { onChangeLng } = useI18nLng();
const list = useMemo(() => {
return Object.entries(langMap).map(([key, lang]) => ({
label: (
<Flex alignItems={'center'}>
<MyIcon borderRadius={'0'} mr={2} name={lang.avatar as any} w={'14px'} h={'9px'} />
<Box>{lang.label}</Box>
</Flex>
),
value: key
}));
}, []);
return (
<MySelect
_hover={{
bg: 'myGray.200'
}}
value={i18n.language}
list={list}
onchange={(val: any) => {
const lang = val;
onChangeLng(lang);
}}
/>
);
};
export default I18nLngSelector;

View File

@@ -7,18 +7,13 @@ import { UserType } from '@fastgpt/global/support/user/type';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
import { langMap } from '@/web/common/utils/i18n';
import { useRouter } from 'next/router';
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import MySelect from '@fastgpt/web/components/common/MySelect';
import TimezoneSelect from '@fastgpt/web/components/common/MySelect/TimezoneSelect';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
const Individuation = () => {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const { userInfo, updateUserInfo } = useUserStore();
const { toast } = useToast();
const { onChangeLng } = useI18nLng();
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
@@ -49,17 +44,7 @@ const Individuation = () => {
<Flex alignItems={'center'} w={['85%', '350px']}>
<Box flex={'0 0 80px'}>{t('common:user.Language')}:&nbsp;</Box>
<Box flex={'1 0 0'}>
<MySelect
value={i18n.language}
list={Object.entries(langMap).map(([key, lang]) => ({
label: lang.label,
value: key
}))}
onchange={(val: any) => {
const lang = val;
onChangeLng(lang);
}}
/>
<I18nLngSelector />
</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '350px']}>

View File

@@ -9,6 +9,8 @@ import { useRouter } from 'next/router';
import { Dispatch, useRef } from 'react';
import { useTranslation } from 'next-i18next';
import Divider from '@/pages/app/detail/components/WorkflowComponents/Flow/components/Divider';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
interface Props {
@@ -24,6 +26,7 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
const { lastRoute = '/app/list' } = router.query as { lastRoute: string };
const state = useRef(nanoid());
const redirectUri = `${location.origin}/login/provider`;
const { isPc } = useSystem();
const oAuthList = [
...(feConfigs?.oauth?.wechat && pageType !== LoginPageTypeEnum.wechat
@@ -72,22 +75,25 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
return (
<Flex flexDirection={'column'} h={'100%'}>
<Flex alignItems={'center'}>
<Flex
w={['48px', '56px']}
h={['48px', '56px']}
bg={'myGray.25'}
borderRadius={'xl'}
borderWidth={'1.5px'}
borderColor={'borderColor.base'}
alignItems={'center'}
justifyContent={'center'}
>
<Image src={LOGO_ICON} w={['24px', '28px']} alt={'icon'} />
<Flex alignItems={'center'} justify={'space-between'}>
<Flex>
<Flex
w={['48px', '56px']}
h={['48px', '56px']}
bg={'myGray.25'}
borderRadius={'xl'}
borderWidth={'1.5px'}
borderColor={'borderColor.base'}
alignItems={'center'}
justifyContent={'center'}
>
<Image src={LOGO_ICON} w={['24px', '28px']} alt={'icon'} />
</Flex>
<Box ml={3} fontSize={['2xl', '3xl']} fontWeight={'bold'}>
{feConfigs?.systemTitle}
</Box>
</Flex>
<Box ml={3} fontSize={['2xl', '3xl']} fontWeight={'bold'}>
{feConfigs?.systemTitle}
</Box>
{!isPc && <I18nLngSelector />}
</Flex>
{children}
{show_oauth && (

View File

@@ -1,5 +1,15 @@
import React, { useState, useCallback, useEffect } from 'react';
import { Box, Center, Flex, useDisclosure } from '@chakra-ui/react';
import {
Box,
Button,
Center,
Drawer,
DrawerCloseButton,
DrawerContent,
DrawerOverlay,
Flex,
useDisclosure
} from '@chakra-ui/react';
import { LoginPageTypeEnum } from '@/web/support/user/login/constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
@@ -12,22 +22,31 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import { clearToken, setToken } from '@/web/support/user/auth';
import Script from 'next/script';
import Loading from '@fastgpt/web/components/common/MyLoading';
import { useMount } from 'ahooks';
import { t } from 'i18next';
import { useLocalStorageState, useMount } from 'ahooks';
import { useTranslation } from 'next-i18next';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
const WechatForm = dynamic(() => import('./components/LoginForm/WechatForm'));
const CommunityModal = dynamic(() => import('@/components/CommunityModal'));
const Login = () => {
const Login = ({ ChineseRedirectUrl }: { ChineseRedirectUrl: string }) => {
const router = useRouter();
const { t } = useTranslation();
const { lastRoute = '' } = router.query as { lastRoute: string };
const { feConfigs } = useSystemStore();
const [pageType, setPageType] = useState<`${LoginPageTypeEnum}`>();
const { setUserInfo } = useUserStore();
const { setLastChatId, setLastChatAppId } = useChatStore();
const { isOpen, onOpen, onClose } = useDisclosure();
const { isPc } = useSystem();
const {
isOpen: isOpenRedirect,
onOpen: onOpenRedirect,
onClose: onCloseRedirect
} = useDisclosure();
const loginSuccess = useCallback(
(res: ResLogin) => {
@@ -69,6 +88,25 @@ const Login = () => {
router.prefetch('/app/list');
});
const [showRedirect, setShowRedirect] = useLocalStorageState<boolean>('showRedirect', {
defaultValue: true
});
const checkIpInChina = useCallback(() => {
const onSuccess = (res: any) => {
if (!res.country.iso_code) {
return;
}
const country = res.country.iso_code.toLowerCase();
if (country === 'cn') {
onOpenRedirect();
}
};
const onError = (e: any) => console.log(e);
geoip2 && geoip2.country(onSuccess, onError);
}, [onOpenRedirect]);
return (
<>
{feConfigs.googleClientVerKey && (
@@ -76,6 +114,15 @@ const Login = () => {
src={`https://www.recaptcha.net/recaptcha/api.js?render=${feConfigs.googleClientVerKey}`}
></Script>
)}
{ChineseRedirectUrl && showRedirect && (
<Script
src="//geoip-js.com/js/apis/geoip2/v2.1/geoip2.js"
type="text/javascript"
onLoad={checkIpInChina}
></Script>
)}
<Flex
alignItems={'center'}
justifyContent={'center'}
@@ -85,6 +132,11 @@ const Login = () => {
h={'100%'}
px={[0, '10vw']}
>
{isPc && (
<Box position={'absolute'} top={'24px'} right={'50px'}>
<I18nLngSelector />
</Box>
)}
<Flex
flexDirection={'column'}
w={['100%', 'auto']}
@@ -123,13 +175,66 @@ const Login = () => {
{isOpen && <CommunityModal onClose={onClose} />}
</Flex>
{showRedirect && (
<RedirectDrawer
isOpen={isOpenRedirect}
onClose={onCloseRedirect}
onRedirect={() => router.push(ChineseRedirectUrl)}
disableDrawer={() => setShowRedirect(false)}
/>
)}
</>
);
};
function RedirectDrawer({
isOpen,
onClose,
disableDrawer,
onRedirect
}: {
isOpen: boolean;
onClose: () => void;
disableDrawer: () => void;
onRedirect: () => void;
}) {
const { t } = useTranslation();
return (
<Drawer placement="bottom" size={'xs'} isOpen={isOpen} onClose={onClose}>
<DrawerOverlay backgroundColor={'rgba(0,0,0,0.2)'} />
<DrawerContent py={'1.75rem'} px={'3rem'}>
<DrawerCloseButton size={'sm'} />
<Flex align={'center'} justify={'space-between'}>
<Box>
<Box color={'myGray.900'} fontWeight={'500'} fontSize={'1rem'}>
{t('login:Chinese_ip_tip')}
</Box>
<Box
color={'primary.700'}
fontWeight={'500'}
fontSize={'1rem'}
textDecorationLine={'underline'}
cursor={'pointer'}
onClick={disableDrawer}
>
{t('login:no_remind')}
</Box>
</Box>
<Button ml={'0.75rem'} onClick={onRedirect}>
{t('login:redirect')}
</Button>
</Flex>
</DrawerContent>
</Drawer>
);
}
export async function getServerSideProps(context: any) {
return {
props: { ...(await serviceSideProps(context, ['app', 'user', 'login'])) }
props: {
ChineseRedirectUrl: process.env.CHINESE_IP_REDIRECT_URL,
...(await serviceSideProps(context, ['app', 'user', 'login']))
}
};
}

View File

@@ -22,7 +22,7 @@ export type RequestPaging = { pageNum: number; pageSize: number; [key]: any };
declare global {
var qaQueueLen: number;
var vectorQueueLen: number;
var geoip2: any;
interface Window {
grecaptcha: any;
QRCode: any;

View File

@@ -7,12 +7,14 @@ export enum LangEnum {
}
export const langMap = {
[LangEnum.en]: {
label: 'English',
icon: 'common/language/en'
label: 'English(US)',
icon: 'common/language/en',
avatar: 'common/language/America'
},
[LangEnum.zh]: {
label: '简体中文',
icon: 'common/language/zh'
icon: 'common/language/zh',
avatar: 'common/language/China'
}
};