Add dashboard password recovery via daily server-log token
- 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 <noreply@anthropic.com>
This commit is contained in:
1 parent
e10bfaa57f
commit
beb627830f
3 files changed
+91
-1
No files matched your search
+33
-1
@@ -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`);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user