feat: add admin web updates and verified username changes
Add isolated update jobs with database backups, progress logs and application rollback. Remove default account hints and prevent default admin recreation after renaming. Enforce customer and order search scopes, secure session cookies, fix quotation rounding and amount wording, and update backend dependencies. Add API and deployment regression coverage.
This commit is contained in:
@@ -5,8 +5,44 @@
|
||||
const express = require('express');
|
||||
const updater = require('../lib/updater');
|
||||
const branding = require('../lib/branding');
|
||||
const auth = require('../lib/auth');
|
||||
const webUpdate = require('../lib/webUpdate');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const router = express.Router();
|
||||
function adminOnly(req, res, next) {
|
||||
if (!req.user) return res.status(401).json({ error: '请先登录' });
|
||||
if (req.user.role !== 'admin') return res.status(403).json({ error: '仅系统管理员可执行系统更新' });
|
||||
next();
|
||||
}
|
||||
router.get('/update/status', adminOnly, async (req, res) => {
|
||||
const status = await webUpdate.readStatus();
|
||||
let log = '';
|
||||
if (/^[a-f0-9]{16}$/.test(status.id || '')) {
|
||||
const data = path.resolve(process.env.DATA_DIR || path.join(__dirname, '../data'));
|
||||
try {
|
||||
const file = path.join(data, 'system-update', status.id, 'update.log');
|
||||
const fd = fs.openSync(file, 'r');
|
||||
try {
|
||||
const size = fs.fstatSync(fd).size;
|
||||
const buffer = Buffer.alloc(Math.min(size, 12000));
|
||||
fs.readSync(fd, buffer, 0, buffer.length, Math.max(0, size - buffer.length));
|
||||
log = buffer.toString('utf8');
|
||||
} finally { fs.closeSync(fd); }
|
||||
} catch { /* 任务尚未开始写日志 */ }
|
||||
}
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
res.json({ ...status, log, capability: webUpdate.capability() });
|
||||
});
|
||||
router.post('/update', adminOnly, async (req, res) => {
|
||||
try {
|
||||
auth.verifyCurrentPassword(req, req.body?.current_password);
|
||||
const status = await webUpdate.start(req);
|
||||
auth.audit({ username: req.user.username, action: 'system_update', detail: `网页更新至 ${status.version}`, req });
|
||||
res.status(202).json(status);
|
||||
} catch (error) { res.status(error.statusCode || 500).json({ error: error.message }); }
|
||||
});
|
||||
|
||||
// 简易缓存:GitHub 未认证请求每小时只有 60 次配额,连续点「检查更新」不该把额度刷完。
|
||||
const CACHE_TTL_MS = 30 * 1000;
|
||||
@@ -16,7 +52,7 @@ let cache = { key: '', at: 0, payload: null };
|
||||
* GET /api/system/info
|
||||
* 当前版本与运行环境。轻量、不发外部请求,设置页打开时就能调。
|
||||
*/
|
||||
router.get('/info', (req, res) => {
|
||||
router.get('/info', auth.requireAuth, (req, res) => {
|
||||
try {
|
||||
res.json(updater.systemInfo());
|
||||
} catch (error) {
|
||||
@@ -28,9 +64,9 @@ router.get('/info', (req, res) => {
|
||||
* GET /api/system/check-update?repo=owner/repo&force=1
|
||||
* 对比远端仓库版本与本机版本。repo 省略时用默认仓库(Drsakura/happy-export)。
|
||||
*/
|
||||
router.get('/check-update', async (req, res) => {
|
||||
router.get('/check-update', auth.requireAuth, async (req, res) => {
|
||||
try {
|
||||
const repo = updater.normalizeRepo(req.query.repo) || updater.DEFAULT_REPO;
|
||||
const repo = updater.normalizeRepo(process.env.HAPPY_UPDATE_REPO) || updater.DEFAULT_REPO;
|
||||
const force = String(req.query.force || '') === '1';
|
||||
|
||||
if (!force && cache.payload && cache.key === repo && Date.now() - cache.at < CACHE_TTL_MS) {
|
||||
|
||||
Reference in New Issue
Block a user