diff --git a/app/(safe-mode-page)/auth/logout/route.tsx b/app/(safe-mode-page)/auth/logout/route.tsx new file mode 100644 index 0000000..d33ed31 --- /dev/null +++ b/app/(safe-mode-page)/auth/logout/route.tsx @@ -0,0 +1,7 @@ +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; + +export const GET = async (request: Request) => { + (await cookies()).delete("auth_token"); + return NextResponse.redirect(new URL("/", request.url), 303); +}; diff --git a/app/(session)/(clean)/auth/providers/[name]/callback/page.tsx b/app/(safe-mode-page)/auth/providers/[name]/callback/page.tsx similarity index 100% rename from app/(session)/(clean)/auth/providers/[name]/callback/page.tsx rename to app/(safe-mode-page)/auth/providers/[name]/callback/page.tsx diff --git a/app/layout.tsx b/app/layout.tsx index f65b89c..b87e432 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono, Inter } from "next/font/google"; import "./globals.css"; +import { Toaster } from "@/shared/libs/shadcn/ui/sonner"; const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }); @@ -29,7 +30,8 @@ export default function RootLayout({ - {children} +
{children}
+ ); diff --git a/features/authCallback/actions/submitProviderCallback.ts b/features/authCallback/actions/submitProviderCallback.ts index 3a0d179..8afb005 100644 --- a/features/authCallback/actions/submitProviderCallback.ts +++ b/features/authCallback/actions/submitProviderCallback.ts @@ -47,6 +47,7 @@ export const submitProviderCallback = async ( } catch (error) { return { success: false, + status: 500, message: "Error submitting provider callback", error: error, }; diff --git a/shared/helpers/backendFetch.ts b/shared/helpers/backendFetch.ts index abb0e99..afe346f 100644 --- a/shared/helpers/backendFetch.ts +++ b/shared/helpers/backendFetch.ts @@ -5,6 +5,7 @@ import { UAParser } from "ua-parser-js"; export interface BackendResponse { success: boolean; + status: number; message: string; data?: T; error?: unknown; @@ -34,16 +35,11 @@ export const backendFetch = async (path: string, options: RequestInit = {}) => { ...options.headers, }, cache: "default", - }); + }).then((response) => response.json()); - const resJson = (await res.json()) as BackendResponse; - - if (!res.ok) { - throw new Error(`Elysia error: ${resJson.error}`); - } - - return resJson; - } catch { + return res as BackendResponse; + } catch (res) { + if (process.env.NODE_ENV === "development") return res; redirect("/status?reason=backend-unreachable"); } }; diff --git a/shared/models/auth/logout.ts b/shared/models/auth/logout.ts index 4e01ddb..4502582 100644 --- a/shared/models/auth/logout.ts +++ b/shared/models/auth/logout.ts @@ -1,18 +1,16 @@ "use server"; -import { backendFetch } from "@/shared/helpers/backendFetch"; -import { cookies } from "next/headers"; +import { backendFetch, BackendResponse } from "@/shared/helpers/backendFetch"; export const logout = async () => { - const res = await backendFetch("auth/logout", { + const res = (await backendFetch("auth/logout", { method: "POST", - }); + })) as BackendResponse; if (res.success) { - (await cookies()).delete("auth_token"); return { success: true, - message: "Logged out successfully", + message: "Logout successful", }; } else { return { diff --git a/shared/models/auth/validateAndDecodeJWT.ts b/shared/models/auth/validateAndDecodeJWT.ts index ba60b0d..96c3ac8 100644 --- a/shared/models/auth/validateAndDecodeJWT.ts +++ b/shared/models/auth/validateAndDecodeJWT.ts @@ -1,7 +1,7 @@ "use server"; import { backendFetch, BackendResponse } from "@/shared/helpers/backendFetch"; -import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; export interface UserSession { id: string; @@ -30,18 +30,14 @@ export interface UserSession { } export const validateAndDecodeJWT = async (): Promise => { - const cookieHeader = (await cookies()).get("auth_token")?.value; - - if (!cookieHeader) { - return null; - } - + "use server"; const res = (await backendFetch("auth/token/validate", { method: "POST", - body: JSON.stringify({ - token: cookieHeader, - }), })) as BackendResponse; - return res.data!; + if (res.status === 403) { + redirect("/auth/logout"); + } + + return res.data ?? null; }; diff --git a/shared/widgets/navbar/components/LogoutAlert.tsx b/shared/widgets/navbar/components/LogoutAlert.tsx index b15bd34..cea86e6 100644 --- a/shared/widgets/navbar/components/LogoutAlert.tsx +++ b/shared/widgets/navbar/components/LogoutAlert.tsx @@ -11,7 +11,9 @@ import { import { Spinner } from "@/shared/libs/shadcn/ui/spinner"; import { logout } from "@/shared/models/auth/logout"; import { Button } from "@base-ui/react"; +import { useRouter } from "next/navigation"; import React from "react"; +import { toast } from "sonner"; const LogoutAlert = ({ openState, @@ -20,12 +22,30 @@ const LogoutAlert = ({ openState: boolean; setOpenState: React.Dispatch>; }) => { + const router = useRouter(); const [isLoading, setIsLoading] = React.useState(false); const continueLogout = async () => { setIsLoading(true); - await logout().then((res) => - res.success ? window.location.reload() : setIsLoading(false), - ); + const res = await logout(); + if (!res.success) { + setIsLoading(false); + toast.error(res.message || "Logout failed", { + position: "bottom-right", + description: + "An error occurred while logging out. Please try again later.", + richColors: true, + }); + } else { + toast.success(res.message || "Logout successful", { + position: "bottom-right", + description: "You have been logged out successfully.", + richColors: true, + }); + router.push("/auth/logout"); + setTimeout(() => { + window.location.reload(); + }, 2000); + } }; return (