-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmiddleware.ts
More file actions
118 lines (105 loc) · 3.96 KB
/
Copy pathmiddleware.ts
File metadata and controls
118 lines (105 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
import { createServerClient } from "@supabase/ssr"
/**
* Authentication middleware — uses @supabase/ssr createServerClient
* to properly handle chunked cookies and base64url-encoded sessions.
*
* SECURITY MODEL:
* - Layer 1 (Middleware): Authentication check only — "are you logged in?"
* - Layer 2 (Server Components): Fetch user role from backend API with verified JWT
* - Layer 3 (Backend API): Full JWT signature verification + database role check
* - Layer 4 (Backend API): RBAC enforcement via require_role() dependency
*/
// Paths that redirect to dashboard when user is authenticated
const ROLE_BASED_PATHS = ["/"]
// Protected routes that require authentication
const PROTECTED_ROUTES = ["/me", "/profile", "/team", "/admin", "/dashboard", "/engines", "/data-ingestion", "/audit-log", "/privacy", "/team-health", "/tenants", "/notifications", "/search", "/ask-sentinel", "/simulation", "/talent-scout", "/workflows", "/marketplace", "/onboarding", "/employee", "/demo"]
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
// If Supabase env vars are missing, allow all requests through
// (build-time or misconfigured deployment — don't crash)
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
if (!supabaseUrl || !supabaseKey) {
return NextResponse.next()
}
// Create a response that we can modify (for cookie refresh)
let supabaseResponse = NextResponse.next({ request })
// Use createServerClient which handles chunked cookies, base64url decoding,
// and token refresh automatically — unlike manual cookie parsing
const supabase = createServerClient(
supabaseUrl,
supabaseKey,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
// Forward cookies to the request (for downstream server components)
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value)
)
// Recreate response with updated request cookies
supabaseResponse = NextResponse.next({ request })
// Set cookies on the response (for the browser)
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// getUser() verifies the JWT and refreshes tokens if needed.
// Wrap in try-catch: stale refresh tokens throw instead of returning null.
let user = null
try {
const { data } = await supabase.auth.getUser()
user = data.user
} catch {
// Invalid/expired refresh token — treat as unauthenticated
}
// Check if this is a protected route
const isProtectedRoute = PROTECTED_ROUTES.some((route) =>
pathname.startsWith(route)
)
// If no user and trying to access protected route, redirect to login
if (!user && isProtectedRoute) {
return NextResponse.redirect(new URL("/login", request.url))
}
// Redirect root to dashboard for authenticated users
if (user && ROLE_BASED_PATHS.includes(pathname)) {
return NextResponse.redirect(new URL("/dashboard", request.url))
}
// Return the response (may include refreshed auth cookies)
return supabaseResponse
}
// Configure middleware to run on specific paths
export const config = {
matcher: [
"/",
"/dashboard/:path*",
"/engines/:path*",
"/me/:path*",
"/profile/:path*",
"/team/:path*",
"/admin/:path*",
"/data-ingestion/:path*",
"/audit-log/:path*",
"/privacy/:path*",
"/team-health/:path*",
"/tenants/:path*",
"/notifications/:path*",
"/search/:path*",
"/ask-sentinel/:path*",
"/simulation/:path*",
"/talent-scout/:path*",
"/workflows/:path*",
"/marketplace/:path*",
"/onboarding/:path*",
"/employee/:path*",
"/demo/:path*",
"/auth/sso/:path*",
],
}