98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
import { auth } from '@/auth';
|
|
import { NextResponse } from 'next/server';
|
|
|
|
const protectedApiPrefixes = ['/api/products', '/api/organizations', '/api/users'];
|
|
const superAdminOnlyRoutes = ['/dashboard/workspaces', '/dashboard/administration/setup'];
|
|
const organizationAdminRoutes = ['/dashboard/users', '/dashboard/workspaces/team'];
|
|
|
|
interface BootstrapStatusResponse {
|
|
initialized: boolean;
|
|
}
|
|
|
|
function isBootstrapAllowedPath(pathname: string) {
|
|
return (
|
|
pathname === '/administration/setup' ||
|
|
pathname.startsWith('/api/auth/') ||
|
|
pathname.startsWith('/api/setup/bootstrap/') ||
|
|
pathname === '/api/setup/templates'
|
|
);
|
|
}
|
|
|
|
async function getBootstrapInitialized(origin: string): Promise<boolean | null> {
|
|
try {
|
|
const response = await fetch(new URL('/api/setup/bootstrap/status', origin), {
|
|
cache: 'no-store',
|
|
headers: {
|
|
'x-setup-proxy-check': '1'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) return null;
|
|
|
|
const payload = (await response.json()) as BootstrapStatusResponse;
|
|
return payload.initialized;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export default auth(async (req) => {
|
|
const pathname = req.nextUrl.pathname;
|
|
const bootstrapAllowedPath = isBootstrapAllowedPath(pathname);
|
|
|
|
if (bootstrapAllowedPath) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
const isInitialized = await getBootstrapInitialized(req.nextUrl.origin);
|
|
|
|
if (isInitialized === false) {
|
|
if (pathname.startsWith('/api/')) {
|
|
return NextResponse.json(
|
|
{ message: 'System setup is required before this API is available.', redirectTo: '/administration/setup' },
|
|
{ status: 503 }
|
|
);
|
|
}
|
|
|
|
return NextResponse.redirect(new URL('/administration/setup', req.nextUrl.origin));
|
|
}
|
|
|
|
const isDashboardRoute = pathname.startsWith('/dashboard');
|
|
const isProtectedApiRoute = protectedApiPrefixes.some((prefix) => pathname.startsWith(prefix));
|
|
const user = req.auth?.user;
|
|
const isSignedIn = Boolean(user);
|
|
const isSuperAdmin = user?.systemRole === 'super_admin';
|
|
const isOrganizationAdmin =
|
|
isSuperAdmin ||
|
|
Boolean(
|
|
user?.activeOrganizationId &&
|
|
(user.activeMembershipRole === 'admin' || user.activePermissions?.includes('users:manage'))
|
|
);
|
|
|
|
if (!isSignedIn && (isDashboardRoute || isProtectedApiRoute)) {
|
|
if (isProtectedApiRoute) {
|
|
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
const signInUrl = new URL('/auth/sign-in', req.nextUrl.origin);
|
|
signInUrl.searchParams.set('callbackUrl', `${pathname}${req.nextUrl.search}`);
|
|
return NextResponse.redirect(signInUrl);
|
|
}
|
|
|
|
if (isSignedIn && superAdminOnlyRoutes.some((prefix) => pathname.startsWith(prefix)) && !isSuperAdmin) {
|
|
return NextResponse.redirect(new URL('/dashboard', req.nextUrl.origin));
|
|
}
|
|
|
|
if (isSignedIn && organizationAdminRoutes.some((prefix) => pathname.startsWith(prefix)) && !isOrganizationAdmin) {
|
|
return NextResponse.redirect(new URL('/dashboard', req.nextUrl.origin));
|
|
}
|
|
|
|
return NextResponse.next();
|
|
});
|
|
|
|
export const config = {
|
|
matcher: [
|
|
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)'
|
|
]
|
|
};
|