diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx
index 438a6b9..8544c9a 100644
--- a/src/app/(main)/layout.tsx
+++ b/src/app/(main)/layout.tsx
@@ -8,6 +8,8 @@ import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig";
import Stack from "@mui/material/Stack";
import Breadcrumb from "@/components/Breadcrumb";
import { I18nProvider } from "@/i18n";
+import RefreshTokenProvider from "@/components/RefreshTokenProvider";
+import SessionProvider from "@/components/SessionProvider"
export default async function MainLayout({
children,
@@ -33,13 +35,16 @@ export default async function MainLayout({
padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" },
}}
>
-
-
-
-
- {children}
-
-
+
+
+
+
+
+
+ {children}
+
+
+
>
);
diff --git a/src/app/api/token/actions.ts b/src/app/api/token/actions.ts
new file mode 100644
index 0000000..1ba5eb0
--- /dev/null
+++ b/src/app/api/token/actions.ts
@@ -0,0 +1,24 @@
+"use server";
+
+import { serverFetchJson } from "@/app/utils/fetchUtil";
+import { BASE_API_URL } from "@/config/api";
+import { cache } from "react";
+
+export interface TokenResponse {
+ accessToken: string;
+ refreshToken: string;
+}
+
+export interface TokenRequest {
+ refreshToken: string;
+}
+
+export const fetchTokenDetails = cache(async (data: TokenRequest) => {
+ return serverFetchJson(`${BASE_API_URL}/refresh-token`,
+ {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: { "Content-Type": "application/json" },
+ },
+ );
+});
\ No newline at end of file
diff --git a/src/app/utils/fetchUtil.ts b/src/app/utils/fetchUtil.ts
index b6498de..b5868a0 100644
--- a/src/app/utils/fetchUtil.ts
+++ b/src/app/utils/fetchUtil.ts
@@ -22,6 +22,7 @@ export const serverFetch: typeof fetch = async (input, init) => {
const session = await getServerSession(authOptions);
const accessToken = session?.accessToken;
+ // console.log("Access Token: ", accessToken)
// console.log(accessToken);
return fetch(input, {
...init,
@@ -49,6 +50,8 @@ export async function serverFetchJson(...args: FetchParams) {
switch (response.status) {
case 401:
signOutUser();
+ case 417:
+ signOutUser();
case 422:
throw new ServerFetchError(
JSON.parse(errorText).error,
diff --git a/src/components/AutoLogoutProvider/AutoLogoutProvider.tsx b/src/components/AutoLogoutProvider/AutoLogoutProvider.tsx
index e82244f..e33b028 100644
--- a/src/components/AutoLogoutProvider/AutoLogoutProvider.tsx
+++ b/src/components/AutoLogoutProvider/AutoLogoutProvider.tsx
@@ -3,35 +3,36 @@ import React, { createContext, useState, useEffect, ReactNode } from 'react';
import { useIdleTimer } from "react-idle-timer";
import { signOut } from "next-auth/react";
import { useTranslation } from 'react-i18next';
-
+import { useSession } from "next-auth/react";
+
interface TimerContextProps {
lastRequestTime: number;
setLastRequestTime: React.Dispatch>;
}
-
+
export const TimerContext = createContext(undefined);
-
+
interface AutoLogoutProviderProps {
children?: ReactNode;
- isUserLoggedIn: boolean;
}
-
-const AutoLogoutProvider: React.FC = ({ children, isUserLoggedIn }) => {
+
+const AutoLogoutProvider: React.FC = ({ children }) => {
const { t } = useTranslation("common")
const [lastRequestTime, setLastRequestTime] = useState(Date.now());
const [logoutInterval, setLogoutInterval] = useState(1); // minute
const [state, setState] = useState('Active');
-
+ const { data: session } = useSession();
+
const onIdle = () => {
setLastRequestTime(Date.now());
setState('Idle')
}
-
+
const onActive = () => {
setLastRequestTime(Date.now());
setState('Active')
}
-
+
const {
isLastActiveTab,
} = useIdleTimer({
@@ -42,21 +43,21 @@ const AutoLogoutProvider: React.FC = ({ children, isUse
crossTab: true,
syncTimers: 200,
})
-
+
const lastActiveTab = isLastActiveTab() === null ? 'loading' : isLastActiveTab()
-
+
const getLogoutInterval = () => {
- if (isUserLoggedIn && logoutInterval === 1) {
+ if (session && logoutInterval === 1) {
//TODO: get auto logout time here
setLogoutInterval(60);
}
else {
- if (!isUserLoggedIn && logoutInterval > 1) {
+ if (!session && logoutInterval > 1) {
setLogoutInterval(1);
}
}
}
-
+
useEffect(() => {
getLogoutInterval()
const interval = setInterval(async () => {
@@ -64,6 +65,8 @@ const AutoLogoutProvider: React.FC = ({ children, isUse
// if (isPasswordExpiry()) {
// navigate('/user/changePassword');
// }
+ // console.log(state)
+ // console.log(lastActiveTab)
if (state !== "Active" && lastActiveTab) {
const timeElapsed = currentTime - lastRequestTime;
if (timeElapsed >= logoutInterval * 60 * 1000) {
@@ -75,18 +78,18 @@ const AutoLogoutProvider: React.FC = ({ children, isUse
}
}
}, 1000); // Check every second
-
+
return () => {
clearInterval(interval);
};
}, [lastRequestTime, logoutInterval]);
-
-
+
+
return (
{children}
);
};
-
+
export default AutoLogoutProvider;
\ No newline at end of file
diff --git a/src/components/AutoLogoutProvider/AutoLogoutProviderWrapper.tsx b/src/components/AutoLogoutProvider/AutoLogoutProviderWrapper.tsx
index ffdc593..24c88bf 100644
--- a/src/components/AutoLogoutProvider/AutoLogoutProviderWrapper.tsx
+++ b/src/components/AutoLogoutProvider/AutoLogoutProviderWrapper.tsx
@@ -1,14 +1,8 @@
import React from "react";
import AutoLogoutProvider from "./AutoLogoutProvider";
-import { getServerSession } from "next-auth";
-import { authOptions } from "@/config/authConfig";
const AutoLogoutProviderWrapper: React.FC = async () => {
-
- const session = await getServerSession(authOptions)
- const isUserLoggedIn = session?.user !== null
-
- return ;
+ return ;
};
export default AutoLogoutProviderWrapper;
diff --git a/src/components/RefreshTokenProvider/RefreshTokenProvider.tsx b/src/components/RefreshTokenProvider/RefreshTokenProvider.tsx
new file mode 100644
index 0000000..8e826ca
--- /dev/null
+++ b/src/components/RefreshTokenProvider/RefreshTokenProvider.tsx
@@ -0,0 +1,90 @@
+'use client'
+import { createContext, useEffect, useRef, useCallback, ReactNode } from 'react';
+import { useSession } from "next-auth/react";
+// import { useTranslation } from 'react-i18next';
+import { SessionWithTokens } from '@/config/authConfig';
+import { fetchTokenDetails } from '@/app/api/token/actions';
+
+interface RefreshTokenContextProps {
+
+}
+
+export const RefreshTokenContext = createContext(undefined);
+
+interface RefreshTokenProviderProps {
+ children?: ReactNode;
+}
+
+const RefreshTokenProvider: React.FC = ({ children }) => {
+ const { data, update } = useSession();
+ const session = useRef(data as SessionWithTokens)
+ // const token = useRef(session.current?.accessToken ?? null)
+ const isRefresh = useRef(false)
+
+ const handleRefreshToken = useCallback(async () => {
+ if (!isRefresh.current) {
+ const refreshToken = session.current.refreshToken ?? ""
+
+ isRefresh.current = true
+
+ try {
+ const response = await fetchTokenDetails({ refreshToken: refreshToken })
+
+ if (response) {
+ const newAccessToken = response.accessToken
+ const newRefreshToken = response.refreshToken
+
+ await update({
+ accessToken: newAccessToken,
+ refreshToken: newRefreshToken
+ })
+
+ // console.log("New Access Token: ", newAccessToken)
+ // console.log("%c [ Old Refresh Token ]:", 'font-size:13px; background:pink; color:#bf2c9f;', `${refreshToken}`)
+ // console.log("%c [ New Refresh Token ]:", 'font-size:13px; background:pink; color:#bf2c9f;', `${newRefreshToken}`)
+
+ }
+
+ isRefresh.current = false
+ } catch (refreshError) {
+ console.log('Failed to refresh token');
+ console.log(refreshError);
+ isRefresh.current = false;
+ };
+ }
+ }, [session.current, update])
+
+ const checkTokenExpiry = useCallback(() => {
+ const accessToken = session.current.accessToken
+ if (accessToken) {
+ const tokenExp = JSON.parse(atob(accessToken.split('.')[1])).exp;
+ const currentTime = Math.floor(Date.now() / 1000);
+ const expiryTime = tokenExp - 30; // Refresh 30 seconds before expiry
+
+ // console.log("%c [ Expiry Time ]:", 'font-size:13px; background:pink; color:#bf2c9f;', `${new Date(expiryTime * 1000).toLocaleString()}`)
+
+ if (currentTime >= expiryTime) {
+ if (session.current) {
+ handleRefreshToken();
+ }
+ }
+ }
+ }, [handleRefreshToken, session.current])
+
+ useEffect(() => {
+ const timer = setInterval(checkTokenExpiry, 10000);
+ return () => clearInterval(timer);
+ }, [checkTokenExpiry]);
+
+ useEffect(() => {
+ session.current = data as SessionWithTokens
+ }, [data])
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default RefreshTokenProvider;
\ No newline at end of file
diff --git a/src/components/RefreshTokenProvider/RefreshTokenProviderWrapper.tsx b/src/components/RefreshTokenProvider/RefreshTokenProviderWrapper.tsx
new file mode 100644
index 0000000..174adc9
--- /dev/null
+++ b/src/components/RefreshTokenProvider/RefreshTokenProviderWrapper.tsx
@@ -0,0 +1,9 @@
+import React from "react";
+import RefreshTokenProvider from "./RefreshTokenProvider";
+
+const RefreshTokenProviderWrapper: React.FC = async () => {
+
+ return ;
+};
+
+export default RefreshTokenProviderWrapper;
\ No newline at end of file
diff --git a/src/components/RefreshTokenProvider/index.ts b/src/components/RefreshTokenProvider/index.ts
new file mode 100644
index 0000000..f6759ec
--- /dev/null
+++ b/src/components/RefreshTokenProvider/index.ts
@@ -0,0 +1 @@
+export { default } from "./RefreshTokenProviderWrapper";
\ No newline at end of file
diff --git a/src/components/SessionProvider/SessionProvider.tsx b/src/components/SessionProvider/SessionProvider.tsx
new file mode 100644
index 0000000..54267a7
--- /dev/null
+++ b/src/components/SessionProvider/SessionProvider.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { SessionProvider as default } from "next-auth/react";
\ No newline at end of file
diff --git a/src/components/SessionProvider/index.ts b/src/components/SessionProvider/index.ts
new file mode 100644
index 0000000..c6d78b1
--- /dev/null
+++ b/src/components/SessionProvider/index.ts
@@ -0,0 +1 @@
+export { default } from "./SessionProvider";
diff --git a/src/config/authConfig.ts b/src/config/authConfig.ts
index 2edd89f..5e12b22 100644
--- a/src/config/authConfig.ts
+++ b/src/config/authConfig.ts
@@ -57,12 +57,27 @@ export const authOptions: AuthOptions = {
callbacks: {
jwt(params) {
// Add the data from user to the token
- const { token, user } = params;
- const newToken = { ...token, ...user };
+ const { token, user, account, trigger, session } = params;
+ // console.log("--------------------------")
+ // console.log("%c [ token ]:", 'font-size:13px; background:#A888B5; color:#bf2c9f;', token)
+ // console.log("%c [ user ]:", 'font-size:13px; background:pink; color:#bf2c9f;', user)
+ // console.log("%c [ account ]:", 'font-size:13px; background:pink; color:#bf2c9f;', account)
+ // console.log("%c [ session ]:", 'font-size:13px; background:#FFD2A0; color:#bf2c9f;', session)
+ // console.log("%c [ trigger ]:", 'font-size:13px; background:#EFB6C8; color:#bf2c9f;', trigger)
+ // console.log(params)
+ // console.log("--------------------------")
+
+ if (trigger === "update" && session?.accessToken && session?.refreshToken) {
+ token.accessToken = session.accessToken
+ token.refreshToken = session.refreshToken
+ }
+
+ let newToken = { ...token, ...user };
return newToken;
},
session({ session, token }) {
+ // console.log(token.accessToken as string | undefined)
const sessionWithToken: SessionWithTokens = {
...session,
role: token.role as string,
@@ -77,5 +92,5 @@ export const authOptions: AuthOptions = {
// console.log(sessionWithToken)
return sessionWithToken;
},
- },
+ }
};