From eae3b2b3fcdb1c7fca8ea6d134b49d1c4d8c59c8 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Tue, 20 Jan 2026 11:27:09 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20create=20auth=20provider=20?= =?UTF-8?q?context?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/layout.tsx | 4 +- .../actions/submitProviderCallback.ts | 8 ++-- features/authCallback/index.tsx | 5 ++- shared/contexts/AuthContext.tsx | 14 +++++++ shared/{helper => helpers}/backendFetch.ts | 0 shared/models/auth/validateAndDecodeJWT.ts | 42 +++++++++++++++++++ shared/providers/AuthSession.client.tsx | 17 ++++++++ shared/providers/AuthSession.tsx | 21 ++++++---- .../signin/actions/getAllThirdPartyAuth.ts | 2 +- .../signin/actions/getOauthEndpoint.ts | 4 +- 10 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 shared/contexts/AuthContext.tsx rename shared/{helper => helpers}/backendFetch.ts (100%) create mode 100644 shared/models/auth/validateAndDecodeJWT.ts create mode 100644 shared/providers/AuthSession.client.tsx diff --git a/app/layout.tsx b/app/layout.tsx index db1f4f3..00aa0d9 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono, Inter } from "next/font/google"; -import Navbar from "@/shared/widgets/navbar/components/Navbar"; import "./globals.css"; +import AuthSessionProviderWrapper from "@/shared/providers/AuthSession"; const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }); @@ -30,7 +30,7 @@ export default function RootLayout({ - {children} + {children} ); diff --git a/features/authCallback/actions/submitProviderCallback.ts b/features/authCallback/actions/submitProviderCallback.ts index f75d849..cc8627e 100644 --- a/features/authCallback/actions/submitProviderCallback.ts +++ b/features/authCallback/actions/submitProviderCallback.ts @@ -1,11 +1,11 @@ "use server"; -import { backendFetch, BackendResponse } from "@/shared/helper/backendFetch"; +import { backendFetch, BackendResponse } from "@/shared/helpers/backendFetch"; import { cookies } from "next/headers"; export const submitProviderCallback = async ( providerName: string, - queries?: unknown + queries?: unknown, ): Promise< BackendResponse<{ authToken: string; @@ -15,7 +15,7 @@ export const submitProviderCallback = async ( const envKey = providerName.toUpperCase() + "_CALLBACK_URL"; const authClientCallbackUrl = (await backendFetch( - "auth/providers/" + providerName + "/callback" + "auth/providers/" + providerName + "/callback", )) as BackendResponse<{ callback_url: string; }>; @@ -26,7 +26,7 @@ export const submitProviderCallback = async ( const responseProvision = (await backendFetch( `${authClientCallbackUrl.data?.callback_url!}?callbackURI=${ process.env.APP_URL - }${process.env[envKey]}&${queries}` + }${process.env[envKey]}&${queries}`, )) as BackendResponse<{ authToken: string; }>; diff --git a/features/authCallback/index.tsx b/features/authCallback/index.tsx index b934d09..5c0e57c 100644 --- a/features/authCallback/index.tsx +++ b/features/authCallback/index.tsx @@ -1,14 +1,14 @@ "use client"; import { Spinner } from "@/shared/libs/shadcn/ui/spinner"; import { submitProviderCallback } from "@/features/authCallback/actions/submitProviderCallback"; -import { useParams, useRouter, useSearchParams } from "next/navigation"; +import { useParams, useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; const AuthCallbackIndex = () => { const { name } = useParams(); const queries = useSearchParams().toString(); const [textDescription, setTextDescription] = useState( - "We are processing your authentication." + "We are processing your authentication.", ); const finishOAuthFlow = (type: string) => { @@ -26,6 +26,7 @@ const AuthCallbackIndex = () => { setTextDescription("Authentication successful! Redirecting..."); finishOAuthFlow("oauth-success"); } else { + console.error("Error in authentication callback:", response); setTextDescription("Authentication failed. Please try again."); finishOAuthFlow("oauth-failed"); } diff --git a/shared/contexts/AuthContext.tsx b/shared/contexts/AuthContext.tsx new file mode 100644 index 0000000..2e360de --- /dev/null +++ b/shared/contexts/AuthContext.tsx @@ -0,0 +1,14 @@ +import { createContext, useContext } from "react"; +import { UserSession } from "../models/auth/validateAndDecodeJWT"; + +type AuthContextType = { + session: UserSession | null; +}; + +export const AuthContext = createContext({ + session: null, +}); + +export function useAuth() { + return useContext(AuthContext); +} diff --git a/shared/helper/backendFetch.ts b/shared/helpers/backendFetch.ts similarity index 100% rename from shared/helper/backendFetch.ts rename to shared/helpers/backendFetch.ts diff --git a/shared/models/auth/validateAndDecodeJWT.ts b/shared/models/auth/validateAndDecodeJWT.ts new file mode 100644 index 0000000..a62fce6 --- /dev/null +++ b/shared/models/auth/validateAndDecodeJWT.ts @@ -0,0 +1,42 @@ +"use server"; + +import { backendFetch, BackendResponse } from "@/shared/helpers/backendFetch"; +import { cookies } from "next/headers"; + +export interface UserSession { + id: string; + isAuthenticated: boolean; + validUntil: Date; + user: { + id: string; + name: string; + email: string; + username: string; + avatar: string; + birthDate: null; + bioProfile: null; + preference: { + id: string; + userId: string; + langPreference: null; + adultFiltering: string; + adultAlert: string; + videoQuality: string; + serviceDefaultId: null; + }; + }; + iat: number; + exp: number; +} + +export const validateAndDecodeJWT = async (): Promise => { + const cookieHeader = (await cookies()).get("auth_token")?.value; + const res = (await backendFetch("auth/token/validate", { + method: "POST", + body: JSON.stringify({ + token: cookieHeader, + }), + })) as BackendResponse; + + return res.data!; +}; diff --git a/shared/providers/AuthSession.client.tsx b/shared/providers/AuthSession.client.tsx new file mode 100644 index 0000000..703f2fa --- /dev/null +++ b/shared/providers/AuthSession.client.tsx @@ -0,0 +1,17 @@ +"use client"; +import { AuthContext } from "../contexts/AuthContext"; +import React from "react"; +import { UserSession } from "../models/auth/validateAndDecodeJWT"; + +const AuthSessionProvider = ({ + children, + session, +}: Readonly<{ children: React.ReactNode; session: UserSession | null }>) => { + return ( + + {children} + + ); +}; + +export default AuthSessionProvider; diff --git a/shared/providers/AuthSession.tsx b/shared/providers/AuthSession.tsx index 66a049b..413d6f8 100644 --- a/shared/providers/AuthSession.tsx +++ b/shared/providers/AuthSession.tsx @@ -1,10 +1,17 @@ -import { cookies } from "next/headers"; -import React from "react"; +import { + UserSession, + validateAndDecodeJWT, +} from "../models/auth/validateAndDecodeJWT"; +import AuthSessionProvider from "./AuthSession.client"; -const AuthSessionProvider = ({children}: readonly<{children: React.ReactNode}>) => { - const cookieHeader = cookies().toString(); - console.log("Cookies in AuthSessionProvider:", cookieHeader); - return {children}; +const AuthSessionProviderWrapper = async ({ + children, +}: Readonly<{ children: React.ReactNode }>) => { + let session: UserSession | null = await validateAndDecodeJWT(); + + return ( + {children} + ); }; -export default AuthSessionProvider; +export default AuthSessionProviderWrapper; diff --git a/shared/widgets/signin/actions/getAllThirdPartyAuth.ts b/shared/widgets/signin/actions/getAllThirdPartyAuth.ts index 065f3ea..6c1b775 100644 --- a/shared/widgets/signin/actions/getAllThirdPartyAuth.ts +++ b/shared/widgets/signin/actions/getAllThirdPartyAuth.ts @@ -1,5 +1,5 @@ "use server"; -import { backendFetch, BackendResponse } from "@/shared/helper/backendFetch"; +import { backendFetch, BackendResponse } from "@/shared/helpers/backendFetch"; export type GetALlThirdPartyAuthCallback = BackendResponse< { diff --git a/shared/widgets/signin/actions/getOauthEndpoint.ts b/shared/widgets/signin/actions/getOauthEndpoint.ts index 913953d..6a3ce36 100644 --- a/shared/widgets/signin/actions/getOauthEndpoint.ts +++ b/shared/widgets/signin/actions/getOauthEndpoint.ts @@ -1,5 +1,5 @@ "use server"; -import { backendFetch, BackendResponse } from "@/shared/helper/backendFetch"; +import { backendFetch, BackendResponse } from "@/shared/helpers/backendFetch"; interface GetOauthEndpointParams { endpointUrl: string; @@ -13,7 +13,7 @@ export const getOauthEndpoint = async ({ const envKey = providerName.toUpperCase() + "_CALLBACK_URL"; return (await backendFetch( - `${endpointUrl}?callback=${process.env.APP_URL}${process.env[envKey]}` + `${endpointUrl}?callback=${process.env.APP_URL}${process.env[envKey]}`, )) as BackendResponse<{ endpointUrl: string; }>;