Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/ Google Login #515

Merged
merged 9 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@socket.io/component-emitter": "^3.1.0",
"cors": "^2.8.5",
"express": "^4.18.2",
"google-auth-library": "^9.15.0",
"jsonwebtoken": "^9.0.0",
"ldapts": "^4.2.6",
"prisma": "^4.16.2",
Expand Down
25 changes: 25 additions & 0 deletions packages/api/src/auth/google.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { z } from "zod";
import { OAuth2Client } from "google-auth-library";
import { env } from "@biseo/api/env";
import jwt from "jsonwebtoken";

const Token = z.object({
email: z.string(),
});

export const gauthenticate = async (code: string): Promise<string | null> => {
try {
const logininfo = new OAuth2Client(
env.GOOGLE_CLIENT,
env.GOOGLE_SECRET,
"postmessage",
);
const token = await logininfo.getToken(code);
const mail = Token.parse(jwt.decode(token.tokens.id_token!)).email;
const username =
mail.split("@")[1] === "sparcs.org" ? mail.split("@")[0] : null;
return username;
} catch {
return null;
}
};
14 changes: 14 additions & 0 deletions packages/api/src/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Request, Response } from "express";
import { prisma } from "@biseo/api/db/prisma";

import { authenticate } from "./ldap";
import { gauthenticate } from "./google";
import { getToken } from "./token";

const Login = z.object({
Expand All @@ -25,3 +26,16 @@ export const loginHandler = async (req: Request, res: Response) => {

return res.json({ token: getToken(user.username) });
};

export const gLoginHandler = async (req: Request, res: Response) => {
const result = req.body;

const username = await gauthenticate(result.code);
if (!username) return res.status(401).send("Unauthorized");

const user =
(await prisma.user.findUnique({ where: { username } })) ||
(await prisma.user.create({ data: { username, displayName: username } }));

return res.json({ token: getToken(user.username) });
};
5 changes: 4 additions & 1 deletion packages/api/src/auth/router.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Router } from "express";
import { loginHandler } from "./login";
import { loginHandler, gLoginHandler } from "./login";

const router = Router();

router.post("/login", (req, res, next) => {
loginHandler(req, res).catch(next);
});
router.post("/glogin", (req, res, next) => {
gLoginHandler(req, res).catch(next);
});

export { router as authRouter };
2 changes: 2 additions & 0 deletions packages/api/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const schema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
SERVER_PORT: z.coerce.number(),
SECRET_KEY: z.string(),
GOOGLE_CLIENT: z.string(),
GOOGLE_SECRET: z.string(),
});

export const env = schema.parse(process.env);
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@react-oauth/google": "^0.12.1",
"axios": "^1.4.0",
"framer-motion": "^10.16.1",
"immer": "^10.0.2",
Expand Down
Binary file added packages/web/src/assets/google.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions packages/web/src/common/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ export const getToken = async (username: string, password: string) => {
return null;
}
};

export const getGoogleToken = async (code: string) => {
try {
const res = await axios.post<{ token: string }>(
`${API_BASE}/api/auth/glogin`,
{
code,
},
);
return res.data.token;
} catch {
return null;
}
};
35 changes: 31 additions & 4 deletions packages/web/src/components/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

import { LogoLargeIcon } from "@biseo/web/assets";
import LandingImg from "@biseo/web/assets/landing.png";
import GoogleLogo from "@biseo/web/assets/google.png";
import { useAuth } from "@biseo/web/services/auth";

import { useInput } from "@biseo/web/common/hooks";
import { theme } from "@biseo/web/theme";
import { Box, Text } from "@biseo/web/components/atoms";
import { text } from "@biseo/web/styles";
import { useGoogleLogin } from "@react-oauth/google";

const Page = styled.div`
width: 100vw;
Expand Down Expand Up @@ -63,7 +65,7 @@

const LoginButton = styled.button`
width: 320px;
height: 45px;
height: 48px;

background-color: ${theme.colors.blue200};
border: none;
Expand All @@ -78,6 +80,7 @@
flex-direction: row;
align-items: center;
justify-content: center;
gap: 6px;

cursor: pointer;
transition: all 0.2s;
Expand All @@ -86,6 +89,9 @@
const LoginBackground = styled.img`
width: 100%;
max-width: 1700px;
height: 30%;

object-fit: cover;

position: absolute;
opacity: 30%;
Expand All @@ -95,8 +101,9 @@
`;

export const LoginPage: React.FC = () => {
const { login, isLoggedIn } = useAuth(state => ({
const { login, glogin, isLoggedIn } = useAuth(state => ({
login: state.login,
glogin: state.glogin,
isLoggedIn: !!state.userInfo,
}));
const [error, setError] = useState<boolean>(false);
Expand All @@ -119,11 +126,26 @@
e.preventDefault();

login(username.value, password.value)
.then(() => console.log("Login success!"))

Check warning on line 129 in packages/web/src/components/pages/LoginPage.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format (18.x, 8.x)

Unexpected console statement
.catch(() => setError(true));
},
[username.value, password.value],
);
const handleGLogin = useCallback((code: string) => {
glogin(code)
.then(() => console.log("Login success!"))

Check warning on line 136 in packages/web/src/components/pages/LoginPage.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format (18.x, 8.x)

Unexpected console statement
.catch(() => setError(true));
}, []);
const googleLogin = useGoogleLogin({
scope: "email",
onSuccess: async ({ code }) => {
handleGLogin(code);
},
onError: errorResponse => {
console.error(errorResponse);

Check warning on line 145 in packages/web/src/components/pages/LoginPage.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format (18.x, 8.x)

Unexpected console statement
},
flow: "auth-code",
});

if (isLoggedIn) return <Navigate to="/" replace />;

Expand All @@ -139,7 +161,6 @@
쉽고 빠른 의사결정은, Biseo
</LoginTitle>
</Box>

<form onSubmit={handleLogin}>
<Box dir="column" gap={12} align="center">
<InputContainer
Expand All @@ -162,13 +183,19 @@
)}

<LoginButton>
<Text variant="body" color="blue600">
<Text variant="boldtitle2" color="blue600">
로그인
</Text>
</LoginButton>
</Box>
</Box>
</form>
<LoginButton onClick={googleLogin}>
<img alt="google-logo" style={{ width: "20px" }} src={GoogleLogo} />
<Text variant="boldtitle2" color="blue600">
구글 로그인
</Text>
</LoginButton>
<LoginBackground src={LandingImg} />
</Page>
);
Expand Down
13 changes: 8 additions & 5 deletions packages/web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom";
import { ThemeProvider } from "@emotion/react";
import { enableMapSet } from "immer";
import { GoogleOAuthProvider } from "@react-oauth/google";

import router from "@biseo/web/router";
import { theme } from "@biseo/web/theme";
Expand All @@ -12,9 +13,11 @@ import "./index.css";
enableMapSet();

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<RouterProvider router={router} />
</ThemeProvider>
</React.StrictMode>,
<GoogleOAuthProvider clientId="630761439137-ouv559b1su1h52t0jqau3g41ok2cf3p0.apps.googleusercontent.com">
<React.StrictMode>
<ThemeProvider theme={theme}>
<RouterProvider router={router} />
</ThemeProvider>
</React.StrictMode>
</GoogleOAuthProvider>,
);
11 changes: 10 additions & 1 deletion packages/web/src/services/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@

import { socket } from "@biseo/web/socket";
import { initSocket } from "@biseo/web/socket/init";
import { getToken } from "@biseo/web/common/api/auth";
import { getToken, getGoogleToken } from "@biseo/web/common/api/auth";

interface AuthState {
token: string | null;
userInfo: Init | null;
init: () => Promise<Init | null>;
login: (username: string, password: string) => Promise<void>;
glogin: (code: string) => Promise<void>;
logout: () => void;
}

Expand Down Expand Up @@ -42,10 +43,18 @@
const userInfo = await initSocket(token);
set({ token, userInfo });
},
glogin: async code => {
const token = await getGoogleToken(code);

if (!token) throw new Error("incorrect username");

const userInfo = await initSocket(token);
set({ token, userInfo });
},
logout: () => {
set({ token: null, userInfo: null });
socket.disconnect();
console.log("logout");

Check warning on line 57 in packages/web/src/services/auth.ts

View workflow job for this annotation

GitHub Actions / Lint and Format (18.x, 8.x)

Unexpected console statement
},
}),
{
Expand All @@ -55,6 +64,6 @@
),
);

useAuth.getState().init().catch(console.error);

Check warning on line 67 in packages/web/src/services/auth.ts

View workflow job for this annotation

GitHub Actions / Lint and Format (18.x, 8.x)

Unexpected console statement

export { useAuth };
Loading
Loading