Files
clover-mail/panel/admin-cli.cjs
T

81 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
const Database = require('better-sqlite3')
const { hash } = require('@node-rs/argon2')
const dbPath = process.env.DATABASE_PATH || '/app/data/clover.db'
const db = new Database(dbPath)
const [action, username, newUsername] = process.argv.slice(2)
const usernamePattern = /^[a-zA-Z0-9_-]{2,32}$/
function fail(message) {
console.error(message)
process.exitCode = 1
}
async function main() {
if (action === 'list') {
const rows = db.prepare('SELECT username, created_at, last_login_at FROM admins ORDER BY id').all()
if (!rows.length) {
console.log('尚未创建管理员。请通过 SSH 运行:clover admin create')
return
}
console.log('管理员账户(密码不可读取;忘记密码时可通过 CLI 重置):')
for (const row of rows) {
console.log(`- ${row.username} · 创建于 ${row.created_at} · 最近登录 ${row.last_login_at || '从未'}`)
}
return
}
let password = ''
for await (const chunk of process.stdin) password += chunk
password = password.replace(/\r?\n$/, '')
if (action === 'create') {
if (!usernamePattern.test(username || '')) return fail('用户名需为 2–32 位字母、数字、下划线或短横线。')
if (password.length < 12 || password.length > 128) {
return fail('密码长度需为 12–128 位。')
}
const passwordHash = await hash(password, { algorithm: 2, memoryCost: 19456, timeCost: 2, parallelism: 1 })
const replaceAdmin = db.transaction(() => {
const previous = db.prepare('SELECT username FROM admins ORDER BY id').all()
db.prepare('DELETE FROM admins').run()
db.prepare('INSERT INTO admins (username, password_hash, created_at) VALUES (?, ?, ?)')
.run(username, passwordHash, new Date().toISOString())
return previous.map((row) => row.username)
})
const previous = replaceAdmin()
if (previous.length) {
console.log(`管理员 ${username} 已创建并替换原管理员(${previous.join('、')})。旧管理员的两步验证和 Passkey 不会转移,旧登录会话已失效。`)
} else {
console.log(`管理员 ${username} 已创建。现在可登录面板。`)
}
return
}
if (action === 'reset') {
if (password.length < 12 || password.length > 128) {
return fail('密码长度需为 12–128 位。')
}
const passwordHash = await hash(password, { algorithm: 2, memoryCost: 19456, timeCost: 2, parallelism: 1 })
const result = db.prepare('UPDATE admins SET password_hash = ? WHERE username = ?').run(passwordHash, username)
if (!result.changes) return fail(`找不到管理员:${username}`)
console.log(`管理员 ${username} 的密码已重置;已启用的两步验证保持不变。`)
return
}
if (action === 'rename') {
if (!usernamePattern.test(newUsername || '')) return fail('新用户名需为 2–32 位字母、数字、下划线或短横线。')
const result = db.prepare('UPDATE admins SET username = ? WHERE username = ?').run(newUsername, username)
if (!result.changes) return fail(`找不到管理员:${username}`)
console.log(`管理员用户名已改为 ${newUsername}。`)
return
}
fail('未知操作。支持 list、create、reset、rename。')
}
main().catch((error) => {
if (error && error.code === 'SQLITE_CONSTRAINT_UNIQUE') fail('该管理员用户名已存在。')
else fail(`管理员操作失败:${error instanceof Error ? error.message : String(error)}`)
}).finally(() => db.close())