Add default credential reset safeguards
This commit is contained in:
+104
-1
@@ -3,8 +3,9 @@ import { spawn, execSync, execFileSync } from 'child_process'
|
||||
import { resolve, dirname, join, delimiter } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync, chmodSync, statSync, existsSync, realpathSync } from 'fs'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { randomBytes, scryptSync } from 'crypto'
|
||||
import { homedir } from 'os'
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
@@ -19,7 +20,11 @@ const PID_DIR = WEB_UI_HOME
|
||||
const PID_FILE = join(PID_DIR, 'server.pid')
|
||||
const LOG_FILE = join(PID_DIR, 'server.log')
|
||||
const TOKEN_FILE = join(PID_DIR, '.token')
|
||||
const LOGIN_LOCK_FILE = join(WEB_UI_HOME, '.login-lock.json')
|
||||
const WEB_UI_DB_FILE = join(WEB_UI_HOME, 'hermes-web-ui.db')
|
||||
const DEFAULT_PORT = 8648
|
||||
const DEFAULT_USERNAME = 'admin'
|
||||
const DEFAULT_PASSWORD = '123456'
|
||||
|
||||
// ─── Auto-fix node-pty native module ──────────────────────────
|
||||
function ensureNativeModules() {
|
||||
@@ -466,6 +471,86 @@ function showStatus() {
|
||||
}
|
||||
}
|
||||
|
||||
function clearLoginLocks(options = {}) {
|
||||
const { silent = false, checkRunning = true } = options
|
||||
const serverRunning = checkRunning ? !!getPid() : false
|
||||
let removed = false
|
||||
|
||||
try {
|
||||
unlinkSync(LOGIN_LOCK_FILE)
|
||||
removed = true
|
||||
if (!silent) console.log(` ✓ Removed login lock file: ${LOGIN_LOCK_FILE}`)
|
||||
} catch (err) {
|
||||
if (err?.code === 'ENOENT') {
|
||||
if (!silent) console.log(` ✓ No login lock file found: ${LOGIN_LOCK_FILE}`)
|
||||
} else {
|
||||
if (!silent) console.log(` ✗ Failed to remove login lock file: ${err.message}`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
if (!silent && serverRunning) {
|
||||
console.log(' ⚠ hermes-web-ui is running; restart it to clear in-memory login locks.')
|
||||
console.log(' Run: hermes-web-ui restart')
|
||||
}
|
||||
|
||||
return { path: LOGIN_LOCK_FILE, removed, serverRunning }
|
||||
}
|
||||
|
||||
function hashPassword(password) {
|
||||
const salt = randomBytes(16).toString('hex')
|
||||
const hash = scryptSync(password, salt, 64).toString('hex')
|
||||
return `scrypt:${salt}:${hash}`
|
||||
}
|
||||
|
||||
function resetDefaultLogin(options = {}) {
|
||||
const { silent = false } = options
|
||||
mkdirSync(WEB_UI_HOME, { recursive: true })
|
||||
const db = new DatabaseSync(WEB_UI_DB_FILE)
|
||||
try {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'admin',
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
last_login_at INTEGER
|
||||
)
|
||||
`)
|
||||
|
||||
const now = Date.now()
|
||||
const passwordHash = hashPassword(DEFAULT_PASSWORD)
|
||||
const existing = db.prepare('SELECT id FROM users WHERE username = ?').get(DEFAULT_USERNAME)
|
||||
if (existing?.id) {
|
||||
db.prepare(
|
||||
`UPDATE users
|
||||
SET password_hash = ?, role = 'super_admin', status = 'active', updated_at = ?
|
||||
WHERE id = ?`
|
||||
).run(passwordHash, now, existing.id)
|
||||
if (!silent) {
|
||||
console.log(` ✓ Reset default login: ${DEFAULT_USERNAME} / ${DEFAULT_PASSWORD}`)
|
||||
console.log(` Database: ${WEB_UI_DB_FILE}`)
|
||||
}
|
||||
return { path: WEB_UI_DB_FILE, username: DEFAULT_USERNAME, password: DEFAULT_PASSWORD, action: 'updated' }
|
||||
}
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO users (username, password_hash, role, status, created_at, updated_at)
|
||||
VALUES (?, ?, 'super_admin', 'active', ?, ?)`
|
||||
).run(DEFAULT_USERNAME, passwordHash, now, now)
|
||||
if (!silent) {
|
||||
console.log(` ✓ Created default login: ${DEFAULT_USERNAME} / ${DEFAULT_PASSWORD}`)
|
||||
console.log(` Database: ${WEB_UI_DB_FILE}`)
|
||||
}
|
||||
return { path: WEB_UI_DB_FILE, username: DEFAULT_USERNAME, password: DEFAULT_PASSWORD, action: 'created' }
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const command = process.argv[2] || 'start'
|
||||
|
||||
@@ -485,6 +570,8 @@ Commands:
|
||||
stop Stop the server
|
||||
restart [port] Restart the server
|
||||
status Show server status
|
||||
clear-login-locks Delete the login IP lock file
|
||||
reset-default-login Create or reset the default login (${DEFAULT_USERNAME} / ${DEFAULT_PASSWORD})
|
||||
update Update to latest version and restart
|
||||
upgrade Alias for update
|
||||
version Show version number
|
||||
@@ -493,6 +580,7 @@ Options:
|
||||
-v, --version Show version number
|
||||
-h, --help Show this help message
|
||||
--port <port> Specify port (used with start/restart)
|
||||
--restart Restart after clear-login-locks
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
@@ -511,6 +599,19 @@ Options:
|
||||
case 'status':
|
||||
showStatus()
|
||||
break
|
||||
case 'clear-login-locks': {
|
||||
const restartAfterClear = process.argv.includes('--restart')
|
||||
const result = clearLoginLocks()
|
||||
if (restartAfterClear && result.serverRunning) {
|
||||
const port = getRunningPort() ?? getPort()
|
||||
stopDaemon()
|
||||
setTimeout(() => startDaemon(port), 500)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'reset-default-login':
|
||||
resetDefaultLogin()
|
||||
break
|
||||
case 'update':
|
||||
case 'upgrade':
|
||||
doUpdate()
|
||||
@@ -599,7 +700,9 @@ if (process.argv[1] && realpathSync(resolve(process.argv[1])) === __filename) {
|
||||
}
|
||||
|
||||
export {
|
||||
clearLoginLocks,
|
||||
commandExists,
|
||||
getListeningPids,
|
||||
parseUnixNetstatListeningPids,
|
||||
resetDefaultLogin,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user