feat(安全): 实现JWT认证和HTTPS支持
- 添加JWT认证中间件保护API端点 - 在登录流程中使用bcrypt哈希密码和JWT令牌 - 配置HTTPS服务器使用自签名证书 - 更新前端API调用以包含认证令牌
This commit is contained in:
+59
-21
@@ -1,9 +1,13 @@
|
||||
import https from 'https'
|
||||
import fs from 'fs'
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { Parser } from 'json2csv'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import mysql from 'mysql2/promise'
|
||||
import dotenv from 'dotenv'
|
||||
import bcrypt from 'bcrypt'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
// Main function to start the server
|
||||
async function startServer() {
|
||||
@@ -43,14 +47,21 @@ async function startServer() {
|
||||
app.post('/api/auth/login', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body
|
||||
// In a real app, use a secure hashing library like bcrypt to compare passwords
|
||||
const [rows] = await db.execute(
|
||||
'SELECT id, role FROM workers WHERE username = ? AND password_hash = ?',
|
||||
[username, password],
|
||||
'SELECT id, role, password_hash FROM workers WHERE username = ?',
|
||||
[username],
|
||||
)
|
||||
if (rows.length > 0) {
|
||||
const user = rows[0]
|
||||
res.json({ message: 'Login successful', role: user.role, userId: user.id })
|
||||
const passwordMatch = await bcrypt.compare(password, user.password_hash)
|
||||
if (passwordMatch) {
|
||||
const token = jwt.sign({ userId: user.id, role: user.role }, process.env.JWT_SECRET, {
|
||||
expiresIn: '1h',
|
||||
})
|
||||
res.json({ message: 'Login successful', token })
|
||||
} else {
|
||||
res.status(401).json({ message: 'Invalid credentials' })
|
||||
}
|
||||
} else {
|
||||
res.status(401).json({ message: 'Invalid credentials' })
|
||||
}
|
||||
@@ -60,8 +71,28 @@ async function startServer() {
|
||||
}
|
||||
})
|
||||
|
||||
// Middleware to verify JWT
|
||||
const authenticateJWT = (req, res, next) => {
|
||||
const authHeader = req.headers.authorization
|
||||
|
||||
if (authHeader) {
|
||||
const token = authHeader.split(' ')[1]
|
||||
|
||||
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
|
||||
if (err) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
req.user = user
|
||||
next()
|
||||
})
|
||||
} else {
|
||||
res.sendStatus(401)
|
||||
}
|
||||
}
|
||||
|
||||
// Worker Clock In/Out Endpoint
|
||||
app.post('/api/clock', async (req, res) => {
|
||||
app.post('/api/clock', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { userId, eventType, qrCodeValue, latitude, longitude } = req.body
|
||||
const [qrRows] = await db.execute('SELECT name, is_active FROM qr_codes WHERE id = ?', [
|
||||
@@ -92,7 +123,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Fetch worker details endpoint
|
||||
app.get('/api/workers/:id', async (req, res) => {
|
||||
app.get('/api/workers/:id', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const [rows] = await db.execute(
|
||||
@@ -111,7 +142,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Worker Status Endpoint
|
||||
app.get('/api/worker/status/:userId', async (req, res) => {
|
||||
app.get('/api/worker/status/:userId', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params
|
||||
const [rows] = await db.execute(
|
||||
@@ -130,7 +161,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Worker History Endpoint
|
||||
app.get('/api/worker/clock-history/:userId', async (req, res) => {
|
||||
app.get('/api/worker/clock-history/:userId', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params
|
||||
// MODIFIED: Use LEFT JOIN and COALESCE to handle manual entries
|
||||
@@ -146,7 +177,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Manager: GET All Workers with Search and Pagination
|
||||
app.get('/api/managers/workers', async (req, res) => {
|
||||
app.get('/api/managers/workers', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { search = '', page = 1, limit = 20 } = req.query
|
||||
const offset = (parseInt(page) - 1) * parseInt(limit)
|
||||
@@ -167,15 +198,17 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Manager: POST (Add new) Worker
|
||||
app.post('/api/managers/workers', async (req, res) => {
|
||||
app.post('/api/managers/workers', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { username, password, fullName } = req.body
|
||||
if (!username || !password || !fullName) {
|
||||
return res.status(400).json({ message: 'Username, password, and full name are required.' })
|
||||
}
|
||||
const saltRounds = 10;
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||
const [result] = await db.execute(
|
||||
"INSERT INTO workers (username, password_hash, full_name, role) VALUES (?, ?, ?, 'worker')",
|
||||
[username, password, fullName],
|
||||
[username, hashedPassword, fullName],
|
||||
)
|
||||
res.status(201).json({ id: result.insertId, username, fullName })
|
||||
} catch (error) {
|
||||
@@ -188,7 +221,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Manager: DELETE Worker
|
||||
app.delete('/api/managers/workers/:id', async (req, res) => {
|
||||
app.delete('/api/managers/workers/:id', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const [result] = await db.execute("DELETE FROM workers WHERE id = ? AND role = 'worker'", [
|
||||
@@ -207,7 +240,7 @@ async function startServer() {
|
||||
// --- NEW --- Manager: POST (Add Manual Attendance Record)
|
||||
// Note: For this to work, you may need to alter your database table:
|
||||
// ALTER TABLE clock_records ADD COLUMN notes TEXT;
|
||||
app.post('/api/managers/add-record', async (req, res) => {
|
||||
app.post('/api/managers/add-record', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { workerId, eventType, timestamp, notes } = req.body
|
||||
|
||||
@@ -244,7 +277,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Manager: GET Attendance Records
|
||||
app.get('/api/managers/attendance-records', async (req, res) => {
|
||||
app.get('/api/managers/attendance-records', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { workerIds, startDate, endDate, format } = req.query
|
||||
if (!workerIds) {
|
||||
@@ -286,7 +319,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Manager: GET QR Codes
|
||||
app.get('/api/managers/qr-codes', async (req, res) => {
|
||||
app.get('/api/managers/qr-codes', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const [rows] = await db.execute(
|
||||
'SELECT id, name, is_active, created_at FROM qr_codes ORDER BY created_at DESC',
|
||||
@@ -299,7 +332,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Manager: POST QR Code
|
||||
app.post('/api/managers/qr-codes', async (req, res) => {
|
||||
app.post('/api/managers/qr-codes', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { name } = req.body
|
||||
if (!name) return res.status(400).json({ message: 'QR Code name is required.' })
|
||||
@@ -319,7 +352,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Manager: PUT QR Code
|
||||
app.put('/api/managers/qr-codes/:id', async (req, res) => {
|
||||
app.put('/api/managers/qr-codes/:id', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { isActive } = req.body
|
||||
@@ -338,7 +371,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Manager: DELETE QR Code
|
||||
app.delete('/api/managers/qr-codes/:id', async (req, res) => {
|
||||
app.delete('/api/managers/qr-codes/:id', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const [result] = await db.execute('DELETE FROM qr_codes WHERE id = ?', [id])
|
||||
@@ -351,7 +384,7 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// Manager: GET single worker's details
|
||||
app.get('/api/managers/worker/:id', async (req, res) => {
|
||||
app.get('/api/managers/worker/:id', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const [rows] = await db.execute(
|
||||
@@ -370,8 +403,13 @@ async function startServer() {
|
||||
})
|
||||
|
||||
// --- Server Start ---
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on http://localhost:${port}`)
|
||||
const httpsOptions = {
|
||||
key: fs.readFileSync(process.env.SSL_KEY_PATH),
|
||||
cert: fs.readFileSync(process.env.SSL_CERT_PATH),
|
||||
}
|
||||
|
||||
https.createServer(httpsOptions, app).listen(port, () => {
|
||||
console.log(`Server is running on https://localhost:${port}`)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user