安全修复:1.密码哈希验证 2.移除硬编码凭据 3.JWT强密钥 4.CORS白名单
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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 });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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 }));
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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 || ''
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
@@ -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,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 = {
|
||||||
|
|||||||
@@ -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 }));
|
||||||
|
|
||||||
|
|||||||
@@ -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 }));
|
||||||
|
|
||||||
|
|||||||
@@ -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 - 明文密码
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{
|
{
|
||||||
"id": "codingplan",
|
"id": "codingplan",
|
||||||
"name": "腾讯云CodingPlan",
|
"name": "腾讯云CodingPlan",
|
||||||
"apiKey": "sk-sp-xGrYANrlwJfsQ8hAoyR2eUlHntiXlpuPZkFPeddakTXEUwDe",
|
"apiKey": "",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"models": [
|
"models": [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user