From 63a785ba5aea805eac7d0d86b72b5800a272d507 Mon Sep 17 00:00:00 2001 From: yznahmad Date: Mon, 23 Jun 2025 00:01:17 +0300 Subject: [PATCH] Add s s 8854yh499j40440 --- .../(GlobalWrapper)/(Auth)/layout.tsx | 138 ++++++------------ webapp/src/app/api/auth/verify/route.ts | 56 ------- .../src/components/common/ErrorBoundary.tsx | 63 -------- webapp/src/lib/api.ts | 50 ------- webapp/src/lib/auth.ts | 47 ------ webapp/src/redux/features/auth-slice.ts | 95 +++++------- 6 files changed, 78 insertions(+), 371 deletions(-) delete mode 100644 webapp/src/app/api/auth/verify/route.ts delete mode 100644 webapp/src/components/common/ErrorBoundary.tsx delete mode 100644 webapp/src/lib/api.ts delete mode 100644 webapp/src/lib/auth.ts diff --git a/webapp/src/app/[locale]/(GlobalWrapper)/(Auth)/layout.tsx b/webapp/src/app/[locale]/(GlobalWrapper)/(Auth)/layout.tsx index b0839ba..1c342cc 100644 --- a/webapp/src/app/[locale]/(GlobalWrapper)/(Auth)/layout.tsx +++ b/webapp/src/app/[locale]/(GlobalWrapper)/(Auth)/layout.tsx @@ -1,110 +1,62 @@ 'use client'; import { useAppSelector } from "@/redux/store" -import { isLoggedIn, mountCheckIfValid, logOut } from "@/redux/features/auth-slice"; +import { isLoggedIn , mountCheckIfValid } from "@/redux/features/auth-slice"; import { useDispatch } from "react-redux" import { AppDispatch } from '@/redux/store'; -import { useEffect, useCallback } from "react"; +import { useEffect } from "react"; import { useRouter } from 'next/navigation' import { ReactNode } from 'react' import FullScreenLoader from "@/components/common/fullScreenLoader"; import { load } from '@/redux/features/settings-slice' -import ErrorBoundary from '@/components/common/ErrorBoundary'; -import axios from 'axios'; // Added axios import -// HOC for auth pages (only accept auth user) -export default function LocaleLayout({ children }: { children: ReactNode }) { - const router = useRouter(); +// HOC for auth pages ( only accept auth user ) +export default function LocaleLayout({children} : { children : ReactNode }) { + + const router = useRouter() const dispatch = useDispatch(); - - // Load states - const isValid = useAppSelector((state) => state.authReducer.value.isValid); - const checkIfValidMounted = useAppSelector((state) => state.authReducer.value.checkIfValidMounted); - const notAuthRedirectPage = useAppSelector((state) => state.settingsReducer.value.notAuthRedirectPage); - const isLoadingSettings = useAppSelector((state) => state.settingsReducer.value.isLoadingSettings); - const loadedFirstTime = useAppSelector((state) => state.settingsReducer.value.loadedFirstTime); - - // Handle not logged in state - const handleNotLoggedIn = useCallback(() => { - // Clear any invalid token from cookies - document.cookie = 'authToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - // Clear Redux state - dispatch(logOut()); - // Redirect to login page - router.push(notAuthRedirectPage); - }, [notAuthRedirectPage, router, dispatch]); - - // Initialize auth check + // load states + const isValid = useAppSelector((state) => state.authReducer.value.isValid) + const checkIfValidMounted = useAppSelector((state) => state.authReducer.value.checkIfValidMounted) + const notAuthRedirectPage = useAppSelector((state) => state.settingsReducer.value.notAuthRedirectPage) + const isLoadingSettings = useAppSelector((state) => state.settingsReducer.value.isLoadingSettings) + const loadedFirstTime = useAppSelector((state) => state.settingsReducer.value.loadedFirstTime) + // Get redux states + // init isLoggedIn useEffect(() => { - if (checkIfValidMounted) return; - - const checkAuth = async () => { - try { - await dispatch(mountCheckIfValid()); - await dispatch(isLoggedIn({ NotLoggedInCallback: handleNotLoggedIn })); - } catch (error) { - console.error('Auth check failed:', error); - handleNotLoggedIn(); - } - }; - - checkAuth(); - }, [checkIfValidMounted, dispatch, handleNotLoggedIn]); - - // Load settings + if(checkIfValidMounted) return + dispatch(mountCheckIfValid()) + async function a() + { + await dispatch(isLoggedIn({NotLoggedInCallback})) + } + a() + }, []) + // load settings useEffect(() => { - if (loadedFirstTime || isLoadingSettings) return; - - const loadSettings = async () => { - try { - await dispatch(load({ page: 1 })); - } catch (error) { - console.error('Failed to load settings:', error); - } - }; - - loadSettings(); - }, [loadedFirstTime, isLoadingSettings, dispatch]); - - // Add global error handler for API requests - useEffect(() => { - const responseInterceptor = (response: any) => response; - - const errorInterceptor = (error: any) => { - if (error.response?.status === 401) { - // Handle unauthorized (token expired/invalid) - handleNotLoggedIn(); - } - return Promise.reject(error); - }; - - // Add request interceptor - const requestInterceptor = axios.interceptors.request.use( - config => config, - error => Promise.reject(error) - ); - - // Add response interceptor - const responseIntercept = axios.interceptors.response.use( - responseInterceptor, - errorInterceptor - ); - - // Cleanup function - return () => { - axios.interceptors.request.eject(requestInterceptor); - axios.interceptors.response.eject(responseIntercept); - }; - }, [handleNotLoggedIn]); - - // Show loading state while checking auth - if (isValid === null) { - return ; + if(loadedFirstTime) return + if(isLoadingSettings) return + async function a() + { + await dispatch(load({page : 1})) + } + a() + }, []) + // if wasnt logged in this will fire + function NotLoggedInCallback() + { + return router.push(notAuthRedirectPage) } - return ( - - {isValid ? children : } - + <> + { + isValid ? + <> + {children} + + : + + } + ); } diff --git a/webapp/src/app/api/auth/verify/route.ts b/webapp/src/app/api/auth/verify/route.ts deleted file mode 100644 index d8e3129..0000000 --- a/webapp/src/app/api/auth/verify/route.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { NextResponse } from 'next/server'; -import { cookies } from 'next/headers'; -import jwt from 'jsonwebtoken'; -import { dbConnect } from '@/lib/dbConnect'; -import User from '@/database/models/userModel'; - -export async function GET() { - try { - await dbConnect(); - - // Get the token from cookies - const cookieStore = cookies(); - const token = cookieStore.get('authToken')?.value; - - if (!token) { - return NextResponse.json( - { valid: false, message: 'No token provided' }, - { status: 401 } - ); - } - - try { - // Verify the token - const decoded = jwt.verify(token, process.env.JWT_SECRET!); - - // Check if user still exists - const user = await User.findById(decoded.userId).select('-password'); - if (!user) { - return NextResponse.json( - { valid: false, message: 'User not found' }, - { status: 401 } - ); - } - - return NextResponse.json({ valid: true }); - } catch (error) { - if (error instanceof jwt.TokenExpiredError) { - return NextResponse.json( - { valid: false, message: 'Token expired' }, - { status: 401 } - ); - } - - return NextResponse.json( - { valid: false, message: 'Invalid token' }, - { status: 401 } - ); - } - } catch (error) { - console.error('Token verification error:', error); - return NextResponse.json( - { valid: false, message: 'Server error' }, - { status: 500 } - ); - } -} diff --git a/webapp/src/components/common/ErrorBoundary.tsx b/webapp/src/components/common/ErrorBoundary.tsx deleted file mode 100644 index 4360be0..0000000 --- a/webapp/src/components/common/ErrorBoundary.tsx +++ /dev/null @@ -1,63 +0,0 @@ -'use client'; - -import { Component, ErrorInfo, ReactNode } from 'react'; -import { useRouter } from 'next/navigation'; - -interface Props { - children: ReactNode; - fallback?: ReactNode; -} - -interface State { - hasError: boolean; - error?: Error; -} - -export class ErrorBoundary extends Component { - public state: State = { - hasError: false - }; - - public static getDerivedStateFromError(error: Error): State { - return { hasError: true, error }; - } - - public componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error('Uncaught error:', error, errorInfo); - } - - private handleReset = () => { - // Clear auth token and reload - document.cookie = 'authToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - window.location.href = '/'; - }; - - public render() { - if (this.state.hasError) { - return ( -
-
-

- {this.state.error?.message === 'Session expired' ? 'Session Expired' : 'Something went wrong'} -

-

- {this.state.error?.message === 'Session expired' - ? 'Your session has expired. Please log in again.' - : 'An unexpected error occurred. Please try again.'} -

- -
-
- ); - } - - return this.props.children; - } -} - -export default ErrorBoundary; diff --git a/webapp/src/lib/api.ts b/webapp/src/lib/api.ts deleted file mode 100644 index afe7ffc..0000000 --- a/webapp/src/lib/api.ts +++ /dev/null @@ -1,50 +0,0 @@ -import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; -import { store } from '@/redux/store'; -import { logOut } from '@/redux/features/auth-slice'; - -const api: AxiosInstance = axios.create({ - baseURL: '/api', - headers: { - 'Content-Type': 'application/json', - }, -}); - -// Request interceptor to add auth token -api.interceptors.request.use( - (config) => { - const token = store.getState().authReducer.value.authToken; - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; - }, - (error) => { - return Promise.reject(error); - } -); - -// Response interceptor to handle 401 errors -api.interceptors.response.use( - (response: AxiosResponse) => response, - async (error: AxiosError) => { - const originalRequest = error.config as any; - - // If the error is 401 and we haven't tried to refresh yet - if (error.response?.status === 401 && !originalRequest._retry) { - originalRequest._retry = true; - - // Clear any existing auth state - store.dispatch(logOut()); - document.cookie = 'authToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - - // Redirect to login page - if (typeof window !== 'undefined') { - window.location.href = '/login'; - } - } - - return Promise.reject(error); - } -); - -export default api; diff --git a/webapp/src/lib/auth.ts b/webapp/src/lib/auth.ts deleted file mode 100644 index 2ae9dba..0000000 --- a/webapp/src/lib/auth.ts +++ /dev/null @@ -1,47 +0,0 @@ -import jwt from 'jsonwebtoken'; -import { cookies } from 'next/headers'; - -export async function verifyToken(token?: string): Promise { - if (!token) return false; - - try { - // Verify the token - const decoded = jwt.verify(token, process.env.JWT_SECRET!); - return !!decoded; - } catch (error) { - if (error instanceof jwt.TokenExpiredError) { - console.log('Token expired'); - } else if (error instanceof jwt.JsonWebTokenError) { - console.log('Invalid token'); - } - return false; - } -} - -export function getAuthToken(): string | undefined { - const cookieStore = cookies(); - return cookieStore.get('authToken')?.value; -} - -export function clearAuthToken() { - document.cookie = 'authToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; -} - -export function setAuthToken(token: string) { - const expires = new Date(); - expires.setTime(expires.getTime() + 3 * 60 * 60 * 1000); // 3 hours - - document.cookie = `authToken=${token}; Path=/; Expires=${expires.toUTCString()}; HttpOnly; SameSite=Lax`; -} - -// Helper to get user ID from token -export function getUserIdFromToken(token?: string): string | null { - if (!token) return null; - - try { - const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { userId: string }; - return decoded.userId; - } catch (error) { - return null; - } -} diff --git a/webapp/src/redux/features/auth-slice.ts b/webapp/src/redux/features/auth-slice.ts index 54fc484..4e6269a 100644 --- a/webapp/src/redux/features/auth-slice.ts +++ b/webapp/src/redux/features/auth-slice.ts @@ -5,7 +5,7 @@ * * https://redux-toolkit.js.org/api/createAsyncThunk */ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' +import { createAsyncThunk , createSlice } from '@reduxjs/toolkit' import axios from 'axios' import Cookies from 'universal-cookie'; import { fireAlert } from './alert-slice'; @@ -33,10 +33,11 @@ const initialState = { const logIn = createAsyncThunk( 'auth/logInStatus', - async (actionPayload: { username: string, password: string, successCallback: any }, thunkAPI) => { + async (actionPayload : {username: string , password: string , successCallback : any}, thunkAPI) => { try { - let { data } = await axios.post('/api/auth', actionPayload) - if (data.success) { + let { data } = await axios.post('/api/auth' , actionPayload) + if(data.success) + { actionPayload.successCallback() // fire the success alert thunkAPI.dispatch(fireAlert({ @@ -45,78 +46,47 @@ const logIn = createAsyncThunk( })) return data } - else { + else + { thunkAPI.dispatch(fireAlert({ success: false, message: data.message })) - return { success: false } + return { success : false } } - } catch (err) { + }catch(err) { thunkAPI.dispatch(fireAlert({ success: false, message: "unkownError", })) - return { success: false } + return { success : false } } } ) const isLoggedIn = createAsyncThunk( 'auth/isLoggedInStatus', - async (actionPayload: { LoggedInCallback?: () => void; NotLoggedInCallback?: () => void }, { dispatch, getState }) => { + async (actionPayload : {LoggedInCallback? : any , NotLoggedInCallback? : any}, thunkAPI) => { try { - const state = getState() as RootState; - const { authToken } = state.authReducer.value; - - // If no token, immediately trigger not logged in - if (!authToken) { - if (actionPayload.NotLoggedInCallback) { - actionPayload.NotLoggedInCallback(); - } - return { isValid: false }; + const state : any = thunkAPI.getState() + let { data } = await axios.get('/api/auth?authToken='+state.authReducer.value.authToken) + if(!data.success && actionPayload.NotLoggedInCallback) + { + actionPayload.NotLoggedInCallback() } - - try { - const { data } = await axios.get('/api/auth?authToken=' + authToken); - - if (!data.success) { - // If token is invalid or expired, clear it - if (data.message === 'expiredToken' || data.message === 'invalidToken') { - dispatch(logOut()); - document.cookie = 'authToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - } - - if (actionPayload.NotLoggedInCallback) { - actionPayload.NotLoggedInCallback(); - } - } else if (actionPayload.LoggedInCallback) { - actionPayload.LoggedInCallback(); - } - - return { isValid: data.success }; - } catch (error: any) { - // Handle network errors or server issues - console.error('Token validation error:', error); - - // If it's an auth-related error, clear the token - if (error.response?.status === 401 || error.response?.data?.message === 'expiredToken') { - dispatch(logOut()); - document.cookie = 'authToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - } - - if (actionPayload.NotLoggedInCallback) { - actionPayload.NotLoggedInCallback(); - } - - return { isValid: false }; + else if(actionPayload.LoggedInCallback && data.success) + { + actionPayload.LoggedInCallback() } - } catch (error) { - console.error('Error in isLoggedIn:', error); - if (actionPayload.NotLoggedInCallback) { - actionPayload.NotLoggedInCallback(); + return { + isValid : data.success + } + }catch(e : any) + { + actionPayload.NotLoggedInCallback() + return { + isValid : false } - return { isValid: false }; } } ) @@ -138,15 +108,16 @@ export const auth = createSlice({ }, extraReducers: (builder) => { // logIn thunk reducer - builder.addCase(logIn.fulfilled, (state: IinitialState, action) => { + builder.addCase(logIn.fulfilled, (state : IinitialState , action) => { // set the state - if (action.payload.success) { + if(action.payload.success) + { state.value.authToken = action.payload.authToken; state.value.isValid = true } }) // check if user authToken cookie is a valid one - builder.addCase(isLoggedIn.fulfilled, (state: IinitialState, action) => { + builder.addCase(isLoggedIn.fulfilled, (state : IinitialState , action) => { // set the state state.value.isValid = action.payload.isValid; state.value.checkIfValidMounted = true @@ -155,6 +126,6 @@ export const auth = createSlice({ }) // export the functions -export const { logOut, mountCheckIfValid } = auth.actions; -export { logIn, isLoggedIn }; +export const { logOut , mountCheckIfValid } = auth.actions; +export { logIn , isLoggedIn }; export default auth.reducer; \ No newline at end of file