feat: 添加 cookie 认证支持,增强中间件的身份验证功能
This commit is contained in:
parent
17a1acfe13
commit
fd68c8a452
@ -2,7 +2,8 @@ export const config = {
|
|||||||
port: process.env.PORT || 3000,
|
port: process.env.PORT || 3000,
|
||||||
auth: {
|
auth: {
|
||||||
username: process.env.AUTH_USERNAME || 'admin',
|
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
|
// For file upload settings
|
||||||
upload: {
|
upload: {
|
||||||
|
|||||||
119
middleware.ts
119
middleware.ts
@ -1,7 +1,89 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
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)
|
const url = new URL(req.url)
|
||||||
|
|
||||||
// Skip auth for certain paths (same logic as original Express app)
|
// Skip auth for certain paths (same logic as original Express app)
|
||||||
@ -21,7 +103,16 @@ export function middleware(req: NextRequest) {
|
|||||||
return NextResponse.next()
|
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')
|
const authHeader = req.headers.get('authorization')
|
||||||
|
|
||||||
if (!authHeader) {
|
if (!authHeader) {
|
||||||
@ -34,11 +125,25 @@ export function middleware(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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(':')
|
const [username, password] = auth.split(':')
|
||||||
|
|
||||||
if (username === config.auth.username && password === config.auth.password) {
|
if (username === appConfig.auth.username && password === appConfig.auth.password) {
|
||||||
return NextResponse.next()
|
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 {
|
} else {
|
||||||
return new NextResponse('Authentication failed', {
|
return new NextResponse('Authentication failed', {
|
||||||
status: 401,
|
status: 401,
|
||||||
@ -58,7 +163,7 @@ export function middleware(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 配置中间件匹配的路径
|
// 配置中间件匹配的路径
|
||||||
export const config_middleware = {
|
export const config = {
|
||||||
matcher: [
|
matcher: [
|
||||||
/*
|
/*
|
||||||
* Match all request paths except for the ones starting with:
|
* Match all request paths except for the ones starting with:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user