cre-sync/lib/auth/middleware.ts
BusyBee3333 4e6467ffb0 Add CRESync CRM application with Setup page
- Build complete Next.js CRM for commercial real estate
- Add authentication with JWT sessions and role-based access
- Add GoHighLevel API integration for contacts, conversations, opportunities
- Add AI-powered Control Center with tool calling
- Add Setup page with onboarding checklist (/setup)
- Add sidebar navigation with Setup menu item
- Fix type errors in onboarding API, GHL services, and control center tools
- Add Prisma schema with SQLite for local development
- Add UI components with clay morphism design system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:30:55 -05:00

143 lines
4.0 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { verifyToken } from './jwt';
import { Role } from '@/types';
import { hasPermission, hasAnyPermission, Permission, isAdmin, isSuperAdmin } from './roles';
export interface AuthenticatedUser {
userId: string;
email: string;
role: Role;
}
export interface AuthenticatedRequest extends NextRequest {
user: AuthenticatedUser;
}
export type AuthMiddlewareOptions = {
requiredPermissions?: Permission[];
anyPermission?: Permission[];
requireAdmin?: boolean;
requireSuperAdmin?: boolean;
roles?: Role[];
};
export function withAuth(
handler: (req: AuthenticatedRequest) => Promise<NextResponse>,
options?: AuthMiddlewareOptions
) {
return async (req: NextRequest): Promise<NextResponse> => {
const authHeader = req.headers.get('authorization');
const token = authHeader?.replace('Bearer ', '');
if (!token) {
return NextResponse.json(
{ error: 'Unauthorized', message: 'Authentication required' },
{ status: 401 }
);
}
try {
const payload = verifyToken(token);
const userRole = payload.role as Role;
// Check super admin requirement
if (options?.requireSuperAdmin && !isSuperAdmin(userRole)) {
return NextResponse.json(
{ error: 'Forbidden', message: 'Super admin access required' },
{ status: 403 }
);
}
// Check admin requirement
if (options?.requireAdmin && !isAdmin(userRole)) {
return NextResponse.json(
{ error: 'Forbidden', message: 'Admin access required' },
{ status: 403 }
);
}
// Check role if required (legacy support)
if (options?.roles && !options.roles.includes(userRole)) {
return NextResponse.json(
{ error: 'Forbidden', message: 'Insufficient permissions' },
{ status: 403 }
);
}
// Check required permissions (all must match)
if (options?.requiredPermissions?.length) {
const hasAll = options.requiredPermissions.every(p =>
hasPermission(userRole, p)
);
if (!hasAll) {
return NextResponse.json(
{ error: 'Forbidden', message: 'Insufficient permissions' },
{ status: 403 }
);
}
}
// Check any permission (at least one must match)
if (options?.anyPermission?.length) {
if (!hasAnyPermission(userRole, options.anyPermission)) {
return NextResponse.json(
{ error: 'Forbidden', message: 'Insufficient permissions' },
{ status: 403 }
);
}
}
// Add user to request
(req as AuthenticatedRequest).user = {
userId: payload.userId,
email: payload.email,
role: userRole,
};
return handler(req as AuthenticatedRequest);
} catch (error) {
return NextResponse.json(
{ error: 'Unauthorized', message: 'Invalid token' },
{ status: 401 }
);
}
};
}
// Helper to create protected API routes
export function createProtectedRoute(
options: AuthMiddlewareOptions,
handler: (req: AuthenticatedRequest) => Promise<NextResponse>
) {
return withAuth(handler, options);
}
// Helper for checking specific roles
export function requireRole(...roles: Role[]) {
return { roles };
}
// Convenience wrappers
export function withAdminAuth(handler: (req: AuthenticatedRequest) => Promise<NextResponse>) {
return withAuth(handler, { requireAdmin: true });
}
export function withSuperAdminAuth(handler: (req: AuthenticatedRequest) => Promise<NextResponse>) {
return withAuth(handler, { requireSuperAdmin: true });
}
// Permission-based wrappers
export function withPermissions(
permissions: Permission[],
handler: (req: AuthenticatedRequest) => Promise<NextResponse>
) {
return withAuth(handler, { requiredPermissions: permissions });
}
export function withAnyPermission(
permissions: Permission[],
handler: (req: AuthenticatedRequest) => Promise<NextResponse>
) {
return withAuth(handler, { anyPermission: permissions });
}