-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmiddleware.ts
More file actions
105 lines (88 loc) · 3.52 KB
/
Copy pathmiddleware.ts
File metadata and controls
105 lines (88 loc) · 3.52 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
import { NextResponse, type NextRequest } from "next/server";
import { AUTH_SESSION_COOKIE } from "./lib/supabase/server";
/**
* Middleware for authentication and route protection
*
* Flow:
* 1. Check custom auth session (from env-based login)
* 2. Check master password unlock state
* 3. Protect vault routes
* 4. Redirect unauthenticated to /login
* 5. Redirect authenticated but locked to /master-password
*/
export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
// Public routes (no auth required)
const publicRoutes = ["/login", "/register"];
const isPublicRoute = publicRoutes.some((route) => pathname.startsWith(route));
// Check custom auth session
// Safety check: ensure cookies and AUTH_SESSION_COOKIE are available
const authCookie = AUTH_SESSION_COOKIE && request.cookies?.get(AUTH_SESSION_COOKIE);
const isAuthenticated = authCookie?.value === "true";
const unlocked = request.cookies?.get("vault_unlocked")?.value === "true";
// Auth routes (require auth but not master password)
const authRoutes = ["/master-password"];
const isAuthRoute = authRoutes.some((route) => pathname.startsWith(route));
// API routes
const isApiRoute = pathname.startsWith("/api");
// Vault routes (require auth + master password unlock)
// Protect everything except public, auth, and api routes
const isVaultRoute = pathname === "/" || (!isPublicRoute && !isAuthRoute && !isApiRoute);
let response = NextResponse.next();
// If not authenticated and trying to access protected route
if (!isAuthenticated && (isVaultRoute || isAuthRoute)) {
// Check if user just logged in (temporary cookie flag)
const justLoggedIn = request.cookies.get("just_logged_in")?.value === "true";
// If they just logged in, allow access to authRoute (master-password)
if (justLoggedIn && isAuthRoute) {
// Clear the flag and allow access
response.cookies.set("just_logged_in", "", { maxAge: 0 });
return response;
}
// Otherwise redirect to login
const redirectUrl = new URL("/login", request.url);
if (pathname !== "/") {
redirectUrl.searchParams.set("redirect", pathname);
}
return NextResponse.redirect(redirectUrl);
}
// If authenticated but on login/register, check master password status
if (isAuthenticated && isPublicRoute) {
if (!unlocked) {
// Locked - redirect to verification
return NextResponse.redirect(new URL("/master-password", request.url));
}
// Unlocked - redirect to vault
return NextResponse.redirect(new URL("/", request.url));
}
// Check master password unlock for vault routes
if (isAuthenticated && isVaultRoute) {
if (!unlocked) {
// Locked - redirect to verification
return NextResponse.redirect(new URL("/master-password", request.url));
}
}
// Update activity timestamp for authenticated users
if (isAuthenticated && (isVaultRoute || isAuthRoute)) {
response.cookies.set("vault_last_activity", Date.now().toString(), {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 60 * 60, // 1 hour
path: "/",
});
}
return response;
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _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)$).*)",
],
};