feat: git login

This commit is contained in:
archer
2023-08-07 17:19:04 +08:00
parent 206eb81bb4
commit ce729dff1f
12 changed files with 310 additions and 32 deletions

View File

@@ -0,0 +1,116 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { User } from '@/service/models/user';
import { generateToken, setCookie } from '@/service/utils/tools';
import axios from 'axios';
import { parseQueryString } from '@/utils/tools';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
type GithubAccessTokenType = {
access_token: string;
expires_in: number;
refresh_token: string;
refresh_token_expires_in: number;
token_type: 'bearer';
scope: string;
};
type GithubUserType = {
email: string;
avatar_url: string;
};
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { code } = req.query as { code: string };
const { data: gitAccessToken } = await axios.post<string>(
`https://github.com/login/oauth/access_token?client_id=${global.feConfigs.gitLoginKey}&client_secret=${global.systemEnv.gitLoginSecret}&code=${code}`
);
const jsonGitAccessToken = parseQueryString(gitAccessToken) as GithubAccessTokenType;
const access_token = jsonGitAccessToken?.access_token;
if (!access_token) {
throw new Error('access_token is null');
}
const {
data: { email, avatar_url }
} = await axios.get<GithubUserType>('https://api.github.com/user', {
headers: {
Authorization: `Bearer ${access_token}`
}
});
try {
jsonRes(res, {
data: await loginByUsername({ username: email, res })
});
} catch (err: any) {
if (err?.code === 500) {
jsonRes(res, {
data: await registerUser({ username: email, avatar: avatar_url, res })
});
}
throw new Error(err);
}
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export async function loginByUsername({
username,
res
}: {
username: string;
res: NextApiResponse;
}) {
const user = await User.findOne({ username });
console.log(user, username);
if (!user) {
return Promise.reject({
code: 500
});
}
const token = generateToken(user._id);
setCookie(res, token);
return { user, token };
}
export async function registerUser({
username,
avatar,
res
}: {
username: string;
avatar?: string;
res: NextApiResponse;
}) {
const response = await User.create({
username,
avatar,
password: nanoid()
});
// 根据 id 获取用户信息
const user = await User.findById(response._id);
if (!user) {
throw new Error('获取用户信息异常');
}
const token = generateToken(user._id);
setCookie(res, token);
return {
user,
token
};
}

View File

@@ -1,11 +1,14 @@
import React, { useState, Dispatch, useCallback } from 'react';
import { FormControl, Flex, Input, Button, FormErrorMessage, Box } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { useRouter } from 'next/router';
import { PageTypeEnum } from '@/constants/user';
import { postLogin } from '@/api/user';
import type { ResLogin } from '@/api/response/user';
import { useToast } from '@/hooks/useToast';
import { feConfigs } from '@/store/static';
import { useGlobalStore } from '@/store/global';
import MyIcon from '@/components/Icon';
interface Props {
setPageType: Dispatch<`${PageTypeEnum}`>;
@@ -18,7 +21,10 @@ interface LoginFormType {
}
const LoginForm = ({ setPageType, loginSuccess }: Props) => {
const router = useRouter();
const { lastRoute = '/app/list' } = router.query as { lastRoute: string };
const { toast } = useToast();
const { setLoginStore } = useGlobalStore();
const {
register,
handleSubmit,
@@ -52,6 +58,19 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
[loginSuccess, toast]
);
const onclickGit = useCallback(() => {
setLoginStore({
provider: 'git',
lastRoute
});
router.replace(
`https://github.com/login/oauth/authorize?client_id=${
feConfigs?.gitLoginKey
}&redirect_uri=${`${location.origin}/login/provider`}&scope=user:email%20read:user`,
'_self'
);
}, [lastRoute, setLoginStore]);
return (
<>
<Box fontWeight={'bold'} fontSize={'2xl'} textAlign={'center'}>
@@ -117,6 +136,17 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
>
</Button>
{feConfigs?.show_register && (
<Flex mt={10} justifyContent={'center'} alignItems={'center'}>
<MyIcon
name="gitFill"
w={'34px'}
cursor={'pointer'}
color={'myGray.800'}
onClick={onclickGit}
/>
</Flex>
)}
</form>
</>
);

View File

@@ -0,0 +1,74 @@
import React, { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useGlobalStore } from '@/store/global';
import { ResLogin } from '@/api/response/user';
import { useChatStore } from '@/store/chat';
import { useUserStore } from '@/store/user';
import { setToken } from '@/utils/user';
import { gitLogin } from '@/api/user';
import { useToast } from '@/hooks/useToast';
import Loading from '@/components/Loading';
const provider = () => {
const { loginStore } = useGlobalStore();
const { setLastChatId, setLastChatAppId } = useChatStore();
const { setUserInfo } = useUserStore();
const router = useRouter();
const { toast } = useToast();
const { code } = router.query as { code?: string };
const loginSuccess = useCallback(
(res: ResLogin) => {
// init store
setLastChatId('');
setLastChatAppId('');
setUserInfo(res.user);
setToken(res.token);
setTimeout(() => {
router.push(
loginStore?.lastRoute ? decodeURIComponent(loginStore?.lastRoute) : '/app/list'
);
}, 100);
},
[setLastChatId, setLastChatAppId, setUserInfo, router, loginStore?.lastRoute]
);
const authCode = useCallback(async () => {
if (!code) return;
if (!loginStore) {
router.replace('/login');
return;
}
try {
const res = await (async () => {
if (loginStore.provider === 'git') {
return gitLogin(code);
}
return null;
})();
if (!res) {
toast({
status: 'warning',
title: '登录异常'
});
return router.replace('/login');
}
loginSuccess(res);
} catch (error) {
toast({
status: 'warning',
title: '登录异常'
});
router.replace('/login');
}
}, [code, loginStore, loginSuccess]);
useEffect(() => {
authCode();
}, [authCode]);
return <Loading />;
};
export default provider;