feat: 实现修改管理员密码的功能
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Request, Response } from 'express';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { getDb } from '../database';
|
||||
import sqlite3, { RunResult } from 'sqlite3'; // 导入 RunResult 类型
|
||||
|
||||
const db = getDb(); // 获取数据库实例
|
||||
|
||||
@@ -80,3 +81,96 @@ export const login = async (req: Request, res: Response): Promise<void> => {
|
||||
// 其他认证相关函数的占位符 (登出, 管理员设置等)
|
||||
// export const logout = ...
|
||||
// export const setupAdmin = ...
|
||||
|
||||
/**
|
||||
* 处理修改密码请求 (PUT /api/v1/auth/password)
|
||||
*/
|
||||
export const changePassword = async (req: Request, res: Response): Promise<void> => {
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const userId = req.session.userId; // 从会话中获取用户 ID
|
||||
|
||||
// 检查用户是否登录
|
||||
if (!userId) {
|
||||
res.status(401).json({ message: '用户未认证,请先登录。' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 基础输入验证
|
||||
if (!currentPassword || !newPassword) {
|
||||
res.status(400).json({ message: '当前密码和新密码不能为空。' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 可选:添加新密码复杂度要求
|
||||
if (newPassword.length < 8) {
|
||||
res.status(400).json({ message: '新密码长度至少需要 8 位。' });
|
||||
return;
|
||||
}
|
||||
if (currentPassword === newPassword) {
|
||||
res.status(400).json({ message: '新密码不能与当前密码相同。' });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 1. 获取当前用户的哈希密码
|
||||
const user = await new Promise<User | undefined>((resolve, reject) => {
|
||||
db.get('SELECT id, hashed_password FROM users WHERE id = ?', [userId], (err, row: User) => {
|
||||
if (err) {
|
||||
console.error(`查询用户 ${userId} 时出错:`, err.message);
|
||||
return reject(new Error('数据库查询失败'));
|
||||
}
|
||||
resolve(row);
|
||||
});
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
// 理论上不应该发生,因为 userId 来自 session
|
||||
console.error(`修改密码错误: 未找到 ID 为 ${userId} 的用户。`);
|
||||
res.status(404).json({ message: '用户不存在。' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 验证当前密码
|
||||
const isMatch = await bcrypt.compare(currentPassword, user.hashed_password);
|
||||
if (!isMatch) {
|
||||
console.log(`修改密码尝试失败: 当前密码错误 - 用户 ID ${userId}`);
|
||||
res.status(400).json({ message: '当前密码不正确。' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 哈希新密码
|
||||
const saltRounds = 10;
|
||||
const newHashedPassword = await bcrypt.hash(newPassword, saltRounds);
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
|
||||
// 4. 更新数据库中的密码
|
||||
await new Promise<void>((resolveUpdate, rejectUpdate) => {
|
||||
const stmt = db.prepare(
|
||||
'UPDATE users SET hashed_password = ?, updated_at = ? WHERE id = ?'
|
||||
);
|
||||
// 在回调函数中明确 this 的类型为 RunResult
|
||||
stmt.run(newHashedPassword, now, userId, function (this: RunResult, err: Error | null) {
|
||||
if (err) {
|
||||
console.error(`更新用户 ${userId} 密码时出错:`, err.message);
|
||||
return rejectUpdate(new Error('更新密码失败'));
|
||||
}
|
||||
if (this.changes === 0) {
|
||||
// 理论上不应该发生
|
||||
console.error(`修改密码错误: 更新影响行数为 0 - 用户 ID ${userId}`);
|
||||
return rejectUpdate(new Error('未找到要更新的用户'));
|
||||
}
|
||||
console.log(`用户 ${userId} 密码已成功修改。`);
|
||||
resolveUpdate();
|
||||
});
|
||||
stmt.finalize();
|
||||
});
|
||||
|
||||
// 5. 返回成功响应
|
||||
res.status(200).json({ message: '密码已成功修改。' });
|
||||
|
||||
} catch (error) {
|
||||
console.error(`修改用户 ${userId} 密码时发生内部错误:`, error);
|
||||
res.status(500).json({ message: '修改密码过程中发生内部服务器错误。' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { Router } from 'express';
|
||||
import { login } from './auth.controller';
|
||||
import { login, changePassword } from './auth.controller'; // 导入 changePassword
|
||||
import { isAuthenticated } from './auth.middleware'; // 导入认证中间件
|
||||
|
||||
const router = Router();
|
||||
|
||||
// POST /api/v1/auth/login - 用户登录接口
|
||||
router.post('/login', login);
|
||||
|
||||
// PUT /api/v1/auth/password - 修改密码接口 (需要认证)
|
||||
router.put('/password', isAuthenticated, changePassword);
|
||||
|
||||
// 未来可以添加的其他认证相关路由
|
||||
// router.post('/logout', logout); // 登出
|
||||
// router.get('/status', getStatus); // 获取当前登录状态
|
||||
|
||||
@@ -13,6 +13,7 @@ import connectionsRouter from './connections/connections.routes';
|
||||
import sftpRouter from './sftp/sftp.routes';
|
||||
import proxyRoutes from './proxies/proxies.routes'; // 导入代理路由
|
||||
import tagsRouter from './tags/tags.routes'; // 导入标签路由
|
||||
import settingsRoutes from './settings/settings.routes'; // 导入设置路由
|
||||
import { initializeWebSocket } from './websocket';
|
||||
|
||||
// 基础 Express 应用设置 (后续会扩展)
|
||||
@@ -86,6 +87,7 @@ app.use('/api/v1/connections', connectionsRouter);
|
||||
app.use('/api/v1/sftp', sftpRouter);
|
||||
app.use('/api/v1/proxies', proxyRoutes); // 挂载代理相关的路由
|
||||
app.use('/api/v1/tags', tagsRouter); // 挂载标签相关的路由
|
||||
app.use('/api/v1/settings', settingsRoutes); // 挂载设置相关的路由
|
||||
|
||||
// 状态检查接口
|
||||
app.get('/api/v1/status', (req: Request, res: Response) => {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { getDb } from '../database'; // 正确导入 getDb 函数
|
||||
|
||||
const db = getDb(); // 获取数据库实例
|
||||
|
||||
export interface Setting {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const settingsRepository = {
|
||||
async getAllSettings(): Promise<Setting[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all('SELECT key, value FROM settings', (err: any, rows: Setting[]) => { // 添加 err 类型
|
||||
if (err) {
|
||||
console.error('获取所有设置时出错:', err); // 更新日志为中文
|
||||
reject(new Error('获取设置失败')); // 更新错误消息为中文
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async getSetting(key: string): Promise<string | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get('SELECT value FROM settings WHERE key = ?', [key], (err: any, row: { value: string } | undefined) => { // 添加 err 类型
|
||||
if (err) {
|
||||
console.error(`获取设置项 ${key} 时出错:`, err); // 更新日志为中文
|
||||
reject(new Error(`获取设置项 ${key} 失败`)); // 更新错误消息为中文
|
||||
} else {
|
||||
resolve(row ? row.value : null);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async setSetting(key: string, value: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
'INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value',
|
||||
[key, value],
|
||||
function (err: any) { // 添加 err 类型
|
||||
if (err) {
|
||||
console.error(`设置设置项 ${key} 时出错:`, err); // 更新日志为中文
|
||||
reject(new Error(`设置设置项 ${key} 失败`)); // 更新错误消息为中文
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
async deleteSetting(key: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run('DELETE FROM settings WHERE key = ?', [key], function (err: any) { // 添加 err 类型
|
||||
if (err) {
|
||||
console.error(`删除设置项 ${key} 时出错:`, err); // 更新日志为中文
|
||||
reject(new Error(`删除设置项 ${key} 失败`)); // 更新错误消息为中文
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async setMultipleSettings(settings: Record<string, string>): Promise<void> {
|
||||
const promises = Object.entries(settings).map(([key, value]) =>
|
||||
this.setSetting(key, value)
|
||||
);
|
||||
await Promise.all(promises);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { settingsRepository, Setting } from '../repositories/settings.repository';
|
||||
|
||||
export const settingsService = {
|
||||
/**
|
||||
* 获取所有设置项
|
||||
* @returns 返回包含所有设置项的数组
|
||||
*/
|
||||
async getAllSettings(): Promise<Record<string, string>> {
|
||||
const settingsArray = await settingsRepository.getAllSettings();
|
||||
const settingsRecord: Record<string, string> = {};
|
||||
settingsArray.forEach(setting => {
|
||||
settingsRecord[setting.key] = setting.value;
|
||||
});
|
||||
return settingsRecord;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取单个设置项的值
|
||||
* @param key 设置项的键
|
||||
* @returns 返回设置项的值,如果不存在则返回 null
|
||||
*/
|
||||
async getSetting(key: string): Promise<string | null> {
|
||||
return settingsRepository.getSetting(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置单个设置项的值 (如果键已存在则更新)
|
||||
* @param key 设置项的键
|
||||
* @param value 设置项的值
|
||||
*/
|
||||
async setSetting(key: string, value: string): Promise<void> {
|
||||
await settingsRepository.setSetting(key, value);
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量设置多个设置项的值
|
||||
* @param settings 包含多个设置项键值对的对象
|
||||
*/
|
||||
async setMultipleSettings(settings: Record<string, string>): Promise<void> {
|
||||
await settingsRepository.setMultipleSettings(settings);
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除单个设置项
|
||||
* @param key 要删除的设置项的键
|
||||
*/
|
||||
async deleteSetting(key: string): Promise<void> {
|
||||
await settingsRepository.deleteSetting(key);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { settingsService } from '../services/settings.service';
|
||||
|
||||
export const settingsController = {
|
||||
/**
|
||||
* 获取所有设置项
|
||||
*/
|
||||
async getAllSettings(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const settings = await settingsService.getAllSettings();
|
||||
res.json(settings);
|
||||
} catch (error: any) {
|
||||
console.error('获取所有设置时出错:', error);
|
||||
res.status(500).json({ message: '获取设置失败', error: error.message });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量更新设置项
|
||||
*/
|
||||
async updateSettings(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
// TODO: 添加输入验证,确保 req.body 是 Record<string, string>
|
||||
const settingsToUpdate: Record<string, string> = req.body;
|
||||
if (typeof settingsToUpdate !== 'object' || settingsToUpdate === null) {
|
||||
res.status(400).json({ message: '无效的请求体,应为 JSON 对象' });
|
||||
return;
|
||||
}
|
||||
// 可以在这里添加更严格的验证,例如检查值的类型等
|
||||
|
||||
await settingsService.setMultipleSettings(settingsToUpdate);
|
||||
res.status(200).json({ message: '设置已成功更新' });
|
||||
} catch (error: any) {
|
||||
console.error('更新设置时出错:', error);
|
||||
res.status(500).json({ message: '更新设置失败', error: error.message });
|
||||
}
|
||||
},
|
||||
|
||||
// 注意:通常不直接通过 API 提供单个设置项的获取、设置或删除,
|
||||
// 而是通过批量获取/更新来管理。如果需要单独操作,可以添加相应方法。
|
||||
// 例如:
|
||||
// async getSetting(req: Request, res: Response): Promise<void> { ... }
|
||||
// async setSetting(req: Request, res: Response): Promise<void> { ... }
|
||||
// async deleteSetting(req: Request, res: Response): Promise<void> { ... }
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import express from 'express';
|
||||
import { settingsController } from './settings.controller';
|
||||
import { isAuthenticated } from '../auth/auth.middleware'; // 导入认证中间件
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 应用认证中间件,确保只有登录用户才能访问设置相关 API
|
||||
router.use(isAuthenticated);
|
||||
|
||||
// 定义路由
|
||||
router.get('/', settingsController.getAllSettings); // GET /api/v1/settings
|
||||
router.put('/', settingsController.updateSettings); // PUT /api/v1/settings
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user