From beb627830fbb4fe0502ff258a18a3c678eb6807f Mon Sep 17 00:00:00 2001 From: Nate Emmert Date: Wed, 19 Aug 2026 15:05:44 -0400 Subject: [PATCH] Add dashboard password recovery via daily server-log token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getDailyRecoveryToken(): HMAC of SESSION_SECRET + current day — changes every 24h, requires no extra storage - Logs token to console on startup (visible in CasaOS container logs) - GET/POST /dashboard/recover: validates token, sets new password - dashboard-recover.html: enter token + new password, with instructions - dashboard-login.html: 'Forgot password?' link + success message after recovery Co-Authored-By: Claude Sonnet 4.6 --- app/public/dashboard-login.html | 3 ++ app/public/dashboard-recover.html | 55 +++++++++++++++++++++++++++++++ app/server.js | 34 ++++++++++++++++++- 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 app/public/dashboard-recover.html diff --git a/app/public/dashboard-login.html b/app/public/dashboard-login.html index bc9f058..082794b 100644 --- a/app/public/dashboard-login.html +++ b/app/public/dashboard-login.html @@ -30,6 +30,7 @@ button:hover { opacity: 0.88; }

Station Dashboard

Sign in to view your weather data

+
@@ -37,9 +38,11 @@ button:hover { opacity: 0.88; }
+ Forgot password? diff --git a/app/public/dashboard-recover.html b/app/public/dashboard-recover.html new file mode 100644 index 0000000..fa83c80 --- /dev/null +++ b/app/public/dashboard-recover.html @@ -0,0 +1,55 @@ + + + + + +Station Dashboard — Recover Access + + + +
+

Recover Access

+

Reset your dashboard password

+
+ Find your recovery token in the server logs — look for a line that says
+ Dashboard recovery token:
+ In CasaOS, open the app's log from the container details panel. The token changes daily. +
+ +
+ + + + + +
+ ← Back to sign in +
+ + + diff --git a/app/server.js b/app/server.js index 0af9794..419d980 100644 --- a/app/server.js +++ b/app/server.js @@ -62,6 +62,11 @@ function hashPassword(password, salt) { return crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex'); } +function getDailyRecoveryToken() { + const day = Math.floor(Date.now() / (24 * 60 * 60 * 1000)); + return crypto.createHmac('sha256', SESSION_SECRET).update('recover:' + day).digest('hex').slice(0, 16); +} + function requireDashboardAuth(req, res, next) { const cookies = parseCookies(req); const user = verifySession(cookies.dsession); @@ -856,6 +861,27 @@ app.get('/dashboard/logout', (_req, res) => { res.redirect('/dashboard/login'); }); +// Password recovery +app.get('/dashboard/recover', (_req, res) => { + if (!dashAuth) return res.redirect('/dashboard/setup'); + res.sendFile(path.join(__dirname, 'public', 'dashboard-recover.html')); +}); + +app.post('/dashboard/recover', (req, res) => { + if (!dashAuth) return res.redirect('/dashboard/setup'); + const { token, password } = req.body || {}; + if (!token || !password || password.length < 6) return res.redirect('/dashboard/recover?error=1'); + const expected = getDailyRecoveryToken(); + let match = false; + try { match = crypto.timingSafeEqual(Buffer.from(token.trim()), Buffer.from(expected)); } catch {} + if (!match) return res.redirect('/dashboard/recover?error=1'); + const salt = crypto.randomBytes(16).toString('hex'); + const hash = hashPassword(password, salt); + dashAuth = { ...dashAuth, salt, hash }; + try { fs.writeFileSync(DASH_AUTH_FILE, JSON.stringify(dashAuth)); } catch {} + res.redirect('/dashboard/login?recovered=1'); +}); + // Main dashboard app.get('/dashboard', requireDashboardAuth, (_req, res) => { if (!dashAuth) return res.redirect('/dashboard/setup'); @@ -1144,4 +1170,10 @@ app.get('/api/conditions/public', (_req, res) => { res.json(cached?.data?.[0]?.lastData || null); }); -app.listen(PORT, () => console.log(`Weather app running on port ${PORT}`)); +app.listen(PORT, () => { + console.log(`Weather app running on port ${PORT}`); + if (dashAuth) { + console.log(`Dashboard recovery token (changes daily): ${getDailyRecoveryToken()}`); + console.log(` Use at http://localhost:${PORT}/dashboard/recover`); + } +});