-
Notifications
You must be signed in to change notification settings - Fork 47
Expand file tree
/
Copy pathmiddleware.ts
More file actions
163 lines (143 loc) · 4.73 KB
/
Copy pathmiddleware.ts
File metadata and controls
163 lines (143 loc) · 4.73 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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { NextRequest, NextResponse } from "next/server";
import { verifyJWT, sha256Hex } from "@/lib/auth";
import { neon } from "@neondatabase/serverless";
// Lazy initialize sql only when needed
let sql: ReturnType<typeof neon> | null = null;
function getSql() {
if (!sql && process.env.DATABASE_URL) {
sql = neon(process.env.DATABASE_URL!);
}
return sql;
}
/**
* Next.js Middleware for Stellar Wallet Authentication
*
* This middleware runs on every request and enforces authentication
* for protected routes. It verifies JWT tokens and maintains session integrity.
*
* Protected Routes:
* - /api/snippets (POST, PUT, DELETE)
* - /api/profile/*
* - /dashboard/*
*/
export async function middleware(req: NextRequest) {
const pathname = req.nextUrl.pathname;
// Define protected routes that require authentication
const protectedRoutes = [
"/api/snippets", // All snippet operations
"/api/profile", // Profile routes
"/dashboard", // Dashboard routes
"/api/logs", // Activity logs (auth required for all methods)
"/api/favorites", // Favorites (auth required for all methods)
];
// Check if current route is protected
const isProtectedRoute = protectedRoutes.some((route) =>
pathname.startsWith(route),
);
// If not a protected route, allow request to proceed
if (!isProtectedRoute) {
return NextResponse.next();
}
// For POST requests to /api/snippets (create), verify authentication
if (pathname === "/api/snippets" && req.method === "POST") {
return verifyAuthenticationMiddleware(req);
}
// /api/logs requires auth on ALL methods (including GET)
if (pathname.startsWith("/api/logs")) {
return verifyAuthenticationMiddleware(req);
}
// /api/favorites requires auth on ALL methods (including GET)
if (pathname.startsWith("/api/favorites")) {
return verifyAuthenticationMiddleware(req);
}
// For other protected routes, verify authentication
if (isProtectedRoute) {
// Only enforce auth on non-GET requests (allow public read access)
if (req.method !== "GET") {
return verifyAuthenticationMiddleware(req);
}
}
return NextResponse.next();
}
/**
* Verify JWT token from Authorization header
*/
async function verifyAuthenticationMiddleware(req: NextRequest) {
try {
const authHeader = req.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return NextResponse.json(
{
error: "Unauthorized - Missing or invalid Authorization header",
details:
"Please include 'Authorization: Bearer <token>' in your request headers",
},
{ status: 401 },
);
}
const token = authHeader.substring(7); // Remove "Bearer " prefix
// Verify JWT format and signature
const verification = await verifyJWT(token);
if (!verification.valid) {
return NextResponse.json(
{
error: "Unauthorized - Invalid or expired token",
details: verification.error || "Token verification failed",
},
{ status: 401 },
);
}
// Verify session exists in database (additional security check)
try {
const db = getSql();
if (db) {
const tokenHash = await sha256Hex(token);
const session = await db`
SELECT * FROM auth_sessions
WHERE token_hash = ${tokenHash}
AND expires_at > now()
`;
if (session.length === 0) {
return NextResponse.json(
{
error: "Unauthorized - Session not found or expired",
details: "Your session has expired. Please authenticate again.",
},
{ status: 401 },
);
}
}
} catch (dbError) {
console.error("Database session verification error:", dbError);
// If database check fails, still allow request but log the error
// This prevents total service failure if database is temporarily unavailable
}
// Authentication successful, proceed with request
// The payload is available in req.auth if needed in handlers
return NextResponse.next();
} catch (error) {
console.error("Middleware authentication error:", error);
return NextResponse.json(
{
error: "Internal server error during authentication",
},
{ status: 500 },
);
}
}
/**
* Matcher configuration for middleware
* This specifies which routes should be processed by this middleware
*/
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|public).*)",
],
};