fix: auth bypass, SPA serving, and provider improvements (#97)

* feat(chat): polish syntax highlighting and tool payload rendering (#94)

* [verified] feat(chat): polish syntax highlighting and tool payload rendering

* [verified] fix(chat): tighten large tool payload rendering

* docs: update data volume path in Docker docs

Align documentation with docker-compose.yml change:
hermes-web-ui-data -> hermes-web-ui, /app/dist/data -> /root/.hermes-web-ui

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: bundle server build and restructure service modules

- Add build-server.mjs script for standalone server compilation
- Add logger service with structured output
- Restructure auth, gateway-manager, hermes-cli, hermes services
- Update docker-compose volume mount path
- Update tsconfig and entry point for bundled server

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: separate controllers from routes and centralize route registration

- Extract business logic from route handlers into controllers/
- Add centralized route registry in routes/index.ts with public/auth/protected layers
- Replace global auth whitelist with sequential middleware registration
- Extract shared helpers to services/config-helpers.ts
- Allow custom provider name to be user-editable in ProviderFormModal
- Deduplicate custom providers by poolKey instead of base_url in getAvailable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: auth bypass via path case, SPA serving, and provider improvements

- Fix auth bypass: path case-insensitive check for /api, /v1, /upload
- Fix SPA returning 401: skip auth for non-API paths (static files)
- Fix profile switch: use local loading state instead of shared store ref
- Auto-append /v1 to base_url when fetching models (frontend + backend)
- Guard .env writing to built-in providers only
- Add builtin field to provider presets, enable base_url input in form
- Print auth token to console on startup (pino only writes to file)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Zhicheng Han <43314240+hanzckernel@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-04-21 12:35:48 +08:00
committed by GitHub
co-authored by Claude Opus 4.6 Zhicheng Han
parent 21296a416b
commit 477af66232
65 changed files with 2743 additions and 2621 deletions
+3 -68
View File
@@ -1,73 +1,8 @@
import Router from '@koa/router'
import { resolve } from 'path'
import { readFileSync } from 'fs'
import { getGatewayManager } from './hermes/gateways'
import * as hermesCli from '../services/hermes/hermes-cli'
import { config } from '../config'
function getLocalVersion(): string {
const candidates = [
resolve(__dirname, '../../../package.json'),
resolve(__dirname, '../../../../package.json'),
]
for (const p of candidates) {
try {
return JSON.parse(readFileSync(p, 'utf-8')).version
} catch { }
}
return '0.0.0'
}
const LOCAL_VERSION = getLocalVersion()
let cachedLatestVersion = ''
export async function checkLatestVersion(): Promise<void> {
try {
const res = await fetch('https://registry.npmjs.org/hermes-web-ui/latest', {
signal: AbortSignal.timeout(5000),
headers: { 'Cache-Control': 'no-cache' },
})
if (res.ok) {
const data = await res.json()
const latest = data.version || ''
if (latest && latest !== cachedLatestVersion) {
cachedLatestVersion = latest
if (latest !== LOCAL_VERSION) {
console.log(`⬆ New version available: v${LOCAL_VERSION} → v${latest}`)
}
}
}
} catch { }
}
export function startVersionCheck(): void {
checkLatestVersion()
setInterval(checkLatestVersion, 60 * 60 * 1000)
}
import * as ctrl from '../controllers/health'
export const healthRoutes = new Router()
healthRoutes.get('/health', async (ctx) => {
const raw = await hermesCli.getVersion()
const hermesVersion = raw.split('\n')[0].replace('Hermes Agent ', '') || ''
healthRoutes.get('/health', ctrl.healthCheck)
let gatewayOk = false
try {
const mgr = getGatewayManager()
const upstream = mgr?.getUpstream() || config.upstream
const res = await fetch(`${upstream.replace(/\/$/, '')}/health`, {
signal: AbortSignal.timeout(5000),
})
gatewayOk = res.ok
} catch { }
ctx.body = {
status: gatewayOk ? 'ok' : 'error',
platform: 'hermes-agent',
version: hermesVersion,
gateway: gatewayOk ? 'running' : 'stopped',
webui_version: LOCAL_VERSION,
webui_latest: cachedLatestVersion,
webui_update_available: cachedLatestVersion && cachedLatestVersion !== LOCAL_VERSION,
}
})
export { startVersionCheck } from '../controllers/health'