安全修复:1.密码哈希验证 2.移除硬编码凭据 3.JWT强密钥 4.CORS白名单

This commit is contained in:
root
2026-04-19 19:20:12 +08:00
parent d00f41a120
commit 38eef97480
14 changed files with 111 additions and 53 deletions
+2
View File
@@ -18,6 +18,8 @@ build/
# Environment variables # Environment variables
.env .env
.env.local .env.local
.env.production
.env.production.local
.env.*.local .env.*.local
# IDE # IDE
+9 -5
View File
@@ -2,16 +2,20 @@
NODE_ENV=production NODE_ENV=production
PORT=5000 PORT=5000
# 生产数据库配置 # 生产数据库配置(请填写实际值,不要提交此文件到版本控制)
DB_HOST=localhost DB_HOST=localhost
DB_PORT=5432 DB_PORT=5432
DB_NAME=company_finance_db DB_NAME=company_finance_db
DB_USER=finance_user DB_USER=finance_user
DB_PASSWORD=FinanceDB2026! DB_PASSWORD=
# 安全配置 # 安全配置(请填写实际值)
JWT_SECRET=your-production-jwt-secret-key-change-this JWT_SECRET=
SESSION_SECRET=your-production-session-secret-change-this SESSION_SECRET=
# 腾讯云COS配置(请填写实际值)
TENCENT_SECRET_ID=
TENCENT_SECRET_KEY=
# 日志配置 # 日志配置
LOG_LEVEL=info LOG_LEVEL=info
+28 -17
View File
@@ -5,6 +5,7 @@ const multer = require('multer');
const xlsx = require('xlsx'); const xlsx = require('xlsx');
require('dotenv').config(); require('dotenv').config();
const db = require('./db-sqlite'); const db = require('./db-sqlite');
const { verifyPassword, generateToken } = require('./utils/auth');
// 文件上传配置 // 文件上传配置
const storage = multer.memoryStorage(); const storage = multer.memoryStorage();
@@ -15,7 +16,13 @@ const app = express();
const PORT = process.argv[2] || process.env.PORT || 3000; const PORT = process.argv[2] || process.env.PORT || 3000;
// 中间件 // 中间件
app.use(cors()); const corsOptions = {
origin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:5173', 'http://localhost:3001'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
@@ -61,17 +68,23 @@ app.post('/api/auth/login', [
try { try {
const { username, password } = req.body; const { username, password } = req.body;
const result = await db.query( const result = await db.query(
'SELECT id, username, role, name FROM users WHERE username = ? AND password = ?', 'SELECT id, username, role, name, password_hash FROM users WHERE username = ?',
[username, password] [username]
); );
if (result.rows.length === 0) { if (result.rows.length === 0) {
return res.status(401).json({ success: false, message: '用户名或密码错误' }); return res.status(401).json({ success: false, message: '用户名或密码错误' });
} }
res.json({ success: true, data: result.rows[0] }); const user = result.rows[0];
if (!user.password_hash || !verifyPassword(password, user.password_hash)) {
return res.status(401).json({ success: false, message: '用户名或密码错误' });
}
const token = generateToken({ id: user.id, username: user.username, role: user.role });
res.json({ success: true, data: { id: user.id, username: user.username, role: user.role, name: user.name, token } });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '登录失败' });
} }
}); });
@@ -81,28 +94,26 @@ app.post('/api/users/login', [
body('password').notEmpty() body('password').notEmpty()
], validate, async (req, res) => { ], validate, async (req, res) => {
try { try {
console.log('收到登录请求:', req.body);
const { username, password } = req.body; const { username, password } = req.body;
const result = await db.query( const result = await db.query(
'SELECT id, username, role, name FROM users WHERE username = ? AND password = ?', 'SELECT id, username, role, name, password_hash FROM users WHERE username = ?',
[username, password] [username]
); );
console.log('查询结果:', result.rows);
if (result.rows.length === 0) { if (result.rows.length === 0) {
console.log('登录失败: 用户不存在或密码错误');
return res.status(401).json({ user: null, token: null, message: '用户名或密码错误' }); return res.status(401).json({ user: null, token: null, message: '用户名或密码错误' });
} }
// 返回前端期望的格式 const user = result.rows[0];
const responseData = { user: result.rows[0], token: 'dummy-token-' + Date.now() }; if (!user.password_hash || !verifyPassword(password, user.password_hash)) {
console.log('登录成功,返回数据:', responseData); return res.status(401).json({ user: null, token: null, message: '用户名或密码错误' });
res.json(responseData); }
const token = generateToken({ id: user.id, username: user.username, role: user.role });
res.json({ user: { id: user.id, username: user.username, role: user.role, name: user.name }, token });
} catch (error) { } catch (error) {
console.error('登录错误:', error); res.status(500).json({ user: null, token: null, message: '登录失败' });
res.status(500).json({ user: null, token: null, message: error.message });
} }
}); });
+7 -1
View File
@@ -9,7 +9,13 @@ const app = express();
const PORT = process.env.PORT || 3002; const PORT = process.env.PORT || 3002;
// 中间件 // 中间件
app.use(cors()); const corsOptions = {
origin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:5173', 'http://localhost:3001'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
+7 -1
View File
@@ -9,7 +9,13 @@ const app = express();
const PORT = process.env.PORT || 3002; const PORT = process.env.PORT || 3002;
// 中间件 // 中间件
app.use(cors()); const corsOptions = {
origin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:5173', 'http://localhost:3001'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
+7 -1
View File
@@ -9,7 +9,13 @@ const app = express();
const PORT = process.env.PORT || 3002; const PORT = process.env.PORT || 3002;
// 中间件 // 中间件
app.use(cors()); const corsOptions = {
origin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:5173', 'http://localhost:3001'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
+6 -5
View File
@@ -1,11 +1,12 @@
const { Pool } = require('pg'); const { Pool } = require('pg');
require('dotenv').config();
const pool = new Pool({ const pool = new Pool({
host: 'localhost', host: process.env.DB_HOST || 'localhost',
port: 5432, port: parseInt(process.env.DB_PORT || '5432'),
database: 'company_finance', database: process.env.DB_NAME || 'company_finance',
user: 'postgres', user: process.env.DB_USER || 'postgres',
password: 'X123c321@', password: process.env.DB_PASSWORD,
max: 20, max: 20,
idleTimeoutMillis: 30000, idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000, connectionTimeoutMillis: 2000,
+1 -1
View File
@@ -17,7 +17,7 @@ module.exports = {
DB_PORT: 5432, DB_PORT: 5432,
DB_NAME: 'company_finance_db', DB_NAME: 'company_finance_db',
DB_USER: 'finance_user', DB_USER: 'finance_user',
DB_PASSWORD: 'FinanceDB2026!' DB_PASSWORD: process.env.DB_PASSWORD || ''
} }
}] }]
}; };
+7 -1
View File
@@ -29,7 +29,13 @@ const app = express();
const PORT = process.env.PORT || 3002; const PORT = process.env.PORT || 3002;
// 中间件 // 中间件
app.use(cors()); const corsOptions = {
origin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:5173', 'http://localhost:3001'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
+1 -1
View File
@@ -1,7 +1,7 @@
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const { hashPassword, verifyPassword, verifyToken } = require('../utils/auth'); const { hashPassword, verifyPassword, verifyToken } = require('../utils/auth');
const JWT_SECRET = process.env.JWT_SECRET || 'your-jwt-secret-change-in-production'; const JWT_SECRET = process.env.JWT_SECRET;
const generateToken = (user) => { const generateToken = (user) => {
const payload = { const payload = {
+7 -1
View File
@@ -10,7 +10,13 @@ const app = express();
const PORT = process.env.PORT || 5000; const PORT = process.env.PORT || 5000;
// 中间件 // 中间件
app.use(cors()); const corsOptions = {
origin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:5173', 'http://localhost:3001'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
+7 -1
View File
@@ -9,7 +9,13 @@ const app = express();
const PORT = process.env.PORT || 3002; const PORT = process.env.PORT || 3002;
// 中间件 // 中间件
app.use(cors()); const corsOptions = {
origin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:5173', 'http://localhost:3001'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
+6 -2
View File
@@ -2,10 +2,14 @@
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
// JWT 密钥(生产环境应从环境变量读取) // JWT 密钥 - 必须通过环境变量设置,不提供默认值
const JWT_SECRET = process.env.JWT_SECRET || 'your-jwt-secret-change-in-production'; const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h'; const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';
if (!JWT_SECRET) {
console.error('⚠️ 警告:未设置 JWT_SECRET 环境变量,请在 .env 文件中配置');
}
/** /**
* 密码哈希 * 密码哈希
* @param {string} password - 明文密码 * @param {string} password - 明文密码
+1 -1
View File
@@ -3,7 +3,7 @@
{ {
"id": "codingplan", "id": "codingplan",
"name": "腾讯云CodingPlan", "name": "腾讯云CodingPlan",
"apiKey": "sk-sp-xGrYANrlwJfsQ8hAoyR2eUlHntiXlpuPZkFPeddakTXEUwDe", "apiKey": "",
"enabled": true, "enabled": true,
"models": [ "models": [
{ {