feat: 添加 cookie 认证支持,增强中间件的身份验证功能

This commit is contained in:
feie9454 2025-11-23 09:58:18 +08:00
parent 17a1acfe13
commit fd68c8a452
2 changed files with 114 additions and 8 deletions

View File

@ -2,7 +2,8 @@ export const config = {
port: process.env.PORT || 3000,
auth: {
username: process.env.AUTH_USERNAME || 'admin',
password: process.env.AUTH_PASSWORD || 'password'
password: process.env.AUTH_PASSWORD || 'password',
secret: process.env.AUTH_SECRET || 'winupdate-neo-secret-key'
},
// For file upload settings
upload: {

View File

@ -1,7 +1,89 @@
import { NextRequest, NextResponse } from 'next/server'
import { config } from './lib/config'
import { config as appConfig } from './lib/config'
export function middleware(req: NextRequest) {
const AUTH_COOKIE_NAME = 'auth_token'
const SECRET_KEY = appConfig.auth.secret
// Helper to generate a random nonce
function generateNonce() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
}
// Helper to create a signed token (simplified JWT-like structure)
async function createToken(username: string) {
const header = { alg: 'HS256', typ: 'JWT' }
const payload = {
sub: username,
iat: Date.now(),
exp: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
nonce: generateNonce() // Ensure token is different every time
}
const encodedHeader = btoa(JSON.stringify(header))
const encodedPayload = btoa(JSON.stringify(payload))
const data = `${encodedHeader}.${encodedPayload}`
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(appConfig.auth.secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
)
const signature = await crypto.subtle.sign(
'HMAC',
key,
new TextEncoder().encode(data)
)
// Convert signature to base64
const signatureArray = Array.from(new Uint8Array(signature))
const encodedSignature = btoa(String.fromCharCode.apply(null, signatureArray))
return `${data}.${encodedSignature}`
}
// Helper to verify token
async function verifyToken(token: string) {
try {
const parts = token.split('.')
if (parts.length !== 3) return false
const [encodedHeader, encodedPayload, encodedSignature] = parts
const data = `${encodedHeader}.${encodedPayload}`
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(SECRET_KEY),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const signature = new Uint8Array(
atob(encodedSignature).split('').map(c => c.charCodeAt(0))
)
const isValid = await crypto.subtle.verify(
'HMAC',
key,
signature,
new TextEncoder().encode(data)
)
if (!isValid) return false
const payload = JSON.parse(atob(encodedPayload))
if (payload.exp < Date.now()) return false
return true
} catch (e) {
return false
}
}
export async function middleware(req: NextRequest) {
const url = new URL(req.url)
// Skip auth for certain paths (same logic as original Express app)
@ -21,7 +103,16 @@ export function middleware(req: NextRequest) {
return NextResponse.next()
}
// For all other paths, require authentication
// 1. Try Cookie Authentication
const authCookie = req.cookies.get(AUTH_COOKIE_NAME)
if (authCookie) {
const isValid = await verifyToken(authCookie.value)
if (isValid) {
return NextResponse.next()
}
}
// 2. Fallback to Basic Auth
const authHeader = req.headers.get('authorization')
if (!authHeader) {
@ -34,11 +125,25 @@ export function middleware(req: NextRequest) {
}
try {
const auth = Buffer.from(authHeader.split(' ')[1], 'base64').toString()
// Use atob for Edge compatibility
const authValue = authHeader.split(' ')[1]
const auth = atob(authValue)
const [username, password] = auth.split(':')
if (username === config.auth.username && password === config.auth.password) {
return NextResponse.next()
if (username === appConfig.auth.username && password === appConfig.auth.password) {
const response = NextResponse.next()
// Set auth cookie on successful Basic Auth
const token = await createToken(username)
response.cookies.set(AUTH_COOKIE_NAME, token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24, // 1 day
path: '/'
})
return response
} else {
return new NextResponse('Authentication failed', {
status: 401,
@ -58,7 +163,7 @@ export function middleware(req: NextRequest) {
}
// 配置中间件匹配的路径
export const config_middleware = {
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with: