united-tattoo/middleware.ts

147 lines
4.7 KiB
TypeScript

import { withAuth } from "next-auth/middleware"
import { NextResponse } from "next/server"
import { UserRole } from "@/types/database"
export default withAuth(
function middleware(req) {
const token = req.nextauth.token
const { pathname } = req.nextUrl
// Permanent redirect for renamed artist slug
if (pathname === "/artists/amari-rodriguez") {
const url = new URL("/artists/amari-kyss", req.url)
const res = NextResponse.redirect(url, 308)
return res
}
// Allow token-based bypass for admin migrate endpoint (non-interactive deployments)
const migrateToken = process.env.MIGRATE_TOKEN
const headerToken = req.headers.get("x-migrate-token")
const urlToken = req.nextUrl.searchParams.get("token")
const hasMigrateBypass =
pathname.startsWith("/api/admin/migrate") &&
((headerToken && headerToken === migrateToken) || (urlToken && urlToken === migrateToken))
// Admin routes protection
if (pathname.startsWith("/admin")) {
if (!token) {
return NextResponse.redirect(new URL("/auth/signin", req.url))
}
// Check if user has admin role
const userRole = token.role as UserRole
if (userRole !== UserRole.SHOP_ADMIN && userRole !== UserRole.SUPER_ADMIN) {
return NextResponse.redirect(new URL("/unauthorized", req.url))
}
}
// Artist dashboard routes
if (pathname.startsWith("/artist-dashboard")) {
if (!token) {
return NextResponse.redirect(new URL("/auth/signin", req.url))
}
const userRole = token.role as UserRole
if (userRole !== UserRole.ARTIST && userRole !== UserRole.SHOP_ADMIN && userRole !== UserRole.SUPER_ADMIN) {
return NextResponse.redirect(new URL("/unauthorized", req.url))
}
}
// Legacy artist-specific routes (if any)
if (pathname.startsWith("/artist") && !pathname.startsWith("/artists")) {
if (!token) {
return NextResponse.redirect(new URL("/auth/signin", req.url))
}
const userRole = token.role as UserRole
if (userRole !== UserRole.ARTIST && userRole !== UserRole.SHOP_ADMIN && userRole !== UserRole.SUPER_ADMIN) {
return NextResponse.redirect(new URL("/unauthorized", req.url))
}
}
// API routes protection
if (pathname.startsWith("/api/admin")) {
// Bypass for migration endpoint with valid token (used for automated deploys)
if (hasMigrateBypass) {
return NextResponse.next()
}
if (!token) {
return NextResponse.json({ error: "Authentication required" }, { status: 401 })
}
const userRole = token.role as UserRole
if (userRole !== UserRole.SHOP_ADMIN && userRole !== UserRole.SUPER_ADMIN) {
return NextResponse.json({ error: "Insufficient permissions" }, { status: 403 })
}
}
return NextResponse.next()
},
{
callbacks: {
authorized: ({ token, req }) => {
const { pathname } = req.nextUrl
// Token-based bypass for migration endpoint (before auth checks)
const migrateToken = process.env.MIGRATE_TOKEN
const headerToken = req.headers.get("x-migrate-token")
const urlToken = req.nextUrl.searchParams.get("token")
if (
pathname.startsWith("/api/admin/migrate") &&
((headerToken && headerToken === migrateToken) || (urlToken && urlToken === migrateToken))
) {
return true
}
// Public routes that don't require authentication
const publicRoutes = [
"/",
"/artists",
"/contact",
"/book",
"/aftercare",
"/gift-cards",
"/specials",
"/terms",
"/privacy",
"/auth/signin",
"/auth/error",
"/unauthorized"
]
// Allow public routes and artist portfolio pages
if (publicRoutes.some(route => pathname === route || pathname.startsWith(route))) {
return true
}
// Allow individual artist portfolio pages (public access)
if (pathname.match(/^\/artists\/[^\/]+$/)) {
return true
}
// Allow public API routes
if (pathname.startsWith("/api/auth") || pathname.startsWith("/api/public")) {
return true
}
// Require authentication for all other routes
return !!token
},
},
}
)
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - public folder
*/
"/((?!_next/static|_next/image|favicon.ico|public|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.gif$|.*\\.svg$).*)",
],
}