feat: add Vitest testing framework, fix proxy auth stripping and 401 handling
- Set up Vitest with jsdom for client tests, node for server tests - Add tests for auth service, proxy handler, API client, and profiles store - Strip Authorization header in proxy to prevent web-ui token leaking to gateway - Distinguish local BFF vs proxied gateway 401s to avoid false logouts - Remove unused hero.png asset Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,8 +41,13 @@ export async function request<T>(path: string, options: RequestInit = {}): Promi
|
||||
|
||||
const res = await fetch(url, { ...options, headers })
|
||||
|
||||
// Global 401 handler — clear auth and redirect to login
|
||||
if (res.status === 401) {
|
||||
// Global 401 handler — only redirect to login for local BFF endpoints
|
||||
// Proxied gateway requests should not trigger logout
|
||||
const isLocalBff = !path.startsWith('/api/hermes/v1/') &&
|
||||
!path.startsWith('/api/hermes/jobs') &&
|
||||
!path.startsWith('/api/hermes/skills')
|
||||
|
||||
if (res.status === 401 && isLocalBff) {
|
||||
clearApiKey()
|
||||
if (router.currentRoute.value.name !== 'login') {
|
||||
router.replace({ name: 'login' })
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
@@ -8,27 +8,22 @@ export async function proxy(ctx: Context) {
|
||||
// /api/hermes/* -> /api/* (upstream uses /api/ prefix)
|
||||
const upstreamPath = ctx.path.replace(/^\/api\/hermes\/v1/, '/v1').replace(/^\/api\/hermes/, '/api')
|
||||
const url = `${upstream}${upstreamPath}${ctx.search || ''}`
|
||||
console.log(`[PROXY] ${ctx.method} ${ctx.path} -> ${url}`)
|
||||
|
||||
// Build headers — forward most, strip browser-specific ones
|
||||
// Build headers — forward most, strip browser/web-ui specific ones
|
||||
const headers: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(ctx.headers)) {
|
||||
if (value == null) continue
|
||||
const lower = key.toLowerCase()
|
||||
if (lower === 'host') {
|
||||
headers['host'] = new URL(upstream).host
|
||||
} else if (lower !== 'origin' && lower !== 'referer' && lower !== 'connection') {
|
||||
} else if (lower === 'authorization' || lower === 'origin' || lower === 'referer' || lower === 'connection') {
|
||||
continue
|
||||
} else {
|
||||
const v = Array.isArray(value) ? value[0] : value
|
||||
if (v) headers[key] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Add SSE-friendly headers
|
||||
if (ctx.path.match(/\/events$/)) {
|
||||
headers['x-accel-buffering'] = 'no'
|
||||
headers['cache-control'] = 'no-cache'
|
||||
}
|
||||
|
||||
try {
|
||||
// Build request body from raw body
|
||||
let body: string | undefined
|
||||
@@ -43,20 +38,14 @@ export async function proxy(ctx: Context) {
|
||||
})
|
||||
|
||||
// Set response headers
|
||||
const resHeaders: Record<string, string> = {}
|
||||
res.headers.forEach((value, key) => {
|
||||
const lower = key.toLowerCase()
|
||||
if (lower !== 'transfer-encoding' && lower !== 'connection') {
|
||||
resHeaders[key] = value
|
||||
ctx.set(key, value)
|
||||
}
|
||||
})
|
||||
if (ctx.path.match(/\/events$/)) {
|
||||
resHeaders['x-accel-buffering'] = 'no'
|
||||
resHeaders['cache-control'] = 'no-cache'
|
||||
}
|
||||
|
||||
ctx.status = res.status
|
||||
ctx.set(resHeaders)
|
||||
|
||||
// Stream response body
|
||||
if (res.body) {
|
||||
|
||||
Reference in New Issue
Block a user