Skip to content

Commit

Permalink
Merge pull request #30 from CS3219-AY2324S1/feat/in-session-component
Browse files Browse the repository at this point in the history
feat: check if user is in a session and render component
  • Loading branch information
raynerljm authored Oct 17, 2023
2 parents 7b1fea2 + f590ded commit 981e96f
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 42 deletions.
55 changes: 48 additions & 7 deletions backend/api-gateway/app/libs/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { RequestHandler } from "express";
import { failApiResponse, unwrapJwt } from "./utils";
import { HTTP_STATUS_CODE } from "../types";
import { HTTP_STATUS, HTTP_STATUS_CODE } from "../types";
import { ROLE, User, userSchema } from "../types/user";
import { API_GATEWAY_AUTH_SECRET } from "./constants";
import { getUserSchema } from "../types/usersService";

export const authMiddleware: RequestHandler = async (req, res, next) => {
// If the auth secret is provided in the header, bypass the auth middleware
Expand Down Expand Up @@ -44,14 +45,54 @@ export const authMiddleware: RequestHandler = async (req, res, next) => {
export const adminMiddleware: RequestHandler = async (req, res, next) => {
authMiddleware(req, res, next);

const userData = userSchema.parse(res.locals.user);
try {
const primaryEmail = userSchema.parse(res.locals.user).email;

if (userData.role === ROLE.ADMIN) {
next();
} else {
return res.status(HTTP_STATUS_CODE.UNAUTHORIZED).send(
const getUserResponse = await fetch(
`${process.env.USERS_SERVICE_URL}/api/users/email/${primaryEmail}`
);
const getUserData = await getUserResponse.json();
const safeParsedUserServiceData = getUserSchema.safeParse(getUserData);

if (!safeParsedUserServiceData.success) {
return res.status(500).send(
failApiResponse({
error: `Failed to parse response from user service GET ${
process.env.USERS_SERVICE_URL
}/api/users/email\n\Reason:\n${JSON.stringify(
safeParsedUserServiceData.error
)}`,
})
);
}

const parsedUserServiceData = safeParsedUserServiceData.data;

if (parsedUserServiceData.status === HTTP_STATUS.FAIL) {
return res.status(401).send(
failApiResponse({
error: `User with email ${primaryEmail} does not exist in user service`,
})
);
}

const userData = parsedUserServiceData.data.user;

if (userData.role === ROLE.ADMIN) {
next();
} else {
return res.status(HTTP_STATUS_CODE.UNAUTHORIZED).send(
failApiResponse({
message: "User is not authorized to perform this action",
})
);
}
} catch (error) {
return res.status(500).send(
failApiResponse({
message: "User is not authorized to perform this action",
error: `An error occurred: ${
error instanceof Error ? error.message : error
}`,
})
);
}
Expand Down
66 changes: 47 additions & 19 deletions backend/api-gateway/app/routes/auth.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import {
githubUserResponseSchema,
} from "../types/github";
import { getUserSchema } from "../types/usersService";
import { HTTP_STATUS } from "../types";
import { HTTP_STATUS, HTTP_STATUS_CODE } from "../types";
import { User } from "../types/user";
import { getRoomIdFromUserIdSchema } from "../types/collaborationService";

export const authRouter = Router();

Expand All @@ -38,7 +39,7 @@ authRouter.get("/", authMiddleware, async (req: Request, res: Response) => {
const safeParsedUserServiceData = getUserSchema.safeParse(getUserData);

if (!safeParsedUserServiceData.success) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: `Failed to parse response from user service GET ${
process.env.USERS_SERVICE_URL
Expand All @@ -53,18 +54,43 @@ authRouter.get("/", authMiddleware, async (req: Request, res: Response) => {

if (parsedUserServiceData.status === HTTP_STATUS.FAIL) {
res.clearCookie(process.env.JWT_COOKIE_NAME);
return res.status(401).send(
return res.status(HTTP_STATUS_CODE.UNAUTHORIZED).send(
failApiResponse({
error: `User with email ${primaryEmail} does not exist in user service`,
})
);
}

return res.send(
successApiResponse({ user: parsedUserServiceData.data.user })
);
let user: User & { roomId?: string } = parsedUserServiceData.data.user;

// Check if user is currently in a room (Do not fail if user is not in a room)
const userId = user.id;
try {
const getRoomIdResponse = await fetch(
`${process.env.COLLABORATION_SERVICE_URL}/api/collaboration/room/user/${userId}`
);
const getRoomIdData = await getRoomIdResponse.json();
const safeParsedRoomIdData =
getRoomIdFromUserIdSchema.safeParse(getRoomIdData);
if (safeParsedRoomIdData.success) {
const parsedRoomIdData = safeParsedRoomIdData.data;
if (
parsedRoomIdData.status === HTTP_STATUS.SUCCESS &&
parsedRoomIdData.data.roomId
) {
user = { ...user, roomId: parsedRoomIdData.data.roomId };
}
}
} catch (error) {
console.error(
"Error getting room ID:",
error instanceof Error ? error.message : JSON.stringify(error)
);
}

return res.send(successApiResponse({ user }));
} catch (error) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: error instanceof Error ? error.message : "An error has occurred",
})
Expand All @@ -88,7 +114,7 @@ authRouter.get("/github/authorize", async (req: Request, res: Response) => {

res.send(successApiResponse({ url: response.url }));
} catch (error) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: error instanceof Error ? error.message : "An error has occurred",
})
Expand All @@ -102,7 +128,9 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {

// Validating state to prevent CSRF
if (state !== GITHUB_OAUTH_STATE) {
return res.status(401).send(failApiResponse({ error: "Invalid state" }));
return res
.status(HTTP_STATUS_CODE.UNAUTHORIZED)
.send(failApiResponse({ error: "Invalid state" }));
}

const queryParams: Record<string, string> = {
Expand All @@ -125,7 +153,7 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {
const tokenData = await tokenResponse.json();

if (tokenData["error"]) {
return res.status(401).send(
return res.status(HTTP_STATUS_CODE.UNAUTHORIZED).send(
failApiResponse({
error: tokenData["error"],
error_description: tokenData["error_description"],
Expand All @@ -135,7 +163,7 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {

if (!tokenData["access_token"]) {
return res
.status(401)
.status(HTTP_STATUS_CODE.UNAUTHORIZED)
.send(failApiResponse({ error: "Access token not found" }));
}

Expand All @@ -152,7 +180,7 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {
const safeParsedEmailData = githubEmailResponseSchema.safeParse(emailData);

if (!safeParsedEmailData.success) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: `Failed to parse response from GitHub GET ${GITHUB_USER_EMAIL_ENDPOINT}\nReason:\n${JSON.stringify(
safeParsedEmailData.error
Expand All @@ -164,7 +192,7 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {
const primaryEmail = getPrimaryEmail(safeParsedEmailData.data);

if (!primaryEmail) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: `Primary GitHub email not found in response\nResponse:\n${safeParsedEmailData.data}`,
})
Expand All @@ -179,7 +207,7 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {
const safeParsedUserServiceData = getUserSchema.safeParse(getUserData);

if (!safeParsedUserServiceData.success) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: `Failed to parse response from user service GET ${
process.env.USERS_SERVICE_URL
Expand Down Expand Up @@ -209,7 +237,7 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {
const safeParsedUserData = githubUserResponseSchema.safeParse(userData);

if (!safeParsedUserData.success) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: `Failed to parse response from GitHub GET ${GITHUB_USER_ENDPOINT}\nReason:\n${JSON.stringify(
safeParsedUserData.error
Expand Down Expand Up @@ -248,7 +276,7 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {
const safeParsedCreateUserData = getUserSchema.safeParse(createUserData);

if (!safeParsedCreateUserData.success) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: `Failed to parse response from user service POST ${
process.env.USERS_SERVICE_URL
Expand All @@ -262,7 +290,7 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {
const parsedCreateUserData = safeParsedCreateUserData.data;

if (parsedCreateUserData.status === HTTP_STATUS.FAIL) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: `Failed to create user in user service\nResponse:\n${JSON.stringify(
parsedCreateUserData.data
Expand All @@ -280,7 +308,7 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {
}

if (!user) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: `Failed to find/generate user in user service`,
})
Expand All @@ -295,7 +323,7 @@ authRouter.get("/github/login", async (req: Request, res: Response) => {
res.cookie(process.env.JWT_COOKIE_NAME, token, cookieOptions);
return res.send(successApiResponse({ message: "Successfully logged in" }));
} catch (error) {
return res.status(500).send(
return res.status(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR).send(
failApiResponse({
error: error instanceof Error ? error.message : "An error has occurred",
})
Expand Down
18 changes: 18 additions & 0 deletions backend/api-gateway/app/types/collaborationService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { z } from "zod";
import { HTTP_STATUS } from "./http";

export const getRoomIdFromUserIdSchema = z.union([
z.object({
status: z.literal(HTTP_STATUS.SUCCESS),
data: z.object({
roomId: z.string().optional(),
userId: z.string(),
}),
}),
z.object({
status: z.literal(HTTP_STATUS.FAIL),
data: z.object({
message: z.string(),
}),
}),
]);
3 changes: 1 addition & 2 deletions frontend/app/src/components/Layout/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ type NavbarProps = {

export const Navbar = ({ isBorderless }: NavbarProps) => {
const { data } = useAuth();
const session: any = undefined; // TODO: Populate session with data from the service

const user = data?.user;

Expand All @@ -25,7 +24,7 @@ export const Navbar = ({ isBorderless }: NavbarProps) => {
>
<Container maxW={MAX_WIDTH} px={WINDOW_X_PADDING} py="1.25rem">
<HStack position="relative" justifyContent="space-between">
{session ? <SessionBar session={session} /> : null}
<SessionBar />
<Text
as={Link}
to={user ? ROUTE.HOME : ROUTE.ROOT}
Expand Down
38 changes: 25 additions & 13 deletions frontend/app/src/components/Layout/SessionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
import { ROUTE } from "@/constants/route";
import { useAuth } from "@/hooks";
import { Box, Text } from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";

/**
* TODO: Set up once the service is implemented
*/
type SessionBarProps = {
session: any;
};
export const SessionBar = () => {
const { data } = useAuth();
const navigate = useNavigate();

const roomId = data?.user?.roomId;

if (!roomId) {
return null;
}

export const SessionBar = ({ session }: SessionBarProps) => {
console.log(session);
return (
<Box
inset="0"
m="auto"
position="absolute"
h="fit-content"
w="fit-content"
px="4.5rem"
py="0.5rem"
borderWidth="3px"
px="4rem"
py="0.375rem"
borderWidth="2px"
borderRadius="full"
borderColor="light.500"
borderColor="primary.500"
background="dark.500"
onClick={() => navigate(`${ROUTE.ROOM}/${roomId}`)}
cursor="pointer"
_hover={{
borderColor: "primary.300",
background: "dark.400",
}}
transition="all 0.2s"
>
<Text>Session in progress...</Text>
<Text textStyle="text-sm">Session in progress...</Text>
</Box>
);
};
4 changes: 3 additions & 1 deletion frontend/app/src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export const GET_AUTH_QUERY_KEY = "auth";

const getAuthResponseSchema = makeSuccessResponseSchema(
z.object({
user: userSchema,
user: userSchema.extend({
roomId: z.string().optional(),
}),
}),
);

Expand Down

0 comments on commit 981e96f

Please sign in to comment.