diff --git a/src/DTO/oauth.dto.ts b/src/DTO/oauth.dto.ts index 854fc10..e9630bf 100644 --- a/src/DTO/oauth.dto.ts +++ b/src/DTO/oauth.dto.ts @@ -1,6 +1,6 @@ import { SchemaToInterface } from 'fastify-schema-to-ts'; -export const kakaoSchema = { +export const oauthRedirctSchema = { tags: ['User'], summary: '로그인', querystring: { @@ -16,4 +16,4 @@ export const kakaoSchema = { }, } as const; -export type kakaoInterface = SchemaToInterface; +export type oauthRedirctInterface = SchemaToInterface; diff --git a/src/back/api/routes/oauth.ts b/src/back/api/routes/oauth.ts index b0ead33..a8afc30 100644 --- a/src/back/api/routes/oauth.ts +++ b/src/back/api/routes/oauth.ts @@ -3,16 +3,26 @@ import * as Oauth from '@DTO/oauth.dto'; import OauthService from '@services/oauth.service'; const api: FastifyPluginAsync = async (server: FastifyInstance) => { - server.get( + server.get( '/kakao', { - schema: Oauth.kakaoSchema + schema: Oauth.oauthRedirctSchema }, async (request, reply) => { const { accessToken, refreshToken } = await OauthService.kakao(request.query); reply.setCookie('authorization', refreshToken).redirect(303, `/login/${accessToken}`); } ); + server.get( + '/google', + { + schema: Oauth.oauthRedirctSchema + }, + async (request, reply) => { + const { accessToken, refreshToken } = await OauthService.google(request.query); + reply.setCookie('authorization', refreshToken).redirect(303, `/login/${accessToken}`); + } + ); }; export default api; diff --git a/src/back/config.ts b/src/back/config.ts index 283b8e5..2d0cff8 100644 --- a/src/back/config.ts +++ b/src/back/config.ts @@ -21,7 +21,9 @@ class Config{ jwtRefreshKey: process.env.JWT_REFRESH_KEY || 'refresh', salt: process.env.SALT || 'salt', kakaoClientId: process.env.KAKAO_CLIENT_ID || 'kakaoClientId', - kakaoRedirectUri: process.env.KAKAO_REDIRECT_URI || 'kakaoRedirectUri', + redirectUri: process.env.REDIRECT_URI || 'RedirectUri', + googleClientId: process.env.GOOGLE_CLIENT_ID || 'googleClientId', + googleClientSecret: process.env.GOOGLE_CLIENT_SECRET || 'googleClientSecret', nodeEnv: process.env.NODE_ENV, } as const; } diff --git a/src/back/loaders/errorHandler.ts b/src/back/loaders/errorHandler.ts index 6d4df96..a4d3215 100644 --- a/src/back/loaders/errorHandler.ts +++ b/src/back/loaders/errorHandler.ts @@ -24,6 +24,7 @@ export default ( request: FastifyRequest, reply: FastifyReply ) => { + console.error(error); if (error instanceof ErrorWithToast) return handleKnownError(error, reply); if (error.validation) { return reply.code(400).send({ diff --git a/src/back/repository/oauth.repo.ts b/src/back/repository/oauth.repo.ts index 26b0cbc..e3c5beb 100644 --- a/src/back/repository/oauth.repo.ts +++ b/src/back/repository/oauth.repo.ts @@ -10,7 +10,7 @@ export const getKakaoAccessToken = async (code: string) => { data: qs.stringify({ grant_type: 'authorization_code', client_id: config.kakaoClientId, - redirect_uri: config.kakaoRedirectUri, + redirect_uri: config.redirectUri+'/api/v1/oauth/kakao', code }), headers: { @@ -46,3 +46,35 @@ export const getKakaoUserInfo = async (kakaoAccessToken: string) => { nickname: kakao_account.profile?.nickname, }; } + +export const getGoogleAccessToken = async (code: string) => { + const {access_token: getGoogleAccessToken} = (await axios<{access_token:string,scope:string}>({ + method: 'post', + url: 'https://oauth2.googleapis.com/token', + data: qs.stringify({ + grant_type: 'authorization_code', + client_id: config.googleClientId, + redirect_uri: config.redirectUri+'/api/v1/oauth/google', + client_secret: config.googleClientSecret, + code + }), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' + } + })).data; + console.log(getGoogleAccessToken); + return getGoogleAccessToken; +} + +export const getGoogleUserInfo = async (getGoogleAccessToken: string) => { + const { id, name } = (await axios.get<{id?:string,name?:string}>('https://www.googleapis.com/userinfo/v2/me', { + headers: { + Authorization: `Bearer ${getGoogleAccessToken}`, + } + })).data; + return { + id, + name + }; +} + diff --git a/src/back/services/oauth.service.ts b/src/back/services/oauth.service.ts index 0a1efad..37018db 100644 --- a/src/back/services/oauth.service.ts +++ b/src/back/services/oauth.service.ts @@ -2,10 +2,10 @@ import { LoginToken } from '@serverUtils/jwt'; import * as OauthDTO from '@DTO/oauth.dto'; import * as UserRepo from '@repository/user.repo'; import { NotFoundError, NotCorrectTypeError, ExistError } from '@errors'; -import { getKakaoAccessToken, getKakaoId, getKakaoUserInfo } from '@repository/oauth.repo'; +import { getKakaoAccessToken, getKakaoId, getKakaoUserInfo, getGoogleAccessToken, getGoogleUserInfo } from '@repository/oauth.repo'; export default { - async kakao({code,error}: OauthDTO.kakaoInterface['Querystring']){ + async kakao({code,error}: OauthDTO.oauthRedirctInterface['Querystring']){ if(error) throw new NotCorrectTypeError("", "code"); if(!code) throw new NotFoundError("", "code"); const kakaoAccessToken = await getKakaoAccessToken(code); @@ -25,4 +25,23 @@ export default { refreshToken }; }, + async google({code,error}: OauthDTO.oauthRedirctInterface['Querystring']){ + if(error) throw new NotCorrectTypeError("", "code"); + if(!code) throw new NotFoundError("", "code"); + const googleAccessToken = await getGoogleAccessToken(code); + const googleUserInfo = await getGoogleUserInfo(googleAccessToken); + if(!googleUserInfo.id) throw new NotFoundError("", "id"); + if(!googleUserInfo.name) throw new NotFoundError("", "name"); + let user = await UserRepo.getUser('GOOGLE', googleUserInfo.id); + if(!user) { + user = await UserRepo.createUser({name: googleUserInfo.name, socialId: googleUserInfo.id, socialType: 'GOOGLE'}); + } + const loginToken = new LoginToken(user.uuid); + const accessToken = loginToken.signAccessToken(); + const refreshToken = loginToken.signRefreshToken(); + return { + accessToken, + refreshToken + }; + } } diff --git a/src/front/assets/googlelogin.svg b/src/front/assets/googlelogin.svg new file mode 100644 index 0000000..c1b5c78 --- /dev/null +++ b/src/front/assets/googlelogin.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/front/pages/Login.tsx b/src/front/pages/Login.tsx index f54a455..4890985 100644 --- a/src/front/pages/Login.tsx +++ b/src/front/pages/Login.tsx @@ -1,5 +1,6 @@ import { Link } from 'react-router-dom'; import kakaologin from '@assets/kakaologin.png'; +import googlelogin from '@assets/googlelogin.svg'; import { LoginContainer} from '@components'; function Login() { @@ -9,9 +10,14 @@ function Login() {

로그인

+ to={`https://kauth.kakao.com/oauth/authorize?client_id=${__KAKAO_CLIENT_ID__}&redirect_uri=${__REDIRECT_URI__}/api/v1/oauth/kakao&response_type=code`}> 카카오 로그인 + + 구글 로그인 + ); } diff --git a/src/front/vite-env.d.ts b/src/front/vite-env.d.ts index 7c0c4a2..a00ce26 100644 --- a/src/front/vite-env.d.ts +++ b/src/front/vite-env.d.ts @@ -1,9 +1,5 @@ /// -interface ImportMetaEnv { - readonly KAKAO_CLIENT_ID: string - readonly KAKAO_REDIRECT_URI: string - } - - interface ImportMeta { - readonly env: ImportMetaEnv - } + +declare const __REDIRECT_URI__: string; +declare const __GOOGLE_CLIENT_ID__: string; +declare const __KAKAO_CLIENT_ID__: string; diff --git a/vite.config.ts b/vite.config.ts index 078f23d..9971999 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,12 +1,20 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import tsconfigPaths from 'vite-tsconfig-paths'; +import _ from 'json-bigint'; +import dotenv from 'dotenv'; + +dotenv.config(); // https://vitejs.dev/config/ export default defineConfig({ root: 'src/front', envDir: '../../', - envPrefix: ['VITE_', 'KAKAO_'], + define: { + __REDIRECT_URI__: JSON.stringify(process.env.REDIRECT_URI), + __GOOGLE_CLIENT_ID__: JSON.stringify(process.env.GOOGLE_CLIENT_ID), + __KAKAO_CLIENT_ID__: JSON.stringify(process.env.KAKAO_CLIENT_ID), + }, plugins: [react(), tsconfigPaths()], server: { port: 3000,