diff --git a/middleware/auth.js b/middleware/auth.js index 1d73e07..d0b00fe 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -1,25 +1,42 @@ import logger from "../utils/logger.js"; import configManager from "../utils/configManager.js"; +/** + * 需要登录的中间件 + * @param {import("express").Request} req + * @param {import("express").Response} res + * @param {import("express").NextFunction} next + */ async function needlogin(req, res, next) { if (!res.locals.login) { - // 未登录,返回401 Unauthorized状态码 + logger.info(`[needlogin] - ${req.ip} - 未登录,返回401 Unauthorized状态码`); return res.status(401).send({ status: "0", msg: "请先登录以继续操作" }); } next(); // 已登录,继续处理请求 } + +/** + * 需要管理员权限的中间件 + * @param {import("express").Request} req + * @param {import("express").Response} res + * @param {import("express").NextFunction} next + */ async function needadmin(req, res, next) { if (!res.locals.login) { - // 未登录,返回401 Unauthorized状态码 + logger.info(`[needadmin] - ${req.ip} - 未登录,返回401 Unauthorized状态码`); return res.status(401).send({ status: "0", msg: "请先登录以继续操作" }); } - if (res.locals.email !==await configManager.getConfig("security.adminuser")) { - // 未登录,返回401 Unauthorized状态码 + + const adminEmail = await configManager.getConfig("security.adminuser"); + if (res.locals.email !== adminEmail) { + logger.info(`[needadmin] - ${req.ip} - 权限不足,返回401 Unauthorized状态码`); return res.status(401).send({ status: "0", msg: "权限不足" }); } next(); // 已登录,继续处理请求 } + export { needlogin, needadmin }; + diff --git a/middleware/captcha.js b/middleware/captcha.js index 13aa20a..5774c9f 100644 --- a/middleware/captcha.js +++ b/middleware/captcha.js @@ -1,39 +1,39 @@ -import { error as _error, debug } from "../logger.js"; +import { error as loggerError, debug } from "../logger.js"; import { getConfig } from "../configManager.js"; +import axios from "axios"; +import { URL } from "url"; -import express from "express"; - -const app = express(); -import { post } from "request"; - -app.use(async (req, res, next) => { - const recaptcha = - req.body.recaptcha || req.body.re || req.query.recaptcha || req.query.re; +const captchaMiddleware = async (req, res, next) => { + const recaptcha = req.body.recaptcha || req.query.recaptcha; if (!recaptcha) { - return res.status(200).send({ message: "请完成验证码" }); + return res.status(400).send({ message: "请完成验证码" }); } - post( - { - url: await getConfig('captcha.reverify'), - form: { secret: await getConfig('captcha.resecret'), response: recaptcha }, - }, - function (error, httpResponse, body) { - if (error) { - _error("Error verifying recaptcha:", error); - res.status(200).send({ message: "验证码验证失败", error: error }); - } + try { + const { url, secret } = await getConfig("captcha"); - const response = JSON.parse(body); - debug(response); - if (response.success) { - next(); - } else { - res.status(200).send({ message: "验证码无效", response: response }); + const response = await axios.post( + new URL("/siteverify", url), + null, + { + params: { + secret, + response: recaptcha, + }, } + ); + + if (response.data.success) { + next(); + } else { + res.status(400).send({ message: "验证码无效", response: response.data }); } - ); -}); + } catch (error) { + loggerError("Error verifying recaptcha:", error); + res.status(500).send({ message: "验证码验证失败", error: error.message }); + } +}; + +export default captchaMiddleware; -export default app; diff --git a/middleware/geetest.js b/middleware/geetest.js index 0cc5101..dbcbd3b 100644 --- a/middleware/geetest.js +++ b/middleware/geetest.js @@ -1,14 +1,8 @@ import logger from "../utils/logger.js"; import configManager from "../utils/configManager.js"; - -import express from "express"; -import { parse } from "querystring"; -import { createHmac } from "crypto"; import axios from "axios"; - -const app = express(); - -// 从配置中读取极验的相关信息 +import { parse } from "url"; +import { createHmac } from "crypto"; const GEE_CAPTCHA_ID = await configManager.getConfig("captcha.GEE_CAPTCHA_ID"); const GEE_CAPTCHA_KEY = await configManager.getConfig("captcha.GEE_CAPTCHA_KEY"); @@ -16,13 +10,29 @@ const GEE_API_SERVER = await configManager.getConfig("captcha.GEE_API_SERVER"); const API_URL = `${GEE_API_SERVER}/validate?captcha_id=${GEE_CAPTCHA_ID}`; logger.debug(API_URL); -// 中间件处理极验验证码验证 -app.use(async (req, res, next) => { +/** + * 生成签名的函数,使用 HMAC-SHA256 + * @param {String} value - 待签名的字符串 + * @param {String} key - 签名密钥 + * @returns {String} 签名结果 + */ +function hmacSha256Encode(value, key) { + return createHmac("sha256", key).update(value, "utf8").digest("hex"); +} + +/** + * 验证码中间件 + * @param {Object} req - express的request对象 + * @param {Object} res - express的response对象 + * @param {Function} next - express的next函数 + */ +async function geetestMiddleware(req, res, next) { // 如果是开发环境,直接放行 if (process.env.NODE_ENV === "development") { logger.debug("In development environment, bypass geetest validation."); next(); + return; } // 验证码信息 @@ -53,7 +63,7 @@ app.use(async (req, res, next) => { logger.debug(geetest); // 生成签名 - const sign_token = hmac_sha256_encode(geetest.lot_number, GEE_CAPTCHA_KEY); + const signToken = hmacSha256Encode(geetest.lot_number, GEE_CAPTCHA_KEY); // 准备请求参数 const datas = { @@ -68,19 +78,11 @@ app.use(async (req, res, next) => { // 发送请求到极验服务 logger.debug("Sending request to Geetest server..."); logger.debug(API_URL); - const result = await axios({ - method: "post", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - url: API_URL, // 极验验证码验证接口 - data: datas, - }); + const result = await axios.post(API_URL, datas); if (result.data.result === "success") { next(); // 验证成功,继续处理请求 } else { - console.log(result.data); logger.debug(`Validate fail: ${result.data.reason}`); res.status(500).send({ code: 500, @@ -92,13 +94,7 @@ app.use(async (req, res, next) => { logger.error("Geetest server error:", error); next(); // 极验服务器出错时放行,避免阻塞业务逻辑 } -}); +}; -// 生成签名的函数,使用 HMAC-SHA256 -function hmac_sha256_encode(value, key) { - logger.debug(value); - logger.debug(key); - return createHmac("sha256", key).update(value, "utf8").digest("hex"); -} +export default geetestMiddleware; -export default app; diff --git a/package.json b/package.json index e73b619..16eee2c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "cookie-parser": "^1.4.7", "cors": "^2.8.5", "crypto-js": "^4.1.1", + "csrf-csrf": "^3.1.0", "dotenv": "^16.4.7", "ejs": "^3.1.10", "express": "^4.21.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c241190..fe78859 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: crypto-js: specifier: ^4.1.1 version: 4.2.0 + csrf-csrf: + specifier: ^3.1.0 + version: 3.1.0 dotenv: specifier: ^16.4.7 version: 16.4.7 @@ -87,9 +90,6 @@ importers: prisma: specifier: ^6.1.0 version: 6.1.0 - win-node-env: - specifier: ^0.6.1 - version: 0.6.1 packages: @@ -671,6 +671,9 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + csrf-csrf@3.1.0: + resolution: {integrity: sha512-kZacFfFbdYFxNnFdigRHCzVAq019vJyUUtgPLjCtzh6jMXcWmf8bGUx/hsqtSEMXaNcPm8iXpjC+hW5aeOsRMg==} + dashdash@1.14.1: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} @@ -1306,11 +1309,6 @@ packages: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} - win-node-env@0.6.1: - resolution: {integrity: sha512-oR+MLQ6jdvwqkLEkKBYxRvBOYbTlQV9dJKn8vLzjA4HCzcqdlCE6H1oOvA7lAdCgNXiOX0xfjl/Y47ZcZN6FDA==} - os: [win32] - hasBin: true - winston-daily-rotate-file@5.0.0: resolution: {integrity: sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==} engines: {node: '>=8'} @@ -2406,6 +2404,10 @@ snapshots: crypto-js@4.2.0: {} + csrf-csrf@3.1.0: + dependencies: + http-errors: 2.0.0 + dashdash@1.14.1: dependencies: assert-plus: 1.0.0 @@ -3066,8 +3068,6 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - win-node-env@0.6.1: {} - winston-daily-rotate-file@5.0.0(winston@3.17.0): dependencies: file-stream-rotator: 0.6.1 diff --git a/routes/router_account.js b/routes/router_account.js index 28b6991..a46ab71 100644 --- a/routes/router_account.js +++ b/routes/router_account.js @@ -18,7 +18,7 @@ router.all("*", function (req, res, next) { }); -import geetest from "../middleware/geetest.js"; +import geetestMiddleware from "../middleware/geetest.js"; router.post("/login", async function (req, res, next) { try { if ( @@ -116,7 +116,7 @@ router.get("/logout", function (req, res) { res.redirect("/"); }); -router.post("/register", geetest, async function (req, res, next) { +router.post("/register", geetestMiddleware, async function (req, res, next) { try { const email = req.body.un; var SQL = `SELECT id FROM ow_users WHERE email='${email}' LIMIT 1`; @@ -155,7 +155,7 @@ router.post("/register", geetest, async function (req, res, next) { } }); -router.post("/repw", geetest, async function (req, res, next) { +router.post("/repw", geetestMiddleware, async function (req, res, next) { try { if (req.body.un == "" || req.body.un == null) { res.status(200).send({ message: "账户格式错误" }); @@ -189,7 +189,7 @@ router.post("/repw", geetest, async function (req, res, next) { } }); -router.post("/torepw", geetest, async function (req, res, next) { +router.post("/torepw", geetestMiddleware, async function (req, res, next) { let SET; let UPDATE; try { @@ -398,7 +398,7 @@ router.post("/totp/protected-route", validateTotpToken, (req, res) => { }); }); -router.post("/magiclink/generate", geetest, async (req, res) => { +router.post("/magiclink/generate", geetestMiddleware, async (req, res) => { try { const { email } = req.body; if (!email || !emailTest(email)) { diff --git a/routes/router_my.js b/routes/router_my.js index a69a67a..4f939de 100644 --- a/routes/router_my.js +++ b/routes/router_my.js @@ -10,7 +10,7 @@ import { createHash } from "crypto"; import { msg_fail, S3update, checkhash, hash as _hash,prisma } from "../utils/global.js"; //数据库 import { query, qww } from "../utils/database.js"; -import geetest from "../middleware/geetest.js"; +import geetestMiddleware from "../middleware/geetest.js"; router.all("*", function (req, res, next) { //限定访问该模块的权限:必须已登录 @@ -249,7 +249,7 @@ router.post("/project/del", function (req, res) { res.status(200).send({ status: "success", msg: "删除成功" }); }); }); -router.post("/set/avatar", geetest,async (req, res) => { +router.post("/set/avatar", geetestMiddleware,async (req, res) => { if (!req.files?.file) { return res.status(200).send({ status: "文件上传失败" }); } @@ -269,7 +269,7 @@ router.post("/set/avatar", geetest,async (req, res) => { }); }); //修改个人信息 -router.post("/set/userinfo", geetest, function (req, res) { +router.post("/set/userinfo", geetestMiddleware, function (req, res) { var UPDATE = `UPDATE ow_users SET ? WHERE id=${res.locals.userid} LIMIT 1`; var SET = { display_name: req.body["display_name"], @@ -290,7 +290,7 @@ router.post("/set/userinfo", geetest, function (req, res) { }); }); //修改个人信息 -router.post("/set/username", geetest, function (req, res) { +router.post("/set/username", geetestMiddleware, function (req, res) { var UPDATE = `UPDATE ow_users SET ? WHERE id=${res.locals.userid} LIMIT 1`; var SET = { username: req.body.username, @@ -308,7 +308,7 @@ router.post("/set/username", geetest, function (req, res) { }); }); //修改密码:动作 -router.post("/set/pw", geetest, function (req, res) { +router.post("/set/pw", geetestMiddleware, function (req, res) { SQL = `SELECT password FROM ow_users WHERE id=? LIMIT 1`; id = res.locals.userid;