From 13bef2461ab00e5b330f53aa4ce2d5157c3a001d Mon Sep 17 00:00:00 2001 From: Hassan Ibrahim Date: Sun, 27 Sep 2026 16:51:21 -0400 Subject: [PATCH] fix: log out and redirect to /login when the auth token expires The dashboard's Unauthorized check never fired: GetUser returned the error as an object, but the check required typeof error === 'string'. GetUser and GetAllUsers now return the HTTP statusCode, getSelf passes it through, and a shared redirectIfUnauthorized helper signs the user out and redirects to /login on a 401. Wired into the dashboard, organizer and director views. Closes #348 --- app/dashboard/page.tsx | 14 +++----------- app/dashboard/views/directorView.tsx | 4 ++++ app/dashboard/views/organizerView.tsx | 3 +++ app/lib/actions.ts | 10 +++++++--- app/lib/authGuard.ts | 18 ++++++++++++++++++ app/lib/data.ts | 3 +++ 6 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 app/lib/authGuard.ts diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 705f8e7..60d1a89 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { UpdateSelf, getSelf, getUsers, RegisterSelf } from '@/app/lib/data'; +import { redirectIfUnauthorized } from '@/app/lib/authGuard'; import { RemoveMember, InviteMember, @@ -565,16 +566,7 @@ export default function Dashboard() { try { const data = await getSelf(); - if (data?.error && typeof data.error === 'string') { - if ( - data.error.includes('Something went wrong') || - data.error.includes('not authenticated') || - data.error.includes('Unauthorized') - ) { - window.location.href = '/login'; - return; - } - } + if (await redirectIfUnauthorized(data.statusCode)) return; const points = await GetPoints(); @@ -589,7 +581,7 @@ export default function Dashboard() { }); if (data.error != '') { - alert(data.error.message); + alert(data.error); } setUserData(data.response); diff --git a/app/dashboard/views/directorView.tsx b/app/dashboard/views/directorView.tsx index a1b295a..570006d 100644 --- a/app/dashboard/views/directorView.tsx +++ b/app/dashboard/views/directorView.tsx @@ -17,6 +17,7 @@ import { getSelf, getUsers } from '@/app/lib/data'; import { generatePagination } from '@/app/lib/utils'; import { useState, useEffect } from 'react'; import { GetAllUsers } from '@/app/lib/actions'; +import { redirectIfUnauthorized } from '@/app/lib/authGuard'; import { DeleteUser } from '@/app/lib/actions'; import { set } from 'zod'; import ConfirmDeleteModal from '@/app/ui/confirmDeleteModal'; @@ -36,6 +37,9 @@ function DirectorView(userData: any) { const fetchUsers = async () => { try { const data = await GetAllUsers(); + + if (await redirectIfUnauthorized(data.statusCode)) return; + const users = data.response; console.log(users); setAllUsers(users); diff --git a/app/dashboard/views/organizerView.tsx b/app/dashboard/views/organizerView.tsx index 8867e9a..c49b08a 100644 --- a/app/dashboard/views/organizerView.tsx +++ b/app/dashboard/views/organizerView.tsx @@ -9,6 +9,7 @@ import EventScan from './eventScan'; import { AttendEventScan, GetUser, SetUser } from '@/app/lib/actions'; import { handleSignOut } from '@/app/lib/actions'; import { getSelf } from '@/app/lib/data'; +import { redirectIfUnauthorized } from '@/app/lib/authGuard'; import PopupDialog from '../components/dialog'; import { set } from 'zod'; import Page from '@/app/(pre-dashboard)/(landing)/page'; @@ -341,6 +342,8 @@ ${clues[5]}: ${clue5Done}`, try { const data = await getSelf(); + if (await redirectIfUnauthorized(data.statusCode)) return; + const domain = data.response.email.slice(-11); setIsSponsor(domain == 'sponsor.com'); if (domain == 'sponsor.com') { diff --git a/app/lib/actions.ts b/app/lib/actions.ts index 05c3392..7f266c7 100644 --- a/app/lib/actions.ts +++ b/app/lib/actions.ts @@ -277,7 +277,7 @@ export async function SignUp( } export async function GetUser(email: string) { - let resp = { + let resp: { error: string; response: string; statusCode?: number } = { error: '', response: '', }; @@ -301,7 +301,8 @@ export async function GetUser(email: string) { if (res.status == 200) { resp.response = res_json; } else { - resp.error = res_json; + resp.error = res_json?.message || 'Unexpected Error'; + resp.statusCode = res.status; } }) .catch((error) => { @@ -309,6 +310,7 @@ export async function GetUser(email: string) { }); } else { resp.error = 'Please log in'; + resp.statusCode = 401; } return resp; } @@ -1033,7 +1035,7 @@ export async function UserExists(email: string) { } export async function GetAllUsers() { noStore(); - let resp = { + let resp: { error: string; response: string; statusCode?: number } = { error: '', response: '', }; @@ -1055,12 +1057,14 @@ export async function GetAllUsers() { let resJSON = await res.json(); if (res.status !== 200) { resp.error = 'Error Getting All Users'; + resp.statusCode = res.status; } else { resp.response = resJSON; } }); } else { resp.error = 'User not authenticated'; + resp.statusCode = 401; } return resp; } diff --git a/app/lib/authGuard.ts b/app/lib/authGuard.ts new file mode 100644 index 0000000..30af619 --- /dev/null +++ b/app/lib/authGuard.ts @@ -0,0 +1,18 @@ +import { handleSignOut } from './actions'; + +/** + * Backend calls return an inconsistent error shape, but a 401 always means the + * session's auth_token was rejected (expired or invalid). Call this right after + * any backend read that runs on page load; it clears the stale session and sends + * the user to /login instead of leaving them stuck on a half-loaded dashboard. + * Returns true if it redirected, so the caller can bail out of its effect. + */ +export async function redirectIfUnauthorized( + statusCode?: number, +): Promise { + if (statusCode !== 401) return false; + + await handleSignOut(); + window.location.href = '/login'; + return true; +} diff --git a/app/lib/data.ts b/app/lib/data.ts index d1a51c6..90c96e7 100644 --- a/app/lib/data.ts +++ b/app/lib/data.ts @@ -127,6 +127,7 @@ export async function getLeaderboard() { export async function getSelf(): Promise<{ error: any; response: Record; + statusCode?: number; }> { const session = await auth(); @@ -144,6 +145,7 @@ export async function getSelf(): Promise<{ return { error: resp.error, response: {}, + statusCode: resp.statusCode, }; } } @@ -151,6 +153,7 @@ export async function getSelf(): Promise<{ return { error: 'Something went wrong', response: {}, + statusCode: 401, }; }