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:
+31
-4
@@ -121,6 +121,11 @@ router.patch('/me', (req, res) => {
|
||||
values.push(value || null);
|
||||
}
|
||||
|
||||
if (req.body?.role !== undefined || req.body?.org_id !== undefined) {
|
||||
if (!auth.permissionsOfRole(req.user.role).includes('user.manage')) {
|
||||
return res.status(403).json({ error: '角色与组织请联系管理员修改' });
|
||||
}
|
||||
}
|
||||
if (req.body?.role !== undefined) {
|
||||
const role = String(req.body.role || '').trim();
|
||||
if (!ROLE_VALUES.includes(role)) throw badRequest('角色取值不在允许范围内');
|
||||
@@ -150,6 +155,31 @@ router.patch('/me', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/** PATCH /api/me/username —— 账号绑定用户 id,改名不改变业务数据归属。 */
|
||||
router.patch('/me/username', auth.requireAuth, (req, res) => {
|
||||
try {
|
||||
const username = String(req.body?.username || '').trim();
|
||||
if (!/^[a-zA-Z0-9_][a-zA-Z0-9_.@-]{2,49}$/.test(username)) {
|
||||
throw badRequest('用户名需为 3–50 位字母、数字或 _ . @ -,以字母、数字或下划线开头');
|
||||
}
|
||||
auth.verifyCurrentPassword(req, req.body?.current_password);
|
||||
if (username === req.user.username) throw badRequest('新用户名与当前用户名相同');
|
||||
const change = db.transaction(() => {
|
||||
if (db.prepare('SELECT id FROM users WHERE username = ? COLLATE NOCASE AND id != ?').get(username, req.user.id)) {
|
||||
const error = badRequest('该用户名已被使用');
|
||||
error.statusCode = 409;
|
||||
throw error;
|
||||
}
|
||||
db.prepare('UPDATE users SET username = ?, updated_at = ? WHERE id = ?')
|
||||
.run(username, new Date().toISOString(), req.user.id);
|
||||
db.prepare('DELETE FROM sessions WHERE user_id = ? AND id != ?').run(req.user.id, req.user.sid);
|
||||
});
|
||||
change();
|
||||
auth.audit({ username, action: 'username_change', detail: `用户名由 ${req.user.username} 修改为 ${username}`, req });
|
||||
res.json({ user: auth.publicUser(db.prepare('SELECT * FROM users WHERE id = ?').get(req.user.id)), message: '用户名已修改,其它设备需重新登录' });
|
||||
} catch (error) { fail(res, error); }
|
||||
});
|
||||
|
||||
/** PATCH /api/me/password —— 改密码:三段校验全过才落库 */
|
||||
router.patch('/me/password', (req, res) => {
|
||||
try {
|
||||
@@ -163,10 +193,7 @@ router.patch('/me/password', (req, res) => {
|
||||
if (next.length < 6) throw badRequest('新密码至少 6 位');
|
||||
if (next !== confirm) throw badRequest('两次输入的新密码不一致');
|
||||
if (next === current) throw badRequest('新密码不能与当前密码相同');
|
||||
if (!auth.verifyPassword(current, req.user.password_hash)) {
|
||||
auth.audit({ username: req.user.username, action: 'password_change_failed', detail: '当前密码不正确', req, ok: 0 });
|
||||
throw badRequest('当前密码不正确');
|
||||
}
|
||||
auth.verifyCurrentPassword(req, current);
|
||||
|
||||
db.prepare('UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?')
|
||||
.run(auth.hashPassword(next), new Date().toISOString(), req.user.id);
|
||||
|
||||
Reference in New Issue
Block a user