From d0aad3400c7f44c8277b83c47473c6cd4e531af1 Mon Sep 17 00:00:00 2001 From: LouisLam Date: Mon, 9 Aug 2021 13:34:44 +0800 Subject: [PATCH] add reset password in cli --- extra/reset-password.js | 59 +++++++++++++++++++++++++++++++++++++++++ package.json | 3 ++- server/database.js | 6 ++++- server/model/user.js | 21 +++++++++++++++ server/server.js | 25 +++++------------ server/util-server.js | 21 +++++++++++++++ 6 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 extra/reset-password.js create mode 100644 server/model/user.js diff --git a/extra/reset-password.js b/extra/reset-password.js new file mode 100644 index 00000000..b849848b --- /dev/null +++ b/extra/reset-password.js @@ -0,0 +1,59 @@ +console.log("== Uptime Kuma Reset Password Tool =="); + +console.log("Loading the database"); + +const Database = require("../server/database"); +const { R } = require("redbean-node"); +const readline = require("readline"); +const { initJWTSecret } = require("../server/util-server"); +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +(async () => { + await Database.connect(); + + try { + const user = await R.findOne("user"); + + if (! user) { + throw new Error("user not found, have you installed?"); + } + + console.log("Found user: " + user.username); + + while (true) { + let password = await question("New Password: "); + let confirmPassword = await question("Confirm New Password: "); + + if (password === confirmPassword) { + await user.resetPassword(password); + + // Reset all sessions by reset jwt secret + await initJWTSecret(); + + rl.close(); + break; + } else { + console.log("Passwords do not match, please try again."); + } + } + + console.log("Password reset successfully."); + } catch (e) { + console.error("Error: " + e.message); + } + + await Database.close(); + + console.log("Finished. You should restart the Uptime Kuma server.") +})(); + +function question(question) { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer); + }) + }); +} diff --git a/package.json b/package.json index f79729a2..26326cef 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push", "setup": "git checkout 1.0.10 && npm install && npm run build", "update-version": "node extra/update-version.js", - "mark-as-nightly": "node extra/mark-as-nightly.js" + "mark-as-nightly": "node extra/mark-as-nightly.js", + "reset-password": "node extra/reset-password.js" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.36", diff --git a/server/database.js b/server/database.js index 3734257c..f10ec40c 100644 --- a/server/database.js +++ b/server/database.js @@ -11,7 +11,7 @@ class Database { static latestVersion = 6; static noReject = true; - static connect() { + static async connect() { const Dialect = require("knex/lib/dialects/sqlite3/index.js"); Dialect.prototype._driver = () => require("@louislam/sqlite3"); @@ -27,6 +27,10 @@ class Database { idleTimeoutMillis: 30000, } })); + + // Auto map the model to a bean object + R.freeze(true) + await R.autoloadModels("./server/model"); } static async patch() { diff --git a/server/model/user.js b/server/model/user.js new file mode 100644 index 00000000..d1d3d200 --- /dev/null +++ b/server/model/user.js @@ -0,0 +1,21 @@ +const { BeanModel } = require("redbean-node/dist/bean-model"); +const passwordHash = require("../password-hash"); +const { R } = require("redbean-node"); + +class User extends BeanModel { + + /** + * Direct execute, no need R.store() + * @param newPassword + * @returns {Promise} + */ + async resetPassword(newPassword) { + await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ + passwordHash.generate(newPassword), + this.id + ]); + this.password = newPassword; + } +} + +module.exports = User; diff --git a/server/server.js b/server/server.js index 98fe9794..845310e2 100644 --- a/server/server.js +++ b/server/server.js @@ -27,7 +27,7 @@ console.log("Importing this project modules"); debug("Importing Monitor"); const Monitor = require("./model/monitor"); debug("Importing Settings"); -const { getSettings, setSettings, setting } = require("./util-server"); +const { getSettings, setSettings, setting, initJWTSecret } = require("./util-server"); debug("Importing Notification"); const { Notification } = require("./notification"); debug("Importing Database"); @@ -414,10 +414,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); if (user && passwordHash.verify(password.currentPassword, user.password)) { - await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ - passwordHash.generate(password.newPassword), - socket.userID, - ]); + user.resetPassword(password.newPassword); callback({ ok: true, @@ -659,30 +656,22 @@ async function initDatabase() { } console.log("Connecting to Database") - Database.connect(); + await Database.connect(); console.log("Connected") // Patch the database await Database.patch() - // Auto map the model to a bean object - R.freeze(true) - await R.autoloadModels("./server/model"); - let jwtSecretBean = await R.findOne("setting", " `key` = ? ", [ "jwtSecret", ]); if (! jwtSecretBean) { - console.log("JWT secret is not found, generate one.") - jwtSecretBean = R.dispense("setting") - jwtSecretBean.key = "jwtSecret" - - jwtSecretBean.value = passwordHash.generate(dayjs() + "") - await R.store(jwtSecretBean) - console.log("Stored JWT secret into database") + console.log("JWT secret is not found, generate one."); + jwtSecretBean = initJWTSecret(); + console.log("Stored JWT secret into database"); } else { - console.log("Load JWT secret from database.") + console.log("Load JWT secret from database."); } // If there is no record in user table, it is a new Uptime Kuma instance, need to setup diff --git a/server/util-server.js b/server/util-server.js index d9bd5410..1baec2a0 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -2,6 +2,27 @@ const tcpp = require("tcp-ping"); const Ping = require("./ping-lite"); const { R } = require("redbean-node"); const { debug } = require("../src/util"); +const passwordHash = require("./password-hash"); +const dayjs = require("dayjs"); + +/** + * Init or reset JWT secret + * @returns {Promise} + */ +exports.initJWTSecret = async () => { + let jwtSecretBean = await R.findOne("setting", " `key` = ? ", [ + "jwtSecret", + ]); + + if (! jwtSecretBean) { + jwtSecretBean = R.dispense("setting"); + jwtSecretBean.key = "jwtSecret"; + } + + jwtSecretBean.value = passwordHash.generate(dayjs() + ""); + await R.store(jwtSecretBean); + return jwtSecretBean; +} exports.tcping = function (hostname, port) { return new Promise((resolve, reject) => {