From e8704557d2c08954a06bbbe3605fcc06348dc215 Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Wed, 7 Aug 2024 00:20:11 -0400 Subject: [PATCH 1/9] feat(#381): Implement User Role Access --- api/serverConfig.ts | 1 + app/(admin)/admin/page.tsx | 23 ++++++++++++++++++++++- context/AuthContextProvider.tsx | 13 +++++++++++++ lib/adminRoutes.ts | 9 +++++++++ utils/utils.ts | 10 ++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 lib/adminRoutes.ts diff --git a/api/serverConfig.ts b/api/serverConfig.ts index 4e90354e..849d0587 100644 --- a/api/serverConfig.ts +++ b/api/serverConfig.ts @@ -13,3 +13,4 @@ const client = new sdk.Client() .setKey(API_KEY); export const users = new sdk.Users(client); +export const account = new sdk.Account(client); diff --git a/app/(admin)/admin/page.tsx b/app/(admin)/admin/page.tsx index 8b7420df..f2f0d4d1 100644 --- a/app/(admin)/admin/page.tsx +++ b/app/(admin)/admin/page.tsx @@ -1,13 +1,34 @@ // Copyright (c) Gridiron Survivor. // Licensed under the MIT License. -import { JSX } from 'react'; +'use client'; + +import { JSX, useEffect } from 'react'; +import { isUserAdmin } from '@/utils/utils'; +import { useRouter } from 'next/navigation'; /** * Renders the admin page. * @returns {JSX.Element} - The rendered login page. */ const AdminHome = (): JSX.Element => { + const router = useRouter(); + + useEffect(() => { + isAdmin(); + }, []); + + /** + * Check if the user is an admin + * @returns {Promise} + */ + const isAdmin = async (): Promise => { + const isAdmin = await isUserAdmin(); + if (!isAdmin) { + router.push('/league/all'); + } + }; + return
Admin Home
; }; diff --git a/context/AuthContextProvider.tsx b/context/AuthContextProvider.tsx index 78b4ee7d..02a0dff5 100644 --- a/context/AuthContextProvider.tsx +++ b/context/AuthContextProvider.tsx @@ -12,6 +12,7 @@ import { IUser } from '@/api/apiFunctions.interface'; import { getCurrentUser } from '@/api/apiFunctions'; import { loginAccount } from './AuthHelper'; import { usePathname } from 'next/navigation'; +import { adminRoutes } from '@/lib/adminRoutes'; type UserCredentials = { email: string; @@ -40,6 +41,7 @@ export const AuthContextProvider = ({ children: React.ReactNode; }): JSX.Element => { const [isSignedIn, setIsSignedIn] = useState(false); + const [isSuperAdmin, setIsSuperAdmin] = useState(false); const { updateUser, resetUser, user } = useDataStore( (state) => state, ); @@ -54,6 +56,14 @@ export const AuthContextProvider = ({ setIsSignedIn(true); }, [user]); + useMemo(() => { + if (isSignedIn) { + if (adminRoutes.includes(pathname)) { + !isSuperAdmin && router.push('/league/all'); + } + } + }, [pathname]); + /** * Authenticate and set session state * @param user - The user credentials. @@ -97,6 +107,9 @@ export const AuthContextProvider = ({ try { const user = await account.get(); + if (user.labels.includes('admin')) { + setIsSuperAdmin(true); + } const userData: IUser = await getCurrentUser(user.$id); updateUser(userData.id, userData.email, userData.leagues); return userData; diff --git a/lib/adminRoutes.ts b/lib/adminRoutes.ts new file mode 100644 index 00000000..6188769e --- /dev/null +++ b/lib/adminRoutes.ts @@ -0,0 +1,9 @@ +// Copyright (c) Gridiron Survivor. +// Licensed under the MIT License. + +export const adminRoutes: string[] = [ + '/admin', + '/admin/leagues', + '/admin/players', + '/admin/notifications', +]; diff --git a/utils/utils.ts b/utils/utils.ts index 3110957b..f98a268b 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -15,6 +15,7 @@ import { } from './utils.interface'; import { ILeague } from '@/api/apiFunctions.interface'; import { IEntry } from '@/app/(main)/league/[leagueId]/entry/Entries.interface'; +import { account } from '@/api/config'; /** * Combine class names @@ -144,3 +145,12 @@ export const getUserLeagues = async ( export const getUserEntries = async (userId: IUser['id'], leagueId: ILeague['leagueId']): Promise => { return await getCurrentUserEntries(userId, leagueId); } + +/** + * Check if the user is an admin + * @returns {boolean} - Whether the user is an admin or not based on their labels + */ +export const isUserAdmin = async (): Promise => { + const user = await account.get(); + return user.labels.includes('admin'); +} From 3e5b4b00c5df4048483dd73ffb0d91680035b58c Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Wed, 7 Aug 2024 00:24:35 -0400 Subject: [PATCH 2/9] fix: restored changed files from develop that did not need to be changed --- api/serverConfig.ts | 1 - app/(admin)/admin/page.tsx | 23 +---------------------- utils/utils.ts | 10 ---------- 3 files changed, 1 insertion(+), 33 deletions(-) diff --git a/api/serverConfig.ts b/api/serverConfig.ts index 849d0587..4e90354e 100644 --- a/api/serverConfig.ts +++ b/api/serverConfig.ts @@ -13,4 +13,3 @@ const client = new sdk.Client() .setKey(API_KEY); export const users = new sdk.Users(client); -export const account = new sdk.Account(client); diff --git a/app/(admin)/admin/page.tsx b/app/(admin)/admin/page.tsx index f2f0d4d1..8b7420df 100644 --- a/app/(admin)/admin/page.tsx +++ b/app/(admin)/admin/page.tsx @@ -1,34 +1,13 @@ // Copyright (c) Gridiron Survivor. // Licensed under the MIT License. -'use client'; - -import { JSX, useEffect } from 'react'; -import { isUserAdmin } from '@/utils/utils'; -import { useRouter } from 'next/navigation'; +import { JSX } from 'react'; /** * Renders the admin page. * @returns {JSX.Element} - The rendered login page. */ const AdminHome = (): JSX.Element => { - const router = useRouter(); - - useEffect(() => { - isAdmin(); - }, []); - - /** - * Check if the user is an admin - * @returns {Promise} - */ - const isAdmin = async (): Promise => { - const isAdmin = await isUserAdmin(); - if (!isAdmin) { - router.push('/league/all'); - } - }; - return
Admin Home
; }; diff --git a/utils/utils.ts b/utils/utils.ts index f98a268b..3110957b 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -15,7 +15,6 @@ import { } from './utils.interface'; import { ILeague } from '@/api/apiFunctions.interface'; import { IEntry } from '@/app/(main)/league/[leagueId]/entry/Entries.interface'; -import { account } from '@/api/config'; /** * Combine class names @@ -145,12 +144,3 @@ export const getUserLeagues = async ( export const getUserEntries = async (userId: IUser['id'], leagueId: ILeague['leagueId']): Promise => { return await getCurrentUserEntries(userId, leagueId); } - -/** - * Check if the user is an admin - * @returns {boolean} - Whether the user is an admin or not based on their labels - */ -export const isUserAdmin = async (): Promise => { - const user = await account.get(); - return user.labels.includes('admin'); -} From 7bbd79a185352a2be910cfc8b842e97affb0aa6d Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Sat, 17 Aug 2024 12:35:36 -0400 Subject: [PATCH 3/9] fix: wildcard route check for /admin routes --- api/apiFunctions.interface.ts | 1 + context/AuthContextProvider.tsx | 15 ++++++++------- lib/adminRoutes.ts | 9 --------- store/dataStore.test.ts | 7 +++++++ store/dataStore.ts | 5 ++++- utils/utils.ts | 9 +++++++++ 6 files changed, 29 insertions(+), 17 deletions(-) delete mode 100644 lib/adminRoutes.ts diff --git a/api/apiFunctions.interface.ts b/api/apiFunctions.interface.ts index 7d54c407..fa09f434 100644 --- a/api/apiFunctions.interface.ts +++ b/api/apiFunctions.interface.ts @@ -11,6 +11,7 @@ export interface IUser { id: string; email: string; leagues: string[]; + labels: string[]; } export interface IUserPick { [userId: string]: { diff --git a/context/AuthContextProvider.tsx b/context/AuthContextProvider.tsx index 13237b30..19b3e7ed 100644 --- a/context/AuthContextProvider.tsx +++ b/context/AuthContextProvider.tsx @@ -12,7 +12,6 @@ import { IUser } from '@/api/apiFunctions.interface'; import { getCurrentUser } from '@/api/apiFunctions'; import { loginAccount, logoutHandler } from './AuthHelper'; import { usePathname } from 'next/navigation'; -import { adminRoutes } from '@/lib/adminRoutes'; type UserCredentials = { email: string; @@ -40,7 +39,6 @@ export const AuthContextProvider = ({ children: React.ReactNode; }): JSX.Element => { const [isSignedIn, setIsSignedIn] = useState(false); - const [isSuperAdmin, setIsSuperAdmin] = useState(false); const { updateUser, resetUser, user } = useDataStore( (state) => state, ); @@ -56,10 +54,8 @@ export const AuthContextProvider = ({ }, [user]); useMemo(() => { - if (isSignedIn) { - if (adminRoutes.includes(pathname)) { - !isSuperAdmin && router.push('/league/all'); - } + if (pathname.startsWith('/admin')) { + !user.labels.includes('admin') && router.push('/league/all'); } }, [pathname]); @@ -103,7 +99,12 @@ export const AuthContextProvider = ({ setIsSuperAdmin(true); } const userData: IUser = await getCurrentUser(user.$id); - updateUser(userData.id, userData.email, userData.leagues); + updateUser( + userData.id, + userData.email, + userData.leagues, + userData.labels, + ); return userData; } catch (error) { resetUser(); diff --git a/lib/adminRoutes.ts b/lib/adminRoutes.ts deleted file mode 100644 index 6188769e..00000000 --- a/lib/adminRoutes.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Gridiron Survivor. -// Licensed under the MIT License. - -export const adminRoutes: string[] = [ - '/admin', - '/admin/leagues', - '/admin/players', - '/admin/notifications', -]; diff --git a/store/dataStore.test.ts b/store/dataStore.test.ts index 364f336e..33103bd5 100644 --- a/store/dataStore.test.ts +++ b/store/dataStore.test.ts @@ -6,6 +6,7 @@ const userData = { userId: '123', userEmail: 'test@email.com', leagues: ['123456'], + labels: ['admin'], }; const NFLTeam = [ @@ -50,11 +51,14 @@ describe('Data Store', () => { userData.userId, userData.userEmail, userData.leagues, + userData.labels, ); }); expect(result.current.user.id).toBe(userData.userId); expect(result.current.user.email).toBe(userData.userEmail); + expect(result.current.user.labels).toStrictEqual(userData.labels); + expect(result.current.user.leagues).toStrictEqual(userData.leagues); }); it('Checks the reset user state matches default', () => { const { result } = renderHook(() => useDataStore()); @@ -64,12 +68,15 @@ describe('Data Store', () => { userData.userId, userData.userEmail, userData.leagues, + userData.labels, ); result.current.resetUser(); }); expect(result.current.user.id).toBe(''); expect(result.current.user.email).toBe(''); + expect(result.current.user.labels).toStrictEqual([]); + expect(result.current.user.leagues).toStrictEqual([]); }); }); diff --git a/store/dataStore.ts b/store/dataStore.ts index 9832e89b..88239b77 100644 --- a/store/dataStore.ts +++ b/store/dataStore.ts @@ -30,6 +30,7 @@ interface IDataStoreAction { id: IUser['id'], email: IUser['email'], leagues: IUser['leagues'], + labels: IUser['labels'], ) => void; updateWeeklyPicks: ({ leagueId, @@ -56,6 +57,7 @@ const initialState: IDataStoreState = { id: '', email: '', leagues: [], + labels: [], }, weeklyPicks: { leagueId: '', @@ -101,12 +103,13 @@ export const useDataStore = create((set) => ({ * @param leagues - The user league * @returns {void} */ - updateUser: (id, email, leagues): void => + updateUser: (id, email, leagues, labels): void => set( produce((state: IDataStoreState) => { state.user.id = id; state.user.email = email; state.user.leagues = [...leagues]; + state.user.labels = [...labels]; }), ), /** diff --git a/utils/utils.ts b/utils/utils.ts index 3110957b..f59eba22 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -144,3 +144,12 @@ export const getUserLeagues = async ( export const getUserEntries = async (userId: IUser['id'], leagueId: ILeague['leagueId']): Promise => { return await getCurrentUserEntries(userId, leagueId); } + +/** + * Check if the route is an /admin route + * @param path - The path to check + * @returns {boolean} - Whether the route is an /admin route + */ +export const isAdminRoute = (path: string): boolean => { + return path.startsWith('/admin'); +}; From b9f911489ee252bca1bfcb962ab02bb4cafac1bc Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Sat, 17 Aug 2024 12:41:19 -0400 Subject: [PATCH 4/9] fix: unused state --- context/AuthContextProvider.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/context/AuthContextProvider.tsx b/context/AuthContextProvider.tsx index 19b3e7ed..f7e714de 100644 --- a/context/AuthContextProvider.tsx +++ b/context/AuthContextProvider.tsx @@ -95,9 +95,6 @@ export const AuthContextProvider = ({ try { const user = await account.get(); - if (user.labels.includes('admin')) { - setIsSuperAdmin(true); - } const userData: IUser = await getCurrentUser(user.$id); updateUser( userData.id, From cc406d447bc39fcb2f3fe0b00e766e929b29d096 Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Sat, 17 Aug 2024 12:44:39 -0400 Subject: [PATCH 5/9] fix: api call not pulling in user labels --- api/apiFunctions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/apiFunctions.ts b/api/apiFunctions.ts index 6f5cab20..26e50b85 100644 --- a/api/apiFunctions.ts +++ b/api/apiFunctions.ts @@ -85,6 +85,7 @@ export async function getCurrentUser(userId: IUser['id']): Promise { id: user.documents[0].userId, email: user.documents[0].email, leagues: user.documents[0].leagues, + labels: user.documents[0].labels, }; } catch (error) { console.error(error); From 3cf0d8b231e2e931275497efbed7479d05ae1805 Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Sat, 17 Aug 2024 13:02:34 -0400 Subject: [PATCH 6/9] fix: admin route waiting for user to load to check for labels --- context/AuthContextProvider.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/context/AuthContextProvider.tsx b/context/AuthContextProvider.tsx index f7e714de..e6d4fdbb 100644 --- a/context/AuthContextProvider.tsx +++ b/context/AuthContextProvider.tsx @@ -50,14 +50,13 @@ export const AuthContextProvider = ({ getUser(); return; } + setIsSignedIn(true); - }, [user]); - useMemo(() => { if (pathname.startsWith('/admin')) { !user.labels.includes('admin') && router.push('/league/all'); } - }, [pathname]); + }, [user, pathname]); /** * Authenticate and set session state @@ -96,12 +95,7 @@ export const AuthContextProvider = ({ try { const user = await account.get(); const userData: IUser = await getCurrentUser(user.$id); - updateUser( - userData.id, - userData.email, - userData.leagues, - userData.labels, - ); + updateUser(userData.id, userData.email, userData.leagues, user.labels); return userData; } catch (error) { resetUser(); From deeb21d82382fbd5fddf4cf33ec389a589077452 Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Sat, 17 Aug 2024 13:21:31 -0400 Subject: [PATCH 7/9] fix: types with different user types (Appwrite Auth and Custom Collection) --- api/apiFunctions.interface.ts | 8 ++++++++ api/apiFunctions.ts | 6 ++++-- context/AuthContextProvider.tsx | 8 +++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/api/apiFunctions.interface.ts b/api/apiFunctions.interface.ts index fa09f434..f36c48d8 100644 --- a/api/apiFunctions.interface.ts +++ b/api/apiFunctions.interface.ts @@ -8,11 +8,19 @@ export interface IAccountData { password: string; } export interface IUser { + // for the appwrite auth collection id: string; email: string; leagues: string[]; labels: string[]; } + +export interface ICollectionuser { + // for the custom user collection + id: string; + email: string; + leagues: string[]; +} export interface IUserPick { [userId: string]: { [entryId: IEntry['$id']]: { diff --git a/api/apiFunctions.ts b/api/apiFunctions.ts index 26e50b85..3d0cd5e0 100644 --- a/api/apiFunctions.ts +++ b/api/apiFunctions.ts @@ -8,6 +8,7 @@ import { ILeague, IGameWeek, IUser, + ICollectionuser, IWeeklyPicks, INFLTeam, } from './apiFunctions.interface'; @@ -74,7 +75,9 @@ export async function logoutAccount(): Promise { * @param userId - The user ID * @returns {Models.DocumentList | Error} - The user object or an error */ -export async function getCurrentUser(userId: IUser['id']): Promise { +export async function getCurrentUser( + userId: IUser['id'], +): Promise { try { const user = await databases.listDocuments( appwriteConfig.databaseId, @@ -85,7 +88,6 @@ export async function getCurrentUser(userId: IUser['id']): Promise { id: user.documents[0].userId, email: user.documents[0].email, leagues: user.documents[0].leagues, - labels: user.documents[0].labels, }; } catch (error) { console.error(error); diff --git a/context/AuthContextProvider.tsx b/context/AuthContextProvider.tsx index e6d4fdbb..27f4932b 100644 --- a/context/AuthContextProvider.tsx +++ b/context/AuthContextProvider.tsx @@ -8,7 +8,7 @@ import { account } from '@/api/config'; import { useRouter } from 'next/navigation'; import { useDataStore } from '@/store/dataStore'; import type { DataStore } from '@/store/dataStore'; -import { IUser } from '@/api/apiFunctions.interface'; +import { ICollectionuser, IUser } from '@/api/apiFunctions.interface'; import { getCurrentUser } from '@/api/apiFunctions'; import { loginAccount, logoutHandler } from './AuthHelper'; import { usePathname } from 'next/navigation'; @@ -52,9 +52,8 @@ export const AuthContextProvider = ({ } setIsSignedIn(true); - if (pathname.startsWith('/admin')) { - !user.labels.includes('admin') && router.push('/league/all'); + !user.labels.includes('admin') && router.push('/'); } }, [user, pathname]); @@ -94,9 +93,8 @@ export const AuthContextProvider = ({ try { const user = await account.get(); - const userData: IUser = await getCurrentUser(user.$id); + const userData: ICollectionuser = await getCurrentUser(user.$id); updateUser(userData.id, userData.email, userData.leagues, user.labels); - return userData; } catch (error) { resetUser(); setIsSignedIn(false); From a0c18ad5e0a25887ad90aa1a9e6eb99c87bd890d Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Thu, 17 Oct 2024 00:09:18 -0400 Subject: [PATCH 8/9] add helper function for isAuthRoute --- api/apiFunctions.interface.ts | 3 +- api/apiFunctions.ts | 4 +-- app/(main)/league/all/page.test.tsx | 7 ++++ app/(main)/league/all/page.tsx | 11 ++++--- .../UpdateEmailForm/UpdateEmailForm.tsx | 2 +- context/AuthContextProvider.tsx | 32 ++++++++++++------- pnpm-lock.yaml | 4 +-- utils/utils.test.ts | 28 ++++++++++++++++ utils/utils.ts | 10 ++++++ 9 files changed, 79 insertions(+), 22 deletions(-) diff --git a/api/apiFunctions.interface.ts b/api/apiFunctions.interface.ts index 4aafb977..e5d1f6fb 100644 --- a/api/apiFunctions.interface.ts +++ b/api/apiFunctions.interface.ts @@ -16,7 +16,8 @@ export interface IUser { labels: string[]; } -export interface ICollectionuser { +export interface ICollectionUser { + documentId: string; // for the custom user collection id: string; email: string; diff --git a/api/apiFunctions.ts b/api/apiFunctions.ts index a1c3ed92..2f3b072d 100644 --- a/api/apiFunctions.ts +++ b/api/apiFunctions.ts @@ -8,7 +8,7 @@ import { ILeague, IGameWeek, IUser, - ICollectionuser, + ICollectionUser, IWeeklyPicks, INFLTeam, IRecoveryToken, @@ -152,7 +152,7 @@ export async function updateUserEmail({ */ export async function getCurrentUser( userId: IUser['id'], -): Promise { +): Promise { try { const user = await databases.listDocuments( appwriteConfig.databaseId, diff --git a/app/(main)/league/all/page.test.tsx b/app/(main)/league/all/page.test.tsx index b1a783c8..8625ee47 100644 --- a/app/(main)/league/all/page.test.tsx +++ b/app/(main)/league/all/page.test.tsx @@ -32,6 +32,7 @@ jest.mock('@/store/dataStore', () => ({ id: '1234', email: 'test@test.com', leagues: ['league1'], + labels: [], }, allLeagues: [ { @@ -82,6 +83,7 @@ describe('Leagues Component', () => { leagues: [], }, allLeagues: [], + labels: [], }); render(); @@ -105,6 +107,7 @@ describe('Leagues Component', () => { leagues: [], }, allLeagues: [], + labels: [], }); render(); @@ -121,6 +124,7 @@ describe('Leagues Component', () => { email: 'test@test.com', id: '123', leagues: [], + labels: [], }, allLeagues: [ { @@ -150,6 +154,7 @@ describe('Leagues Component', () => { email: 'test@test.com', id: '123', leagues: [], + labels: [], }; const league = { @@ -202,6 +207,7 @@ describe('Leagues Component', () => { user.id, user.email, [...user.leagues, league.leagueId], + user.labels, ); expect(toast.custom).toHaveBeenCalledWith( { email: 'test@test.com', id: '123', leagues: [], + labels: [], }; const league = { diff --git a/app/(main)/league/all/page.tsx b/app/(main)/league/all/page.tsx index 56c04323..8e8a77de 100644 --- a/app/(main)/league/all/page.tsx +++ b/app/(main)/league/all/page.tsx @@ -100,10 +100,13 @@ const Leagues = (): JSX.Element => { }); setLeagues([...leagues, league]); - updateUser(user.documentId, user.id, user.email, [ - ...user.leagues, - league.leagueId, - ]); + updateUser( + user.documentId, + user.id, + user.email, + [...user.leagues, league.leagueId], + user.labels, + ); toast.custom( { />, ); - updateUser(user.documentId, user.id, email, user.leagues); + updateUser(user.documentId, user.id, email, user.leagues, user.labels); form.reset({ email: email || '', password: '' }); } catch (error) { console.error('Email Update Failed', error); diff --git a/context/AuthContextProvider.tsx b/context/AuthContextProvider.tsx index 8d428da6..eb1adb9b 100644 --- a/context/AuthContextProvider.tsx +++ b/context/AuthContextProvider.tsx @@ -8,10 +8,11 @@ import { account } from '@/api/config'; import { useRouter } from 'next/navigation'; import { useDataStore } from '@/store/dataStore'; import type { DataStore } from '@/store/dataStore'; -import { IUser } from '@/api/apiFunctions.interface'; +import { ICollectionUser, IUser } from '@/api/apiFunctions.interface'; import { getCurrentUser } from '@/api/apiFunctions'; import { loginAccount, logoutHandler } from './AuthHelper'; import { usePathname } from 'next/navigation'; +import { isAuthRequiredPath } from '@/utils/utils'; type UserCredentials = { email: string; @@ -85,11 +86,7 @@ export const AuthContextProvider = ({ */ const getUser = async (): Promise => { if (!isSessionInLocalStorage()) { - if ( - pathname !== '/register' && - pathname !== '/account/recovery' && - pathname !== '/recover-password' - ) { + if (isAuthRequiredPath(pathname)) { router.push('/login'); } return; @@ -97,14 +94,25 @@ export const AuthContextProvider = ({ try { const user = await account.get(); - const userData: IUser = await getCurrentUser(user.$id); + const userData: ICollectionUser = await getCurrentUser(user.$id); + + const currentUser: IUser = { + documentId: userData.documentId, + id: userData.id, + email: userData.email, + leagues: userData.leagues, + labels: user.labels, + }; + updateUser( - userData.documentId, - userData.id, - userData.email, - userData.leagues, + currentUser.documentId, + currentUser.id, + currentUser.email, + currentUser.leagues, + user.labels, ); - return userData; + + return currentUser; } catch (error) { resetUser(); setIsSignedIn(false); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22e5ef82..1549f263 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11428,7 +11428,7 @@ snapshots: debug: 4.3.5 enhanced-resolve: 5.17.0 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.1.3))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 @@ -11440,7 +11440,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: diff --git a/utils/utils.test.ts b/utils/utils.test.ts index 408a3b89..1c471924 100644 --- a/utils/utils.test.ts +++ b/utils/utils.test.ts @@ -5,6 +5,7 @@ import { getUserPick, parseUserPick, getUserLeagues, + isAuthRequiredPath, } from './utils'; import { getCurrentLeague, getAllWeeklyPicks } from '@/api/apiFunctions'; @@ -179,6 +180,33 @@ describe('utils', () => { }); }); }); + describe('isAuthRequiredPath', () => { + it('should return false for non-auth paths', () => { + expect(isAuthRequiredPath('/register')).toBe(false); + expect(isAuthRequiredPath('/account/recovery')).toBe(false); + expect(isAuthRequiredPath('/recover-password')).toBe(false); + }); + + it('should return true for auth-required paths', () => { + expect(isAuthRequiredPath('/')).toBe(true); + expect(isAuthRequiredPath('/dashboard')).toBe(true); + expect(isAuthRequiredPath('/profile')).toBe(true); + expect(isAuthRequiredPath('/settings')).toBe(true); + }); + + it('should handle edge cases', () => { + expect(isAuthRequiredPath('')).toBe(true); + expect(isAuthRequiredPath('/register/')).toBe(true); // Trailing slash + expect(isAuthRequiredPath('/REGISTER')).toBe(true); // Case sensitivity + expect(isAuthRequiredPath('/register/subpage')).toBe(true); + }); + + it('should handle unusual inputs', () => { + expect(isAuthRequiredPath(' /register ')).toBe(true); // Spaces + expect(isAuthRequiredPath('/account/recovery?param=value')).toBe(true); // Query parameters + }); + }); + xdescribe('getUserLeagues', () => { it('should return the list of leagues the user is a part of', async () => { (getCurrentLeague as jest.Mock).mockResolvedValue(mockLeague); diff --git a/utils/utils.ts b/utils/utils.ts index 914d7333..ca28a1da 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -176,3 +176,13 @@ export const hasTeamBeenPicked = (teamName: string, selectedTeams: string[]): bo export const getNFLTeamLogo = (NFLTeams: INFLTeam[], teamName: string): string => { return NFLTeams.find((teams) => teams.teamName === teamName)?.teamLogo as string; } + +/** + * Checks if the current path requires authentication + * @param pathname - The current path + * @returns {boolean} - Whether the current path requires authentication + */ +export const isAuthRequiredPath = (pathname: string): boolean => { + const nonAuthPaths = ['/register', '/account/recovery', '/recover-password']; + return !nonAuthPaths.includes(pathname); +}; From a3a6946c8293fd96dcd40035a6914d5dbc227450 Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Mon, 21 Oct 2024 17:20:27 -0400 Subject: [PATCH 9/9] updated pnpmlock.yml --- pnpm-lock.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1549f263..22e5ef82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11428,7 +11428,7 @@ snapshots: debug: 4.3.5 enhanced-resolve: 5.17.0 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.1.3))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 @@ -11440,7 +11440,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: