Files
wish-pool/middleware.ts

141 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { isIpAllowed, getClientIp, isValidIp, isValidIPv6 } from '@/lib/ip-utils'
export function middleware(request: NextRequest) {
// 檢查是否啟用IP白名單
const enableIpWhitelist = process.env.ENABLE_IP_WHITELIST === 'true'
if (!enableIpWhitelist) {
return NextResponse.next()
}
// 獲取客戶端IP - 使用與API相同的邏輯
let clientIp = getClientIp(request)
// 根據你的環境,優先使用 cf-connecting-ip (支持IPv4和IPv6)
const cfConnectingIp = request.headers.get('cf-connecting-ip')
if (cfConnectingIp && cfConnectingIp.trim() !== '') {
const cleanCfIp = cfConnectingIp.trim()
// 检查是否是有效的IPv4或IPv6地址
if (isValidIp(cleanCfIp) || isValidIPv6(cleanCfIp)) {
clientIp = cleanCfIp
console.log(`[Middleware] 使用 cf-connecting-ip: ${clientIp} ${isValidIPv6(clientIp) ? '(IPv6)' : '(IPv4)'}`)
}
}
// 獲取允許的IP列表
const allowedIps = process.env.ALLOWED_IPS || ''
// 調試信息
console.log(`[Middleware] 最終IP檢測: ${clientIp}, 路徑: ${request.nextUrl.pathname}`)
console.log(`[Middleware] 白名單狀態: ${enableIpWhitelist}`)
console.log(`[Middleware] 允許的IP: ${allowedIps}`)
// 檢查IP是否被允許
if (!isIpAllowed(clientIp, allowedIps)) {
// 記錄被拒絕的訪問
console.warn(`[Middleware] Access denied for IP: ${clientIp} - Path: ${request.nextUrl.pathname}`)
console.warn(`[Middleware] 允許的IP列表: ${allowedIps}`)
// 返回403禁止訪問頁面
return new NextResponse(
`
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>訪問被拒絕</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
color: white;
}
.container {
text-align: center;
background: rgba(255, 255, 255, 0.1);
padding: 3rem;
border-radius: 1rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
max-width: 500px;
margin: 1rem;
}
h1 {
font-size: 2rem;
margin-bottom: 1rem;
color: #ff6b6b;
}
p {
font-size: 1.1rem;
line-height: 1.6;
margin-bottom: 1rem;
opacity: 0.9;
}
.ip-info {
background: rgba(255, 255, 255, 0.1);
padding: 1rem;
border-radius: 0.5rem;
margin: 1rem 0;
font-family: 'Courier New', monospace;
}
.contact {
font-size: 0.9rem;
opacity: 0.7;
margin-top: 2rem;
}
</style>
</head>
<body>
<div class="container">
<h1>🚫 訪問被拒絕</h1>
<p>很抱歉您的IP地址不在允許的訪問列表中。</p>
<div class="ip-info">
<strong>您的IP地址</strong><br>
${clientIp}
</div>
<p>如果您認為這是一個錯誤,請聯繫系統管理員。</p>
<div class="contact">
錯誤代碼403 Forbidden<br>
時間:${new Date().toLocaleString('zh-TW')}
</div>
</div>
</body>
</html>
`,
{
status: 403,
headers: {
'Content-Type': 'text/html; charset=utf-8',
},
}
)
}
// IP檢查通過繼續處理請求
return NextResponse.next()
}
// 配置中間件適用的路徑
export const config = {
matcher: [
/*
* 匹配所有路徑,除了:
* - api/ip (IP檢測API允許訪問)
* - api/ip-diagnostic (IP診斷API允許訪問)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - icon.png (icon file)
*/
'/((?!api/ip|api/ip-diagnostic|_next/static|_next/image|favicon.ico|icon.png).*)',
],
}