From 2adafd3b2cae82f081c3dafa1d871e5de955e9af Mon Sep 17 00:00:00 2001
From: lim <lbooks424@gmail.com>
Date: Tue, 5 Mar 2024 23:35:35 +0900
Subject: [PATCH 1/6] [frontend] Create an API Route on the frontend side that
 receives oauth(signup) callbacks and calls the backend API

---
 .../callback/auth/signup/oauth2/42/route.ts   | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)
 create mode 100644 frontend/app/callback/auth/signup/oauth2/42/route.ts

diff --git a/frontend/app/callback/auth/signup/oauth2/42/route.ts b/frontend/app/callback/auth/signup/oauth2/42/route.ts
new file mode 100644
index 00000000..a320a6bd
--- /dev/null
+++ b/frontend/app/callback/auth/signup/oauth2/42/route.ts
@@ -0,0 +1,25 @@
+import { redirect } from "next/navigation";
+
+export async function GET(request: Request) {
+  const { searchParams } = new URL(request.url);
+  console.error("Route handler called");
+  console.error(
+    "fetching",
+    `${process.env.API_URL}/auth/signup/oauth2/42/authenticate?${searchParams}`,
+  );
+  const res = await fetch(
+    `${process.env.API_URL}/auth/signup/oauth2/42/authenticate?${searchParams}`,
+    {
+      headers: {
+        "Content-Type": "application/json",
+      },
+    },
+  );
+  const data = await res.json();
+  if (!res.ok) {
+    console.error("Failed to authenticate", res);
+    console.error("Failed to authenticate", data);
+    redirect("/signup");
+  }
+  redirect("/login");
+}

From b354a4488cb144ffcb358161ce3098d8d1e48864 Mon Sep 17 00:00:00 2001
From: lim <lbooks424@gmail.com>
Date: Tue, 5 Mar 2024 23:40:12 +0900
Subject: [PATCH 2/6] [backend] Create function that only redirects to frontend
 with authorization code

---
 backend/src/auth/auth.controller.ts | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts
index 31708d23..da38e8e6 100644
--- a/backend/src/auth/auth.controller.ts
+++ b/backend/src/auth/auth.controller.ts
@@ -64,7 +64,14 @@ export class AuthController {
 
   @Get('signup/oauth2/42/callback')
   @ApiOkResponse({ type: AuthEntity })
-  @Redirect('/login')
+  @Redirect()
+  async signupWithOauth42Callback(@Query('code') code: string) {
+    // only redirect to the frontend with the code in the query
+    return { url: `/callback/auth/signup/oauth2/42?code=${code}` };
+  }
+
+  @Get('signup/oauth2/42/authenticate')
+  @ApiOkResponse({ type: AuthEntity })
   async signupWithOauth42(@Query('code') code: string) {
     return this.authService.signupWithOauth42(code);
   }

From beecf91eedf7005b7989cc88c3382e41c542bf67 Mon Sep 17 00:00:00 2001
From: lim <lbooks424@gmail.com>
Date: Wed, 6 Mar 2024 01:15:15 +0900
Subject: [PATCH 3/6] [frontend] Create an API Route on the frontend side that
 receives oauth(login) callbacks and calls the backend API

---
 .../callback/auth/login/oauth2/42/route.ts    | 22 +++++++++++++++++++
 1 file changed, 22 insertions(+)
 create mode 100644 frontend/app/callback/auth/login/oauth2/42/route.ts

diff --git a/frontend/app/callback/auth/login/oauth2/42/route.ts b/frontend/app/callback/auth/login/oauth2/42/route.ts
new file mode 100644
index 00000000..2f07706f
--- /dev/null
+++ b/frontend/app/callback/auth/login/oauth2/42/route.ts
@@ -0,0 +1,22 @@
+import { redirect } from "next/navigation";
+import { setAccessToken } from "@/app/lib/session";
+
+export async function GET(request: Request) {
+  const { searchParams } = new URL(request.url);
+  const res = await fetch(
+    `${process.env.API_URL}/auth/login/oauth2/42/authenticate?${searchParams}`,
+    {
+      headers: {
+        "Content-Type": "application/json",
+      },
+    },
+  );
+  const data = await res.json();
+  if (!res.ok) {
+    console.error("Failed to authenticate", res);
+    console.error("Failed to authenticate", data);
+    redirect("/login");
+  }
+  setAccessToken(data.accessToken);
+  redirect("/");
+}

From ef506534e6a5481cafecc25bb3e05c9ab0f04683 Mon Sep 17 00:00:00 2001
From: lim <lbooks424@gmail.com>
Date: Wed, 6 Mar 2024 01:25:22 +0900
Subject: [PATCH 4/6] [backend] Create function that only redirects to frontend
 with authorization code

---
 backend/src/auth/auth.controller.ts | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts
index da38e8e6..5d4e02b7 100644
--- a/backend/src/auth/auth.controller.ts
+++ b/backend/src/auth/auth.controller.ts
@@ -7,7 +7,6 @@ import {
   Post,
   Query,
   Redirect,
-  Res,
   UseGuards,
 } from '@nestjs/common';
 import {
@@ -17,7 +16,6 @@ import {
   ApiTags,
 } from '@nestjs/swagger';
 import type { User } from '@prisma/client';
-import { Response } from 'express';
 import { CurrentUser } from 'src/common/decorators/current-user.decorator';
 import { AuthService } from './auth.service';
 import { LoginDto } from './dto/login.dto';
@@ -86,11 +84,16 @@ export class AuthController {
 
   @Get('login/oauth2/42/callback')
   @ApiOkResponse({ type: AuthEntity })
-  loginWithOauth42(@Query('code') code: string, @Res() res: Response) {
-    return this.authService.loginWithOauth42(code).then((auth) => {
-      res.cookie('token', auth.accessToken);
-      res.redirect('/');
-    });
+  @Redirect()
+  loginWithOauth42Callback(@Query('code') code: string) {
+    // only redirect to the frontend with the code in the query
+    return { url: `/callback/auth/login/oauth2/42?code=${code}` };
+  }
+
+  @Get('login/oauth2/42/authenticate')
+  @ApiOkResponse({ type: AuthEntity })
+  loginWithOauth42(@Query('code') code: string) {
+    return this.authService.loginWithOauth42(code);
   }
 
   @Post('2fa/generate')

From a7159540277bc2e64d6f7deb5280988909f01caa Mon Sep 17 00:00:00 2001
From: lim <lbooks424@gmail.com>
Date: Wed, 6 Mar 2024 18:50:24 +0900
Subject: [PATCH 5/6] [frontend] Add query to redirect in case of OAuth failure

---
 .../callback/auth/login/oauth2/42/route.ts    | 22 ++++++++++++++----
 .../callback/auth/signup/oauth2/42/route.ts   | 23 ++++++++++++-------
 2 files changed, 32 insertions(+), 13 deletions(-)

diff --git a/frontend/app/callback/auth/login/oauth2/42/route.ts b/frontend/app/callback/auth/login/oauth2/42/route.ts
index 2f07706f..b6cd28f7 100644
--- a/frontend/app/callback/auth/login/oauth2/42/route.ts
+++ b/frontend/app/callback/auth/login/oauth2/42/route.ts
@@ -1,7 +1,14 @@
-import { redirect } from "next/navigation";
 import { setAccessToken } from "@/app/lib/session";
+import { redirect } from "next/navigation";
+
+const isValidUrl = (url: string) => {
+  return URL.canParse(url);
+};
 
 export async function GET(request: Request) {
+  if (!isValidUrl(request.url)) {
+    redirect("/login");
+  }
   const { searchParams } = new URL(request.url);
   const res = await fetch(
     `${process.env.API_URL}/auth/login/oauth2/42/authenticate?${searchParams}`,
@@ -13,10 +20,15 @@ export async function GET(request: Request) {
   );
   const data = await res.json();
   if (!res.ok) {
-    console.error("Failed to authenticate", res);
     console.error("Failed to authenticate", data);
-    redirect("/login");
+    const params = new URLSearchParams({
+      status: data.statusCode,
+      message: data.message,
+    });
+    const query = "?" + params.toString();
+    redirect("/login" + query);
+  } else {
+    setAccessToken(data.accessToken);
+    redirect("/");
   }
-  setAccessToken(data.accessToken);
-  redirect("/");
 }
diff --git a/frontend/app/callback/auth/signup/oauth2/42/route.ts b/frontend/app/callback/auth/signup/oauth2/42/route.ts
index a320a6bd..c698906a 100644
--- a/frontend/app/callback/auth/signup/oauth2/42/route.ts
+++ b/frontend/app/callback/auth/signup/oauth2/42/route.ts
@@ -1,12 +1,14 @@
 import { redirect } from "next/navigation";
 
+const isValidUrl = (url: string) => {
+  return URL.canParse(url);
+};
+
 export async function GET(request: Request) {
+  if (!isValidUrl(request.url)) {
+    redirect("/signup");
+  }
   const { searchParams } = new URL(request.url);
-  console.error("Route handler called");
-  console.error(
-    "fetching",
-    `${process.env.API_URL}/auth/signup/oauth2/42/authenticate?${searchParams}`,
-  );
   const res = await fetch(
     `${process.env.API_URL}/auth/signup/oauth2/42/authenticate?${searchParams}`,
     {
@@ -17,9 +19,14 @@ export async function GET(request: Request) {
   );
   const data = await res.json();
   if (!res.ok) {
-    console.error("Failed to authenticate", res);
     console.error("Failed to authenticate", data);
-    redirect("/signup");
+    const params = new URLSearchParams({
+      status: data.statusCode,
+      message: data.message,
+    });
+    const query = "?" + params.toString();
+    redirect("/signup" + query);
+  } else {
+    redirect("/login");
   }
-  redirect("/login");
 }

From 61d29f2be9477e8cb25e9a7974ed27c471843f44 Mon Sep 17 00:00:00 2001
From: lim <lbooks424@gmail.com>
Date: Wed, 6 Mar 2024 18:57:47 +0900
Subject: [PATCH 6/6] [frontend] Added toast display when using useSearchParams
 to get error status and message

---
 frontend/app/ui/auth/login-form.tsx  | 47 +++++++++++++++++++++++++-
 frontend/app/ui/auth/signup-form.tsx | 50 ++++++++++++++++++++++++++++
 2 files changed, 96 insertions(+), 1 deletion(-)

diff --git a/frontend/app/ui/auth/login-form.tsx b/frontend/app/ui/auth/login-form.tsx
index 78073afb..0a128744 100644
--- a/frontend/app/ui/auth/login-form.tsx
+++ b/frontend/app/ui/auth/login-form.tsx
@@ -7,11 +7,56 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
 import { Input } from "@/components/ui/input";
 import { Label } from "@/components/ui/label";
 import { Separator } from "@/components/ui/separator";
-import { useRouter } from "next/navigation";
+import { toast } from "@/components/ui/use-toast";
+import { useRouter, useSearchParams } from "next/navigation";
 import { useEffect } from "react";
 import { useFormState, useFormStatus } from "react-dom";
 
+const getStatusText = (statusCode: string) => {
+  let statusText = "";
+  switch (statusCode) {
+    case "400":
+      statusText = "Bad Request";
+      break;
+    case "401":
+      statusText = "Unauthorized";
+      break;
+    case "403":
+      statusText = "Forbidden";
+      break;
+    case "404":
+      statusText = "Not Found";
+      break;
+    case "409":
+      statusText = "Conflict";
+      break;
+    case "500":
+      statusText = "Internal Server Error";
+      break;
+    default:
+      statusText = "Error";
+      break;
+  }
+  return statusText;
+};
+
+const showErrorToast = (statusCode: string, message: string) => {
+  const statusText = getStatusText(statusCode);
+  toast({
+    title: statusCode + " " + statusText,
+    description: message,
+  });
+};
+
 export default function LoginForm() {
+  const searchParams = useSearchParams();
+  const errorStatusCode = searchParams.get("status");
+  const errorMessage = searchParams.get("message");
+  useEffect(() => {
+    if (errorStatusCode && errorMessage) {
+      showErrorToast(errorStatusCode, errorMessage);
+    }
+  }, [errorStatusCode, errorMessage]);
   return (
     <>
       <Card>
diff --git a/frontend/app/ui/auth/signup-form.tsx b/frontend/app/ui/auth/signup-form.tsx
index 6ab3c752..d9a89be2 100644
--- a/frontend/app/ui/auth/signup-form.tsx
+++ b/frontend/app/ui/auth/signup-form.tsx
@@ -1,9 +1,59 @@
+"use client";
+
 import Form from "@/app/ui/user/create-form";
 import { Button } from "@/components/ui/button";
 import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
 import { Separator } from "@/components/ui/separator";
+import { toast } from "@/components/ui/use-toast";
+import { useSearchParams } from "next/navigation";
+import { useEffect } from "react";
+
+const getStatusText = (statusCode: string) => {
+  let statusText = "";
+  switch (statusCode) {
+    case "400":
+      statusText = "Bad Request";
+      break;
+    case "401":
+      statusText = "Unauthorized";
+      break;
+    case "403":
+      statusText = "Forbidden";
+      break;
+    case "404":
+      statusText = "Not Found";
+      break;
+    case "409":
+      statusText = "Conflict";
+      break;
+    case "500":
+      statusText = "Internal Server Error";
+      break;
+    default:
+      statusText = "Error";
+      break;
+  }
+  return statusText;
+};
+
+const showErrorToast = (statusCode: string, message: string) => {
+  const statusText = getStatusText(statusCode);
+  toast({
+    title: statusCode + " " + statusText,
+    description: message,
+  });
+};
 
 export default function SignUpForm() {
+  const searchParams = useSearchParams();
+  const errorStatusCode = searchParams.get("status");
+  const errorMessage = searchParams.get("message");
+
+  useEffect(() => {
+    if (errorStatusCode && errorMessage) {
+      showErrorToast(errorStatusCode, errorMessage);
+    }
+  }, [errorStatusCode, errorMessage]);
   return (
     <Card>
       <CardHeader>