feat: git login
This commit is contained in:
116
client/src/pages/api/user/account/gitLogin.ts
Normal file
116
client/src/pages/api/user/account/gitLogin.ts
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
74
client/src/pages/login/provider.tsx
Normal file
74
client/src/pages/login/provider.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user