fix: keep manually set admin password and echo SSH input
TallyNote release / linux-x64 (push) Failing after 3m0s

- manual admin password no longer forces first-login change
- --generate still requires password change on first login
- add --mark-password-configured to repair legacy flag with current password
- echo interactive username/password input in SSH terminal
- installer prints absolute admin-init path (sudo secure_path compat)
release: 1.2.9
This commit is contained in:
Qiufeng
2026-09-10 17:01:42 +08:00
parent a070ad0434
commit 19a3b5157c
5 changed files with 147 additions and 13 deletions
+80
View File
@@ -85,6 +85,86 @@ describe("生产管理员初始化 CLI", () => {
}
}, 15_000);
it("交互式输入正式密码后不会强制首次改密", () => {
const dataDir = mkdtempSync(path.join(tmpdir(), "tallynote-admin-init-"));
try {
const expectScript = [
"set timeout 15",
`spawn ${process.execPath} ${tsx} ${cli}`,
'expect "用户名: "',
'send "manual-admin\\r"',
'expect "显示名称: "',
'send "手动管理员\\r"',
'expect "密码(至少 12 个字符): "',
'send "Strong-password-2026!\\r"',
'expect "再次输入密码: "',
'send "Strong-password-2026!\\r"',
'expect eof',
].join("\n");
const result = spawnSync("expect", ["-c", expectScript], {
cwd: root,
env: {
...process.env,
NODE_ENV: "test",
TALLYNOTE_DATA_DIR: dataDir,
TALLYNOTE_PUBLIC_ORIGIN: "http://127.0.0.1:3999",
TALLYNOTE_COOKIE_SECURE: "false",
TALLYNOTE_UPDATE_STRATEGY: "disabled",
},
encoding: "utf8",
});
expect(result.status).toBe(0);
expect(`${result.stdout}${result.stderr}`).toContain("已创建首位管理员");
expect(`${result.stdout}${result.stderr}`).toContain("Strong-password-2026!");
const database = new Database(path.join(dataDir, "tallynote.db"));
const admin = database.prepare("SELECT username, must_change_password FROM admins").get() as { username: string; must_change_password: number };
expect(admin).toEqual({ username: "manual-admin", must_change_password: 0 });
database.close();
} finally {
rmSync(dataDir, { recursive: true, force: true });
}
}, 30_000);
it("可以验证当前密码并清除旧版本遗留的首次改密标志", () => {
const dataDir = mkdtempSync(path.join(tmpdir(), "tallynote-admin-init-"));
try {
const first = runAdmin(dataDir, ["--username", "legacy-admin", "--display-name", "旧版管理员", "--generate"]);
expect(first.status).toBe(0);
const generated = first.stdout.match(/一次性密码:([^\s]+)/)?.[1];
expect(generated).toBeTruthy();
const expectScript = [
"set timeout 15",
`spawn ${process.execPath} ${tsx} ${cli} --mark-password-configured --username legacy-admin`,
'expect "当前密码: "',
`send "${generated}\\r"`,
'expect eof',
].join("\n");
const result = spawnSync("expect", ["-c", expectScript], {
cwd: root,
env: {
...process.env,
NODE_ENV: "test",
TALLYNOTE_DATA_DIR: dataDir,
TALLYNOTE_PUBLIC_ORIGIN: "http://127.0.0.1:3999",
TALLYNOTE_COOKIE_SECURE: "false",
TALLYNOTE_UPDATE_STRATEGY: "disabled",
},
encoding: "utf8",
});
expect(result.status).toBe(0);
expect(`${result.stdout}${result.stderr}`).toContain("已确认当前密码为正式密码");
const database = new Database(path.join(dataDir, "tallynote.db"));
const admin = database.prepare("SELECT must_change_password FROM admins WHERE username_norm='legacy-admin'").get() as { must_change_password: number };
expect(admin.must_change_password).toBe(0);
database.close();
} finally {
rmSync(dataDir, { recursive: true, force: true });
}
}, 30_000);
it("密码输入不是 TTY 时明确拒绝通过管道传入", () => {
const dataDir = mkdtempSync(path.join(tmpdir(), "tallynote-admin-init-"));
try {