- 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>
143 lines
4.0 KiB
TypeScript
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 });
|
|
}
|