From d03d5e6ac55aea6fbca7082e4e288b70a3aeecbb Mon Sep 17 00:00:00 2001 From: ekko <152005280+EKKOLearnAI@users.noreply.github.com> Date: Mon, 25 May 2026 12:49:01 +0800 Subject: [PATCH] remove auth disabled support (#1013) --- README.md | 2 -- README_zh.md | 2 -- bin/hermes-web-ui.mjs | 3 +-- docker-compose.yml | 1 - docs/cli-chat-sessions.md | 2 +- docs/docker.md | 8 ++---- packages/server/src/config.ts | 1 - packages/server/src/controllers/auth.ts | 2 +- packages/server/src/middleware/user-auth.ts | 14 +++------- packages/server/src/services/auth.ts | 13 ++------- packages/website/src/i18n/en.ts | 1 - packages/website/src/i18n/zh.ts | 1 - tests/server/auth.test.ts | 29 +++++---------------- 13 files changed, 18 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 3d45ff71..1abdf245 100644 --- a/README.md +++ b/README.md @@ -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. | diff --git a/README_zh.md b/README_zh.md index bad4e5cc..e2bbb15a 100644 --- a/README_zh.md +++ b/README_zh.md @@ -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 日志级别。 | diff --git a/bin/hermes-web-ui.mjs b/bin/hermes-web-ui.mjs index 3f88d784..7099867f 100755 --- a/bin/hermes-web-ui.mjs +++ b/bin/hermes-web-ui.mjs @@ -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() diff --git a/docker-compose.yml b/docker-compose.yml index a7dd8653..e15dde93 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/docs/cli-chat-sessions.md b/docs/cli-chat-sessions.md index e5c479eb..201e0d7f 100644 --- a/docs/cli-chat-sessions.md +++ b/docs/cli-chat-sessions.md @@ -220,7 +220,7 @@ io(`${baseUrl}/chat-run`, { }) ``` -如果未设置 `AUTH_DISABLED=1`,服务端会与 Web UI token 比对。 +服务端会与 Web UI token 比对。 --- diff --git a/docs/docker.md b/docs/docker.md index 427a5637..01522b43 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -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 diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index 013066fe..ab8dfefc 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -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: diff --git a/packages/server/src/controllers/auth.ts b/packages/server/src/controllers/auth.ts index 164625af..9a0b8e64 100644 --- a/packages/server/src/controllers/auth.ts +++ b/packages/server/src/controllers/auth.ts @@ -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 } diff --git a/packages/server/src/middleware/user-auth.ts b/packages/server/src/middleware/user-auth.ts index 58617dcc..49223508 100644 --- a/packages/server/src/middleware/user-auth.ts +++ b/packages/server/src/middleware/user-auth.ts @@ -60,7 +60,7 @@ function safeEqual(a: string, b: string): boolean { } } -async function getJwtSecret(): Promise { +async function getJwtSecret(): Promise { 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 { 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): Promise { 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 { 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 { - return !!await getJwtSecret() + await getJwtSecret() + return true } export async function requireUserJwt(ctx: Context, next: Next): Promise { @@ -167,11 +166,6 @@ export async function requireUserJwt(ctx: Context, next: Next): Promise { } const secret = await getJwtSecret() - if (!secret) { - await next() - return - } - const token = requestToken(ctx) const payload = token ? verifyUserJwt(token, secret) : null if (!payload) { diff --git a/packages/server/src/services/auth.ts b/packages/server/src/services/auth.ts index 028f4e54..b437005f 100644 --- a/packages/server/src/services/auth.ts +++ b/packages/server/src/services/auth.ts @@ -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 { - if (process.env.AUTH_DISABLED === '1' || process.env.AUTH_DISABLED === 'true') { - return null - } - +export async function getToken(): Promise { if (process.env.AUTH_TOKEN) { return process.env.AUTH_TOKEN } @@ -45,11 +41,6 @@ export async function getToken(): Promise { */ export function requireAuth(token: string | null) { return async (ctx: any, next: () => Promise) => { - if (!token) { - await next() - return - } - const auth = ctx.headers.authorization || '' const provided = auth.startsWith('Bearer ') ? auth.slice(7) diff --git a/packages/website/src/i18n/en.ts b/packages/website/src/i18n/en.ts index 39c46891..544a500a 100644 --- a/packages/website/src/i18n/en.ts +++ b/packages/website/src/i18n/en.ts @@ -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.'], diff --git a/packages/website/src/i18n/zh.ts b/packages/website/src/i18n/zh.ts index b5777759..b0fbde31 100644 --- a/packages/website/src/i18n/zh.ts +++ b/packages/website/src/i18n/zh.ts @@ -133,7 +133,6 @@ export default { envVars: { title: '环境变量', rows: [ - ['AUTH_DISABLED', '设为 "1" 禁用认证'], ['AUTH_TOKEN', '自定义认证令牌(覆盖自动生成的令牌)'], ['PORT', '服务器监听端口(默认:8648)'], ['BIND_HOST', '服务器绑定地址(默认:0.0.0.0)。如需 IPv6,请显式设置为 ::。'], diff --git a/tests/server/auth.test.ts b/tests/server/auth.test.ts index b5cc9895..e87749d3 100644 --- a/tests/server/auth.test.ts +++ b/tests/server/auth.test.ts @@ -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')