feat: enable Entra ID authentication
- Enable Better Auth middleware (was bypassed with return NextResponse.next()) - Re-enable AuthProvider in root layout - Add public routes for webhooks, sync, kiosk, QBO, legal, health endpoints - Set Microsoft Entra ID credentials and production Better Auth URLs - Hide Engagement and Admin nav sections from non-super-admins - Fix auth DB columns: snake_case → camelCase for Better Auth compatibility - Add twoFactorEnabled column to user table
This commit is contained in:
parent
b98c67482a
commit
02c81bf4e4
3 changed files with 70 additions and 40 deletions
|
|
@ -4,8 +4,7 @@ import "./globals.css";
|
|||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { AppNavigation } from "@/components/navigation/app-navigation";
|
||||
import { Toaster } from "sonner";
|
||||
// TEMPORARY: AuthProvider disabled for private site access
|
||||
// import { AuthProvider } from "@/components/auth/auth-provider";
|
||||
import { AuthProvider } from "@/components/auth/auth-provider";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
|
|
@ -36,11 +35,12 @@ export default function RootLayout({
|
|||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{/* TEMPORARY: AuthProvider removed - TODO: Re-enable when site has public access */}
|
||||
<div className="min-h-screen bg-background">
|
||||
<AppNavigation />
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
<AuthProvider>
|
||||
<div className="min-h-screen bg-background">
|
||||
<AppNavigation />
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
</AuthProvider>
|
||||
<Toaster position="top-right" richColors />
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import {
|
|||
} from '@/components/ui/navigation-menu';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ThemeToggle } from '@/components/theme-toggle';
|
||||
import { useSession } from '@/lib/auth-client';
|
||||
|
||||
interface NavItem {
|
||||
title: string;
|
||||
|
|
@ -190,12 +191,23 @@ const navigationItems: NavItem[] = [
|
|||
|
||||
export function AppNavigation() {
|
||||
const pathname = usePathname();
|
||||
const { data: session } = useSession();
|
||||
const userRole = (session?.user as any)?.role || 'user';
|
||||
const isSuperAdmin = userRole === 'super-admin';
|
||||
|
||||
const isActive = (href?: string) => {
|
||||
if (!href) return false;
|
||||
return pathname === href || pathname.startsWith(href + '/');
|
||||
};
|
||||
|
||||
// Hide Engagement and Admin from non-super-admins
|
||||
const visibleItems = navigationItems.filter((item) => {
|
||||
if (item.title === 'Engagement' || item.title === 'Admin') {
|
||||
return isSuperAdmin;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container mx-auto px-6 flex h-16 items-center justify-between">
|
||||
|
|
@ -215,7 +227,7 @@ export function AppNavigation() {
|
|||
{/* Main Navigation — centered */}
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
{navigationItems.map((item) => (
|
||||
{visibleItems.map((item) => (
|
||||
<NavigationMenuItem key={item.title}>
|
||||
{item.children ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,28 @@ const publicRoutes = [
|
|||
"/auth/2fa",
|
||||
"/auth/setup",
|
||||
"/api/auth",
|
||||
// External service callbacks and webhooks
|
||||
"/api/webhooks",
|
||||
"/api/kiosk",
|
||||
"/api/qbo/auth",
|
||||
"/api/qbo/disconnect",
|
||||
"/api/zabbix/webhook",
|
||||
// Health and status checks
|
||||
"/api/health",
|
||||
"/api/integrations/status",
|
||||
// Legal pages required by Intuit
|
||||
"/legal",
|
||||
// Sync endpoints called by scheduler
|
||||
"/api/sync",
|
||||
"/api/datto-rmm/sync",
|
||||
"/api/itglue/sync",
|
||||
"/api/veeam/sync",
|
||||
"/api/sentinelone/sync",
|
||||
"/api/engagement/sync",
|
||||
"/api/zoom/sync",
|
||||
"/api/qbo/sync",
|
||||
"/api/reports/ticket-digest",
|
||||
"/api/notifications/morning-summary/send",
|
||||
];
|
||||
|
||||
// Routes that require admin or super-admin role
|
||||
|
|
@ -17,44 +39,40 @@ const adminRoutes = ["/admin"];
|
|||
export async function middleware(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
// TEMPORARY: Authentication bypassed for private site access
|
||||
// TODO: Re-enable authentication when site has public access
|
||||
return NextResponse.next();
|
||||
|
||||
// Allow public routes
|
||||
// if (publicRoutes.some((route) => pathname.startsWith(route))) {
|
||||
// return NextResponse.next();
|
||||
// }
|
||||
if (publicRoutes.some((route) => pathname.startsWith(route))) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// // Allow static files and API routes (except admin API)
|
||||
// if (
|
||||
// pathname.startsWith("/_next") ||
|
||||
// pathname.startsWith("/favicon") ||
|
||||
// pathname.includes(".")
|
||||
// ) {
|
||||
// return NextResponse.next();
|
||||
// }
|
||||
// Allow static files
|
||||
if (
|
||||
pathname.startsWith("/_next") ||
|
||||
pathname.startsWith("/favicon") ||
|
||||
pathname.includes(".")
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// // Check for session cookie
|
||||
// const sessionCookie = getSessionCookie(request);
|
||||
// Check for session cookie
|
||||
const sessionCookie = getSessionCookie(request);
|
||||
|
||||
// if (!sessionCookie) {
|
||||
// // Redirect to sign-in if no session
|
||||
// const signInUrl = new URL("/auth/sign-in", request.url);
|
||||
// signInUrl.searchParams.set("callbackUrl", pathname);
|
||||
// return NextResponse.redirect(signInUrl);
|
||||
// }
|
||||
if (!sessionCookie) {
|
||||
// Redirect to sign-in if no session
|
||||
const signInUrl = new URL("/auth/sign-in", request.url);
|
||||
signInUrl.searchParams.set("callbackUrl", pathname);
|
||||
return NextResponse.redirect(signInUrl);
|
||||
}
|
||||
|
||||
// // For admin routes, we need to verify the role
|
||||
// // This is a basic check - the actual role verification happens in the API routes
|
||||
// if (adminRoutes.some((route) => pathname.startsWith(route))) {
|
||||
// // The session cookie exists, but we can't decode it here without the secret
|
||||
// // Role-based access control is enforced at the API level
|
||||
// // This middleware just ensures there's a session
|
||||
// return NextResponse.next();
|
||||
// }
|
||||
// For admin routes, we need to verify the role
|
||||
// This is a basic check - the actual role verification happens in the API routes
|
||||
if (adminRoutes.some((route) => pathname.startsWith(route))) {
|
||||
// The session cookie exists, but we can't decode it here without the secret
|
||||
// Role-based access control is enforced at the API level
|
||||
// This middleware just ensures there's a session
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// return NextResponse.next();
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue