-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.ts
More file actions
130 lines (114 loc) · 3.58 KB
/
Copy pathproxy.ts
File metadata and controls
130 lines (114 loc) · 3.58 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
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { betterFetch } from "@better-fetch/fetch";
const publicRoutes = [
"/sign-in",
"/sign-up",
"/forgot-password",
"/reset-password",
"/verify-email",
];
const authRoutes = ["/sign-in", "/sign-up"];
type Session = {
user: {
id: string;
email: string;
name: string;
};
session: {
token: string;
expiresAt: string;
};
} | null;
/**
* Add security headers to response
*/
function addSecurityHeaders(response: NextResponse): NextResponse {
// Prevent clickjacking
response.headers.set("X-Frame-Options", "DENY");
// Prevent MIME-type sniffing
response.headers.set("X-Content-Type-Options", "nosniff");
// Control referrer information
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
// XSS protection (legacy browsers)
response.headers.set("X-XSS-Protection", "1; mode=block");
// Permissions Policy - restrict browser features
response.headers.set(
"Permissions-Policy",
"camera=(), microphone=(), geolocation=()"
);
// HSTS - only in production (enforce HTTPS)
if (process.env.NODE_ENV === "production") {
response.headers.set(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains"
);
}
// CSP - prevent framing and restrict form targets
// Note: Next.js requires 'unsafe-inline' for styles, 'unsafe-eval' for dev
response.headers.set(
"Content-Security-Policy",
"frame-ancestors 'none'; form-action 'self';"
);
return response;
}
export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
// Allow API routes and static files
if (
pathname.startsWith("/api/") ||
pathname.startsWith("/_next/") ||
pathname.startsWith("/favicon.ico") ||
pathname.startsWith("/kdf.worker.js")
) {
return addSecurityHeaders(NextResponse.next());
}
// Properly validate session using betterFetch (Better Auth best practice)
const { data: session } = await betterFetch<Session>(
"/api/auth/get-session",
{
baseURL: request.nextUrl.origin,
headers: {
cookie: request.headers.get("cookie") || "",
},
}
);
const isAuthenticated = !!session;
// If authenticated and trying to access auth pages, redirect to dashboard
if (isAuthenticated && authRoutes.includes(pathname)) {
return addSecurityHeaders(
NextResponse.redirect(new URL("/dashboard", request.url))
);
}
// Allow unlock page for authenticated users
if (isAuthenticated && pathname.startsWith("/unlock")) {
return addSecurityHeaders(NextResponse.next());
}
// Allow onboarding routes for authenticated users
if (isAuthenticated && pathname.startsWith("/onboarding")) {
return addSecurityHeaders(NextResponse.next());
}
// If not authenticated and trying to access protected route, redirect to sign-in
if (
!isAuthenticated &&
!publicRoutes.includes(pathname) &&
(pathname.startsWith("/dashboard") || pathname.startsWith("/onboarding"))
) {
const signInUrl = new URL("/sign-in", request.url);
signInUrl.searchParams.set("callbackUrl", pathname);
return addSecurityHeaders(NextResponse.redirect(signInUrl));
}
return addSecurityHeaders(NextResponse.next());
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
};