forked from ava-labs/builders-hub
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathproxy.ts
More file actions
149 lines (132 loc) · 5.47 KB
/
Copy pathproxy.ts
File metadata and controls
149 lines (132 loc) · 5.47 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
import { getToken } from "next-auth/jwt";
import { NextRequestWithAuth, withAuth } from "next-auth/middleware";
import { NextMiddlewareResult } from "next/dist/server/web/types";
import { NextRequest, NextResponse } from "next/server";
export async function proxy(req: NextRequest) {
const pathname = req.nextUrl.pathname;
const response = NextResponse.next();
response.headers.set("Access-Control-Allow-Origin", "*");
response.headers.set(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS"
);
response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization"
);
if (req.method === "OPTIONS") {
return new Response(null, { status: 204 });
}
// Content negotiation: serve markdown when Accept: text/markdown is requested
const contentPrefixes = ['/docs/', '/academy/', '/blog/', '/integrations/'];
const isContentPath = contentPrefixes.some(prefix => pathname.startsWith(prefix));
const acceptHeader = req.headers.get('accept') || '';
const wantsMarkdown = acceptHeader.includes('text/markdown');
// Protected academy sub-paths must NOT skip auth
const protectedAcademySuffixes = ['/get-certificate', '/certificate'];
const isProtectedAcademyPath = pathname.startsWith('/academy/') &&
protectedAcademySuffixes.some(suffix => pathname.endsWith(suffix));
if (wantsMarkdown && isContentPath && !isProtectedAcademyPath) {
const apiUrl = new URL(`/api/raw${pathname}`, req.url);
const rewriteResponse = NextResponse.rewrite(apiUrl);
rewriteResponse.headers.set('Vary', 'Accept');
return rewriteResponse;
}
// For content paths without markdown request, add Vary header and pass through
if (isContentPath && !isProtectedAcademyPath) {
const contentResponse = NextResponse.next();
contentResponse.headers.set('Vary', 'Accept');
return contentResponse;
}
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
const isAuthenticated = !!token;
const isLoginPage = pathname === "/login";
const isShowCase = pathname.startsWith("/showcase");
const isSendNotifications = pathname.startsWith("/send-notifications");
const custom_attributes = token?.custom_attributes as string[] ?? []
const protectedPaths = [
"/hackathons/registration-form",
"/hackathons/project-submission",
"/events/registration-form",
"/events/project-submission",
"/events/edit",
"/showcase",
"/send-notifications",
"/profile",
"/student-launchpad",
"/grants/"
];
const isProtectedPath = protectedPaths.some(path => pathname.startsWith(path));
// Protect routes: block unauthenticated access to protected paths without redirecting
// The client-side component (AutoLoginModalTrigger) will detect this and show the login modal
if (!isAuthenticated && !isLoginPage && isProtectedPath) {
// If it's /events/edit, redirect to home
if (pathname.startsWith("/hackathons/edit") || pathname.startsWith("/events/edit")) {
return NextResponse.redirect(new URL("/", req.url));
}
// Block access by setting a header, but allow the request to continue
// The page will render but the client will show the login modal
const blockedResponse = NextResponse.next();
blockedResponse.headers.set("x-auth-required", "true");
return blockedResponse;
}
if (isAuthenticated) {
if (isLoginPage)
return NextResponse.redirect(new URL("/", req.url));
if (isShowCase && !custom_attributes.includes('showcase'))
return NextResponse.redirect(new URL("/events", req.url))
if (isSendNotifications && !(custom_attributes.includes('devrel') || custom_attributes.includes('notify_event')))
return NextResponse.redirect(new URL("/", req.url))
// Protect hackathons/edit and events/edit routes - only team1-admin and hackathonCreator can access
if (pathname.startsWith("/hackathons/edit") || pathname.startsWith("/events/edit")) {
const hasRequiredPermissions = custom_attributes.includes("team1-admin") ||
custom_attributes.includes("hackathonCreator") ||
custom_attributes.includes("devrel");
if (!hasRequiredPermissions) {
return NextResponse.redirect(new URL("/", req.url));
}
}
// For authenticated users on protected paths, use withAuth to ensure protection
if (isProtectedPath) {
return withAuth(
(authReq: NextRequestWithAuth): NextMiddlewareResult => {
return NextResponse.next();
},
{
pages: {
signIn: "/login",
},
callbacks: {
authorized: ({ token }) => !!token,
}
}
)(req as NextRequestWithAuth, {} as any);
}
}
// For non-protected paths or unauthenticated users on non-protected paths, allow access
return NextResponse.next();
}
export const config = {
matcher: [
// Auth-protected paths
"/hackathons/registration-form/:path*",
"/hackathons/project-submission/:path*",
"/hackathons/edit/:path*",
"/events/registration-form/:path*",
"/events/project-submission/:path*",
"/events/edit/:path*",
"/showcase/:path*",
"/send-notifications/:path*",
"/login/:path*",
"/profile/:path*",
"/academy/:path*/get-certificate",
"/academy/:path*/certificate",
"/console/utilities/data-api-keys",
"/grants/:path+",
// Content paths for Accept: text/markdown negotiation
"/docs/:path*",
"/academy/:path*",
"/blog/:path*",
"/integrations/:path*",
],
};