-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmiddleware.js
More file actions
95 lines (82 loc) · 4.36 KB
/
middleware.js
File metadata and controls
95 lines (82 loc) · 4.36 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
// middleware.js - Protect dashboard routes + security headers
import { NextResponse } from 'next/server';
const APP_URL = process.env.NEXT_PUBLIC_APP_URL || 'https://bucketurl.onrender.com';
const CANONICAL_URL = 'https://bucketurl.antqr.xyz';
const OLD_HOSTS = ['bucketurl.onrender.com', 'bucketurl.vercel.app'];
export function middleware(request) {
const { pathname } = request.nextUrl;
// ── Redirect old hosts → custom domain ─────────────────────────────
if (OLD_HOSTS.includes(request.headers.get('host'))) {
const destination = `${CANONICAL_URL}${pathname}${request.nextUrl.search}`;
return NextResponse.redirect(destination, { status: 301 });
}
// Check for Firebase auth session cookie
const session = request.cookies.get('__session')?.value;
// Protected routes
if (pathname.startsWith('/dashboard') || pathname.startsWith('/links') || pathname.startsWith('/teams') || pathname.startsWith('/settings') || pathname.startsWith('/billing')) {
if (!session) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', pathname);
return NextResponse.redirect(loginUrl);
}
}
// Redirect logged-in users away from auth pages
if ((pathname === '/login' || pathname === '/signup') && session) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
const response = NextResponse.next();
// ── Security headers ──────────────────────────────────────────────────
// Prevent "open in app" prompts and ensure OG images load correctly
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'SAMEORIGIN');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
// Auth pages: need Google/Firebase domains whitelisted
const isAuthPage = pathname === '/login' || pathname === '/signup' || pathname === '/forgot-password';
if (isAuthPage) {
response.headers.set(
'Content-Security-Policy',
[
`default-src 'self' ${APP_URL}`,
// Google Identity Services + Firebase Auth scripts
`script-src 'self' 'unsafe-inline' 'unsafe-eval' https://accounts.google.com https://apis.google.com https://*.firebaseapp.com https://*.firebase.com`,
`style-src 'self' 'unsafe-inline' https://fonts.googleapis.com`,
`font-src 'self' https://fonts.gstatic.com`,
`img-src 'self' data: blob: https: ${APP_URL}`,
// Firebase auth + Google OAuth API calls
`connect-src 'self' https: wss: https://accounts.google.com https://oauth2.googleapis.com https://*.googleapis.com https://*.firebaseio.com https://securetoken.googleapis.com https://identitytoolkit.googleapis.com`,
// Google OAuth popup/redirect frame
`frame-src 'self' https://accounts.google.com https://*.firebaseapp.com`,
`frame-ancestors 'none'`,
].join('; ')
);
} else if (!pathname.startsWith('/dashboard') && !pathname.startsWith('/api')) {
// For slug pages: allow images from our domain + cloudinary + flagcdn
// This prevents browsers from blocking OG image loads
response.headers.set(
'Content-Security-Policy',
[
`default-src 'self' ${APP_URL}`,
`script-src 'self' 'unsafe-inline' 'unsafe-eval'`,
`style-src 'self' 'unsafe-inline' https://fonts.googleapis.com`,
`font-src 'self' https://fonts.gstatic.com`,
`img-src 'self' data: blob: https: ${APP_URL}`,
`connect-src 'self' https: wss:`,
`frame-ancestors 'none'`,
].join('; ')
);
}
return response;
}
export const config = {
matcher: [
'/dashboard/:path*',
'/links/:path*',
'/teams/:path*',
'/settings/:path*',
'/billing/:path*',
'/login',
'/signup',
// Match short link slug pages (single path segment, not a known route)
'/((?!_next/static|_next/image|favicon.ico|logo.png|og-default.png|robots.txt|sitemap.xml|api/).*)',
],
};