-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.ts
More file actions
162 lines (143 loc) · 4.97 KB
/
proxy.ts
File metadata and controls
162 lines (143 loc) · 4.97 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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function proxy(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
})
// Track cookies that need to be set on any response (including redirects)
const cookiesToSet: { name: string; value: string; options: Record<string, unknown> }[] = []
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookies) {
cookies.forEach(({ name, value }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({
request,
})
cookies.forEach(({ name, value, options }) => {
supabaseResponse.cookies.set(name, value, options)
cookiesToSet.push({ name, value, options })
})
},
},
}
)
// Helper to create redirect with cookies preserved
const redirectWithCookies = (url: URL) => {
const response = NextResponse.redirect(url)
cookiesToSet.forEach(({ name, value, options }) => {
response.cookies.set(name, value, options as Parameters<typeof response.cookies.set>[2])
})
return response
}
// Refresh session if expired - required for Server Components
const {
data: { user },
} = await supabase.auth.getUser()
const { pathname } = request.nextUrl
// Allow access to all /auth routes
if (pathname.startsWith('/auth')) {
return supabaseResponse
}
// Allow access to cron API routes (they use CRON_SECRET for auth)
if (pathname.startsWith('/api/cron')) {
return supabaseResponse
}
// Protect all other routes - redirect to signin if not authenticated
if (!user) {
const url = request.nextUrl.clone()
url.pathname = '/auth/signin'
return redirectWithCookies(url)
}
// Determine user type
const orgMember = await prisma.organizationMember.findFirst({
where: { userId: user.id },
})
const volunteer = await prisma.volunteer.findUnique({
where: { userId: user.id },
})
// Redirect root to appropriate dashboard
if (pathname === '/') {
const url = request.nextUrl.clone()
if (orgMember) {
url.pathname = '/org'
} else if (volunteer) {
url.pathname = '/vol'
} else {
// User has no account type, redirect to signin
url.pathname = '/auth/signin'
}
return redirectWithCookies(url)
}
// Allow public profile routes - these are accessible to all authenticated users
// Public volunteer profile: /vol/[id] where [id] is not a protected route
const volPublicProfileMatch = pathname.match(/^\/vol\/([^\/]+)$/)
if (volPublicProfileMatch) {
const id = volPublicProfileMatch[1]
// Check if it's not a protected route (like explore, profile, settings, messaging)
const protectedVolRoutes = ['explore', 'profile', 'settings', 'messaging']
if (!protectedVolRoutes.includes(id)) {
// This is a public volunteer profile, allow access
return supabaseResponse
}
}
// Public organization profile: /org/[id] where [id] is not a protected route
const orgPublicProfileMatch = pathname.match(/^\/org\/([^\/]+)$/)
if (orgPublicProfileMatch) {
const id = orgPublicProfileMatch[1]
// Check if it's not a protected route (like opportunities, profile, settings, messaging, volunteers, team)
const protectedOrgRoutes = ['opportunities', 'profile', 'settings', 'messaging', 'volunteers', 'team']
if (!protectedOrgRoutes.includes(id)) {
// This is a public organization profile, allow access
return supabaseResponse
}
}
// Protect organization routes - only orgs can access
if (pathname.startsWith('/org')) {
if (!orgMember) {
const url = request.nextUrl.clone()
// If volunteer, redirect to their dashboard, otherwise signin
if (volunteer) {
url.pathname = '/vol'
} else {
url.pathname = '/auth/signin'
}
return redirectWithCookies(url)
}
return supabaseResponse
}
// Protect volunteer routes - only volunteers can access
if (pathname.startsWith('/vol')) {
if (!volunteer) {
const url = request.nextUrl.clone()
// If org, redirect to their dashboard, otherwise signin
if (orgMember) {
url.pathname = '/org'
} else {
url.pathname = '/auth/signin'
}
return redirectWithCookies(url)
}
return supabaseResponse
}
// For any other authenticated route, allow access
return supabaseResponse
}
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 files
*/
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
}