fix(auth): real client IP rate-limiting + remote default-password guard
- Add custom-server.js: inject unspoofable socket IP, strip client XFF (wired into Docker CMD + CLI spawn + build-cli copy) - loginLimiter: key on trusted x-9r-real-ip, TRUST_PROXY opt-in, global fallback - Force password change on first remote login while default is in use - Add /api/auth/reset-password (local-only) so CLI reset writes live SQLite - CLI settings: reset via API instead of stale db.json - Fix OAuth modals opening duplicate browser tabs on add-connection - Add cli:pack / cli:publish scripts Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -410,6 +410,14 @@ async function updateSettings(data) {
|
||||
return makeRequest("PATCH", "/api/settings", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset dashboard password to default (clears stored hash server-side)
|
||||
* @returns {Promise<Object>} { success }
|
||||
*/
|
||||
async function resetPassword() {
|
||||
return makeRequest("POST", "/api/auth/reset-password");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MODELS API
|
||||
// ============================================================================
|
||||
@@ -528,6 +536,7 @@ module.exports = {
|
||||
// Settings
|
||||
getSettings,
|
||||
updateSettings,
|
||||
resetPassword,
|
||||
|
||||
// Tunnel
|
||||
getTunnelStatus,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const api = require("../api/client");
|
||||
const { confirm, pause } = require("../utils/input");
|
||||
const { showStatus } = require("../utils/display");
|
||||
@@ -18,13 +15,6 @@ const COLORS = {
|
||||
|
||||
const DEFAULT_PASSWORD = "123456";
|
||||
|
||||
// Resolve db.json path (matches app/src/lib/dataDir.js convention)
|
||||
function getDbPath() {
|
||||
return process.platform === "win32"
|
||||
? path.join(process.env.APPDATA || "", "9router", "db.json")
|
||||
: path.join(os.homedir(), ".9router", "db.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show settings menu (tunnel + RTK + reset password)
|
||||
* @param {Array<string>} breadcrumb - Breadcrumb path
|
||||
@@ -171,18 +161,10 @@ async function toggleRtk(currentlyOn) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset dashboard password by clearing the hash in db.json (Phase B).
|
||||
* Reset dashboard password to default via server API (writes the live SQLite DB).
|
||||
* After reset, user can log in with the default password "123456".
|
||||
*/
|
||||
async function resetPassword() {
|
||||
const dbPath = getDbPath();
|
||||
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
showStatus(`db.json not found at ${dbPath}`, "error");
|
||||
await pause();
|
||||
return;
|
||||
}
|
||||
|
||||
const ok = await confirm(`Reset dashboard password to default "${DEFAULT_PASSWORD}"?`);
|
||||
if (!ok) {
|
||||
showStatus("Cancelled", "info");
|
||||
@@ -190,16 +172,11 @@ async function resetPassword() {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = fs.readFileSync(dbPath, "utf-8");
|
||||
const db = JSON.parse(raw);
|
||||
if (db.settings && Object.prototype.hasOwnProperty.call(db.settings, "password")) {
|
||||
delete db.settings.password;
|
||||
}
|
||||
fs.writeFileSync(dbPath, JSON.stringify(db, null, 2));
|
||||
const result = await api.resetPassword();
|
||||
if (result.success) {
|
||||
showStatus(`Password reset. Default: ${DEFAULT_PASSWORD}`, "success");
|
||||
} catch (err) {
|
||||
showStatus(`Failed to reset password: ${err.message}`, "error");
|
||||
} else {
|
||||
showStatus(`Failed to reset password: ${result.error}`, "error");
|
||||
}
|
||||
await pause();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user