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
+49 -3
View File
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
import { StringDecoder } from "node:string_decoder";
import { openDatabase, openDatabaseReadOnly } from "../db/index.js";
import { acquireInstanceLock, loadConfig, prepareDataDirectories } from "../config.js";
import { hashPassword, normalizeUsername, validateNewPassword, temporaryPassword } from "../security.js";
import { hashPassword, normalizeUsername, validateNewPassword, temporaryPassword, verifyPassword } from "../security.js";
import { writeAudit } from "../audit.js";
function arg(name: string): string | undefined {
@@ -79,8 +79,13 @@ async function readSecret(prompt: string): Promise<string> {
return;
} else if (character === "\u007f" || character === "\b") {
value = value.slice(0, -1);
// Keep the credential visible in the SSH terminal as requested.
// Redraw the current line so backspace behaves predictably without
// putting the value into logs or command arguments.
output.write("\r\u001b[2K" + prompt + value);
} else {
value += character;
output.write(character);
}
}
};
@@ -128,6 +133,39 @@ async function main() {
const release = acquireInstanceLock(config);
const database = openDatabase(config);
try {
const markPasswordConfigured = process.argv.includes("--mark-password-configured");
if (markPasswordConfigured) {
const username = arg("--username") ?? (await readSecret("用户名: "));
const password = await readSecret("当前密码: ");
const normalized = normalizeUsername(username);
const admin = database.sqlite.prepare(
"SELECT id, password_hash, must_change_password, version FROM admins WHERE username_norm = ?",
).get(normalized) as { id: string; password_hash: string; must_change_password: number; version: number } | undefined;
if (!admin || !(await verifyPassword(admin.password_hash, password))) {
throw new Error("用户名或当前密码不正确");
}
if (!admin.must_change_password) {
console.log("该管理员已经可以直接使用当前密码登录。");
return;
}
const now = Date.now();
database.sqlite.transaction(() => {
const result = database.sqlite.prepare(
"UPDATE admins SET must_change_password=0, auth_version=auth_version+1, version=version+1 WHERE id=? AND version=?",
).run(admin.id, admin.version);
if (result.changes !== 1) throw new Error("管理员资料已被其他操作更新,请重试");
writeAudit(database.sqlite, {
requestId: `cli:${randomUUID()}`,
actorUsername: "cli",
action: "admin.password_policy_cleared",
targetType: "admin",
targetId: admin.id,
after: { username: normalized, mustChangePassword: false, changedAt: now },
});
})();
console.log("已确认当前密码为正式密码,后续登录不再要求修改密码。");
return;
}
const existing = database.sqlite.prepare("SELECT COUNT(*) AS count FROM admins").get() as { count: number };
if (existing.count > 0) throw new Error("INITIAL_ADMIN_EXISTS:管理员已经初始化");
const username = arg("--username") ?? (await readSecret("用户名: "));
@@ -154,8 +192,16 @@ async function main() {
database.sqlite.prepare(`
INSERT INTO admins(id, username, username_norm, display_name, password_hash, status,
must_change_password, auth_version, version, created_at)
VALUES (?, ?, ?, ?, ?, 'active', 1, 1, 1, ?)
`).run(id, username.normalize("NFKC").trim(), normalized, normalizedDisplayName, passwordHash, now);
VALUES (?, ?, ?, ?, ?, 'active', ?, 1, 1, ?)
`).run(
id,
username.normalize("NFKC").trim(),
normalized,
normalizedDisplayName,
passwordHash,
generate ? 1 : 0,
now,
);
writeAudit(database.sqlite, {
requestId: `cli:${randomUUID()}`,
actorUsername: "cli",