From 468d24c3bb87b5cf125a6c5f3cec6bd8facffa50 Mon Sep 17 00:00:00 2001 From: Stev_Wang <304865932@qq.com> Date: Mon, 5 Jan 2026 16:04:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=B3=BB=E7=BB=9F=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=EF=BC=88=E8=BF=90=E8=90=A5=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=90=8E=E5=8F=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.env.development | 7 +- backend/.env.production | 4 + backend/database/config_init.sql | 79 + backend/package-lock.json | 1377 ++++++++++++++++- backend/package.json | 3 + .../src/controllers/adminAuthController.ts | 33 +- backend/src/controllers/captchaController.ts | 89 ++ backend/src/controllers/configController.ts | 261 ++++ .../src/controllers/playerAuthController.ts | 30 +- backend/src/models/Config.ts | 25 + backend/src/routes/adminRoutes.ts | 14 + backend/src/routes/playerRoutes.ts | 12 + backend/src/services/captchaService.ts | 130 ++ backend/src/utils/envHelper.ts | 90 ++ frontend/src/api/admin.ts | 51 +- frontend/src/api/player.ts | 34 +- frontend/src/layouts/AdminLayout.vue | 7 +- frontend/src/router/index.ts | 6 + frontend/src/stores/admin.ts | 4 +- frontend/src/stores/player.ts | 4 +- frontend/src/types/api.ts | 37 + frontend/src/views/admin/Login.vue | 97 +- frontend/src/views/admin/SystemConfig.vue | 447 ++++++ frontend/src/views/player/Login.vue | 151 +- 24 files changed, 2966 insertions(+), 26 deletions(-) create mode 100644 backend/database/config_init.sql create mode 100644 backend/src/controllers/captchaController.ts create mode 100644 backend/src/controllers/configController.ts create mode 100644 backend/src/models/Config.ts create mode 100644 backend/src/services/captchaService.ts create mode 100644 backend/src/utils/envHelper.ts create mode 100644 frontend/src/views/admin/SystemConfig.vue diff --git a/backend/.env.development b/backend/.env.development index 25cd7f5..c00a71a 100644 --- a/backend/.env.development +++ b/backend/.env.development @@ -1,4 +1,5 @@ # 服务器配置 +BACKEND_HOST=127.0.0.1 PORT=3000 NODE_ENV=development @@ -15,6 +16,10 @@ JWT_EXPIRES_IN=2h # 游戏服务端代理地址 GAME_SERVER_PROXY_URL=http://127.0.0.1:8080/tool/http +GAME_PSK=THIS_IS_A_32_BYTE_FIXED_PSK!!!!1 + +# 日志配置 +LOG_LEVEL=info # CORS配置 -CORS_ORIGIN=http://localhost:5173 +CORS_ORIGIN=http://localhost:5173 \ No newline at end of file diff --git a/backend/.env.production b/backend/.env.production index eb6a351..2cd149b 100644 --- a/backend/.env.production +++ b/backend/.env.production @@ -15,6 +15,10 @@ JWT_EXPIRES_IN=2h # 游戏服务端代理地址 GAME_SERVER_PROXY_URL=http://127.0.0.1:8080/tool/http +GAME_PSK=THIS_IS_A_32_BYTE_FIXED_PSK!!!!! + +# 日志配置 +LOG_LEVEL=info # CORS配置(生产环境请修改为实际域名) CORS_ORIGIN=http://your-frontend-domain.com diff --git a/backend/database/config_init.sql b/backend/database/config_init.sql new file mode 100644 index 0000000..9c000e4 --- /dev/null +++ b/backend/database/config_init.sql @@ -0,0 +1,79 @@ +-- ============================================ +-- 梦幻西游一站式运营管理平台 - 系统配置表初始化脚本 +-- MySQL 8.4 兼容版本 +-- 创建日期: 2026-01-05 +-- ============================================ + +-- 使用数据库 +USE mhxy_web_vue; + +-- 设置字符集 +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ============================================ +-- 表: configs (系统配置表) +-- ============================================ +DROP TABLE IF EXISTS `configs`; +CREATE TABLE `configs` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `config_key` VARCHAR(100) NOT NULL COMMENT '配置键', + `config_value` TEXT NOT NULL COMMENT '配置值', + `config_type` VARCHAR(50) NOT NULL COMMENT '配置类型 (basic:基础配置, security:安全配置, game:游戏配置, payment:充值配置, email:邮件配置)', + `description` VARCHAR(255) NULL COMMENT '配置描述', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE INDEX `idx_config_key` (`config_key`), + INDEX `idx_config_type` (`config_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表'; + +-- ============================================ +-- 插入默认配置数据 +-- ============================================ + +-- 基础配置 +INSERT INTO `configs` (`config_key`, `config_value`, `config_type`, `description`) VALUES +('backend_host', '127.0.0.1', 'basic', '后端IP或域名地址'), +('backend_port', '3000', 'basic', '后端端口'), +('log_level', 'info', 'basic', '日志级别 (info/debug/error)'), +('login_captcha_enabled', 'true', 'basic', '登录验证码开关 (true:开启, false:关闭)'), +('player_service_enabled', 'true', 'basic', '玩家服务中心开关 (true:开启, false:关闭)'), +('player_service_close_msg', '玩家服务中心系统维护中', 'basic', '玩家服务中心关闭提示文本'); + +-- 安全配置 +INSERT INTO `configs` (`config_key`, `config_value`, `config_type`, `description`) VALUES +('cors_origin', 'http://localhost:5173', 'security', '跨域地址'), +('jwt_secret', 'your-secret-key-change-in-production', 'security', 'JWT密钥'), +('jwt_expires_in', '2h', 'security', 'JWT有效期 (如: 2h, 7d, 30m)'); + +-- 游戏配置 +INSERT INTO `configs` (`config_key`, `config_value`, `config_type`, `description`) VALUES +('game_server_proxy_url', 'http://127.0.0.1:8080/tool/http', 'game', '游戏服务端代理地址'), +('game_server_psk', 'THIS_IS_A_32_BYTE_FIXED_PSK!!!!', 'game', '游戏服务端PSK密钥'); + +-- 邮件配置 +INSERT INTO `configs` (`config_key`, `config_value`, `config_type`, `description`) VALUES +('mail_from', '', 'email', '发件人邮箱'), +('mail_smtp_host', '', 'email', 'SMTP服务器地址'), +('mail_smtp_port', '587', 'email', 'SMTP服务器端口'), +('mail_smtp_user', '', 'email', 'SMTP用户名'), +('mail_smtp_password', '', 'email', 'SMTP密码'); + +-- ============================================ +-- 验证数据 +-- ============================================ +SELECT + id, + config_key, + config_value, + config_type, + description, + created_at, + updated_at +FROM configs +ORDER BY config_type, id; + +SET FOREIGN_KEY_CHECKS = 1; + +-- 初始化完成 diff --git a/backend/package-lock.json b/backend/package-lock.json index 790a080..1107b91 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@types/nodemailer": "^7.0.4", "axios": "^1.13.2", "bcrypt": "^6.0.0", "cors": "^2.8.5", @@ -16,6 +17,8 @@ "express": "^5.2.1", "jsonwebtoken": "^9.0.3", "mysql2": "^3.16.0", + "nodemailer": "^7.0.12", + "svg-captcha": "^1.4.0", "typeorm": "^0.3.28" }, "devDependencies": { @@ -29,6 +32,713 @@ "typescript": "^5.9.3" } }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.962.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/client-sesv2/-/client-sesv2-3.962.0.tgz", + "integrity": "sha512-1kQdfoQJmE+FgMnIrhzayP4leKhuAPxpAjmYVRgRhlS8nsOawwnNn+7k6/R0nYFAtCUrUUHOvNxwNUuYDHJ9Pg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.962.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/signature-v4-multi-region": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.958.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/client-sso/-/client-sso-3.958.0.tgz", + "integrity": "sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/core/-/core-3.957.0.tgz", + "integrity": "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@aws-sdk/xml-builder": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.957.0.tgz", + "integrity": "sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.957.0.tgz", + "integrity": "sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.962.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.962.0.tgz", + "integrity": "sha512-h0kVnXLW2d3nxbcrR/Pfg3W/+YoCguasWz7/3nYzVqmdKarGrpJzaFdoZtLgvDSZ8VgWUC4lWOTcsDMV0UNqUQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-login": "3.962.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.958.0", + "@aws-sdk/credential-provider-web-identity": "3.958.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.962.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-login/-/credential-provider-login-3.962.0.tgz", + "integrity": "sha512-kHYH6Av2UifG3mPkpPUNRh/PuX6adaAcpmsclJdHdxlixMCRdh8GNeEihq480DC0GmfqdpoSf1w2CLmLLPIS6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.962.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.962.0.tgz", + "integrity": "sha512-CS78NsWRxLa+nWqeWBEYMZTLacMFIXs1C5WJuM9kD05LLiWL32ksljoPsvNN24Bc7rCSQIIMx/U3KGvkDVZMVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-ini": "3.962.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.958.0", + "@aws-sdk/credential-provider-web-identity": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.957.0.tgz", + "integrity": "sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.958.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.958.0.tgz", + "integrity": "sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.958.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/token-providers": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.958.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.958.0.tgz", + "integrity": "sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.957.0.tgz", + "integrity": "sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/middleware-logger/-/middleware-logger-3.957.0.tgz", + "integrity": "sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.957.0.tgz", + "integrity": "sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.957.0.tgz", + "integrity": "sha512-5B2qY2nR2LYpxoQP0xUum5A1UNvH2JQpLHDH1nWFNF/XetV7ipFHksMxPNhtJJ6ARaWhQIDXfOUj0jcnkJxXUg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-arn-parser": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.957.0.tgz", + "integrity": "sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.958.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/nested-clients/-/nested-clients-3.958.0.tgz", + "integrity": "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.957.0.tgz", + "integrity": "sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.957.0.tgz", + "integrity": "sha512-t6UfP1xMUigMMzHcb7vaZcjv7dA2DQkk9C/OAP1dKyrE0vb4lFGDaTApi17GN6Km9zFxJthEMUbBc7DL0hq1Bg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.958.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/token-providers/-/token-providers-3.958.0.tgz", + "integrity": "sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.957.0.tgz", + "integrity": "sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/util-endpoints/-/util-endpoints-3.957.0.tgz", + "integrity": "sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-endpoints": "^3.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/util-locate-window/-/util-locate-window-3.957.0.tgz", + "integrity": "sha512-nhmgKHnNV9K+i9daumaIz8JTLsIIML9PE/HUks5liyrjUzenjW/aHoc7WJ9/Td/gPZtayxFnXQSJRb/fDlBuJw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.957.0.tgz", + "integrity": "sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.957.0.tgz", + "integrity": "sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.957.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/xml-builder/-/xml-builder-3.957.0.tgz", + "integrity": "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", + "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -97,6 +807,586 @@ "node": ">=14" } }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/abort-controller/-/abort-controller-4.2.7.tgz", + "integrity": "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.5", + "resolved": "https://registry.npmmirror.com/@smithy/config-resolver/-/config-resolver-4.4.5.tgz", + "integrity": "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.20.0", + "resolved": "https://registry.npmmirror.com/@smithy/core/-/core-3.20.0.tgz", + "integrity": "sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.8", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.7.tgz", + "integrity": "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.8", + "resolved": "https://registry.npmmirror.com/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.8.tgz", + "integrity": "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/hash-node/-/hash-node-4.2.7.tgz", + "integrity": "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/invalid-dependency/-/invalid-dependency-4.2.7.tgz", + "integrity": "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/middleware-content-length/-/middleware-content-length-4.2.7.tgz", + "integrity": "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.1.tgz", + "integrity": "sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.20.0", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-middleware": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.17", + "resolved": "https://registry.npmmirror.com/@smithy/middleware-retry/-/middleware-retry-4.4.17.tgz", + "integrity": "sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/service-error-classification": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.8", + "resolved": "https://registry.npmmirror.com/@smithy/middleware-serde/-/middleware-serde-4.2.8.tgz", + "integrity": "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/middleware-stack/-/middleware-stack-4.2.7.tgz", + "integrity": "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.7", + "resolved": "https://registry.npmmirror.com/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz", + "integrity": "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.7", + "resolved": "https://registry.npmmirror.com/@smithy/node-http-handler/-/node-http-handler-4.4.7.tgz", + "integrity": "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/property-provider/-/property-provider-4.2.7.tgz", + "integrity": "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/@smithy/protocol-http/-/protocol-http-5.3.7.tgz", + "integrity": "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/querystring-builder/-/querystring-builder-4.2.7.tgz", + "integrity": "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/querystring-parser/-/querystring-parser-4.2.7.tgz", + "integrity": "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/service-error-classification/-/service-error-classification-4.2.7.tgz", + "integrity": "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz", + "integrity": "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/@smithy/signature-v4/-/signature-v4-5.3.7.tgz", + "integrity": "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.10.2", + "resolved": "https://registry.npmmirror.com/@smithy/smithy-client/-/smithy-client-4.10.2.tgz", + "integrity": "sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.20.0", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.11.0", + "resolved": "https://registry.npmmirror.com/@smithy/types/-/types-4.11.0.tgz", + "integrity": "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/url-parser/-/url-parser-4.2.7.tgz", + "integrity": "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.16", + "resolved": "https://registry.npmmirror.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.16.tgz", + "integrity": "sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.19", + "resolved": "https://registry.npmmirror.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.19.tgz", + "integrity": "sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.5", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz", + "integrity": "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/util-middleware/-/util-middleware-4.2.7.tgz", + "integrity": "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.7", + "resolved": "https://registry.npmmirror.com/@smithy/util-retry/-/util-retry-4.2.7.tgz", + "integrity": "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.8", + "resolved": "https://registry.npmmirror.com/@smithy/util-stream/-/util-stream-4.5.8.tgz", + "integrity": "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@sqltools/formatter": { "version": "1.2.5", "resolved": "https://registry.npmmirror.com/@sqltools/formatter/-/formatter-1.2.5.tgz", @@ -226,12 +1516,21 @@ "version": "25.0.3", "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.0.3.tgz", "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, + "node_modules/@types/nodemailer": { + "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/@types/nodemailer/-/nodemailer-7.0.4.tgz", + "integrity": "sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-sesv2": "^3.839.0", + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.14.0.tgz", @@ -487,6 +1786,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmmirror.com/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -1083,6 +2388,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmmirror.com/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", @@ -1872,6 +3195,15 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/nodemailer": { + "version": "7.0.12", + "resolved": "https://registry.npmmirror.com/nodemailer/-/nodemailer-7.0.12.tgz", + "integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.11", "resolved": "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.11.tgz", @@ -1977,6 +3309,18 @@ "wrappy": "1" } }, + "node_modules/opentype.js": { + "version": "0.7.3", + "resolved": "https://registry.npmmirror.com/opentype.js/-/opentype.js-0.7.3.tgz", + "integrity": "sha512-Veui5vl2bLonFJ/SjX/WRWJT3SncgiZNnKUyahmXCc2sa1xXW15u3R/3TN5+JFiP7RsjK5ER4HA5eWaEmV9deA==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.2" + }, + "bin": { + "ot": "bin/ot" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -2537,6 +3881,18 @@ "node": ">=8" } }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", @@ -2550,6 +3906,24 @@ "node": ">=4" } }, + "node_modules/svg-captcha": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/svg-captcha/-/svg-captcha-1.4.0.tgz", + "integrity": "sha512-/fkkhavXPE57zRRCjNqAP3txRCSncpMx3NnNZL7iEoyAtYwUjPhJxW6FQTQPG5UPEmCrbFoXS10C3YdJlW7PDg==", + "license": "MIT", + "dependencies": { + "opentype.js": "^0.7.3" + }, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, "node_modules/to-buffer": { "version": "1.2.2", "resolved": "https://registry.npmmirror.com/to-buffer/-/to-buffer-1.2.2.tgz", @@ -2813,7 +4187,6 @@ "version": "7.16.0", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "devOptional": true, "license": "MIT" }, "node_modules/unpipe": { diff --git a/backend/package.json b/backend/package.json index 6ab17b1..38bc591 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,6 +12,7 @@ "author": "", "license": "ISC", "dependencies": { + "@types/nodemailer": "^7.0.4", "axios": "^1.13.2", "bcrypt": "^6.0.0", "cors": "^2.8.5", @@ -19,6 +20,8 @@ "express": "^5.2.1", "jsonwebtoken": "^9.0.3", "mysql2": "^3.16.0", + "nodemailer": "^7.0.12", + "svg-captcha": "^1.4.0", "typeorm": "^0.3.28" }, "devDependencies": { diff --git a/backend/src/controllers/adminAuthController.ts b/backend/src/controllers/adminAuthController.ts index caaf68d..b8ba894 100644 --- a/backend/src/controllers/adminAuthController.ts +++ b/backend/src/controllers/adminAuthController.ts @@ -8,6 +8,8 @@ import jwt from 'jsonwebtoken'; import { AppDataSource } from '../config/database'; // 导入管理员用户模型 import { AdminUser } from '../models/AdminUser'; +// 导入验证码服务 +import { CaptchaService } from '../services/captchaService'; /** * 管理员认证控制器 @@ -22,8 +24,8 @@ export class AdminAuthController { */ async login(req: Request, res: Response) { try { - // 从请求体中获取用户名和密码 - const { username, password } = req.body; + // 从请求体中获取用户名、密码和验证码信息 + const { username, password, captchaId, captchaCode } = req.body; // 验证用户名和密码是否为空 if (!username || !password) { @@ -33,6 +35,33 @@ export class AdminAuthController { }); } + // 创建验证码服务实例 + const captchaService = new CaptchaService(); + + // 检查是否启用了验证码功能 + const captchaEnabled = await captchaService.checkCaptchaEnabled(); + + // 如果启用了验证码,则验证验证码 + if (captchaEnabled) { + // 验证验证码参数是否完整 + if (!captchaId || !captchaCode) { + return res.status(400).json({ + success: false, + message: '请输入验证码' + }); + } + + // 验证验证码是否正确 + const captchaVerify = captchaService.verifyCaptcha(captchaId, captchaCode); + + if (!captchaVerify.success) { + return res.status(400).json({ + success: false, + message: captchaVerify.message || '验证码错误或已过期' + }); + } + } + // 获取管理员用户仓库 const adminUserRepository = AppDataSource.getRepository(AdminUser); // 根据用户名查找管理员用户 diff --git a/backend/src/controllers/captchaController.ts b/backend/src/controllers/captchaController.ts new file mode 100644 index 0000000..9b6f6b6 --- /dev/null +++ b/backend/src/controllers/captchaController.ts @@ -0,0 +1,89 @@ +import { Request, Response } from 'express'; +import { CaptchaService } from '../services/captchaService'; + +/** + * 验证码控制器 + * 处理验证码的生成和验证 + */ +export class CaptchaController { + private captchaService: CaptchaService; + + constructor() { + this.captchaService = new CaptchaService(); + } + + /** + * 生成验证码 + * @param req - Express请求对象 + * @param res - Express响应对象 + */ + async generateCaptcha(req: Request, res: Response) { + try { + const { captchaId, svg } = this.captchaService.generateCaptcha(); + + return res.json({ + success: true, + data: { + captchaId, + svg + } + }); + } catch (error) { + console.error('生成验证码失败:', error); + return res.status(500).json({ + success: false, + message: '生成验证码失败' + }); + } + } + + /** + * 验证验证码 + * @param req - Express请求对象 + * @param res - Express响应对象 + */ + async verifyCaptcha(req: Request, res: Response) { + try { + const { captchaId, code } = req.body; + + const result = this.captchaService.verifyCaptcha(captchaId, code); + + if (!result.success) { + return res.status(400).json(result); + } + + return res.json({ + success: true, + message: '验证码正确' + }); + } catch (error) { + console.error('验证验证码失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } + + /** + * 检查验证码是否启用 + * @param req - Express请求对象 + * @param res - Express响应对象 + */ + async checkCaptchaEnabled(req: Request, res: Response) { + try { + const enabled = await this.captchaService.checkCaptchaEnabled(); + + return res.json({ + success: true, + data: { enabled } + }); + } catch (error) { + console.error('检查验证码状态失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } +} diff --git a/backend/src/controllers/configController.ts b/backend/src/controllers/configController.ts new file mode 100644 index 0000000..78d2f43 --- /dev/null +++ b/backend/src/controllers/configController.ts @@ -0,0 +1,261 @@ +import { Request, Response } from 'express'; +import { AppDataSource } from '../config/database'; +import { Config } from '../models/Config'; +import { EnvHelper } from '../utils/envHelper'; +import nodemailer from 'nodemailer'; + +/** + * 系统配置控制器 + * 处理系统配置相关的操作,包括获取配置、更新配置、测试邮件等 + */ +export class ConfigController { + /** + * 获取所有配置 + * 按配置类型分组返回 + * @param req - Express请求对象 + * @param res - Express响应对象 + */ + async getAllConfigs(req: Request, res: Response) { + try { + const configRepository = AppDataSource.getRepository(Config); + + const configs = await configRepository.find({ + order: { configType: 'ASC', id: 'ASC' } + }); + + const groupedConfigs: Record = { + basic: [], + security: [], + game: [], + payment: [], + email: [] + }; + + configs.forEach(config => { + if (groupedConfigs[config.configType]) { + groupedConfigs[config.configType].push({ + id: config.id, + configKey: config.configKey, + configValue: config.configValue, + description: config.description + }); + } + }); + + return res.json({ + success: true, + data: groupedConfigs + }); + } catch (error) { + console.error('获取配置失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } + + /** + * 更新配置 + * 批量更新配置,同时更新数据库和.env文件 + * @param req - Express请求对象 + * @param res - Express响应对象 + */ + async updateConfigs(req: Request, res: Response) { + try { + const { configs } = req.body; + + if (!configs || !Array.isArray(configs)) { + return res.status(400).json({ + success: false, + message: '配置数据格式错误' + }); + } + + const configRepository = AppDataSource.getRepository(Config); + const envUpdates: Record = {}; + + for (const config of configs) { + if (!config.configKey || config.configValue === undefined) { + continue; + } + + const existingConfig = await configRepository.findOne({ + where: { configKey: config.configKey } + }); + + if (existingConfig) { + existingConfig.configValue = String(config.configValue); + await configRepository.save(existingConfig); + } + + const envKey = this.mapConfigKeyToEnvKey(config.configKey); + if (envKey) { + envUpdates[envKey] = String(config.configValue); + } + } + + const envFilePath = EnvHelper.getEnvFilePath(process.env.NODE_ENV || 'development'); + const updateSuccess = EnvHelper.updateEnvFile(envFilePath, envUpdates); + + if (!updateSuccess) { + return res.status(500).json({ + success: false, + message: '更新.env文件失败' + }); + } + + return res.json({ + success: true, + message: '配置更新成功' + }); + } catch (error) { + console.error('更新配置失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } + + /** + * 测试邮件发送 + * @param req - Express请求对象 + * @param res - Express响应对象 + */ + async testEmail(req: Request, res: Response) { + try { + const { to } = req.body; + + if (!to || !this.isValidEmail(to)) { + return res.status(400).json({ + success: false, + message: '请输入有效的邮箱地址' + }); + } + + const configRepository = AppDataSource.getRepository(Config); + + const mailConfigs = await configRepository.find({ + where: [ + { configKey: 'mail_from' }, + { configKey: 'mail_smtp_host' }, + { configKey: 'mail_smtp_port' }, + { configKey: 'mail_smtp_user' }, + { configKey: 'mail_smtp_password' } + ] + }); + + const configMap = new Map(mailConfigs.map(c => [c.configKey, c.configValue])); + + const mailFrom = configMap.get('mail_from'); + const smtpHost = configMap.get('mail_smtp_host'); + const smtpPort = configMap.get('mail_smtp_port'); + const smtpUser = configMap.get('mail_smtp_user'); + const smtpPassword = configMap.get('mail_smtp_password'); + + if (!mailFrom || !smtpHost || !smtpPort || !smtpUser || !smtpPassword) { + return res.status(400).json({ + success: false, + message: '邮件配置不完整,请先配置邮件信息' + }); + } + + const transporter = nodemailer.createTransport({ + host: smtpHost, + port: Number(smtpPort), + secure: Number(smtpPort) === 465, + auth: { + user: smtpUser, + pass: smtpPassword + } + }); + + const mailOptions = { + from: mailFrom, + to: to, + subject: '梦幻西游运营管理系统 - 邮件测试', + text: '这是一封测试邮件,如果您收到此邮件,说明邮件配置正确。' + }; + + await transporter.sendMail(mailOptions); + + return res.json({ + success: true, + message: '邮件发送成功' + }); + } catch (error) { + console.error('测试邮件发送失败:', error); + return res.status(500).json({ + success: false, + message: '邮件发送失败,请检查配置' + }); + } + } + + /** + * 将配置键映射到.env文件中的环境变量键 + * @param configKey - 配置键 + * @returns 环境变量键,如果不映射则返回null + */ + private mapConfigKeyToEnvKey(configKey: string): string | null { + const mapping: Record = { + 'backend_host': 'BACKEND_HOST', + 'backend_port': 'PORT', + 'log_level': 'LOG_LEVEL', + 'cors_origin': 'CORS_ORIGIN', + 'jwt_secret': 'JWT_SECRET', + 'jwt_expires_in': 'JWT_EXPIRES_IN', + 'game_server_proxy_url': 'GAME_SERVER_PROXY_URL', + 'game_server_psk': 'GAME_PSK' + }; + + return mapping[configKey] || null; + } + + /** + * 验证邮箱格式 + * @param email - 邮箱地址 + * @returns 是否有效 + */ + private isValidEmail(email: string): boolean { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + } + + /** + * 检查玩家服务中心状态 + * @param req - Express请求对象 + * @param res - Express响应对象 + */ + async checkPlayerServiceStatus(req: Request, res: Response) { + try { + const configRepository = AppDataSource.getRepository(Config); + + const playerServiceEnabledConfig = await configRepository.findOne({ + where: { configKey: 'player_service_enabled' } + }); + + const playerServiceCloseMsgConfig = await configRepository.findOne({ + where: { configKey: 'player_service_close_msg' } + }); + + const enabled = playerServiceEnabledConfig?.configValue === 'true'; + const closeMsg = playerServiceCloseMsgConfig?.configValue || '玩家服务中心系统维护中'; + + return res.json({ + success: true, + data: { + enabled, + closeMsg + } + }); + } catch (error) { + console.error('检查玩家服务中心状态失败:', error); + return res.status(500).json({ + success: false, + message: '服务器内部错误' + }); + } + } +} diff --git a/backend/src/controllers/playerAuthController.ts b/backend/src/controllers/playerAuthController.ts index 664e608..de855c1 100644 --- a/backend/src/controllers/playerAuthController.ts +++ b/backend/src/controllers/playerAuthController.ts @@ -1,5 +1,6 @@ import { Request, Response } from 'express'; import axios from 'axios'; +import { CaptchaService } from '../services/captchaService'; export class PlayerAuthController { private readonly gameServerUrl: string; @@ -10,7 +11,7 @@ export class PlayerAuthController { async login(req: Request, res: Response) { try { - const { username, password } = req.body; + const { username, password, captchaId, captchaCode } = req.body; if (!username || !password) { return res.status(400).json({ @@ -19,6 +20,33 @@ export class PlayerAuthController { }); } + // 创建验证码服务实例 + const captchaService = new CaptchaService(); + + // 检查是否启用了验证码功能 + const captchaEnabled = await captchaService.checkCaptchaEnabled(); + + // 如果启用了验证码,则验证验证码 + if (captchaEnabled) { + // 验证验证码参数是否完整 + if (!captchaId || !captchaCode) { + return res.status(400).json({ + success: false, + message: '请输入验证码' + }); + } + + // 验证验证码是否正确 + const captchaVerify = captchaService.verifyCaptcha(captchaId, captchaCode); + + if (!captchaVerify.success) { + return res.status(400).json({ + success: false, + message: captchaVerify.message || '验证码错误或已过期' + }); + } + } + const response = await axios.post( `${this.gameServerUrl}?code=auth/login`, { diff --git a/backend/src/models/Config.ts b/backend/src/models/Config.ts new file mode 100644 index 0000000..853e986 --- /dev/null +++ b/backend/src/models/Config.ts @@ -0,0 +1,25 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; + +@Entity('configs') +export class Config { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ type: 'varchar', length: 100, unique: true, name: 'config_key' }) + configKey: string; + + @Column({ type: 'text', name: 'config_value' }) + configValue: string; + + @Column({ type: 'varchar', length: 50, name: 'config_type' }) + configType: string; + + @Column({ type: 'varchar', length: 255, nullable: true }) + description: string; + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date; +} diff --git a/backend/src/routes/adminRoutes.ts b/backend/src/routes/adminRoutes.ts index ee07466..73a187d 100644 --- a/backend/src/routes/adminRoutes.ts +++ b/backend/src/routes/adminRoutes.ts @@ -2,12 +2,16 @@ import { Router } from 'express'; import { AdminAuthController } from '../controllers/adminAuthController'; import { AdminUserController } from '../controllers/adminUserController'; import { AdminRoleController } from '../controllers/adminRoleController'; +import { ConfigController } from '../controllers/configController'; +import { CaptchaController } from '../controllers/captchaController'; import { adminAuthMiddleware, AuthRequest } from '../middleware/adminAuth'; const router = Router(); const adminAuthController = new AdminAuthController(); const adminUserController = new AdminUserController(); const adminRoleController = new AdminRoleController(); +const configController = new ConfigController(); +const captchaController = new CaptchaController(); // 认证相关路由 router.post('/login', (req, res) => adminAuthController.login(req, res)); @@ -27,4 +31,14 @@ router.post('/roles', adminAuthMiddleware, (req: AuthRequest, res) => adminRoleC router.put('/roles/:id', adminAuthMiddleware, (req: AuthRequest, res) => adminRoleController.updateRole(req, res)); router.delete('/roles/:id', adminAuthMiddleware, (req: AuthRequest, res) => adminRoleController.deleteRole(req, res)); +// 系统配置路由(需要认证) +router.get('/configs', adminAuthMiddleware, (req: AuthRequest, res) => configController.getAllConfigs(req, res)); +router.put('/configs', adminAuthMiddleware, (req: AuthRequest, res) => configController.updateConfigs(req, res)); +router.post('/configs/test-email', adminAuthMiddleware, (req: AuthRequest, res) => configController.testEmail(req, res)); + +// 验证码路由 +router.get('/captcha', (req, res) => captchaController.generateCaptcha(req, res)); +router.post('/captcha/verify', (req, res) => captchaController.verifyCaptcha(req, res)); +router.get('/captcha/enabled', (req, res) => captchaController.checkCaptchaEnabled(req, res)); + export default router; diff --git a/backend/src/routes/playerRoutes.ts b/backend/src/routes/playerRoutes.ts index 40bf7a2..26d23ea 100644 --- a/backend/src/routes/playerRoutes.ts +++ b/backend/src/routes/playerRoutes.ts @@ -1,9 +1,13 @@ import { Router } from 'express'; import { PlayerAuthController } from '../controllers/playerAuthController'; +import { CaptchaController } from '../controllers/captchaController'; +import { ConfigController } from '../controllers/configController'; import { playerAuthMiddleware, PlayerAuthRequest } from '../middleware/playerAuth'; const router = Router(); const playerAuthController = new PlayerAuthController(); +const captchaController = new CaptchaController(); +const configController = new ConfigController(); router.post('/login', (req, res) => playerAuthController.login(req, res)); @@ -11,4 +15,12 @@ router.post('/logout', (req, res) => playerAuthController.logout(req, res)); router.get('/account', playerAuthMiddleware, (req: PlayerAuthRequest, res) => playerAuthController.getAccountInfo(req, res)); +// 验证码路由 +router.get('/captcha', (req, res) => captchaController.generateCaptcha(req, res)); +router.post('/captcha/verify', (req, res) => captchaController.verifyCaptcha(req, res)); +router.get('/captcha/enabled', (req, res) => captchaController.checkCaptchaEnabled(req, res)); + +// 玩家服务中心状态路由 +router.get('/service-status', (req, res) => configController.checkPlayerServiceStatus(req, res)); + export default router; diff --git a/backend/src/services/captchaService.ts b/backend/src/services/captchaService.ts new file mode 100644 index 0000000..ea2999d --- /dev/null +++ b/backend/src/services/captchaService.ts @@ -0,0 +1,130 @@ +import svgCaptcha from 'svg-captcha'; + +/** + * 验证码存储 + * 使用内存存储验证码,生产环境建议使用Redis + */ +const captchaStore = new Map(); + +/** + * 清理过期的验证码 + */ +setInterval(() => { + const now = Date.now(); + for (const [key, value] of captchaStore.entries()) { + if (value.expireTime < now) { + captchaStore.delete(key); + } + } +}, 60000); + +/** + * 验证码服务类 + * 提供验证码的生成和验证功能 + */ +export class CaptchaService { + /** + * 生成验证码 + * @returns 验证码ID和SVG图片 + */ + generateCaptcha(): { captchaId: string; svg: string } { + const captcha = svgCaptcha.create({ + size: 4, + ignoreChars: '0o1iIl', + noise: 2, + color: true, + background: '#f5f5f5', + width: 120, + height: 40, + fontSize: 36 + }); + + const captchaId = this.generateCaptchaId(); + const expireTime = Date.now() + 5 * 60 * 1000; + + captchaStore.set(captchaId, { + code: captcha.text.toLowerCase(), + expireTime + }); + + return { + captchaId, + svg: captcha.data + }; + } + + /** + * 验证验证码 + * @param captchaId - 验证码ID + * @param code - 用户输入的验证码 + * @returns 验证结果 + */ + verifyCaptcha(captchaId: string, code: string): { success: boolean; message?: string } { + if (!captchaId || !code) { + return { + success: false, + message: '验证码参数不完整' + }; + } + + const captchaData = captchaStore.get(captchaId); + + if (!captchaData) { + return { + success: false, + message: '验证码已过期或不存在' + }; + } + + if (captchaData.expireTime < Date.now()) { + captchaStore.delete(captchaId); + return { + success: false, + message: '验证码已过期' + }; + } + + if (captchaData.code !== code.toLowerCase()) { + captchaStore.delete(captchaId); + return { + success: false, + message: '验证码错误' + }; + } + + captchaStore.delete(captchaId); + + return { + success: true + }; + } + + /** + * 检查验证码是否启用 + * @returns 是否启用验证码 + */ + async checkCaptchaEnabled(): Promise { + try { + const { AppDataSource } = await import('../config/database'); + const { Config } = await import('../models/Config'); + + const configRepository = AppDataSource.getRepository(Config); + const config = await configRepository.findOne({ + where: { configKey: 'login_captcha_enabled' } + }); + + return config ? config.configValue === 'true' : true; + } catch (error) { + console.error('检查验证码状态失败:', error); + return true; + } + } + + /** + * 生成验证码ID + * @returns 验证码ID + */ + private generateCaptchaId(): string { + return `captcha_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`; + } +} diff --git a/backend/src/utils/envHelper.ts b/backend/src/utils/envHelper.ts new file mode 100644 index 0000000..30a4b5e --- /dev/null +++ b/backend/src/utils/envHelper.ts @@ -0,0 +1,90 @@ +import fs from 'fs'; +import path from 'path'; + +/** + * .env文件更新工具 + * 用于读取、更新和保存.env文件 + */ +export class EnvHelper { + /** + * 读取.env文件内容 + * @param envFilePath .env文件路径 + * @returns 环境变量键值对 + */ + static readEnvFile(envFilePath: string): Record { + try { + const content = fs.readFileSync(envFilePath, 'utf-8'); + const envVars: Record = {}; + + const lines = content.split('\n'); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine && !trimmedLine.startsWith('#')) { + const equalIndex = trimmedLine.indexOf('='); + if (equalIndex > 0) { + const key = trimmedLine.substring(0, equalIndex).trim(); + const value = trimmedLine.substring(equalIndex + 1).trim(); + envVars[key] = value; + } + } + } + + return envVars; + } catch (error) { + console.error('读取.env文件失败:', error); + return {}; + } + } + + /** + * 更新.env文件中的指定变量 + * @param envFilePath .env文件路径 + * @param updates 要更新的环境变量键值对 + * @returns 是否更新成功 + */ + static updateEnvFile(envFilePath: string, updates: Record): boolean { + try { + const content = fs.readFileSync(envFilePath, 'utf-8'); + const lines = content.split('\n'); + const updatedLines: string[] = []; + const updatedKeys = new Set(); + + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine && !trimmedLine.startsWith('#')) { + const equalIndex = trimmedLine.indexOf('='); + if (equalIndex > 0) { + const key = trimmedLine.substring(0, equalIndex).trim(); + if (updates.hasOwnProperty(key)) { + updatedLines.push(`${key}=${updates[key]}`); + updatedKeys.add(key); + continue; + } + } + } + updatedLines.push(line); + } + + for (const key in updates) { + if (!updatedKeys.has(key)) { + updatedLines.push(`${key}=${updates[key]}`); + } + } + + fs.writeFileSync(envFilePath, updatedLines.join('\n'), 'utf-8'); + return true; + } catch (error) { + console.error('更新.env文件失败:', error); + return false; + } + } + + /** + * 获取.env文件路径 + * @param env 环境名称 (development/production) + * @returns .env文件路径 + */ + static getEnvFilePath(env: string = 'development'): string { + return path.join(process.cwd(), '.env'); + } +} diff --git a/frontend/src/api/admin.ts b/frontend/src/api/admin.ts index 2f7da22..515b969 100644 --- a/frontend/src/api/admin.ts +++ b/frontend/src/api/admin.ts @@ -1,11 +1,11 @@ import request from '@/utils/request' -import type { ApiResponse, LoginResponseData, UserInfo } from '@/types/api' +import type { ApiResponse, LoginResponseData, UserInfo, ConfigItem, CaptchaData, CaptchaEnabled } from '@/types/api' -export const login = (username: string, password: string): Promise> => { +export const login = (username: string, password: string, captchaId?: string, captchaCode?: string): Promise> => { return request({ url: '/api/admin/login', method: 'post', - data: { username, password } + data: { username, password, captchaId, captchaCode } }) } @@ -15,3 +15,48 @@ export const getCurrentUser = (): Promise> => { method: 'get' }) } + +export const getAllConfigs = (): Promise> => { + return request({ + url: '/api/admin/configs', + method: 'get' + }) +} + +export const updateConfigs = (configs: Array<{ configKey: string; configValue: string }>): Promise => { + return request({ + url: '/api/admin/configs', + method: 'put', + data: { configs } + }) +} + +export const testEmail = (toEmail: string): Promise => { + return request({ + url: '/api/admin/configs/test-email', + method: 'post', + data: { toEmail } + }) +} + +export const generateCaptcha = (): Promise> => { + return request({ + url: '/api/admin/captcha', + method: 'get' + }) +} + +export const verifyCaptcha = (captchaId: string, code: string): Promise => { + return request({ + url: '/api/admin/captcha/verify', + method: 'post', + data: { captchaId, code } + }) +} + +export const checkCaptchaEnabled = (): Promise> => { + return request({ + url: '/api/admin/captcha/enabled', + method: 'get' + }) +} diff --git a/frontend/src/api/player.ts b/frontend/src/api/player.ts index e881e0e..b090b22 100644 --- a/frontend/src/api/player.ts +++ b/frontend/src/api/player.ts @@ -1,10 +1,11 @@ import request from '@/utils/request' +import type { ApiResponse, CaptchaData, CaptchaEnabled } from '@/types/api' -export const login = (username: string, password: string) => { +export const login = (username: string, password: string, captchaId?: string, captchaCode?: string) => { return request({ url: '/api/player/login', method: 'post', - data: { username, password } + data: { username, password, captchaId, captchaCode } }) } @@ -21,3 +22,32 @@ export const getAccountInfo = () => { method: 'get' }) } + +export const generateCaptcha = (): Promise> => { + return request({ + url: '/api/player/captcha', + method: 'get' + }) +} + +export const verifyCaptcha = (captchaId: string, code: string): Promise => { + return request({ + url: '/api/player/captcha/verify', + method: 'post', + data: { captchaId, code } + }) +} + +export const checkCaptchaEnabled = (): Promise> => { + return request({ + url: '/api/player/captcha/enabled', + method: 'get' + }) +} + +export const checkPlayerServiceStatus = (): Promise> => { + return request({ + url: '/api/player/service-status', + method: 'get' + }) +} diff --git a/frontend/src/layouts/AdminLayout.vue b/frontend/src/layouts/AdminLayout.vue index 20ed9f5..ab7417c 100644 --- a/frontend/src/layouts/AdminLayout.vue +++ b/frontend/src/layouts/AdminLayout.vue @@ -48,7 +48,7 @@ import { ref, h } from 'vue' import { NLayout, NLayoutSider, NLayoutHeader, NLayoutContent, NLayoutFooter, NMenu, NDropdown } from 'naive-ui' import { RouterLink, useRoute, useRouter } from 'vue-router' import { useAdminStore } from '@/stores/admin' -import { RiDashboardLine, RiArrowDownSLine, RiUserLine, RiLogoutBoxRLine, RiSettings3Line, RiUserSettingsLine } from '@remixicon/vue' +import { RiDashboardLine, RiArrowDownSLine, RiUserLine, RiLogoutBoxRLine, RiSettings3Line, RiUserSettingsLine, RiToolsLine } from '@remixicon/vue' const route = useRoute() const router = useRouter() @@ -71,6 +71,11 @@ const menuOptions = [ label: () => h(RouterLink, { to: '/admin/user-management' }, { default: () => '用户管理' }), key: 'UserManagement', icon: () => h(RiUserSettingsLine, { size: '20px' }) + }, + { + label: () => h(RouterLink, { to: '/admin/system-config' }, { default: () => '系统配置' }), + key: 'SystemConfig', + icon: () => h(RiToolsLine, { size: '20px' }) } ] } diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 53247a9..9a50dbe 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -45,6 +45,12 @@ const routes = [ name: 'UserManagement', component: () => import('@/views/admin/UserManagement.vue'), meta: { title: '用户管理', requiresAdminAuth: true } + }, + { + path: 'system-config', + name: 'SystemConfig', + component: () => import('@/views/admin/SystemConfig.vue'), + meta: { title: '系统配置', requiresAdminAuth: true } } ] } diff --git a/frontend/src/stores/admin.ts b/frontend/src/stores/admin.ts index 0fb9e3b..3e800e3 100644 --- a/frontend/src/stores/admin.ts +++ b/frontend/src/stores/admin.ts @@ -27,9 +27,9 @@ export const useAdminStore = defineStore('admin', () => { localStorage.removeItem('admin_userInfo') } - const login = async (username: string, password: string) => { + const login = async (username: string, password: string, captchaId?: string, captchaCode?: string) => { try { - const data = await loginApi(username, password) + const data = await loginApi(username, password, captchaId, captchaCode) if (data.success && data.data) { setToken(data.data.token) setUserInfo(data.data.user) diff --git a/frontend/src/stores/player.ts b/frontend/src/stores/player.ts index 8d58edc..8fcf439 100644 --- a/frontend/src/stores/player.ts +++ b/frontend/src/stores/player.ts @@ -16,8 +16,8 @@ export const usePlayerStore = defineStore('player', () => { sessionStorage.removeItem('player_token') } - const login = async (username: string, password: string) => { - const response = await loginApi(username, password) + const login = async (username: string, password: string, captchaId?: string, captchaCode?: string) => { + const response = await loginApi(username, password, captchaId, captchaCode) if (response.success && response.data) { setToken(response.data) return true diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index a68251b..4639f30 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -33,3 +33,40 @@ export interface UserInfo { realName?: string roleId: number } + +/** + * 系统配置项 + */ +export interface ConfigItem { + id: number + configKey: string + configValue: string + configType: string + description?: string + createdAt: string + updatedAt: string +} + +/** + * 系统配置分组 + */ +export interface ConfigGroup { + type: string + title: string + configs: ConfigItem[] +} + +/** + * 验证码响应数据 + */ +export interface CaptchaData { + captchaId: string + svg: string +} + +/** + * 验证码启用状态 + */ +export interface CaptchaEnabled { + enabled: boolean +} diff --git a/frontend/src/views/admin/Login.vue b/frontend/src/views/admin/Login.vue index 986a877..a72b41b 100644 --- a/frontend/src/views/admin/Login.vue +++ b/frontend/src/views/admin/Login.vue @@ -13,6 +13,16 @@ placeholder="请输入密码" /> + + + +
+
+
登录 @@ -24,10 +34,11 @@ diff --git a/frontend/src/views/admin/SystemConfig.vue b/frontend/src/views/admin/SystemConfig.vue new file mode 100644 index 0000000..c90866e --- /dev/null +++ b/frontend/src/views/admin/SystemConfig.vue @@ -0,0 +1,447 @@ + + + + + diff --git a/frontend/src/views/player/Login.vue b/frontend/src/views/player/Login.vue index e20c950..7a0effa 100644 --- a/frontend/src/views/player/Login.vue +++ b/frontend/src/views/player/Login.vue @@ -1,6 +1,20 @@