fix(auth): prevent unauthorized role-based redirects on login
Overview
This MR fixes a critical routing bug where a user logging out of a role-restricted page would leave that URL in the router's state.from history, incorrectly redirecting the next logged-in user to that unauthorized page. It also resolves an unmounted component state update memory leak in NotificationContext that was causing the Vitest pre-commit hooks to fail.
What does this MR do and why?
Motivation:
If an admin logged out while on /admin/medical-camps, the application preserved that location in state.from. If a volunteer then logged in on the same browser tab, they were incorrectly redirected to the admin page because ProtectedRoute only verified if a user was authenticated without actively checking their specific role. Additionally, test suites were failing due to a ReferenceError: window is not defined inside NotificationContext.
Approach:
- Modified ProtectedRoute to actively check
user.rolesagainst the requested URL prefix (/admin,/volunteer,/coordinator) and immediately redirect unauthorized users to the home dashboard (/). - Modified NotificationContext to track notification timeouts in a
useRefand explicitlyclearTimeouton them when the NotificationProvider unmounts, fixing the unmounted state update issue during test teardowns.
Changes Made
- src/components/ProtectedRoute.tsx: Added strict path-based role validation before rendering component children.
-
src/contexts/NotificationContext.tsx: Added a
timeoutsRefdictionary and a cleanupuseEffectto safely cancel timeouts when the component is unmounted.
Technical Details
-
Routing Issue: The root cause was that ProtectedRoute allowed any authenticated user to pass. By adopting a
.startsWith()string matching approach onlocation.pathname, we securely scale to nested paths (e.g.,/admin/manage-doctors) without heavy refactoring. -
Test Error Issue: The Vitest
window is not definederror was caused bysetTimeoutfiring 5 seconds after the React Testing Library had already torn down the DOM environment. By registering theNodeJS.TimeoutIDs in a ref, we forcefully clear them on unmount.
Type of Change
-
🐛 Bug fix (non-breaking change that fixes an issue) -
✨ New feature (non-breaking change that adds functionality) -
💥 Breaking change (fix or feature that would cause existing functionality to change) -
📝 Documentation update -
🎨 UI/UX improvement -
♻ ️ Refactor (no functional changes) -
⚡ Performance improvement -
🧪 Test update -
🔧 Configuration change -
🚨 Security fix
Related Issues / References
-
Closes #246 (closed)
-
Resolves the cross-role routing redirection bug.
-
Resolves the
husky pre-commitvitest failure.
Screenshots or Screen Recordings
Before: Screencast_from_2026-03-22_12-08-36 After: Screencast_from_2026-03-24_15-32-03
How to Set Up and Validate Locally
- Pull this branch / checkout the MR.
- Run development server: bash npm run dev 3.Log in as an Admin. Navigate to an admin page like http://localhost:5173/admin/medical-camps
- Log out using the sidebar. You will be redirected to /login.
- Without refreshing or closing the tab, log in as a Volunteer.
- Expected behavior: You should not see the admin page. The app will intercept the disallowed
Testing Done
Test Cases Covered:
| Scenario | Expected Result | Status |
|---|---|---|
| Admin logs out -> Volunteer logs in | Volunteer redirected to /, not /admin/* | |
| Admin attempts access to /volunteer/* | Admin cleanly redirected to / |
|
NotificationContext.test.tsx
Memory Leak | Test passes without unmounted window undefined error |
Code Quality Checklist
Code Standards
-
Code follows project conventions (naming, structure, formatting) -
No console.log() or debugger statements left in code -
No unused imports, variables, or functions -
No duplicate code and use of existing components for reusability -
i18n check passed with no hardcoded strings in codebase for i18n support -
TypeScript types are properly defined (no anyunless justified) -
ESLint and Prettier checks pass bun run lint
React Best Practices
-
Components are properly split and single-responsibility -
Hooks follow rules (no conditional hooks, proper dependencies) -
State management is appropriate (local vs global state) -
No unnecessary re-renders (memoization used where needed) -
Event handlers are properly cleaned up
Component Patterns
-
shadcn/ui components used correctly -
Tailwind classes follow utility-first approach -
Responsive design considered (mobile-first if applicable) -
Accessibility attributes included (aria-*, role, etc.) -
Icons from lucide-react used consistently
API & Data Fetching
-
TanStack Query used for server state (if applicable) -
Loading and error states handled -
API types defined in src/types/api.ts -
Axios interceptors handle auth tokens correctly -
Use of Zod for data validations
Error Handling
-
Errors are caught and handled gracefully -
User-friendly error messages displayed -
Errors are being toasted properly -
Network failures handled appropriately
Documentation
-
README.md updated (if setup steps changed) -
.env.exampleupdated (if new env vars added) -
CHANGELOG.md updated (if applicable)
Known Limitations / Technical Debt
Additional Notes
MR Acceptance Checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.