remove auth disabled support (#1013)

This commit is contained in:
ekko
2026-05-25 12:49:01 +08:00
committed by GitHub
parent 56c6cf3e2d
commit d03d5e6ac5
13 changed files with 18 additions and 61 deletions
-2
View File
@@ -134,7 +134,6 @@ Unified configuration for **8 platforms** in one page:
- Username/password login with account management in Settings
- Default bootstrap credentials are `admin` / `123456`; users are prompted after login to change the default username and password
- Super administrators can manage users and profile bindings; regular administrators can manage their own account details
- Auth can be disabled with `AUTH_DISABLED=1`
CLI maintenance commands:
@@ -240,7 +239,6 @@ These variables configure Hermes Web UI itself. Provider API keys and Hermes Age
| `HERMES_WEB_UI_HOME` | `~/.hermes-web-ui` | Web UI data home for auth token, credentials, logs, DB, and default uploads. `HERMES_WEBUI_STATE_DIR` is also supported as a compatibility alias. |
| `UPLOAD_DIR` | `$HERMES_WEB_UI_HOME/upload` | Upload root override. Files are stored below profile-scoped subdirectories. |
| `CORS_ORIGINS` | `*` | Koa CORS origin setting. |
| `AUTH_DISABLED` | unset | Set to `1` or `true` to disable Web UI auth. |
| `AUTH_TOKEN` | auto-generated | Explicit bearer token. If unset, Web UI creates one under `HERMES_WEB_UI_HOME`. |
| `PROFILE` | `default` | Startup/default Hermes profile. Runtime requests use the profile selected by the frontend and authorized for the current account. |
| `LOG_LEVEL` | `info` | Server log level. |
-2
View File
@@ -142,7 +142,6 @@
- 用户名/密码登录,并在设置页提供账户管理
- 默认登录名/密码为 `admin` / `123456`;登录后会提示尽快修改默认账户和密码
- 超级管理员可以管理用户和 Profile 绑定;普通管理员只能管理自己的账户信息
- 可通过 `AUTH_DISABLED=1` 禁用认证
CLI 维护命令:
@@ -247,7 +246,6 @@ Web UI 启动后端聊天能力时,会优先使用包含 `run_agent.py` 的源
| `HERMES_WEB_UI_HOME` | `~/.hermes-web-ui` | Web UI 数据目录,用于认证 token、登录凭据、日志、数据库和默认上传目录。兼容支持 `HERMES_WEBUI_STATE_DIR` 作为别名。 |
| `UPLOAD_DIR` | `$HERMES_WEB_UI_HOME/upload` | 覆盖上传根目录。文件会保存在按 Profile 隔离的子目录下。 |
| `CORS_ORIGINS` | `*` | Koa CORS origin 配置。 |
| `AUTH_DISABLED` | 未设置 | 设置为 `1``true` 可关闭 Web UI 认证。 |
| `AUTH_TOKEN` | 自动生成 | 显式指定 bearer token。未设置时,Web UI 会在 `HERMES_WEB_UI_HOME` 下自动生成。 |
| `PROFILE` | `default` | 启动/默认 Hermes profile。运行时请求使用前端当前选择且当前账号有权限访问的 Profile。 |
| `LOG_LEVEL` | `info` | Server 日志级别。 |
+1 -2
View File
@@ -43,8 +43,7 @@ function getToken() {
}
function ensureToken() {
// If AUTH_DISABLED or AUTH_TOKEN is set, let server handle it
if (process.env.AUTH_DISABLED === '1' || process.env.AUTH_DISABLED === 'true') return null
// If AUTH_TOKEN is set, let server handle it.
if (process.env.AUTH_TOKEN) return process.env.AUTH_TOKEN
let token = getToken()
-1
View File
@@ -18,7 +18,6 @@ services:
- HERMES_WEB_UI_MANAGED_GATEWAY=1
- HERMES_WEB_UI_XAI_CALLBACK_BIND_HOST=0.0.0.0
- PATH=/opt/hermes/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
- AUTH_DISABLED=${AUTH_DISABLED:-false}
- HERMES_ALLOW_ROOT_GATEWAY=1
restart: unless-stopped
stdin_open: true
+1 -1
View File
@@ -220,7 +220,7 @@ io(`${baseUrl}/chat-run`, {
})
```
如果未设置 `AUTH_DISABLED=1`服务端会与 Web UI token 比对。
服务端会与 Web UI token 比对。
---
+2 -6
View File
@@ -40,14 +40,11 @@ All key runtime settings are configured from compose variables.
| `HERMES_AGENT_IMAGE` | `nousresearch/hermes-agent:latest` | Hermes Agent base image (used only during build) |
| `WEBUI_IMAGE` | `hermes-web-ui-local:latest` | Web UI image (set to `ekkoye8888/hermes-web-ui` to use pre-built) |
| `HERMES_DATA_DIR` | `./hermes_data` | Hermes runtime data directory |
| `AUTH_DISABLED` | `false` | Set to `true` to disable login authentication |
Override variables directly from shell:
```bash
PORT=16060 \
AUTH_DISABLED=true \
docker compose up -d
PORT=16060 docker compose up -d
```
Or create a `.env` file in the project root:
@@ -55,7 +52,6 @@ Or create a `.env` file in the project root:
```
WEBUI_IMAGE=ekkoye8888/hermes-web-ui
PORT=6060
AUTH_DISABLED=false
```
## Data Persistence
@@ -67,7 +63,7 @@ AUTH_DISABLED=false
- Hermes data persists in `./hermes_data`, mapped to `/home/agent/.hermes` in the container.
- Web UI data persists in `./hermes_data/hermes-web-ui/`, mapped to `/home/agent/.hermes-web-ui` in the container.
- When `AUTH_DISABLED=false`, the auth token is auto-generated on first run and printed to container logs.
- The auth token is auto-generated on first run and printed to container logs.
- Deleting the token file and restarting will generate a new one.
## Port Mapping
-1
View File
@@ -16,7 +16,6 @@ import { homedir } from 'os'
* - UPLOAD_DIR: Upload directory override. Default: join(HERMES_WEB_UI_HOME, 'upload').
*
* Auth:
* - AUTH_DISABLED: Set to 1 or true to disable Web UI auth.
* - AUTH_TOKEN: Explicit bearer token. If unset, Web UI stores an auto-generated token under HERMES_WEB_UI_HOME.
*
* Runtime behavior:
+1 -1
View File
@@ -99,7 +99,7 @@ export async function login(ctx: Context) {
token = await issueUserJwt(user)
} catch (err: any) {
ctx.status = 500
ctx.body = { error: err?.message || 'Auth is disabled on this server' }
ctx.body = { error: err?.message || 'Failed to issue login token' }
return
}
+4 -10
View File
@@ -60,7 +60,7 @@ function safeEqual(a: string, b: string): boolean {
}
}
async function getJwtSecret(): Promise<string | null> {
async function getJwtSecret(): Promise<string> {
return process.env.AUTH_JWT_SECRET || await getToken()
}
@@ -78,7 +78,7 @@ const SERVER_TOKEN_MEDIA_PATHS = new Set([
async function allowServerTokenForMedia(ctx: Context, token: string): Promise<boolean> {
if (!token || !SERVER_TOKEN_MEDIA_PATHS.has(ctx.path)) return false
const serverToken = await getToken()
if (!serverToken || token !== serverToken) return false
if (token !== serverToken) return false
ctx.state.serverTokenAuth = true
return true
}
@@ -128,7 +128,6 @@ export function verifyUserJwt(token: string, secret: string, now = Date.now()):
export async function issueUserJwt(user: Pick<UserRecord, 'id' | 'username' | 'role'>): Promise<string> {
const secret = await getJwtSecret()
if (!secret) throw new Error('Auth is disabled on this server')
return signUserJwt(user, secret)
}
@@ -146,7 +145,6 @@ export function toAuthenticatedUser(user: Pick<UserRecord, 'id' | 'username' | '
export async function authenticateUserToken(token: string): Promise<AuthenticatedUser | null> {
const secret = await getJwtSecret()
if (!secret) return null
const payload = token ? verifyUserJwt(token, secret) : null
if (!payload) return null
@@ -157,7 +155,8 @@ export async function authenticateUserToken(token: string): Promise<Authenticate
}
export async function isAuthEnabled(): Promise<boolean> {
return !!await getJwtSecret()
await getJwtSecret()
return true
}
export async function requireUserJwt(ctx: Context, next: Next): Promise<void> {
@@ -167,11 +166,6 @@ export async function requireUserJwt(ctx: Context, next: Next): Promise<void> {
}
const secret = await getJwtSecret()
if (!secret) {
await next()
return
}
const token = requestToken(ctx)
const payload = token ? verifyUserJwt(token, secret) : null
if (!payload) {
+2 -11
View File
@@ -12,13 +12,9 @@ function generateToken(): string {
}
/**
* Get or create the auth token. Returns null if auth is disabled.
* Get or create the auth token.
*/
export async function getToken(): Promise<string | null> {
if (process.env.AUTH_DISABLED === '1' || process.env.AUTH_DISABLED === 'true') {
return null
}
export async function getToken(): Promise<string> {
if (process.env.AUTH_TOKEN) {
return process.env.AUTH_TOKEN
}
@@ -45,11 +41,6 @@ export async function getToken(): Promise<string | null> {
*/
export function requireAuth(token: string | null) {
return async (ctx: any, next: () => Promise<void>) => {
if (!token) {
await next()
return
}
const auth = ctx.headers.authorization || ''
const provided = auth.startsWith('Bearer ')
? auth.slice(7)
-1
View File
@@ -133,7 +133,6 @@ export default {
envVars: {
title: 'Environment Variables',
rows: [
['AUTH_DISABLED', 'Set to "1" to disable authentication'],
['AUTH_TOKEN', 'Custom auth token (overrides auto-generated)'],
['PORT', 'Server listen port (default: 8648)'],
['BIND_HOST', 'Server bind host (default: 0.0.0.0). Set :: explicitly to enable IPv6 listening.'],
-1
View File
@@ -133,7 +133,6 @@ export default {
envVars: {
title: '环境变量',
rows: [
['AUTH_DISABLED', '设为 "1" 禁用认证'],
['AUTH_TOKEN', '自定义认证令牌(覆盖自动生成的令牌)'],
['PORT', '服务器监听端口(默认:8648'],
['BIND_HOST', '服务器绑定地址(默认:0.0.0.0)。如需 IPv6,请显式设置为 ::。'],
+7 -22
View File
@@ -50,21 +50,17 @@ describe('Auth Service', () => {
})
describe('getToken', () => {
it('returns null when AUTH_DISABLED=1', async () => {
it('ignores legacy AUTH_DISABLED=1 and still creates an auth token', async () => {
process.env.AUTH_DISABLED = '1'
const { getToken, mocks } = await loadAuth()
const readFile = vi.fn().mockRejectedValue(new Error('ENOENT'))
const writeFile = vi.fn()
const mkdir = vi.fn()
const { getToken } = await loadAuth({ readFile, writeFile, mkdir })
const token = await getToken()
expect(token).toBeNull()
expect(mocks.readFile).not.toHaveBeenCalled()
})
it('returns null when AUTH_DISABLED=true', async () => {
process.env.AUTH_DISABLED = 'true'
const { getToken } = await loadAuth()
await expect(getToken()).resolves.toBeNull()
expect(token).toMatch(/^[a-f0-9]{64}$/)
expect(writeFile).toHaveBeenCalled()
})
it('returns AUTH_TOKEN env var if set', async () => {
@@ -108,17 +104,6 @@ describe('Auth Service', () => {
})
describe('requireAuth', () => {
it('allows all requests when auth is disabled (null token)', async () => {
const { requireAuth } = await loadAuth()
const middleware = requireAuth(null)
const ctx = createMockCtx('/api/hermes/sessions')
const next = vi.fn(async () => {})
await middleware(ctx, next)
expect(next).toHaveBeenCalledOnce()
})
it('skips /health', async () => {
const { requireAuth } = await loadAuth()
const middleware = requireAuth('secret')