-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.ts
More file actions
141 lines (120 loc) · 4.95 KB
/
Copy pathproxy.ts
File metadata and controls
141 lines (120 loc) · 4.95 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
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { createServerClient } from '@supabase/ssr';
// Security: Prevent open redirect attacks by ensuring redirect path is always
// a relative path (starts with / but not //) and not a full external URL.
function getSafeRedirectPath(path: string, fallback = '/forms'): string {
if (path.startsWith('/') && !path.startsWith('//')) {
return path;
}
return fallback;
}
// Routes that require authentication
const protectedRoutes = ['/dashboard', '/builder', '/settings', '/certificates', '/qr-builder', '/forms', '/shortener', '/responses', '/audit'];
// Routes that are always public
const publicRoutes = ['/login', '/form', '/s', '/check', '/verify', '/api'];
export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
// Skip static files and images
if (pathname.startsWith('/_next') || pathname.startsWith('/favicon') || pathname.includes('.')) {
return NextResponse.next();
}
// Auth callback needs to pass through WITHOUT any Supabase client interaction.
// Creating a Supabase client or calling getUser() here would consume the PKCE
// code verifier cookie before the route handler can use it.
if (pathname.startsWith('/auth/callback')) {
return NextResponse.next();
}
// Skip public routes
if (publicRoutes.some((route) => pathname.startsWith(route))) {
return NextResponse.next();
}
// Check if route requires authentication
const isProtected = protectedRoutes.some((route) => pathname.startsWith(route));
if (!isProtected) {
return NextResponse.next();
}
// Create Supabase client for proxy
let response = NextResponse.next({
request: {
headers: request.headers,
},
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value));
response = NextResponse.next({
request,
});
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options)
);
},
},
}
);
// Check authentication - handle refresh token errors gracefully
try {
const {
data: { user },
error,
} = await supabase.auth.getUser();
if (error || !user) {
// Check for specific error types that indicate session issues
const isAuthError =
error?.message?.includes('Refresh Token') ||
error?.code === 'session_not_found' ||
error?.message?.includes('JWT') ||
// If we have an error but no user, treat as a session issue to be safe
(error && !user);
if (isAuthError) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', getSafeRedirectPath(pathname));
const redirectResponse = NextResponse.redirect(loginUrl);
// Clear all Supabase auth cookies to break out of "Invalid Refresh Token" loops
request.cookies.getAll().forEach((cookie) => {
if (cookie.name.startsWith('sb-')) {
redirectResponse.cookies.delete(cookie.name);
}
});
return redirectResponse;
}
// Normal case: user not authenticated, redirect to login
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', getSafeRedirectPath(pathname));
return NextResponse.redirect(loginUrl);
}
} catch {
// Catch any unexpected errors during auth check
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', getSafeRedirectPath(pathname));
const redirectResponse = NextResponse.redirect(loginUrl);
// Safety clear cookies here too
request.cookies.getAll().forEach((cookie) => {
if (cookie.name.startsWith('sb-')) {
redirectResponse.cookies.delete(cookie.name);
}
});
return redirectResponse;
}
return response;
}
export const config = {
matcher: [
/*
* Match all request paths except:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - public folder
*/
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
};