73 lines
2.4 KiB
TypeScript
73 lines
2.4 KiB
TypeScript
import { NextResponse } from 'next/server'
|
||
import type { NextRequest } from 'next/server'
|
||
|
||
function decodeJwtPayload(token: string): Record<string, unknown> | null {
|
||
try {
|
||
const parts = token.split('.')
|
||
if (parts.length !== 3) return null
|
||
let payload = parts[1].replace(/-/g, '+').replace(/_/g, '/')
|
||
while (payload.length % 4) payload += '='
|
||
return JSON.parse(atob(payload))
|
||
} catch { return null }
|
||
}
|
||
|
||
function isValidPayload(payload: Record<string, unknown> | null): boolean {
|
||
if (!payload) return false
|
||
return !(payload.exp && (payload.exp as number) < Math.floor(Date.now() / 1000))
|
||
}
|
||
|
||
function noCache(response: NextResponse) {
|
||
response.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||
return response
|
||
}
|
||
|
||
export function middleware(request: NextRequest) {
|
||
const { pathname } = request.nextUrl
|
||
|
||
// 登录页:已登录用户自动跳转首页
|
||
if (pathname === '/login') {
|
||
const token = request.cookies.get('tlyq_session')?.value
|
||
const payload = token ? decodeJwtPayload(token) : null
|
||
if (isValidPayload(payload)) {
|
||
return NextResponse.redirect(new URL('/', request.url))
|
||
}
|
||
return NextResponse.next()
|
||
}
|
||
|
||
// 设置密码/API 路径放行(API 路由自行验证)
|
||
if (pathname === '/setup-password' || pathname.startsWith('/api/auth/') || pathname.startsWith('/api/admin/')) {
|
||
return NextResponse.next()
|
||
}
|
||
// /admin 管理页面需要认证
|
||
if (pathname.startsWith('/admin')) {
|
||
const token = request.cookies.get('tlyq_session')?.value
|
||
const payload = token ? decodeJwtPayload(token) : null
|
||
if (!isValidPayload(payload)) {
|
||
return NextResponse.redirect(new URL('/login', request.url))
|
||
}
|
||
return noCache(NextResponse.next())
|
||
}
|
||
|
||
// 静态资源放行(已有路径哈希,允许缓存)
|
||
if (pathname.startsWith('/_next/') || pathname === '/favicon.ico') {
|
||
return NextResponse.next()
|
||
}
|
||
|
||
const token = request.cookies.get('tlyq_session')?.value
|
||
const payload = token ? decodeJwtPayload(token) : null
|
||
|
||
if (isValidPayload(payload)) {
|
||
const response = NextResponse.next()
|
||
response.cookies.set('session', JSON.stringify({ username: payload!.username }), {
|
||
httpOnly: true,
|
||
sameSite: 'lax',
|
||
path: '/',
|
||
})
|
||
return noCache(response)
|
||
}
|
||
|
||
return noCache(NextResponse.redirect(new URL('/login', request.url)))
|
||
}
|
||
|
||
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico|public).*)'] }
|