Skip to content

Commit

Permalink
feat:desktop support wechat public account login (#4519)
Browse files Browse the repository at this point in the history
* feat:desktop support wechat public account login

Signed-off-by: jingyang <3161362058@qq.com>

* add i18n

Signed-off-by: jingyang <3161362058@qq.com>

* fix ci

* fix openid

---------

Signed-off-by: jingyang <3161362058@qq.com>
  • Loading branch information
zjy365 committed Feb 3, 2024
1 parent 08a9f55 commit 84e033d
Show file tree
Hide file tree
Showing 14 changed files with 513 additions and 81 deletions.
1 change: 1 addition & 0 deletions frontend/desktop/package.json
Expand Up @@ -57,6 +57,7 @@
"sass": "^1.68.0",
"sealos-desktop-sdk": "workspace:*",
"uuid": "^9.0.1",
"xml2js": "^0.6.2",
"zustand": "^4.4.1"
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion frontend/desktop/public/locales/en/common.json
Expand Up @@ -117,5 +117,6 @@
"Start your Sealos journey": "Start your Sealos journey",
"gift amount": "Reward {{amount}} balance.",
"Recharge Amount": "Recharge Amount",
"Doc": "Doc"
"Doc": "Doc",
"Official account login": "Official account login"
}
3 changes: 2 additions & 1 deletion frontend/desktop/public/locales/zh/common.json
Expand Up @@ -110,5 +110,6 @@
"Click on any shadow to skip": "点击任意阴影跳过",
"Start your Sealos journey": "开始您的 Sealos 之旅",
"gift amount": "赠送 {{amount}} 余额.",
"Doc": "文档"
"Doc": "文档",
"Official account login": "公众号登录"
}
12 changes: 11 additions & 1 deletion frontend/desktop/src/api/platform.ts
@@ -1,5 +1,5 @@
import request from '@/services/request';
import { ApiResp, SystemConfigType, SystemEnv } from '@/types';
import { ApiResp, Session, SystemConfigType, SystemEnv } from '@/types';
import { AccountCRD } from '@/types/user';

// handle baidu
Expand Down Expand Up @@ -43,3 +43,13 @@ export const getPriceBonus = () => {
}>
>('/api/price/bonus');
};

export const getWechatQR = () =>
request.get<any, ApiResp<{ code: string; codeUrl: string }>>(
'/api/auth/publicWechat/getWechatQR'
);

export const getWechatResult = (payload: { code: string }) =>
request.get<any, ApiResp<Session>>('/api/auth/publicWechat/getWechatResult', {
params: payload
});
55 changes: 55 additions & 0 deletions frontend/desktop/src/components/signin/auth/useWechat.tsx
@@ -0,0 +1,55 @@
import { getWechatQR, getWechatResult } from '@/api/platform';
import useSessionStore from '@/stores/session';
import { Box, Image, useToast } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import React, { useCallback } from 'react';

export default function useWechat() {
const { t } = useTranslation();
const router = useRouter();
const { setSession } = useSessionStore();

const [wechatInfo, setwechatInfo] = React.useState<{
code: string;
codeUrl: string;
}>();

const login = React.useCallback(() => {
getWechatQR().then((res) => {
console.log(res);
setwechatInfo(res.data);
});
}, []);

useQuery(
['getWechatResult', wechatInfo?.code],
() => getWechatResult({ code: wechatInfo?.code || '' }),
{
refetchInterval: 3 * 1000,
enabled: !!wechatInfo?.code,
onSuccess(data) {
console.log(data);
if (data.code === 200 && data.data) {
setSession(data.data);
router.replace('/');
}
}
}
);

const WechatComponent = useCallback(
() => (
<Box p={5}>
{wechatInfo?.codeUrl && <Image w="200px" src={wechatInfo?.codeUrl} alt="qrcode"></Image>}
</Box>
),
[wechatInfo?.codeUrl]
);

return {
login,
WechatComponent
};
}
72 changes: 44 additions & 28 deletions frontend/desktop/src/components/signin/index.tsx
Expand Up @@ -27,10 +27,10 @@ import Head from 'next/head';
import { useRouter } from 'next/router';
import sealosTitle from 'public/images/sealos-title.png';
import { useEffect, useMemo, useState } from 'react';
import useWechat from './auth/useWechat';

export default function SigninComponent() {
const { data: platformEnv } = useQuery(['getPlatformEnv'], getSystemEnv);

const {
service_protocol_zh = '',
private_protocol_zh = '',
Expand All @@ -57,8 +57,9 @@ export default function SigninComponent() {
service_protocol: service_protocol_en,
private_protocol: private_protocol_en
};
console.log(protocol_data);

const { Protocol, isAgree, setIsInvalid } = useProtocol(protocol_data!);
const { WechatComponent, login: wechatSubmit } = useWechat();
const { SmsModal, login: smsSubmit, isLoading: smsLoading } = useSms({ showError });
const {
PasswordComponent,
Expand Down Expand Up @@ -86,9 +87,13 @@ export default function SigninComponent() {
login: passwordSubmit,
component: <PasswordComponent />
},
[LoginType.WeChat]: {
login: wechatSubmit,
component: <WechatComponent />
},
[LoginType.NONE]: null
};
}, [PasswordComponent, SmsModal, passwordSubmit, smsSubmit]);
}, [PasswordComponent, SmsModal, WechatComponent, passwordSubmit, smsSubmit, wechatSubmit]);

useEffect(() => {
setTabIndex(needSms ? LoginType.SMS : needPassword ? LoginType.PASSWORD : LoginType.NONE);
Expand Down Expand Up @@ -150,7 +155,12 @@ export default function SigninComponent() {
{pageState === 0 && needTabs && (
<Tabs
index={tabIndex}
onChange={(idx) => setTabIndex(idx)}
onChange={(idx) => {
if (idx === LoginType.WeChat) {
wechatSubmit();
}
setTabIndex(idx);
}}
variant="unstyled"
p={'0'}
width={'full'}
Expand All @@ -170,37 +180,43 @@ export default function SigninComponent() {
<Tab px="0" _selected={{ color: 'white' }}>
{t('Password Login')}
</Tab>
<Tab px="0" _selected={{ color: 'white' }}>
{t('Official account login')}
</Tab>
</TabList>
<TabIndicator mt="-2px" height="2px" bg="#FFFFFF" borderRadius="1px" />
</Tabs>
)}

{LoginComponent}

<Protocol />

<Button
variant={'unstyled'}
background="linear-gradient(90deg, #000000 0%, rgba(36, 40, 44, 0.9) 98.29%)"
boxShadow="0px 4px 4px rgba(0, 0, 0, 0.25)"
color="#fff"
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
type="submit"
_hover={{
opacity: '0.85'
}}
width="266px"
minH="42px"
mb="14px"
borderRadius="4px"
p="10px"
onClick={handleLogin}
>
{isLoading ? (t('Loading') || 'Loading') + '...' : t('Log In') || 'Log In'}
</Button>
<AuthList />
{tabIndex !== LoginType.WeChat && (
<>
<Protocol />
<Button
variant={'unstyled'}
background="linear-gradient(90deg, #000000 0%, rgba(36, 40, 44, 0.9) 98.29%)"
boxShadow="0px 4px 4px rgba(0, 0, 0, 0.25)"
color="#fff"
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
type="submit"
_hover={{
opacity: '0.85'
}}
width="266px"
minH="42px"
mb="14px"
borderRadius="4px"
p="10px"
onClick={handleLogin}
>
{isLoading ? (t('Loading') || 'Loading') + '...' : t('Log In') || 'Log In'}
</Button>
<AuthList />
</>
)}
</Flex>
</Flex>
<Language disclosure={disclosure} i18n={i18n} />
Expand Down
54 changes: 54 additions & 0 deletions frontend/desktop/src/pages/api/auth/publicWechat/getWechatQR.ts
@@ -0,0 +1,54 @@
import { getWeChatAccessToken } from '@/services/backend/db/wechatCode';
import { jsonRes } from '@/services/backend/response';
import { nanoid } from 'nanoid';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const access_token = await getWeChatAccessToken();
if (!access_token) {
return jsonRes(res, {
message: 'Failed to authenticate with wechat',
code: 400,
data: 'access_token is null'
});
}

const sceneStr = nanoid(8);

const ticketPayload = {
expire_seconds: 1 * 60 * 60, // s
action_name: 'QR_STR_SCENE',
action_info: { scene: { scene_str: sceneStr } }
};

const ticketInfo = (await (
await fetch(`https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=${access_token}`, {
method: 'POST',
body: JSON.stringify(ticketPayload)
})
).json()) as { ticket: string; expire_seconds: number; url: string; errcode: number };

if (ticketInfo.errcode === 40001) {
return jsonRes(res, { code: 201, message: 'ticket error try again' });
}

const qrCodeUrl = `https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=${encodeURIComponent(
ticketInfo.ticket
)}`;

return jsonRes(res, {
code: 200,
data: {
code: sceneStr,
codeUrl: qrCodeUrl
}
});
} catch (err) {
console.log(err);
jsonRes(res, {
code: 500,
message: 'service getWechatQR error'
});
}
}
@@ -0,0 +1,58 @@
import { getWeChatAccessToken, verifyWechatCode } from '@/services/backend/db/wechatCode';
import { getOauthRes } from '@/services/backend/oauth';
import { jsonRes } from '@/services/backend/response';
import { TWechatUser } from '@/types/user';
import { getBase64FromRemote } from '@/utils/tools';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const access_token = await getWeChatAccessToken();
const { code } = req.query as { code: string };
if (!code) {
return jsonRes(res, {
code: 400,
data: 'Parameter error'
});
}

const verifyInfo = await verifyWechatCode({ code });
if (!verifyInfo?.openid) {
return jsonRes(res, {
code: 201,
data: 'Verification code expired'
});
}

const { openid } = verifyInfo;
const userUrl = `https://api.weixin.qq.com/cgi-bin/user/info?access_token=${access_token}&openid=${openid}&lang=zh_CN`;
const userInfo = (await (await fetch(userUrl)).json()) as TWechatUser;
const avatar_url = (await getBase64FromRemote(userInfo?.headimgurl)) as string;

if (!userInfo?.openid) {
return jsonRes(res, {
code: 500,
message: 'Failed to obtain WeChat information'
});
}

const data = await getOauthRes({
provider: 'wechat_open',
id: userInfo.openid,
name: userInfo?.nickname,
avatar_url
});

return jsonRes(res, {
code: 200,
message: 'Successfully',
data
});
} catch (err) {
console.log(err);
jsonRes(res, {
code: 500,
message: 'service getWechatResult error'
});
}
}

0 comments on commit 84e033d

Please sign in to comment.