-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.ts
More file actions
126 lines (108 loc) · 4.19 KB
/
proxy.ts
File metadata and controls
126 lines (108 loc) · 4.19 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
import { NextRequest, NextResponse } from 'next/server';
import { SIGN_IN_ATTEMPT_EMAIL_COOKIE, getSessionFromCookie } from '@/lib/auth/utils';
/**
* Create a route matcher function
* Supports exact matches and wildcard patterns with (.*)
*
* @example
* const isPublic = createRouteMatcher(['/home', '/sign-in(.*)']);
* isPublic('/sign-in/verify') // true
*/
function createRouteMatcher(routes: string[]) {
return (request: NextRequest) => {
const pathname = request.nextUrl.pathname;
return routes.some((route) => {
if (route === pathname) return true;
if (route.includes('(.*)')) {
const baseRoute = route.replace('(.*)', '');
return pathname === baseRoute || pathname.startsWith(baseRoute + '/');
}
return false;
});
};
}
// Define public routes that don't require authentication
const isPublicRoute = createRouteMatcher([
'/',
'/home',
'/sign-in(.*)',
'/sign-up(.*)',
'/privacy',
'/terms',
'/share(.*)', // Public deck sharing
// SEO and metadata routes
'/robots.txt',
'/sitemap.xml',
'/favicon.ico',
'/favicon.svg',
'/favicon-96x96.png',
'/apple-touch-icon.png',
'/opengraph-image.png',
]);
const isOnboardingRoute = createRouteMatcher(['/onboarding']);
const isRootRoute = createRouteMatcher(['/']);
const isAdminRoute = createRouteMatcher(['/admin(.*)']);
const isProtectedRoute = createRouteMatcher(['/org(.*)', '/settings(.*)', '/admin(.*)']);
const isApiRoute = createRouteMatcher(['/api(.*)']);
const isSignInVerifyRoute = createRouteMatcher([
'/sign-in/verify',
'/sign-up/verify-email-address',
]);
const isAuthRoute = createRouteMatcher(['/sign-in(.*)', '/sign-up(.*)']);
export async function proxy(req: NextRequest) {
// API routes handle their own authentication via protectedProcedures
if (isApiRoute(req)) return NextResponse.next();
const sessionData = getSessionFromCookie(req);
const isAuthenticated = !!sessionData?.session;
const activeOrganizationSlug = sessionData?.session?.activeOrganizationSlug ?? null;
// Protect /sign-in/verify - requires active sign-in attempt cookie
if (isSignInVerifyRoute(req)) {
const attemptEmail = req.cookies.get(SIGN_IN_ATTEMPT_EMAIL_COOKIE)?.value;
if (attemptEmail) return NextResponse.next();
return NextResponse.redirect(new URL('/sign-in', req.url));
}
// Admin route authorization - must be super admin (authentication checked below)
if (isAuthenticated && isAdminRoute(req)) {
if (sessionData.user.role !== 'admin') {
const redirectUrl = activeOrganizationSlug
? `/org/${activeOrganizationSlug}/dashboard`
: '/onboarding';
return NextResponse.redirect(new URL(redirectUrl, req.url));
}
return NextResponse.next();
}
// If user has an organization, redirect to dashboard
if (isAuthenticated && activeOrganizationSlug) {
if (isProtectedRoute(req)) return NextResponse.next();
if (isPublicRoute(req) && !isRootRoute(req) && !isAuthRoute(req)) return NextResponse.next();
return NextResponse.redirect(new URL(`/org/${activeOrganizationSlug}/dashboard`, req.url));
}
// Allow authenticated users to access onboarding
if (isAuthenticated && isOnboardingRoute(req)) {
return NextResponse.next();
}
// Redirect unauthenticated users trying to access protected routes
if (!isAuthenticated && !isPublicRoute(req)) {
const signInUrl = new URL('/sign-in', req.url);
signInUrl.searchParams.set('redirect', req.nextUrl.pathname);
return NextResponse.redirect(signInUrl);
}
// Redirect authenticated users without organization to onboarding
if (isAuthenticated && !activeOrganizationSlug) {
// Prevent redirect loop - only redirect if not already on onboarding
if (!isOnboardingRoute(req)) {
return NextResponse.redirect(new URL('/onboarding', req.url));
}
return NextResponse.next();
}
// Allow all other requests for authenticated users
return NextResponse.next();
}
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
};