Files
imphnen-frontend-service/apps/hackathon/src/middleware.ts
T

137 lines
3.6 KiB
TypeScript
Raw Normal View History

2025-11-25 01:47:56 +07:00
import { SessionUser } from '@imphnen-frontend-service/utils';
2025-11-28 17:40:34 +07:00
import { hackathonApi, SessionToken } from '@imphnen-frontend-service/service';
2025-11-24 12:51:41 +07:00
import { LoaderFunctionArgs, redirect } from 'react-router';
const mappingPublicRoutes = [
'/',
];
2025-11-25 01:47:56 +07:00
const mappingOnboardingRoutes = [
'/onboarding/user',
];
2025-11-24 12:51:41 +07:00
const mappingRoutePermissions = [
{
path: '/dashboard',
permissions: [],
},
2025-11-25 01:47:56 +07:00
{
path: '/teams/browse',
permissions: [],
},
{
path: '/teams/create',
permissions: [],
},
2025-11-24 12:51:41 +07:00
];
const mappingPublicPrefixRoutes = [
'/hackathons',
];
2025-11-25 01:47:56 +07:00
// Cache to prevent redundant checks (cache for 5 seconds)
const onboardingCache = new Map<string, { hasLocation: boolean; timestamp: number }>();
const CACHE_DURATION = 5000; // 5 seconds
2025-11-24 12:51:41 +07:00
export const middleware = async ({ request }: LoaderFunctionArgs) => {
const url = new URL(request.url);
const pathname = url.pathname;
2025-11-25 01:47:56 +07:00
2025-11-28 17:40:34 +07:00
// Get token from cookies and user from local storage
const tokenData = SessionToken.get();
const user = SessionUser.get();
const isAuthenticated = !!tokenData?.token?.access_token;
2025-11-24 12:51:41 +07:00
// Allow to access the hackathon pages without authentication
if (mappingPublicPrefixRoutes.some((prefix) => pathname.startsWith(prefix))) {
return null;
}
// Public routes - allow everyone to view the landing page
2025-11-24 12:51:41 +07:00
if (mappingPublicRoutes.includes(pathname)) {
return null;
}
2025-11-25 01:47:56 +07:00
// Auth callback - allow without authentication check (for OAuth callback)
if (pathname === '/auth/callback') {
return null;
}
// Auth routes (all /auth/* paths) - redirect to dashboard if already authenticated
if (pathname.startsWith('/auth')) {
if (isAuthenticated) return redirect('/dashboard');
2025-11-25 01:47:56 +07:00
return null;
}
// Require authentication for all other routes
if (!isAuthenticated) {
2025-11-25 01:47:56 +07:00
return redirect('/auth/login');
}
// Check if user has completed onboarding
2025-11-25 01:47:56 +07:00
// Skip onboarding check for onboarding routes themselves
if (!mappingOnboardingRoutes.includes(pathname)) {
try {
2025-11-28 17:40:34 +07:00
const userId = user?.id;
if (!userId) {
return redirect('/auth/login');
}
2025-11-25 01:47:56 +07:00
const now = Date.now();
// Check cache first
const cached = onboardingCache.get(userId);
let hasLocation = false;
if (cached && (now - cached.timestamp) < CACHE_DURATION) {
hasLocation = cached.hasLocation;
} else {
2025-11-28 17:40:34 +07:00
// First check user data (faster)
if (user?.location) {
hasLocation = true;
} else {
// Fetch from backend API
try {
const response = await hackathonApi.get('/users/me');
hasLocation = !!response.data?.data?.location;
} catch {
2025-11-28 17:40:34 +07:00
// If API fails, check user data as fallback
hasLocation = !!user?.location;
}
2025-11-25 01:47:56 +07:00
}
// Update cache
onboardingCache.set(userId, { hasLocation, timestamp: now });
}
if (!hasLocation) {
return redirect('/onboarding/user');
}
} catch (error) {
console.error('[Middleware] Unexpected error checking onboarding:', error);
// On error, allow access (fail open)
return null;
}
}
2025-11-28 17:40:34 +07:00
// Check route permissions using user data
2025-11-25 01:47:56 +07:00
const userPermissions =
2025-11-28 17:40:34 +07:00
user?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
2025-11-24 12:51:41 +07:00
const matchedRoute = mappingRoutePermissions.find(
(route) => route.path === pathname
);
if (matchedRoute) {
const hasPermission =
!matchedRoute.permissions ||
matchedRoute.permissions.some((perm) => userPermissions.includes(perm));
if (!hasPermission) {
2025-11-25 01:47:56 +07:00
return redirect('/dashboard');
2025-11-24 12:51:41 +07:00
}
}
return null;
};