Enable default emergency account
Some checks are pending
ci / test (push) Waiting to run
ci / test-postgresql (push) Waiting to run

This commit is contained in:
oliver 2026-10-12 10:46:39 +08:00
parent 70bde46eff
commit 07cfbefb6c
9 changed files with 43 additions and 31 deletions

View file

@ -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).

View file

@ -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 状态,不会因开放注册自动复活。

View file

@ -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.

View file

@ -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 验证该协议桥。

View file

@ -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",

View file

@ -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)',

View file

@ -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')
}

View file

@ -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/)
})

View file

@ -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 () => {