feat: add password reset, SMTP settings, and Add Account button

Password reset: users with a registered email can request a reset link
from the login screen. A one-hour signed token is emailed via SMTP;
clicking the link opens a set-new-password form. Tokens are hashed
(SHA-256) before storage and invalidated after use.

SMTP settings: admin-only panel in the Users modal lets admins
configure host, port, encryption, credentials, and from address.
Settings persisted in a new key-value settings table. The SMTP
password is never returned to the client.

Users: email field added to the create/edit form and stored in a new
users.email column. Email is used for password reset lookup.

Add Account: admins now have a + button in the header that opens the
existing setup wizard to add additional checking accounts.

Schema: adds password_reset_tokens and settings tables with automatic
runtime migrations for existing databases.
This commit is contained in:
2026-03-31 10:21:49 -06:00
parent 444e24a191
commit fc114d0ec6
10 changed files with 378 additions and 12 deletions
+39
View File
@@ -0,0 +1,39 @@
'use strict';
const express = require('express');
const router = express.Router();
const db = require('../db/database');
const { requireAdmin } = require('../middleware/auth');
router.use(requireAdmin);
// GET /api/settings/smtp
router.get('/smtp', (req, res) => {
const rows = db.prepare("SELECT key, value FROM settings WHERE key LIKE 'smtp_%'").all();
const s = Object.fromEntries(rows.map(r => [r.key.replace('smtp_', ''), r.value || '']));
res.json({
host: s.host || '',
port: s.port || '587',
secure: s.secure === '1',
user: s.user || '',
from: s.from || '',
has_password: !!(rows.find(r => r.key === 'smtp_pass') || {}).value,
});
});
// PUT /api/settings/smtp
router.put('/smtp', (req, res) => {
const { host, port, secure, user, pass, from } = req.body;
const upsert = db.prepare('INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)');
db.transaction(() => {
upsert.run('smtp_host', host || '');
upsert.run('smtp_port', String(parseInt(port, 10) || 587));
upsert.run('smtp_secure', secure ? '1' : '0');
upsert.run('smtp_user', user || '');
if (pass !== undefined && pass !== '') upsert.run('smtp_pass', pass);
upsert.run('smtp_from', from || '');
})();
res.json({ ok: true });
});
module.exports = router;