From 07cfbefb6c92e4beae5eb5a03bb0ae6ca3ba7051 Mon Sep 17 00:00:00 2001 From: oliver Date: Mon, 12 Oct 2026 10:46:39 +0800 Subject: [PATCH] Enable default emergency account --- uds-auth/README.md | 2 +- uds-auth/README.zh.md | 2 +- uds-auth/config.default.yaml | 4 +-- uds-auth/docs/upgrade-0.3.10.zh.md | 4 +-- uds-auth/lib/client.js | 12 ++++---- uds-auth/lib/i18n.js | 4 +-- uds-auth/lib/roles.js | 38 ++++++++++++++++--------- uds-auth/test/acceptance-batch2.test.js | 2 +- uds-auth/test/auth-session-p0.test.js | 6 ++-- 9 files changed, 43 insertions(+), 31 deletions(-) diff --git a/uds-auth/README.md b/uds-auth/README.md index b6a10c09..b37f0d2c 100644 --- a/uds-auth/README.md +++ b/uds-auth/README.md @@ -56,7 +56,7 @@ Durable state defaults to `~/.uds-auth/` (roles / owners / credentials / bridge ## Admin / emergency - `super_admin` / `admin` / `fallback_admin` share the same admin-class permissions (identity labels, not stronger isolation). -- No shared default password. Set emergency password in Settings (≥10 chars; `Admin@123` rejected). Legacy default hashes are cleared on upgrade. +- The emergency account is enabled by default: username `administrator`, password `Admin@123`. Change it in Settings after first sign-in. Admins can also disable it. Older roles files without an explicit emergency-account state receive this default; an explicitly disabled account stays disabled. Historical SHA-256 hashes of `Admin@123` migrate to scrypt. - Local unlock: `node scripts/seal-local-admin.mjs` (interactive / `--stdin` — **no argv passphrase**) → `UDS_AUTH_LOCAL_ADMIN_BOX`. - Removing a user disables the account by default (open registration cannot auto-revive). diff --git a/uds-auth/README.zh.md b/uds-auth/README.zh.md index 6fffe0a8..635a3946 100644 --- a/uds-auth/README.zh.md +++ b/uds-auth/README.zh.md @@ -61,7 +61,7 @@ skillCredentialTtlSeconds: 604800 ## 管理员与应急 - **三类管理员同权**(`super_admin` / `admin` / `fallback_admin`):用户管理、设置、建工作区;标签不是更强隔离。 -- **无共享默认密码**。须在设置中显式设置应急口令(≥10 位,禁止 `Admin@123`)。旧安装若仍存该默认 hash,升级后自动禁用直至重置。 +- **应急账号默认开启**:账号 `administrator`,密码 `Admin@123`。首次登录后建议立即在设置中修改;管理员也可关闭该账号。旧 roles 文件未记录应急账号状态时会启用此默认账号;已明确关闭的状态会保留。历史 `Admin@123` SHA-256 hash 会迁移为 scrypt hash。 - **本机密钥解锁**:`node scripts/seal-local-admin.mjs`(交互输入,勿把口令放 argv)→ 设置 `UDS_AUTH_LOCAL_ADMIN_BOX` → 登录面板解密。 - **停用账号**:删除用户默认写入 disabled 状态,不会因开放注册自动复活。 diff --git a/uds-auth/config.default.yaml b/uds-auth/config.default.yaml index d61686fb..f119935a 100644 --- a/uds-auth/config.default.yaml +++ b/uds-auth/config.default.yaml @@ -15,5 +15,5 @@ skillCredentialTtlSeconds: 604800 outboundAllowedHosts: icenterapi.zte.com.cn,icentermsg.dt.zte.com.cn # Optional exact Host allowlist for /uds-auth writes; empty = Origin must match Host only trustedHosts: '' -# Emergency fallback password is NOT enabled by default. Set via Settings (min 10 chars). -# Legacy Admin@123 hashes are refused on upgrade. +# Emergency fallback account is enabled by default: administrator / Admin@123. +# Change or disable it from Settings after signing in. diff --git a/uds-auth/docs/upgrade-0.3.10.zh.md b/uds-auth/docs/upgrade-0.3.10.zh.md index 9871973f..286ba4bf 100644 --- a/uds-auth/docs/upgrade-0.3.10.zh.md +++ b/uds-auth/docs/upgrade-0.3.10.zh.md @@ -1,10 +1,10 @@ # 升级到 0.3.10 -本版修复密码并发写入、Desktop 凭据附带范围、Gateway 全局接口遗漏、扫码浏览器绑定、任务授权注入和安全状态迁移。先备份 profile 中的安全状态及密钥,再替换插件并重启 Host;不要删除 roles/owners 文件,也不要恢复历史共享默认密码。 +本版修复密码并发写入、Desktop 凭据附带范围、Gateway 全局接口遗漏、扫码浏览器绑定、任务授权注入和安全状态迁移。先备份 profile 中的安全状态及密钥,再替换插件并重启 Host;不要删除 roles/owners 文件。 ## 登录和 Desktop -应急密码入口始终显示;未设置密码时保持禁用。已登录的授权管理员可在「账号 / 权限」设置至少 10 位的新密码。Local Admin 密封盒解锁保持独立,轮换或关闭应急密码不撤销密封盒会话。 +应急账号默认启用,账号为 `administrator`、密码为 `Admin@123`;首次登录后请尽快在「账号 / 权限」设置新密码,也可在此关闭应急账号。旧 roles 文件若没有应急账号状态会启用默认账号;显式关闭状态会保留。Local Admin 密封盒解锁保持独立,轮换或关闭应急密码不撤销密封盒会话。 Desktop 只把 bridge 附到 `dsh-app://app` 或 Host 启动时提供的 `__DSH_TRANSPORT__.streamBaseUrl` 对应 authority。启动信息缺失时绝不向其他本机端口外发凭据;需要支持提供这个正式 transport 字段的 DSH 版本。带 bridge 的 fetch 拒绝重定向,保留原 Request headers。一次性 WS ticket 只用于 GET `/api/remote.mux` 的真正 WebSocket upgrade;普通 HTTP、其他路径和重放均无效。构造 WebSocket 仍采用同步 XHR 取票,需真实 Desktop 验证该协议桥。 diff --git a/uds-auth/lib/client.js b/uds-auth/lib/client.js index 8310e8c8..e140dece 100644 --- a/uds-auth/lib/client.js +++ b/uds-auth/lib/client.js @@ -33,7 +33,7 @@ window.__ModuleLoader__.load({ "ui.settingsTitle": "UAC 认证", "ui.settingsIntro": "工号+token 双校验;UAC 挂死时用应急账号 administrator 密码登录。", "ui.loginRequiredPage": "请先登录后查看此页", - "ui.roleHint": "当前角色:{role}。超管只是身份标签,与管理员权限相同;显式配置的初始管理员在首次登录时获得超管身份,也可在「用户管理」设为管理员。应急账号 administrator 需先设密码,再在登录面板用账密登录。", + "ui.roleHint": "当前角色:{role}。超管只是身份标签,与管理员权限相同;显式配置的初始管理员在首次登录时获得超管身份,也可在「用户管理」设为管理员。应急账号默认账号 administrator、密码 Admin@123,首次登录后请修改。", "ui.deployConfig": "部署配置", "ui.userSearchUrl": "用户搜索 URL(token 校验)", "ui.workspaceRoot": "工作区根目录(空=$DSH_HOME/user-workspaces)", @@ -78,8 +78,8 @@ window.__ModuleLoader__.load({ "ui.refreshQr": "刷新二维码", "ui.fallbackLink": "UAC 不可用?应急账号登录", "ui.fallbackDisabledLink": "应急登录(未启用)", - "ui.fallbackDisabledHint": "应急密码未配置或已停用。请用已登录的管理员在「账户 / 权限」中设置新的独立强密码后重试。旧共享默认密码不可用。", - "ui.fallbackDisabledDetail": "当前未启用应急密码。用已授权管理员登录后,在设置中配置新密码即可恢复此入口。", + "ui.fallbackDisabledHint": "应急账号已关闭。请用已登录的管理员在「账户 / 权限」中重新设置并启用。", + "ui.fallbackDisabledDetail": "当前未启用应急账号。用已授权管理员登录后,在设置中重新设置密码即可恢复此入口。", "ui.fallbackLogin": "应急登录", "ui.fallbackDetail": "UAC / 扫码不可用时使用", "ui.localKeyLink": "本机密钥解锁", @@ -180,7 +180,7 @@ window.__ModuleLoader__.load({ "ui.settingsTitle": "UAC Auth", "ui.settingsIntro": "EmpNo + token verification; when UAC is down, sign in with emergency account administrator.", "ui.loginRequiredPage": "Sign in to view this page", - "ui.roleHint": "Current role: {role}. Super admin is only an identity label with the same permissions as admin; the explicitly configured initial administrator receives that identity on first login, or grant admin in User management. Set the emergency password before using administrator on the login panel.", + "ui.roleHint": "Current role: {role}. Super admin is only an identity label with the same permissions as admin; the explicitly configured initial administrator receives that identity on first login, or grant admin in User management. The emergency account defaults to administrator / Admin@123; change it after first sign-in.", "ui.deployConfig": "Deploy config", "ui.userSearchUrl": "User search URL (token verify)", "ui.workspaceRoot": "Workspace root (empty=$DSH_HOME/user-workspaces)", @@ -225,8 +225,8 @@ window.__ModuleLoader__.load({ "ui.refreshQr": "Refresh QR", "ui.fallbackLink": "UAC down? Emergency account", "ui.fallbackDisabledLink": "Emergency login (disabled)", - "ui.fallbackDisabledHint": "Emergency password is not configured or was disabled. Ask a signed-in admin to set a new strong password under Account / Permissions. The old shared default password cannot be used.", - "ui.fallbackDisabledDetail": "Emergency password is off. After an authorized admin sets a new password in settings, this entry works again.", + "ui.fallbackDisabledHint": "The emergency account is disabled. Ask a signed-in admin to set a new password and enable it under Account / Permissions.", + "ui.fallbackDisabledDetail": "The emergency account is off. An authorized admin can set its password again in settings to restore this entry.", "ui.fallbackLogin": "Emergency login", "ui.fallbackDetail": "Use when UAC / QR is unavailable", "ui.localKeyLink": "Unlock with local key", diff --git a/uds-auth/lib/i18n.js b/uds-auth/lib/i18n.js index e08fa8b5..9e459cbb 100644 --- a/uds-auth/lib/i18n.js +++ b/uds-auth/lib/i18n.js @@ -26,7 +26,7 @@ export const MESSAGES = { 'ui.settingsTitle': 'UAC 认证', 'ui.settingsIntro': '工号+token 双校验;UAC 挂死时用应急账号 administrator 密码登录。', 'ui.loginRequiredPage': '请先登录后查看此页', - 'ui.roleHint': '当前角色:{role}。超管只是身份标签,与管理员权限相同;显式配置的初始管理员在首次登录时获得超管身份,也可在「用户管理」设为管理员。应急账号 administrator 需先设密码,再在登录面板用账密登录。', + 'ui.roleHint': '当前角色:{role}。超管只是身份标签,与管理员权限相同;显式配置的初始管理员在首次登录时获得超管身份,也可在「用户管理」设为管理员。应急账号默认账号 administrator、密码 Admin@123,首次登录后请修改。', 'ui.deployConfig': '部署配置', 'ui.userSearchUrl': '用户搜索 URL(token 校验)', 'ui.workspaceRoot': '工作区根目录(空=$DSH_HOME/user-workspaces)', @@ -213,7 +213,7 @@ export const MESSAGES = { 'ui.settingsTitle': 'UAC Auth', 'ui.settingsIntro': 'EmpNo + token verification; when UAC is down, sign in with emergency account administrator.', 'ui.loginRequiredPage': 'Sign in to view this page', - 'ui.roleHint': 'Current role: {role}. Super admin is only an identity label with the same permissions as admin; the explicitly configured initial administrator receives that identity on first login, or grant admin in User management. Set the emergency password before using administrator on the login panel.', + 'ui.roleHint': 'Current role: {role}. Super admin is only an identity label with the same permissions as admin; the explicitly configured initial administrator receives that identity on first login, or grant admin in User management. The emergency account defaults to administrator / Admin@123; change it after first sign-in.', 'ui.deployConfig': 'Deploy config', 'ui.userSearchUrl': 'User search URL (token verify)', 'ui.workspaceRoot': 'Workspace root (empty=$DSH_HOME/user-workspaces)', diff --git a/uds-auth/lib/roles.js b/uds-auth/lib/roles.js index 5ff6650d..35f5c891 100644 --- a/uds-auth/lib/roles.js +++ b/uds-auth/lib/roles.js @@ -29,7 +29,7 @@ const ACCOUNT_STATUS = { DISABLED: 'disabled', } -/** Known shared default from pre-0.3 installs — must never remain usable (R10). */ +/** Shared emergency default requested for first boot and legacy migrations. */ const LEGACY_DEFAULT_SHA256 = createHash('sha256').update('Admin@123').digest('hex') /** @@ -141,7 +141,7 @@ export function canToggleViewAllSessions(role) { return isAdminClass(role) } -/** Historical default — never enable automatically (R10). */ +/** Default emergency credentials. Rotate the password after first sign-in. */ export const DEFAULT_FALLBACK_PASSWORD = 'Admin@123' export const DEFAULT_FALLBACK_USERNAME = 'administrator' @@ -149,7 +149,7 @@ export const DEFAULT_FALLBACK_USERNAME = 'administrator' * RolesStore — 角色存储 * 内存 Map + 可选 JSON 文件持久化 * - * fallback admin: 默认关闭;须在设置中显式设置 scrypt 口令(≥10,禁 Admin@123)。 + * fallback admin: first boot uses the shared Admin@123 password; admins can rotate or disable it. * 登录路径: POST /uds-auth/api/fallback/login { username, password } */ export class RolesStore { @@ -170,18 +170,21 @@ export class RolesStore { this._saveTimer = null } - /** - * New installs do NOT enable a shared default password (SEC-08). - * Admins must set a password explicitly via settings / setFallbackPassword. - */ + /** Seed the requested default emergency password on first initialization. */ _ensureDefaultFallback() { - // no-op — kept for call-site compatibility + if (this._fallbackPasswordHash !== null) return false + this._fallbackPasswordHash = hashPasswordScrypt(DEFAULT_FALLBACK_PASSWORD) + this._fallbackNeedsRehash = false + this._fallbackCredVersion = Math.max(1, this._fallbackCredVersion + 1) + this._dirty = true + return true } // === 持久化 === async init() { let loadedOk = false + let seedDefaultFallback = !this._rolesFile if (this._rolesFile) { try { const raw = await readFile(this._rolesFile, 'utf-8') @@ -196,6 +199,9 @@ export class RolesStore { } if (Object.prototype.hasOwnProperty.call(data, 'fallbackPasswordHash')) { this._fallbackPasswordHash = data.fallbackPasswordHash || null + } else { + // Upgrade older roles files that predate explicit fallback state. + seedDefaultFallback = true } this._fallbackCredVersion = Number.isSafeInteger(data.fallbackCredVersion) && data.fallbackCredVersion >= 0 ? data.fallbackCredVersion : 0 @@ -207,9 +213,10 @@ export class RolesStore { loadedOk = true } catch (err) { if (err.code === 'ENOENT') { - // First boot — empty roles, fallback disabled until explicitly configured. + // First boot — seed and persist the requested default credentials. + seedDefaultFallback = true console.info( - '[uds-auth] roles file missing; fallback password disabled until configured' + '[uds-auth] roles file missing; enabling default emergency account administrator' + ` (set initialAdmin via UDS_AUTH_INITIAL_ADMIN)`, ) } else { @@ -219,11 +226,12 @@ export class RolesStore { } } } - // R10: known shared default hash cannot remain enabled after upgrade. + // Upgrade the historical SHA-256 default to a salted scrypt hash. if (loadedOk && this._fallbackPasswordHash && String(this._fallbackPasswordHash).toLowerCase() === LEGACY_DEFAULT_SHA256) { - console.warn('[uds-auth] refusing legacy shared default fallback password — disabled until reset') - this._fallbackPasswordHash = null + console.warn('[uds-auth] migrating legacy default fallback password to scrypt') + this._fallbackPasswordHash = hashPasswordScrypt(DEFAULT_FALLBACK_PASSWORD) + this._fallbackCredVersion = Math.max(1, this._fallbackCredVersion + 1) this._dirty = true await this._save() } else if (loadedOk && this._fallbackPasswordHash @@ -231,6 +239,10 @@ export class RolesStore { this._fallbackNeedsRehash = true console.warn('[uds-auth] legacy SHA-256 fallback hash loaded — will upgrade on next successful login') } + if (seedDefaultFallback && !this._loadFailed) { + this._ensureDefaultFallback() + await this.flush() + } if (loadedOk && this._fallbackPasswordHash) { console.info('[uds-auth] fallback_admin password loaded from roles file') } diff --git a/uds-auth/test/acceptance-batch2.test.js b/uds-auth/test/acceptance-batch2.test.js index c2e02773..843edd18 100644 --- a/uds-auth/test/acceptance-batch2.test.js +++ b/uds-auth/test/acceptance-batch2.test.js @@ -182,7 +182,7 @@ test('R13/R14: QR challenge store + live identity patch + docs (source)', () => const acl = readFileSync(join(root, 'lib/dsh-acl.js'), 'utf8') assert.match(acl, /__udsAuthIdentityLive/) const readme = readFileSync(join(root, 'README.zh.md'), 'utf8') - assert.doesNotMatch(readme, /Admin@123.*首次启动自动启用/) + assert.match(readme, /administrator.*Admin@123/) assert.match(readme, /mintTaskCapability|UDS_TASK_CAPABILITY/) }) diff --git a/uds-auth/test/auth-session-p0.test.js b/uds-auth/test/auth-session-p0.test.js index daf09950..106ff79c 100644 --- a/uds-auth/test/auth-session-p0.test.js +++ b/uds-auth/test/auth-session-p0.test.js @@ -234,11 +234,11 @@ test('SEC-06: identity-cache never restores a principal', () => { assert.equal(peekBrowserIdentity(), null) }) -test('SEC-08: new RolesStore does not enable shared default password', async () => { +test('SEC-08: new RolesStore enables the default emergency password', async () => { const store = new RolesStore({}) await store.init() - assert.equal(store.isFallbackEnabled(), false) - assert.equal(store.verifyFallback(DEFAULT_FALLBACK_PASSWORD, '127.0.0.1'), false) + assert.equal(store.isFallbackEnabled(), true) + assert.equal(store.verifyFallback(DEFAULT_FALLBACK_PASSWORD, '127.0.0.1'), true) }) test('SEC-09: empty roles + non-initial admin cannot bootstrap via middleware helper', async () => {