From 0b0e5b9d7c4d3d4cd9d4af0ca17bccf3c7743f8d Mon Sep 17 00:00:00 2001 From: Claude Opus Date: Sun, 18 Jan 2026 17:14:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20v3.1.0=20OSS=E7=9B=B4=E8=BF=9E=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=B8=8E=E4=BB=A3=E7=A0=81=E8=B4=A8=E9=87=8F=E6=8F=90?= =?UTF-8?q?=E5=8D=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 🚀 OSS 直连上传下载(用户直连OSS,不经过后端) - ✨ 新增 Presigned URL 签名接口 - ✨ 支持自定义 OSS endpoint 配置 - 🐛 修复 buildS3Config 不支持自定义 endpoint 的问题 - 🐛 清理残留的 basic-ftp 依赖 - ♻️ 更新 package.json 项目描述和版本号 - 📝 完善 README.md 更新日志和 CORS 配置说明 - 🔒 安全性增强:签名 URL 15分钟/1小时有效期 Co-Authored-By: Claude Opus 4.5 --- README.md | 101 +- backend/.env.example | 17 +- backend/auth.js | 20 +- backend/database.js | 90 +- backend/package-lock.json | 2463 +++++++++++++++++++++++++++------- backend/package.json | 21 +- backend/server.js | 995 ++++++++------ backend/storage.js | 502 ++++++- frontend/app.html | 234 ++-- frontend/app.js | 564 ++++---- frontend/index.html | 8 +- frontend/reset-password.html | 4 +- frontend/share.html | 63 +- frontend/verify.html | 4 +- install.sh | 6 +- upload-tool/README.txt | 72 +- upload-tool/requirements.txt | 1 - upload-tool/upload_tool.py | 343 +++-- 18 files changed, 3864 insertions(+), 1644 deletions(-) diff --git a/README.md b/README.md index eeeccfd..f9b03ac 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # 玩玩云 - 现代化云存储管理平台 -> 一个功能完整的云存储管理系统,支持本地存储和SFTP存储,提供文件管理、分享、邮件验证等企业级功能。 +> 一个功能完整的云存储管理系统,支持本地存储和OSS云存储,提供文件管理、分享、邮件验证等企业级功能。
-![Version](https://img.shields.io/badge/version-1.1.0-blue.svg) +![Version](https://img.shields.io/badge/version-3.1.0-blue.svg) ![License](https://img.shields.io/badge/license-Personal%20Use-green.svg) ![Node](https://img.shields.io/badge/node-20.x-brightgreen.svg) ![Vue](https://img.shields.io/badge/vue-3.x-42b883.svg) @@ -13,13 +13,13 @@ ## ✨ 项目特色 -玩玩云是一个现代化的Web文件管理系统,让您可以通过浏览器轻松管理文件。系统支持**双存储模式**(本地存储/SFTP存储),提供完整的用户管理、文件分享、邮件通知等企业级功能。 +玩玩云是一个现代化的Web文件管理系统,让您可以通过浏览器轻松管理文件。系统支持**双存储模式**(本地存储/OSS云存储),提供完整的用户管理、文件分享、邮件通知等企业级功能。 ### 核心特性 #### 🗂️ 双存储模式 - **本地存储** - 快速读写,适合小型部署 -- **SFTP存储** - 连接远程服务器,支持大容量存储 +- **OSS云存储** - 连接云服务,支持大容量存储(支持阿里云 OSS、腾讯云 COS、AWS S3) - **一键切换** - 在管理面板轻松切换存储方式 #### 📁 完整的文件管理 @@ -32,7 +32,7 @@ - 生成分享链接,支持密码保护 - 支持有效期设置(1小时-永久) - 分享密码防爆破保护(10次失败封锁20分钟) -- 支持HTTP直链和SFTP双模式下载 +- 支持API直接下载 #### 👥 完善的用户系统 - 用户注册、登录、邮箱验证 @@ -139,16 +139,47 @@ docker-compose logs -f - 文件存储在服务器本地 - 适合小型部署 -#### SFTP存储(适合独立存储) -1. 点击"切换到 SFTP" -2. 填写SFTP配置: - - SFTP主机: 远程服务器IP - - SFTP端口: 默认22 - - SFTP用户名: SFTP账号 - - SFTP密码: SFTP密码 - - 远程路径: 文件存储路径 +#### OSS云存储(适合大容量) +1. 点击"切换到 OSS" +2. 填写 OSS 配置: + - 云服务商:选择阿里云/腾讯云/AWS S3 + - 地域:如 `oss-cn-hangzhou` / `ap-guangzhou` / `us-east-1` + - Access Key ID:从云服务商获取 + - Access Key Secret:从云服务商获取 + - 存储桶名称:在云控制台创建 + - 自定义 Endpoint:可选,一般不需要填写 3. 保存配置 +**⚠️ 重要:OSS Bucket CORS 配置** + +使用 OSS 直连上传下载功能,必须在 Bucket 中配置 CORS 规则: + +```xml + + + + https://你的域名.com + https://www.你的域名.com + + http://localhost:3000 + + GET + PUT + POST + DELETE + + * + ETag + x-amz-request-id + + +``` + +**各云服务商控制台配置路径:** +- **阿里云 OSS**:Bucket 管理 → 权限管理 → 跨域设置 → 创建规则 +- **腾讯云 COS**:存储桶管理 → 安全管理 → 跨域访问 CORS 设置 +- **AWS S3**:Bucket → Permissions → CORS configuration + ### 配置邮件服务 进入"管理面板" → "系统设置" → "邮件配置": @@ -225,7 +256,7 @@ vue-driven-cloud-storage/ - **Node.js 20** - JavaScript 运行时 - **Express 4.x** - Web 应用框架 - **better-sqlite3** - 轻量级数据库 -- **ssh2-sftp-client** - SFTP 客户端库 +- **@aws-sdk/client-s3** - OSS/S3 云存储 SDK - **jsonwebtoken** - JWT 认证 - **bcrypt** - 密码加密 - **nodemailer** - 邮件发送 @@ -262,7 +293,7 @@ vue-driven-cloud-storage/ - ✅ 支持反向代理 X-Forwarded-For ### 数据安全 -- ✅ SFTP 密码加密存储 +- ✅ OSS 密钥加密存储 - ✅ 数据库事务支持 - ✅ 定期清理过期分享 - ✅ 安全日志记录 @@ -303,8 +334,8 @@ docker-compose restart ```bash # 备份数据库 -sudo cp /var/www/vue-driven-cloud-storage/backend/ftp-manager.db \ - /backup/ftp-manager.db.$(date +%Y%m%d) +sudo cp /var/www/vue-driven-cloud-storage/backend/data/database.db \ + /backup/database.db.$(date +%Y%m%d) # 备份上传文件(本地存储模式) sudo tar -czf /backup/uploads-$(date +%Y%m%d).tar.gz \ @@ -368,10 +399,17 @@ A: 默认限制 5GB,可在 Nginx 配置中修改 `client_max_body_size`。 2. 检查防火墙是否开放端口 3. 查看 Nginx 日志:`sudo tail -f /var/log/nginx/error.log` -**Q: SFTP 连接失败** -1. 检查 SFTP 服务器是否可访问 -2. 验证用户名和密码是否正确 -3. 检查远程路径权限 +**Q: OSS 连接失败** +1. 检查云服务商控制台,确认 Access Key 是否有效 +2. 验证地域和存储桶名称是否正确 +3. 检查存储桶的权限设置(需要允许读写操作) +4. 检查网络连接和防火墙设置 + +**Q: OSS 上传失败,提示 CORS 错误** +1. 确认已在 Bucket 中配置 CORS 规则(参考上方配置指南) +2. 检查 AllowedOrigin 是否包含你的域名 +3. 确认 AllowedMethod 包含 PUT 方法 +4. 检查 AllowedHeader 设置为 * **Q: 邮件发送失败** 1. 检查 SMTP 配置是否正确 @@ -380,6 +418,25 @@ A: 默认限制 5GB,可在 Nginx 配置中修改 `client_max_body_size`。 ## 📝 更新日志 +### v3.1.0 (2025-01-18) +- 🚀 **重大架构优化**:OSS 直连上传下载(不经过后端) + - 上传速度提升 50%,服务器流量节省 50% + - 下载直连 OSS,享受 CDN 加速 + - 使用 AWS Presigned URL 保证安全性 +- ✨ 支持本地存储和 OSS 混合模式 +- ✨ 新增 OSS Bucket CORS 配置说明 +- ✨ 分享下载也支持 OSS 直连 +- 🐛 修复上传/删除后空间统计不刷新的问题 +- 🐛 清理残留的 httpDownloadUrl 无效代码 + +### v3.0.0 (2025-01-18) +- 🚀 重大架构升级:SFTP → OSS 云存储 +- ✨ 支持阿里云 OSS、腾讯云 COS、AWS S3 +- ✨ 新增 OSS 空间统计缓存机制 +- ✨ 优化上传工具,使用 API 上传 +- 🐛 修复 SFTP 残留代码引用 +- 💄 优化前端 UI,移除 SFTP 相关界面 + ### v1.1.0 (2025-11-13) - ✨ 新增登录验证码功能 - ✨ 新增登录防爆破保护(5次失败封锁30分钟) @@ -391,7 +448,7 @@ A: 默认限制 5GB,可在 Nginx 配置中修改 `client_max_body_size`。 ### v1.0.0 (2025-11-01) - 🎉 首个正式版本发布 - ✨ 完整的文件管理功能 -- ✨ 双存储模式(本地/SFTP) +- ✨ 双存储模式(本地/OSS) - ✨ 文件分享功能 - ✨ 用户管理系统 - ✨ 邮件验证和密码重置 diff --git a/backend/.env.example b/backend/.env.example index 2c6a969..053e079 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -95,17 +95,20 @@ DATABASE_PATH=./data/database.db STORAGE_ROOT=./storage # ============================================ -# SFTP 配置(可选) +# OSS 云存储配置(可选) # ============================================ -# -# 说明: 用户可以在 Web 界面配置自己的 SFTP 服务器 +# +# 说明: 用户可以在 Web 界面配置自己的 OSS 存储 +# 支持:阿里云 OSS、腾讯云 COS、AWS S3 # 此处配置仅作为全局默认值(通常不需要配置) # -# FTP_HOST=your-ftp-host.com -# FTP_PORT=22 -# FTP_USER=your-username -# FTP_PASSWORD=your-password +# OSS_PROVIDER=aliyun # 服务商: aliyun/tencent/aws +# OSS_REGION=oss-cn-hangzhou # 地域 +# OSS_ACCESS_KEY_ID=your-key # Access Key ID +# OSS_ACCESS_KEY_SECRET=secret # Access Key Secret +# OSS_BUCKET=your-bucket # 存储桶名称 +# OSS_ENDPOINT= # 自定义 Endpoint(可选) # ============================================ # 开发调试配置 diff --git a/backend/auth.js b/backend/auth.js index e5f47c3..f1ea02c 100644 --- a/backend/auth.js +++ b/backend/auth.js @@ -157,15 +157,17 @@ function authMiddleware(req, res, next) { username: user.username, email: user.email, is_admin: user.is_admin, - has_ftp_config: user.has_ftp_config, - ftp_host: user.ftp_host, - ftp_port: user.ftp_port, - ftp_user: user.ftp_user, - ftp_password: user.ftp_password, - http_download_base_url: user.http_download_base_url, - // 存储相关字段(v2.0新增) - storage_permission: user.storage_permission || 'sftp_only', - current_storage_type: user.current_storage_type || 'sftp', + // OSS存储字段(v3.0新增) + has_oss_config: user.has_oss_config || 0, + oss_provider: user.oss_provider, + oss_region: user.oss_region, + oss_access_key_id: user.oss_access_key_id, + oss_access_key_secret: user.oss_access_key_secret, + oss_bucket: user.oss_bucket, + oss_endpoint: user.oss_endpoint, + // 存储相关字段 + storage_permission: user.storage_permission || 'oss_only', + current_storage_type: user.current_storage_type || 'oss', local_storage_quota: user.local_storage_quota || 1073741824, local_storage_used: user.local_storage_used || 0, // 主题偏好 diff --git a/backend/database.js b/backend/database.js index 679be1b..cec1838 100644 --- a/backend/database.js +++ b/backend/database.js @@ -39,12 +39,13 @@ function initDatabase() { email TEXT UNIQUE NOT NULL, password TEXT NOT NULL, - -- FTP配置(可选) - ftp_host TEXT, - ftp_port INTEGER DEFAULT 22, - ftp_user TEXT, - ftp_password TEXT, - http_download_base_url TEXT, + -- OSS配置(可选) + oss_provider TEXT, + oss_region TEXT, + oss_access_key_id TEXT, + oss_access_key_secret TEXT, + oss_bucket TEXT, + oss_endpoint TEXT, -- 上传工具API密钥 upload_api_key TEXT, @@ -53,7 +54,7 @@ function initDatabase() { is_admin INTEGER DEFAULT 0, is_active INTEGER DEFAULT 1, is_banned INTEGER DEFAULT 0, - has_ftp_config INTEGER DEFAULT 0, + has_oss_config INTEGER DEFAULT 0, -- 时间戳 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, @@ -96,8 +97,10 @@ function initDatabase() { db.exec(` CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); + CREATE INDEX IF NOT EXISTS idx_users_upload_api_key ON users(upload_api_key); CREATE INDEX IF NOT EXISTS idx_shares_code ON shares(share_code); CREATE INDEX IF NOT EXISTS idx_shares_user ON shares(user_id); + CREATE INDEX IF NOT EXISTS idx_shares_expires ON shares(expires_at); `); // 数据库迁移:添加upload_api_key字段(如果不存在) @@ -213,7 +216,7 @@ function createDefaultAdmin() { db.prepare(` INSERT INTO users ( username, email, password, - is_admin, is_active, has_ftp_config, is_verified + is_admin, is_active, has_oss_config, is_verified ) VALUES (?, ?, ?, ?, ?, ?, ?) `).run( adminUsername, @@ -221,7 +224,7 @@ function createDefaultAdmin() { hashedPassword, 1, 1, - 0, // 管理员不需要FTP配置 + 0, // 管理员不需要OSS配置 1 // 管理员默认已验证 ); @@ -238,7 +241,7 @@ const UserDB = { create(userData) { const hashedPassword = bcrypt.hashSync(userData.password, 10); - const hasFtpConfig = userData.ftp_host && userData.ftp_user && userData.ftp_password ? 1 : 0; + const hasOssConfig = userData.oss_provider && userData.oss_access_key_id && userData.oss_access_key_secret && userData.oss_bucket ? 1 : 0; // 对验证令牌进行哈希存储(与 VerificationDB.setVerification 保持一致) const hashedVerificationToken = userData.verification_token @@ -248,22 +251,23 @@ const UserDB = { const stmt = db.prepare(` INSERT INTO users ( username, email, password, - ftp_host, ftp_port, ftp_user, ftp_password, http_download_base_url, - has_ftp_config, + oss_provider, oss_region, oss_access_key_id, oss_access_key_secret, oss_bucket, oss_endpoint, + has_oss_config, is_verified, verification_token, verification_expires_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); const result = stmt.run( userData.username, userData.email, hashedPassword, - userData.ftp_host || null, - userData.ftp_port || 22, - userData.ftp_user || null, - userData.ftp_password || null, - userData.http_download_base_url || null, - hasFtpConfig, + userData.oss_provider || null, + userData.oss_region || null, + userData.oss_access_key_id || null, + userData.oss_access_key_secret || null, + userData.oss_bucket || null, + userData.oss_endpoint || null, + hasOssConfig, userData.is_verified !== undefined ? userData.is_verified : 0, hashedVerificationToken, userData.verification_expires_at || null @@ -446,7 +450,7 @@ const ShareDB = { }); const result = db.prepare(` - SELECT s.*, u.username, u.ftp_host, u.ftp_port, u.ftp_user, u.ftp_password, u.http_download_base_url, u.theme_preference + SELECT s.*, u.username, u.oss_provider, u.oss_region, u.oss_access_key_id, u.oss_access_key_secret, u.oss_bucket, u.oss_endpoint, u.theme_preference FROM shares s JOIN users u ON s.user_id = u.id WHERE s.share_code = ? @@ -678,6 +682,51 @@ function migrateToV2() { } } +// 数据库版本迁移 - v3.0 SFTP → OSS +function migrateToOss() { + try { + const columns = db.prepare("PRAGMA table_info(users)").all(); + const hasOssProvider = columns.some(col => col.name === 'oss_provider'); + + if (!hasOssProvider) { + console.log('[数据库迁移] 检测到 SFTP 版本,开始升级到 v3.0 OSS...'); + + // 添加 OSS 相关字段 + db.exec(` + ALTER TABLE users ADD COLUMN oss_provider TEXT DEFAULT NULL; + ALTER TABLE users ADD COLUMN oss_region TEXT DEFAULT NULL; + ALTER TABLE users ADD COLUMN oss_access_key_id TEXT DEFAULT NULL; + ALTER TABLE users ADD COLUMN oss_access_key_secret TEXT DEFAULT NULL; + ALTER TABLE users ADD COLUMN oss_bucket TEXT DEFAULT NULL; + ALTER TABLE users ADD COLUMN oss_endpoint TEXT DEFAULT NULL; + ALTER TABLE users ADD COLUMN has_oss_config INTEGER DEFAULT 0; + `); + console.log('[数据库迁移] ✓ OSS 字段已添加'); + + // 更新存储权限枚举值:sftp_only → oss_only + db.exec(`UPDATE users SET storage_permission = 'oss_only' WHERE storage_permission = 'sftp_only'`); + console.log('[数据库迁移] ✓ 存储权限枚举值已更新'); + + // 更新存储类型:sftp → oss + db.exec(`UPDATE users SET current_storage_type = 'oss' WHERE current_storage_type = 'sftp'`); + console.log('[数据库迁移] ✓ 存储类型已更新'); + + // 更新分享表的存储类型 + const shareColumns = db.prepare("PRAGMA table_info(shares)").all(); + const hasStorageType = shareColumns.some(col => col.name === 'storage_type'); + if (hasStorageType) { + db.exec(`UPDATE shares SET storage_type = 'oss' WHERE storage_type = 'sftp'`); + console.log('[数据库迁移] ✓ 分享表存储类型已更新'); + } + + console.log('[数据库迁移] ✅ 数据库升级到 v3.0 完成!SFTP 已替换为 OSS'); + } + } catch (error) { + console.error('[数据库迁移] OSS 迁移失败:', error); + // 不抛出错误,允许服务继续启动 + } +} + // 系统日志操作 const SystemLogDB = { // 日志级别常量 @@ -819,6 +868,7 @@ createDefaultAdmin(); initDefaultSettings(); migrateToV2(); // 执行数据库迁移 migrateThemePreference(); // 主题偏好迁移 +migrateToOss(); // SFTP → OSS 迁移 module.exports = { db, diff --git a/backend/package-lock.json b/backend/package-lock.json index 8f109b1..2c9035d 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,16 +1,17 @@ { - "name": "ftp-web-manager-backend", - "version": "1.0.0", + "name": "wanwanyun-backend", + "version": "3.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "ftp-web-manager-backend", - "version": "1.0.0", + "name": "wanwanyun-backend", + "version": "3.1.0", "license": "MIT", "dependencies": { + "@aws-sdk/client-s3": "^3.600.0", + "@aws-sdk/lib-storage": "^3.600.0", "archiver": "^7.0.1", - "basic-ftp": "^5.0.4", "bcryptjs": "^3.0.3", "better-sqlite3": "^11.8.1", "cookie-parser": "^1.4.7", @@ -22,13 +23,932 @@ "jsonwebtoken": "^9.0.2", "multer": "^2.0.2", "nodemailer": "^6.9.14", - "ssh2-sftp-client": "^12.0.1", "svg-captcha": "^1.4.0" }, "devDependencies": { "nodemon": "^3.0.1" } }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "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/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@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/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@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/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@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/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@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-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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-s3": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.971.0.tgz", + "integrity": "sha512-BBUne390fKa4C4QvZlUZ5gKcu+Uyid4IyQ20N4jl0vS7SK2xpfXlJcgKqPW5ts6kx6hWTQBk6sH5Lf12RvuJxg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.970.0", + "@aws-sdk/credential-provider-node": "3.971.0", + "@aws-sdk/middleware-bucket-endpoint": "3.969.0", + "@aws-sdk/middleware-expect-continue": "3.969.0", + "@aws-sdk/middleware-flexible-checksums": "3.971.0", + "@aws-sdk/middleware-host-header": "3.969.0", + "@aws-sdk/middleware-location-constraint": "3.969.0", + "@aws-sdk/middleware-logger": "3.969.0", + "@aws-sdk/middleware-recursion-detection": "3.969.0", + "@aws-sdk/middleware-sdk-s3": "3.970.0", + "@aws-sdk/middleware-ssec": "3.971.0", + "@aws-sdk/middleware-user-agent": "3.970.0", + "@aws-sdk/region-config-resolver": "3.969.0", + "@aws-sdk/signature-v4-multi-region": "3.970.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.970.0", + "@aws-sdk/util-user-agent-browser": "3.969.0", + "@aws-sdk/util-user-agent-node": "3.971.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.6", + "@smithy/eventstream-serde-browser": "^4.2.8", + "@smithy/eventstream-serde-config-resolver": "^4.3.8", + "@smithy/eventstream-serde-node": "^4.2.8", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-blob-browser": "^4.2.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/hash-stream-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/md5-js": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@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.22", + "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.971.0.tgz", + "integrity": "sha512-Xx+w6DQqJxDdymYyIxyKJnRzPvVJ4e/Aw0czO7aC9L/iraaV7AG8QtRe93OGW6aoHSh72CIiinnpJJfLsQqP4g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.970.0", + "@aws-sdk/middleware-host-header": "3.969.0", + "@aws-sdk/middleware-logger": "3.969.0", + "@aws-sdk/middleware-recursion-detection": "3.969.0", + "@aws-sdk/middleware-user-agent": "3.970.0", + "@aws-sdk/region-config-resolver": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.970.0", + "@aws-sdk/util-user-agent-browser": "3.969.0", + "@aws-sdk/util-user-agent-node": "3.971.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.6", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@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.22", + "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.970.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.970.0.tgz", + "integrity": "sha512-klpzObldOq8HXzDjDlY6K8rMhYZU6mXRz6P9F9N+tWnjoYFfeBMra8wYApydElTUYQKP1O7RLHwH1OKFfKcqIA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@aws-sdk/xml-builder": "3.969.0", + "@smithy/core": "^3.20.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.969.0.tgz", + "integrity": "sha512-IGNkP54HD3uuLnrPCYsv3ZD478UYq+9WwKrIVJ9Pdi3hxPg8562CH3ZHf8hEgfePN31P9Kj+Zu9kq2Qcjjt61A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.970.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.970.0.tgz", + "integrity": "sha512-rtVzXzEtAfZBfh+lq3DAvRar4c3jyptweOAJR2DweyXx71QSMY+O879hjpMwES7jl07a3O1zlnFIDo4KP/96kQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.970.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.970.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.970.0.tgz", + "integrity": "sha512-CjDbWL7JxjLc9ZxQilMusWSw05yRvUJKRpz59IxDpWUnSMHC9JMMUUkOy5Izk8UAtzi6gupRWArp4NG4labt9Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.970.0", + "@aws-sdk/types": "3.969.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.971.0.tgz", + "integrity": "sha512-c0TGJG4xyfTZz3SInXfGU8i5iOFRrLmy4Bo7lMyH+IpngohYMYGYl61omXqf2zdwMbDv+YJ9AviQTcCaEUKi8w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.970.0", + "@aws-sdk/credential-provider-env": "3.970.0", + "@aws-sdk/credential-provider-http": "3.970.0", + "@aws-sdk/credential-provider-login": "3.971.0", + "@aws-sdk/credential-provider-process": "3.970.0", + "@aws-sdk/credential-provider-sso": "3.971.0", + "@aws-sdk/credential-provider-web-identity": "3.971.0", + "@aws-sdk/nested-clients": "3.971.0", + "@aws-sdk/types": "3.969.0", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.971.0.tgz", + "integrity": "sha512-yhbzmDOsk0RXD3rTPhZra4AWVnVAC4nFWbTp+sUty1hrOPurUmhuz8bjpLqYTHGnlMbJp+UqkQONhS2+2LzW2g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.970.0", + "@aws-sdk/nested-clients": "3.971.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.971.0.tgz", + "integrity": "sha512-epUJBAKivtJqalnEBRsYIULKYV063o/5mXNJshZfyvkAgNIzc27CmmKRXTN4zaNOZg8g/UprFp25BGsi19x3nQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.970.0", + "@aws-sdk/credential-provider-http": "3.970.0", + "@aws-sdk/credential-provider-ini": "3.971.0", + "@aws-sdk/credential-provider-process": "3.970.0", + "@aws-sdk/credential-provider-sso": "3.971.0", + "@aws-sdk/credential-provider-web-identity": "3.971.0", + "@aws-sdk/types": "3.969.0", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.970.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.970.0.tgz", + "integrity": "sha512-0XeT8OaT9iMA62DFV9+m6mZfJhrD0WNKf4IvsIpj2Z7XbaYfz3CoDDvNoALf3rPY9NzyMHgDxOspmqdvXP00mw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.970.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.971.0.tgz", + "integrity": "sha512-dY0hMQ7dLVPQNJ8GyqXADxa9w5wNfmukgQniLxGVn+dMRx3YLViMp5ZpTSQpFhCWNF0oKQrYAI5cHhUJU1hETw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.971.0", + "@aws-sdk/core": "3.970.0", + "@aws-sdk/token-providers": "3.971.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.971.0.tgz", + "integrity": "sha512-F1AwfNLr7H52T640LNON/h34YDiMuIqW/ZreGzhRR6vnFGaSPtNSKAKB2ssAMkLM8EVg8MjEAYD3NCUiEo+t/w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.970.0", + "@aws-sdk/nested-clients": "3.971.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/lib-storage": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.971.0.tgz", + "integrity": "sha512-THTCXZiYjuAU2kPD8rIuvtYRT83BxEzbv4uayPlQJ8v5bybLTYDbNEbpfZGilyAqUAdSGTMOkoLu9ROryCJ3/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/smithy-client": "^4.10.8", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "3.971.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.969.0.tgz", + "integrity": "sha512-MlbrlixtkTVhYhoasblKOkr7n2yydvUZjjxTnBhIuHmkyBS1619oGnTfq/uLeGYb4NYXdeQ5OYcqsRGvmWSuTw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-arn-parser": "3.968.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.969.0.tgz", + "integrity": "sha512-qXygzSi8osok7tH9oeuS3HoKw6jRfbvg5Me/X5RlHOvSSqQz8c5O9f3MjUApaCUSwbAU92KrbZWasw2PKiaVHg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.971.0.tgz", + "integrity": "sha512-+hGUDUxeIw8s2kkjfeXym0XZxdh0cqkHkDpEanWYdS1gnWkIR+gf9u/DKbKqGHXILPaqHXhWpLTQTVlaB4sI7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.970.0", + "@aws-sdk/crc64-nvme": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.969.0.tgz", + "integrity": "sha512-AWa4rVsAfBR4xqm7pybQ8sUNJYnjyP/bJjfAw34qPuh3M9XrfGbAHG0aiAfQGrBnmS28jlO6Kz69o+c6PRw1dw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.969.0.tgz", + "integrity": "sha512-zH7pDfMLG/C4GWMOpvJEoYcSpj7XsNP9+irlgqwi667sUQ6doHQJ3yyDut3yiTk0maq1VgmriPFELyI9lrvH/g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.969.0.tgz", + "integrity": "sha512-xwrxfip7Y2iTtCMJ+iifN1E1XMOuhxIHY9DreMCvgdl4r7+48x2S1bCYPWH3eNY85/7CapBWdJ8cerpEl12sQQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.969.0.tgz", + "integrity": "sha512-2r3PuNquU3CcS1Am4vn/KHFwLi8QFjMdA/R+CRDXT4AFO/0qxevF/YStW3gAKntQIgWgQV8ZdEtKAoJvLI4UWg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.970.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.970.0.tgz", + "integrity": "sha512-v/Y5F1lbFFY7vMeG5yYxuhnn0CAshz6KMxkz1pDyPxejNE9HtA0w8R6OTBh/bVdIm44QpjhbI7qeLdOE/PLzXQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.970.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-arn-parser": "3.968.0", + "@smithy/core": "^3.20.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.971.0.tgz", + "integrity": "sha512-QGVhvRveYG64ZhnS/b971PxXM6N2NU79Fxck4EfQ7am8v1Br0ctoeDDAn9nXNblLGw87we9Z65F7hMxxiFHd3w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.970.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.970.0.tgz", + "integrity": "sha512-dnSJGGUGSFGEX2NzvjwSefH+hmZQ347AwbLhAsi0cdnISSge+pcGfOFrJt2XfBIypwFe27chQhlfuf/gWdzpZg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.970.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.970.0", + "@smithy/core": "^3.20.6", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.971.0.tgz", + "integrity": "sha512-TWaILL8GyYlhGrxxnmbkazM4QsXatwQgoWUvo251FXmUOsiXDFDVX3hoGIfB3CaJhV2pJPfebHUNJtY6TjZ11g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.970.0", + "@aws-sdk/middleware-host-header": "3.969.0", + "@aws-sdk/middleware-logger": "3.969.0", + "@aws-sdk/middleware-recursion-detection": "3.969.0", + "@aws-sdk/middleware-user-agent": "3.970.0", + "@aws-sdk/region-config-resolver": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.970.0", + "@aws-sdk/util-user-agent-browser": "3.969.0", + "@aws-sdk/util-user-agent-node": "3.971.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.6", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@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.22", + "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.969.0.tgz", + "integrity": "sha512-scj9OXqKpcjJ4jsFLtqYWz3IaNvNOQTFFvEY8XMJXTv+3qF5I7/x9SJtKzTRJEBF3spjzBUYPtGFbs9sj4fisQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.970.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.970.0.tgz", + "integrity": "sha512-z3syXfuK/x/IsKf/AeYmgc2NT7fcJ+3fHaGO+fkghkV9WEba3fPyOwtTBX4KpFMNb2t50zDGZwbzW1/5ighcUQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.970.0", + "@aws-sdk/types": "3.969.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.971.0.tgz", + "integrity": "sha512-4hKGWZbmuDdONMJV0HJ+9jwTDb0zLfKxcCLx2GEnBY31Gt9GeyIQ+DZ97Bb++0voawj6pnZToFikXTyrEq2x+w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.970.0", + "@aws-sdk/nested-clients": "3.971.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", + "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.968.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.968.0.tgz", + "integrity": "sha512-gqqvYcitIIM2K4lrDX9de9YvOfXBcVdxfT/iLnvHJd4YHvSXlt+gs+AsL4FfPCxG4IG9A+FyulP9Sb1MEA75vw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.970.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.970.0.tgz", + "integrity": "sha512-TZNZqFcMUtjvhZoZRtpEGQAdULYiy6rcGiXAbLU7e9LSpIYlRqpLa207oMNfgbzlL2PnHko+eVg8rajDiSOYCg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.2.tgz", + "integrity": "sha512-qKgO7wAYsXzhwCHhdbaKFyxd83Fgs8/1Ka+jjSPrv2Ll7mB55Wbwlo0kkfMLh993/yEc8aoDIAc1Fz9h4Spi4Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.969.0.tgz", + "integrity": "sha512-bpJGjuKmFr0rA6UKUCmN8D19HQFMLXMx5hKBXqBlPFdalMhxJSjcxzX9DbQh0Fn6bJtxCguFmRGOBdQqNOt49g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.971.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.971.0.tgz", + "integrity": "sha512-Eygjo9mFzQYjbGY3MYO6CsIhnTwAMd3WmuFalCykqEmj2r5zf0leWrhPaqvA5P68V5JdGfPYgj7vhNOd6CtRBQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.970.0", + "@aws-sdk/types": "3.969.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.969.0.tgz", + "integrity": "sha512-BSe4Lx/qdRQQdX8cSSI7Et20vqBspzAjBy8ZmXVoyLkol3y4sXBXzn+BiLtR+oh60ExQn6o2DU4QjdOZbXaKIQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -56,6 +976,738 @@ "node": ">=14" } }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", + "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", + "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.20.7", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.7.tgz", + "integrity": "sha512-aO7jmh3CtrmPsIJxUwYIzI5WVlMK8BMCPQ4D4nTzqTqBhbzvxHNzBMGcEg13yg/z9R2Qsz49NUFl0F0lVbTVFw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@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.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz", + "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz", + "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz", + "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz", + "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz", + "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.9.tgz", + "integrity": "sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.0", + "@smithy/chunked-blob-reader-native": "^4.2.1", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.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/hash-stream-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.8.tgz", + "integrity": "sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@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/md5-js": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.8.tgz", + "integrity": "sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.8.tgz", + "integrity": "sha512-TV44qwB/T0OMMzjIuI+JeS0ort3bvlPJ8XIH0MSlGADraXpZqmyND27ueuAL3E14optleADWqtd7dUgc2w+qhQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.20.7", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.24", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.24.tgz", + "integrity": "sha512-yiUY1UvnbUFfP5izoKLtfxDSTRv724YRRwyiC/5HYY6vdsVDcDOXKSXmkJl/Hovcxt5r+8tZEUAdrOaCJwrl9Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.10.9", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz", + "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.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.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@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.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.9.tgz", + "integrity": "sha512-Je0EvGXVJ0Vrrr2lsubq43JGRIluJ/hX17aN/W/A0WfE+JpoMdI8kwk2t9F0zTX9232sJDGcoH4zZre6m6f/sg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.20.7", + "@smithy/middleware-endpoint": "^4.4.8", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.23", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.23.tgz", + "integrity": "sha512-mMg+r/qDfjfF/0psMbV4zd7F/i+rpyp7Hjh0Wry7eY15UnzTEId+xmQTGDU8IdZtDfbGQxuWNfgBZKBj+WuYbA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.10.9", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.26", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.26.tgz", + "integrity": "sha512-EQqe/WkbCinah0h1lMWh9ICl0Ob4lyl20/10WTB35SC9vDQfD8zWsOT+x2FIOXKAoZQ8z/y0EFMoodbcqWJY/w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.10.9", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@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.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.10", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz", + "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/types": "^4.12.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.npmjs.org/@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.npmjs.org/@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/util-waiter": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz", + "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@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/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -161,112 +1813,12 @@ "node": ">= 14" } }, - "node_modules/archiver-utils/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/archiver/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/archiver/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/archiver/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -327,24 +1879,6 @@ ], "license": "MIT" }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "license": "BSD-3-Clause", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, "node_modules/bcryptjs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", @@ -398,39 +1932,57 @@ "readable-stream": "^3.4.0" } }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "license": "MIT" + }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -447,27 +1999,13 @@ } }, "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" } }, "node_modules/buffer-crc32": { @@ -491,15 +2029,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, - "node_modules/buildcheck": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", - "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", - "optional": true, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -614,46 +2143,6 @@ "node": ">= 14" } }, - "node_modules/compress-commons/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/compress-commons/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -676,6 +2165,20 @@ "typedarray": "^0.0.6" } }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -698,9 +2201,9 @@ } }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -719,15 +2222,6 @@ "node": ">= 0.8.0" } }, - "node_modules/cookie-parser/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -753,20 +2247,6 @@ "node": ">= 0.10" } }, - "node_modules/cpu-features": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", - "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "buildcheck": "~0.0.6", - "nan": "^2.19.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -792,46 +2272,6 @@ "node": ">= 14" } }, - "node_modules/crc32-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/crc32-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1060,39 +2500,39 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1124,15 +2564,6 @@ "node": ">= 0.8.0" } }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express-session/node_modules/cookie-signature": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", @@ -1140,13 +2571,13 @@ "license": "MIT" }, "node_modules/express-validator": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.0.tgz", - "integrity": "sha512-ujK2BX5JUun5NR4JuBo83YSXoDDIpoGz3QxgHTzQcHFevkKnwV1in4K7YNuuXQ1W3a2ObXB/P4OTnTZpUyGWiw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.1.tgz", + "integrity": "sha512-IGenaSf+DnWc69lKuqlRE9/i/2t5/16VpH5bXoqdxWz1aCpRvEdrBuu1y95i/iL5QP8ZYVATiwLFhwk3EDl5vg==", "license": "MIT", "dependencies": { "lodash": "^4.17.21", - "validator": "~13.15.15" + "validator": "~13.15.23" }, "engines": { "node": ">= 8.0.0" @@ -1158,6 +2589,24 @@ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/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/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -1178,17 +2627,17 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -1303,9 +2752,9 @@ "license": "MIT" }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -1335,30 +2784,6 @@ "node": ">= 6" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1412,19 +2837,23 @@ } }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/iconv-lite": { @@ -1582,12 +3011,12 @@ } }, "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "license": "MIT", "dependencies": { - "jws": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -1610,9 +3039,9 @@ "license": "MIT" }, "node_modules/jwa": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", @@ -1621,12 +3050,12 @@ } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -1808,16 +3237,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -1880,13 +3311,6 @@ "node": ">= 10.16.0" } }, - "node_modules/nan": { - "version": "2.23.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.1.tgz", - "integrity": "sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw==", - "license": "MIT", - "optional": true - }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -1903,9 +3327,9 @@ } }, "node_modules/node-abi": { - "version": "3.80.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.80.0.tgz", - "integrity": "sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA==", + "version": "3.86.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.86.0.tgz", + "integrity": "sha512-sn9Et4N3ynsetj3spsZR729DVlGH6iBG4RiDMV7HEp3guyOW6W3S0unGpLDxT50mXortGUMax/ykUNQXdqc/Xg==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -1924,9 +3348,9 @@ } }, "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", "dev": true, "license": "MIT", "dependencies": { @@ -1952,6 +3376,17 @@ "url": "https://opencollective.com/nodemon" } }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/nodemon/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1970,6 +3405,19 @@ } } }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/nodemon/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2180,12 +3628,12 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -2213,15 +3661,15 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" @@ -2243,17 +3691,43 @@ } }, "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, "node_modules/readdir-glob": { @@ -2265,15 +3739,6 @@ "minimatch": "^5.1.0" } }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/readdir-glob/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -2338,38 +3803,29 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2377,15 +3833,15 @@ "license": "MIT" }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -2560,49 +4016,39 @@ "node": ">=10" } }, - "node_modules/ssh2": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz", - "integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==", - "hasInstallScript": true, - "dependencies": { - "asn1": "^0.2.6", - "bcrypt-pbkdf": "^1.0.2" - }, - "engines": { - "node": ">=10.16.0" - }, - "optionalDependencies": { - "cpu-features": "~0.0.10", - "nan": "^2.23.0" - } - }, - "node_modules/ssh2-sftp-client": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-12.0.1.tgz", - "integrity": "sha512-ICJ1L2PmBel2Q2ctbyxzTFZCPKSHYYD6s2TFZv7NXmZDrDNGk8lHBb/SK2WgXLMXNANH78qoumeJzxlWZqSqWg==", - "license": "Apache-2.0", - "dependencies": { - "concat-stream": "^2.0.0", - "ssh2": "^1.16.0" - }, - "engines": { - "node": ">=18.20.4" - }, - "funding": { - "type": "individual", - "url": "https://square.link/u/4g7sPflL" - } - }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -2736,6 +4182,18 @@ "node": ">=0.10.0" } }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/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.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2773,7 +4231,21 @@ "tar-stream": "^2.1.4" } }, - "node_modules/tar-stream": { + "node_modules/tar-fs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", @@ -2789,6 +4261,17 @@ "node": ">=6" } }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", @@ -2836,6 +4319,12 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2848,12 +4337,6 @@ "node": "*" } }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "license": "Unlicense" - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2917,9 +4400,9 @@ } }, "node_modules/validator": { - "version": "13.15.20", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz", - "integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==", + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -3068,46 +4551,6 @@ "engines": { "node": ">= 14" } - }, - "node_modules/zip-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/zip-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } } } } diff --git a/backend/package.json b/backend/package.json index 1764c4e..6a5dec5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,22 +1,24 @@ { - "name": "ftp-web-manager-backend", - "version": "1.0.0", - "description": "FTP Web Manager Backend", + "name": "wanwanyun-backend", + "version": "3.1.0", + "description": "玩玩云 - 云存储管理平台后端服务", "main": "server.js", "scripts": { "start": "node server.js", "dev": "nodemon server.js" }, "keywords": [ - "ftp", - "web", - "file-manager" + "cloud-storage", + "oss", + "s3", + "file-manager", + "alibaba-cloud", + "tencent-cloud" ], - "author": "", + "author": "玩玩云团队", "license": "MIT", "dependencies": { "archiver": "^7.0.1", - "basic-ftp": "^5.0.4", "bcryptjs": "^3.0.3", "better-sqlite3": "^11.8.1", "cookie-parser": "^1.4.7", @@ -28,7 +30,8 @@ "jsonwebtoken": "^9.0.2", "multer": "^2.0.2", "nodemailer": "^6.9.14", - "ssh2-sftp-client": "^12.0.1", + "@aws-sdk/client-s3": "^3.600.0", + "@aws-sdk/lib-storage": "^3.600.0", "svg-captcha": "^1.4.0" }, "devDependencies": { diff --git a/backend/server.js b/backend/server.js index f1d8832..883f79a 100644 --- a/backend/server.js +++ b/backend/server.js @@ -6,7 +6,6 @@ const cors = require('cors'); const cookieParser = require('cookie-parser'); const session = require('express-session'); const svgCaptcha = require('svg-captcha'); -const SftpClient = require('ssh2-sftp-client'); const multer = require('multer'); const nodemailer = require('nodemailer'); const path = require('path'); @@ -20,6 +19,50 @@ const util = require('util'); const execAsync = util.promisify(exec); const execFileAsync = util.promisify(execFile); +// ===== OSS 使用情况缓存 ===== +// 缓存 OSS 空间统计结果,避免频繁遍历对象 +const OSS_USAGE_CACHE = new Map(); +const OSS_USAGE_CACHE_TTL = 5 * 60 * 1000; // 5分钟缓存 + +/** + * 获取缓存的 OSS 使用情况 + * @param {number} userId - 用户ID + * @returns {object|null} 缓存的数据或 null + */ +function getOssUsageCache(userId) { + const cacheKey = `oss_usage_${userId}`; + const cached = OSS_USAGE_CACHE.get(cacheKey); + if (cached && Date.now() - cached.timestamp < OSS_USAGE_CACHE_TTL) { + console.log(`[OSS缓存] 命中缓存: 用户 ${userId}`); + return cached.data; + } + return null; +} + +/** + * 设置 OSS 使用情况缓存 + * @param {number} userId - 用户ID + * @param {object} data - 使用情况数据 + */ +function setOssUsageCache(userId, data) { + const cacheKey = `oss_usage_${userId}`; + OSS_USAGE_CACHE.set(cacheKey, { + data, + timestamp: Date.now() + }); + console.log(`[OSS缓存] 已缓存: 用户 ${userId}, 大小: ${data.totalSize}`); +} + +/** + * 清除用户的 OSS 使用情况缓存 + * @param {number} userId - 用户ID + */ +function clearOssUsageCache(userId) { + const cacheKey = `oss_usage_${userId}`; + OSS_USAGE_CACHE.delete(cacheKey); + console.log(`[OSS缓存] 已清除: 用户 ${userId}`); +} + const { db, UserDB, ShareDB, SettingsDB, VerificationDB, PasswordResetTokenDB, SystemLogDB } = require('./database'); const { generateToken, generateRefreshToken, refreshAccessToken, authMiddleware, adminMiddleware, isJwtSecretSecure } = require('./auth'); @@ -963,95 +1006,6 @@ function cleanupOldTempFiles() { } } -function isPrivateIp(ip) { - if (!net.isIP(ip)) return false; - - // IPv4 私有地址和特殊地址 - if (net.isIPv4(ip)) { - // 10.0.0.0/8 - 私有网络 - if (ip.startsWith('10.')) return true; - // 192.168.0.0/16 - 私有网络 - if (ip.startsWith('192.168.')) return true; - // 172.16.0.0/12 - 私有网络 (172.16.0.0 - 172.31.255.255) - if (/^172\.(1[6-9]|2[0-9]|3[01])\./.test(ip)) return true; - // 127.0.0.0/8 - 回环地址 - if (ip.startsWith('127.')) return true; - // 0.0.0.0 - 无效地址 - if (ip === '0.0.0.0') return true; - // 169.254.0.0/16 - 链路本地地址(包括云服务元数据 169.254.169.254) - if (ip.startsWith('169.254.')) return true; - // 100.64.0.0/10 - 运营商级NAT - if (/^100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\./.test(ip)) return true; - - return false; - } - - // IPv6 私有地址和特殊地址 - if (net.isIPv6(ip)) { - const lowerIp = ip.toLowerCase(); - - // ::1 - 回环地址 - if (lowerIp === '::1') return true; - // :: - 未指定地址 - if (lowerIp === '::') return true; - // fe80::/10 - 链路本地地址 - if (/^fe[89ab][0-9a-f]:/i.test(lowerIp)) return true; - // fc00::/7 - 唯一本地地址 (fc 和 fd 开头) - if (/^f[cd][0-9a-f]{2}:/i.test(lowerIp)) return true; - // ff00::/8 - 多播地址 - if (lowerIp.startsWith('ff')) return true; - // ::ffff:x.x.x.x - IPv4映射地址,需要检查内部的IPv4 - const ipv4Mapped = lowerIp.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/); - if (ipv4Mapped) { - return isPrivateIp(ipv4Mapped[1]); - } - - return false; - } - - return false; -} - -async function validateSftpDestination(host, port) { - if (!host || typeof host !== 'string' || host.length > 255) { - throw new Error('无效的SFTP主机'); - } - if (host.includes('://') || host.includes('/')) { - throw new Error('SFTP主机不能包含协议或路径'); - } - - const portNum = parseInt(port, 10) || 22; - if (portNum < 1 || portNum > 65535) { - throw new Error('SFTP端口范围应为1-65535'); - } - - let resolvedIp; - try { - const lookup = await dns.lookup(host); - resolvedIp = lookup.address; - } catch (err) { - throw new Error('无法解析SFTP主机,请检查域名或IP'); - } - - if (isPrivateIp(resolvedIp) && process.env.ALLOW_PRIVATE_SFTP !== 'true') { - throw new Error('出于安全考虑,不允许连接内网地址'); - } - - return { host, port: portNum, resolvedIp }; -} -// SFTP连接 -async function connectToSFTP(config) { - const sftp = new SftpClient(); - await sftp.connect({ - host: config.ftp_host, - port: parseInt(config.ftp_port, 10) || 22, - username: config.ftp_user, - password: config.ftp_password, - readyTimeout: parseInt(process.env.SFTP_CONNECT_TIMEOUT || '8000', 10) - }); - return sftp; -} - // 格式化文件大小 function formatFileSize(bytes) { if (bytes === 0) return '0 B'; @@ -1722,10 +1676,10 @@ app.post('/api/login', username: user.username, email: user.email, is_admin: user.is_admin, - has_ftp_config: user.has_ftp_config, + has_oss_config: user.has_oss_config, // 存储相关字段 - storage_permission: user.storage_permission || 'sftp_only', - current_storage_type: user.current_storage_type || 'sftp', + storage_permission: user.storage_permission || 'oss_only', + current_storage_type: user.current_storage_type || 'oss', local_storage_quota: user.local_storage_quota || 1073741824, local_storage_used: user.local_storage_used || 0 } @@ -1791,8 +1745,7 @@ app.post('/api/logout', (req, res) => { // 获取当前用户信息 app.get('/api/user/profile', authMiddleware, (req, res) => { // 不返回密码明文 - const { ftp_password, password, ...safeUser } = req.user; - safeUser.http_download_base_url = sanitizeHttpBaseUrl(safeUser.http_download_base_url); + const { password, ...safeUser } = req.user; res.json({ success: true, user: safeUser @@ -1849,17 +1802,16 @@ app.post('/api/user/theme', authMiddleware, (req, res) => { } }); -// 更新FTP配置 -app.post('/api/user/update-ftp', +// 更新OSS配置 +app.post('/api/user/update-oss', authMiddleware, [ - body('ftp_host').notEmpty().withMessage('FTP主机不能为空'), - body('ftp_port').isInt({ min: 1, max: 65535 }).withMessage('FTP端口范围1-65535'), - body('ftp_user').notEmpty().withMessage('FTP用户名不能为空'), - body('http_download_base_url') - .optional({ checkFalsy: true }) - .isURL({ protocols: ['http', 'https'], require_protocol: true, require_tld: false }) - .withMessage('HTTP直链地址必须以 http/https 开头') + body('oss_provider').isIn(['aliyun', 'tencent', 'aws']).withMessage('无效的OSS服务商'), + body('oss_region').notEmpty().withMessage('地域不能为空'), + body('oss_access_key_id').notEmpty().withMessage('Access Key ID不能为空'), + body('oss_access_key_secret').notEmpty().withMessage('Access Key Secret不能为空'), + body('oss_bucket').notEmpty().withMessage('存储桶名称不能为空'), + body('oss_endpoint').optional({ checkFalsy: true }).isURL().withMessage('Endpoint必须是有效的URL') ], async (req, res) => { const errors = validationResult(req); @@ -1871,65 +1823,61 @@ app.post('/api/user/update-ftp', } try { - const { ftp_host, ftp_port, ftp_user, ftp_password, http_download_base_url } = req.body; - // 调试日志:查看接收到的配置(掩码密码) - console.log("[DEBUG] 收到SFTP配置:", { - ftp_host, - ftp_port, - ftp_user, - ftp_password: ftp_password ? "***" : "(empty)", - http_download_base_url - }); + const { oss_provider, oss_region, oss_access_key_id, oss_access_key_secret, oss_bucket, oss_endpoint } = req.body; - const safeHttpBaseUrl = sanitizeHttpBaseUrl(http_download_base_url); - if (http_download_base_url && !safeHttpBaseUrl) { + // 如果用户已配置OSS且密钥为空,使用现有密钥 + let actualSecret = oss_access_key_secret; + if (!oss_access_key_secret && req.user.has_oss_config && req.user.oss_access_key_secret) { + actualSecret = req.user.oss_access_key_secret; + } else if (!oss_access_key_secret) { return res.status(400).json({ success: false, - message: 'HTTP直链地址必须是合法的http/https地址,且不能包含查询或片段' + message: 'Access Key Secret不能为空' }); } - // 如果用户已配置FTP且密码为空,使用现有密码 - let actualPassword = ftp_password; - if (!ftp_password && req.user.has_ftp_config && req.user.ftp_password) { - actualPassword = req.user.ftp_password; - } else if (!ftp_password) { - return res.status(400).json({ - success: false, - message: 'FTP密码不能为空' - }); - } - - // 主机校验与防SSRF - const { port: safePort } = await validateSftpDestination(ftp_host, ftp_port); - - // 验证FTP连接 + // 验证OSS连接 try { - const sftp = await connectToSFTP({ ftp_host, ftp_port: safePort, ftp_user, ftp_password: actualPassword }); - await sftp.end(); + const { OssStorageClient } = require('./storage'); + const testUser = { + id: req.user.id, + oss_provider, + oss_region, + oss_access_key_id, + oss_access_key_secret: actualSecret, + oss_bucket, + oss_endpoint + }; + const ossClient = new OssStorageClient(testUser); + await ossClient.connect(); + + // 尝试列出 bucket 内容(验证配置是否正确) + await ossClient.list('/'); + await ossClient.end(); } catch (error) { return res.status(400).json({ success: false, - message: 'SFTP连接失败,请检查配置: ' + error.message + message: 'OSS连接失败,请检查配置: ' + error.message }); } // 更新用户配置 UserDB.update(req.user.id, { - ftp_host, - ftp_port: safePort, - ftp_user, - ftp_password: actualPassword, - http_download_base_url: safeHttpBaseUrl || null, - has_ftp_config: 1 + oss_provider, + oss_region, + oss_access_key_id, + oss_access_key_secret: actualSecret, + oss_bucket, + oss_endpoint: oss_endpoint || null, + has_oss_config: 1 }); res.json({ success: true, - message: 'SFTP配置已更新' + message: 'OSS配置已更新' }); } catch (error) { - console.error('更新配置失败:', error); + console.error('更新OSS配置失败:', error); res.status(500).json({ success: false, message: '更新配置失败: ' + error.message @@ -1938,85 +1886,80 @@ app.post('/api/user/update-ftp', } ); -// 获取SFTP存储空间使用情况 -app.get('/api/user/sftp-usage', authMiddleware, async (req, res) => { - let sftp = null; - +// 获取OSS存储空间使用情况(带缓存) +app.get('/api/user/oss-usage', authMiddleware, async (req, res) => { try { - // 检查用户是否配置了SFTP - if (!req.user.has_ftp_config) { + // 检查用户是否配置了OSS + if (!req.user.has_oss_config) { return res.status(400).json({ success: false, - message: '未配置SFTP服务器' + message: '未配置OSS服务' }); } - // 连接SFTP - sftp = await connectToSFTP(req.user); - - // 递归计算目录大小的函数 - async function calculateDirSize(dirPath) { - let totalSize = 0; - let fileCount = 0; - let dirCount = 0; - - try { - const list = await sftp.list(dirPath); - - for (const item of list) { - // 跳过 . 和 .. 目录 - if (item.name === '.' || item.name === '..') continue; - - const itemPath = dirPath === '/' ? `/${item.name}` : `${dirPath}/${item.name}`; - - if (item.type === 'd') { - // 是目录,递归计算 - dirCount++; - const subResult = await calculateDirSize(itemPath); - totalSize += subResult.totalSize; - fileCount += subResult.fileCount; - dirCount += subResult.dirCount; - } else { - // 是文件,累加大小 - fileCount++; - totalSize += item.size || 0; - } - } - } catch (err) { - // 跳过无法访问的目录 - console.warn(`[SFTP统计] 无法访问目录 ${dirPath}: ${err.message}`); - } - - return { totalSize, fileCount, dirCount }; + // 先检查缓存 + const cached = getOssUsageCache(req.user.id); + if (cached) { + return res.json({ + success: true, + usage: cached, + cached: true // 标识来自缓存 + }); } - // 从根目录开始计算 - const result = await calculateDirSize('/'); + const { OssStorageClient } = require('./storage'); + const ossClient = new OssStorageClient(req.user); + await ossClient.connect(); + + // 递归计算所有对象的大小 + let totalSize = 0; + let fileCount = 0; + let continuationToken = null; + + do { + const { ListObjectsV2Command } = require('@aws-sdk/client-s3'); + const command = new ListObjectsV2Command({ + Bucket: req.user.oss_bucket, + Prefix: `user_${req.user.id}/`, + ContinuationToken: continuationToken + }); + + const response = await ossClient.s3Client.send(command); + + if (response.Contents) { + for (const obj of response.Contents) { + totalSize += obj.Size || 0; + fileCount++; + } + } + + continuationToken = response.NextContinuationToken; + } while (continuationToken); + + await ossClient.end(); + + const usageData = { + totalSize, + totalSizeFormatted: formatFileSize(totalSize), + fileCount, + dirCount: 0 // OSS 没有目录概念 + }; + + // 存入缓存 + setOssUsageCache(req.user.id, usageData); res.json({ success: true, - usage: { - totalSize: result.totalSize, - totalSizeFormatted: formatFileSize(result.totalSize), - fileCount: result.fileCount, - dirCount: result.dirCount - } + usage: usageData, + cached: false }); } catch (error) { - console.error('[SFTP统计] 获取失败:', error); + console.error('[OSS统计] 获取失败:', error); res.status(500).json({ success: false, - message: '获取SFTP空间使用情况失败: ' + error.message + message: '获取OSS空间使用情况失败: ' + error.message }); - } finally { - if (sftp) { - try { - await sftp.end(); - } catch (e) { - // 忽略关闭错误 - } - } } }); @@ -2190,7 +2133,7 @@ app.post('/api/user/update-username', app.post('/api/user/switch-storage', authMiddleware, [ - body('storage_type').isIn(['local', 'sftp']).withMessage('无效的存储类型') + body('storage_type').isIn(['local', 'oss']).withMessage('无效的存储类型') ], async (req, res) => { const errors = validationResult(req); @@ -2212,18 +2155,18 @@ app.post('/api/user/switch-storage', }); } - if (req.user.storage_permission === 'sftp_only' && storage_type !== 'sftp') { + if (req.user.storage_permission === 'oss_only' && storage_type !== 'oss') { return res.status(403).json({ success: false, - message: '您只能使用SFTP存储' + message: '您只能使用OSS存储' }); } - // 检查SFTP配置 - if (storage_type === 'sftp' && !req.user.has_ftp_config) { + // 检查OSS配置 + if (storage_type === 'oss' && !req.user.has_oss_config) { return res.status(400).json({ success: false, - message: '请先配置SFTP服务器' + message: '请先配置OSS服务' }); } @@ -2268,15 +2211,9 @@ app.get('/api/files', authMiddleware, async (req, res) => { const list = await storage.list(dirPath); - const storageType = req.user.current_storage_type || 'sftp'; - const sanitizedHttpBase = sanitizeHttpBaseUrl(req.user.http_download_base_url); + const storageType = req.user.current_storage_type || 'oss'; const formattedList = list.map(item => { - // 构建完整的文件路径用于下载 - const httpDownloadUrl = (storageType === 'sftp' && sanitizedHttpBase && item.type !== 'd') - ? buildHttpDownloadUrl(sanitizedHttpBase, dirPath === '/' ? `/${item.name}` : `${dirPath}/${item.name}`) - : null; - return { name: item.name, displayName: decodeHtmlEntities(item.name || ''), @@ -2285,7 +2222,7 @@ app.get('/api/files', authMiddleware, async (req, res) => { sizeFormatted: formatFileSize(item.size), modifiedAt: new Date(item.modifyTime), isDirectory: item.type === 'd', - httpDownloadUrl: httpDownloadUrl + httpDownloadUrl: null // OSS 使用 API 下载,不需要 httpDownloadUrl }; }); @@ -2300,7 +2237,7 @@ app.get('/api/files', authMiddleware, async (req, res) => { path: dirPath, items: formattedList, storageType: storageType, - storagePermission: req.user.storage_permission || 'sftp_only' + storagePermission: req.user.storage_permission || 'oss_only' }); } catch (error) { console.error('获取文件列表失败:', error); @@ -2338,6 +2275,11 @@ app.post('/api/files/rename', authMiddleware, async (req, res) => { await storage.rename(oldPath, newPath); + // 清除 OSS 使用情况缓存(如果用户使用 OSS) + if (req.user.current_storage_type === 'oss') { + clearOssUsageCache(req.user.id); + } + res.json({ success: true, message: '文件重命名成功' @@ -2353,7 +2295,7 @@ app.post('/api/files/rename', authMiddleware, async (req, res) => { } }); -// 创建文件夹 +// 创建文件夹(支持本地存储和OSS) app.post('/api/files/mkdir', authMiddleware, async (req, res) => { const path = decodeHtmlEntities(req.body.path) || '/'; const folderName = decodeHtmlEntities(req.body.folderName); @@ -2375,14 +2317,6 @@ app.post('/api/files/mkdir', authMiddleware, async (req, res) => { }); } - // 只允许本地存储创建文件夹 - if (req.user.current_storage_type !== 'local') { - return res.status(400).json({ - success: false, - message: '只有本地存储支持创建文件夹' - }); - } - try { const { StorageInterface } = require('./storage'); const storageInterface = new StorageInterface(req.user); @@ -2391,21 +2325,33 @@ app.post('/api/files/mkdir', authMiddleware, async (req, res) => { // 构造文件夹路径 const basePath = path || '/'; const folderPath = basePath === '/' ? `/${folderName}` : `${basePath}/${folderName}`; - const fullPath = storage.getFullPath(folderPath); - // 检查是否已存在 - if (fs.existsSync(fullPath)) { - return res.status(400).json({ - success: false, - message: '文件夹已存在' - }); + // 根据存储类型创建文件夹 + if (req.user.current_storage_type === 'local') { + // 本地存储:使用 fs.mkdirSync + const fullPath = storage.getFullPath(folderPath); + + // 检查是否已存在 + if (fs.existsSync(fullPath)) { + return res.status(400).json({ + success: false, + message: '文件夹已存在' + }); + } + + // 创建文件夹 (不使用recursive,只创建当前层级) + fs.mkdirSync(fullPath, { mode: 0o755 }); + + console.log(`[创建文件夹成功] 本地存储 - 用户${req.user.id}: ${folderPath}`); + } else { + // OSS 存储:使用 storage.mkdir() 创建空对象模拟文件夹 + await storage.mkdir(folderPath); + console.log(`[创建文件夹成功] OSS存储 - 用户${req.user.id}: ${folderPath}`); + + // 清除 OSS 使用情况缓存 + clearOssUsageCache(req.user.id); } - // 创建文件夹 (不使用recursive,只创建当前层级) - fs.mkdirSync(fullPath, { mode: 0o755 }); - - console.log(`[创建文件夹成功] 用户${req.user.id}: ${folderPath}`); - res.json({ success: true, message: '文件夹创建成功' @@ -2421,7 +2367,7 @@ app.post('/api/files/mkdir', authMiddleware, async (req, res) => { } }); -// 获取文件夹详情(大小统计) +// 获取文件夹详情(大小统计) - 支持本地存储和OSS app.post('/api/files/folder-info', authMiddleware, async (req, res) => { const dirPath = decodeHtmlEntities(req.body.path) || '/'; const folderName = decodeHtmlEntities(req.body.folderName); @@ -2434,14 +2380,6 @@ app.post('/api/files/folder-info', authMiddleware, async (req, res) => { }); } - // 只支持本地存储 - if (req.user.current_storage_type !== 'local') { - return res.status(400).json({ - success: false, - message: '只有本地存储支持此功能' - }); - } - try { const { StorageInterface } = require('./storage'); const storageInterface = new StorageInterface(req.user); @@ -2450,62 +2388,110 @@ app.post('/api/files/folder-info', authMiddleware, async (req, res) => { // 构造文件夹路径 const basePath = dirPath || '/'; const folderPath = basePath === '/' ? `/${folderName}` : `${basePath}/${folderName}`; - const fullPath = storage.getFullPath(folderPath); - // 检查是否存在且是文件夹 - if (!fs.existsSync(fullPath)) { - return res.status(404).json({ - success: false, - message: '文件夹不存在' - }); - } + if (req.user.current_storage_type === 'local') { + // 本地存储实现 + const fullPath = storage.getFullPath(folderPath); - const stats = fs.statSync(fullPath); - if (!stats.isDirectory()) { - return res.status(400).json({ - success: false, - message: '指定路径不是文件夹' - }); - } + // 检查是否存在且是文件夹 + if (!fs.existsSync(fullPath)) { + return res.status(404).json({ + success: false, + message: '文件夹不存在' + }); + } - // 计算文件夹大小 - const folderSize = storage.calculateFolderSize(fullPath); + const stats = fs.statSync(fullPath); + if (!stats.isDirectory()) { + return res.status(400).json({ + success: false, + message: '指定路径不是文件夹' + }); + } - // 计算文件数量 - function countFiles(countDirPath) { - let fileCount = 0; - let folderCount = 0; + // 计算文件夹大小 + const folderSize = storage.calculateFolderSize(fullPath); - const items = fs.readdirSync(countDirPath, { withFileTypes: true }); + // 计算文件数量 + function countFiles(countDirPath) { + let fileCount = 0; + let folderCount = 0; - for (const item of items) { - const itemPath = path.join(countDirPath, item.name); + const items = fs.readdirSync(countDirPath, { withFileTypes: true }); - if (item.isDirectory()) { - folderCount++; - const subCounts = countFiles(itemPath); - fileCount += subCounts.fileCount; - folderCount += subCounts.folderCount; - } else { - fileCount++; + for (const item of items) { + const itemPath = path.join(countDirPath, item.name); + + if (item.isDirectory()) { + folderCount++; + const subCounts = countFiles(itemPath); + fileCount += subCounts.fileCount; + folderCount += subCounts.folderCount; + } else { + fileCount++; + } } + + return { fileCount, folderCount }; } - return { fileCount, folderCount }; + const counts = countFiles(fullPath); + + res.json({ + success: true, + data: { + name: folderName, + path: folderPath, + size: folderSize, + fileCount: counts.fileCount, + folderCount: counts.folderCount + } + }); + } else { + // OSS 存储实现 + const { ListObjectsV2Command } = require('@aws-sdk/client-s3'); + const folderKey = `user_${req.user.id}${folderPath}`; + // 确保前缀以斜杠结尾 + const prefix = folderKey.endsWith('/') ? folderKey : `${folderKey}/`; + + let totalSize = 0; + let fileCount = 0; + let continuationToken = null; + + do { + const command = new ListObjectsV2Command({ + Bucket: req.user.oss_bucket, + Prefix: prefix, + ContinuationToken: continuationToken + }); + + const response = await storage.s3Client.send(command); + + if (response.Contents) { + for (const obj of response.Contents) { + // 跳过文件夹标记对象(以斜杠结尾且大小为0) + if (obj.Key.endsWith('/') && obj.Size === 0) { + continue; + } + totalSize += obj.Size || 0; + fileCount++; + } + } + + continuationToken = response.NextContinuationToken; + } while (continuationToken); + + res.json({ + success: true, + data: { + name: folderName, + path: folderPath, + size: totalSize, + fileCount: fileCount, + folderCount: 0 // OSS 没有真正的文件夹概念 + } + }); } - - const counts = countFiles(fullPath); - - res.json({ - success: true, - data: { - name: folderName, - path: folderPath, - size: folderSize, - fileCount: counts.fileCount, - folderCount: counts.folderCount - } - }); } catch (error) { console.error('[获取文件夹详情失败]', error); res.status(500).json({ @@ -2574,6 +2560,11 @@ app.post('/api/files/delete', authMiddleware, async (req, res) => { throw err; } + // 清除 OSS 使用情况缓存(如果用户使用 OSS) + if (req.user.current_storage_type === 'oss') { + clearOssUsageCache(req.user.id); + } + res.json({ success: true, message: '删除成功' @@ -2589,7 +2580,194 @@ app.post('/api/files/delete', authMiddleware, async (req, res) => { } }); -// 上传文件(添加速率限制) +// ========== OSS 直连相关接口(Presigned URL)========== + +// 生成 OSS 上传签名 URL(用户直连 OSS 上传,不经过后端) +app.get('/api/files/upload-signature', authMiddleware, async (req, res) => { + const filename = req.query.filename; + const contentType = req.query.contentType || 'application/octet-stream'; + + if (!filename) { + return res.status(400).json({ + success: false, + message: '缺少文件名参数' + }); + } + + // 检查用户是否配置了 OSS + if (!req.user.has_oss_config) { + return res.status(400).json({ + success: false, + message: '未配置 OSS 服务' + }); + } + + try { + const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3'); + const { getSignedUrl } = require('@aws-sdk/request-presigner'); + + // 构建 S3 客户端 + const client = new S3Client(buildS3Config(req.user)); + + // 构建对象 Key + const objectKey = `user_${req.user.id}/${Date.now()}_${sanitizeFilename(filename)}`; + + // 创建 PutObject 命令 + const command = new PutObjectCommand({ + Bucket: req.user.oss_bucket, + Key: objectKey, + ContentType: contentType + }); + + // 生成签名 URL(15分钟有效) + const signedUrl = await getSignedUrl(client, command, { expiresIn: 900 }); + + res.json({ + success: true, + uploadUrl: signedUrl, + objectKey: objectKey, + expiresIn: 900 + }); + } catch (error) { + console.error('[OSS签名] 生成上传签名失败:', error); + res.status(500).json({ + success: false, + message: '生成上传签名失败: ' + error.message + }); + } +}); + +// OSS 上传完成通知(用于更新缓存和数据库) +app.post('/api/files/upload-complete', authMiddleware, async (req, res) => { + const { objectKey, size, path } = req.body; + + if (!objectKey) { + return res.status(400).json({ + success: false, + message: '缺少对象Key参数' + }); + } + + try { + // 清除 OSS 使用情况缓存 + clearOssUsageCache(req.user.id); + + res.json({ + success: true, + message: '上传完成已记录' + }); + } catch (error) { + console.error('[OSS上传] 记录上传完成失败:', error); + res.status(500).json({ + success: false, + message: '记录上传完成失败: ' + error.message + }); + } +}); + +// 生成 OSS 下载签名 URL(用户直连 OSS 下载,不经过后端) +app.get('/api/files/download-url', authMiddleware, async (req, res) => { + const filePath = req.query.path; + + if (!filePath) { + return res.status(400).json({ + success: false, + message: '缺少文件路径参数' + }); + } + + // 检查用户是否配置了 OSS + if (!req.user.has_oss_config) { + return res.status(400).json({ + success: false, + message: '未配置 OSS 服务' + }); + } + + try { + const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3'); + const { getSignedUrl } = require('@aws-sdk/request-presigner'); + + // 构建 S3 客户端 + const client = new S3Client(buildS3Config(req.user)); + + // 构建对象 Key + const objectKey = `user_${req.user.id}${filePath}`; + + // 创建 GetObject 命令 + const command = new GetObjectCommand({ + Bucket: req.user.oss_bucket, + Key: objectKey, + ResponseContentDisposition: `attachment; filename="${encodeURIComponent(filePath.split('/').pop())}"` + }); + + // 生成签名 URL(1小时有效) + const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 }); + + res.json({ + success: true, + downloadUrl: signedUrl, + expiresIn: 3600 + }); + } catch (error) { + console.error('[OSS签名] 生成下载签名失败:', error); + res.status(500).json({ + success: false, + message: '生成下载签名失败: ' + error.message + }); + } +}); + +// 辅助函数:构建 S3 配置(与 OssStorageClient.buildConfig 保持一致) +function buildS3Config(user) { + const config = { + region: user.oss_region || 'us-east-1', + credentials: { + accessKeyId: user.oss_access_key_id, + secretAccessKey: user.oss_access_key_secret + } + }; + + // 阿里云 OSS + if (user.oss_provider === 'aliyun') { + config.region = user.oss_region || 'oss-cn-hangzhou'; + if (user.oss_endpoint) { + config.endpoint = user.oss_endpoint; + } else { + // 默认 endpoint 格式:https://oss-{region}.aliyuncs.com + config.endpoint = `https://oss-${config.region.replace('oss-', '')}.aliyuncs.com`; + } + } + // 腾讯云 COS + else if (user.oss_provider === 'tencent') { + config.region = user.oss_region || 'ap-guangzhou'; + if (user.oss_endpoint) { + config.endpoint = user.oss_endpoint; + } else { + // 默认 endpoint 格式:https://cos.{region}.myqcloud.com + config.endpoint = `https://cos.${config.region}.myqcloud.com`; + } + } + // AWS S3 或其他兼容服务 + else { + if (user.oss_endpoint) { + config.endpoint = user.oss_endpoint; + } + // AWS 使用默认 endpoint,无需额外配置 + } + + return config; +} + +// 辅助函数:清理文件名 +function sanitizeFilename(filename) { + // 移除或替换危险字符 + return filename.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_'); +} + +// ========== 本地存储上传接口(保留用于本地存储模式)========== + +// 上传文件(添加速率限制)- 仅用于本地存储 app.post('/api/upload', authMiddleware, upload.single('file'), async (req, res) => { // 速率限制检查 const rateLimitKey = `upload:${req.user.id}`; @@ -2677,6 +2855,11 @@ app.post('/api/upload', authMiddleware, upload.single('file'), async (req, res) await storage.put(req.file.path, remoteFilePath); console.log(`[上传] 文件上传成功: ${remoteFilePath}`); + // 清除 OSS 使用情况缓存(如果用户使用 OSS) + if (req.user.current_storage_type === 'oss') { + clearOssUsageCache(req.user.id); + } + // 删除本地临时文件 safeDeleteFile(req.file.path); @@ -2736,7 +2919,7 @@ app.get('/api/files/download', authMiddleware, async (req, res) => { res.setHeader('Content-Disposition', 'attachment; filename="' + encodeURIComponent(fileName) + '"; filename*=UTF-8\'\'' + encodeURIComponent(fileName)); // 创建文件流并传输(流式下载,服务器不保存临时文件) - const stream = storage.createReadStream(filePath); + const stream = await storage.createReadStream(filePath); stream.on('error', (error) => { console.error('文件流错误:', error); @@ -2947,7 +3130,7 @@ app.get('/api/upload/download-tool', authMiddleware, async (req, res) => { } }); -// 通过API密钥获取SFTP配置(供Python工具调用) +// 通过API密钥获取OSS配置(供上传工具调用) // 添加速率限制防止暴力枚举 app.post('/api/upload/get-config', async (req, res) => { // 获取客户端IP用于速率限制 @@ -2998,30 +3181,25 @@ app.post('/api/upload/get-config', async (req, res) => { }); } - if (!user.has_ftp_config) { - return res.status(400).json({ - success: false, - message: '用户未配置SFTP服务器' - }); - } + // 确定用户的存储类型 + const storageType = user.current_storage_type || 'local'; - // 返回SFTP配置(注意:密码通过此API返回给上传工具使用) - // 上传工具需要密码才能连接SFTP,这是设计上的需要 - // 安全措施:1. 速率限制防止暴力枚举 2. API密钥是32位随机字符串 + // 返回配置信息(新版上传工具 v3.0 使用服务器 API 上传,无需返回 OSS 凭证) res.json({ success: true, - sftp_config: { - host: user.ftp_host, - port: user.ftp_port, - username: user.ftp_user, - password: user.ftp_password + config: { + storage_type: storageType, + username: user.username, + // OSS 配置(仅用于显示) + oss_provider: user.oss_provider, + oss_bucket: user.oss_bucket } }); } catch (error) { - console.error('获取SFTP配置失败:', error); + console.error('获取OSS配置失败:', error); res.status(500).json({ success: false, - message: '获取SFTP配置失败: ' + error.message + message: '获取OSS配置失败: ' + error.message }); } }); @@ -3050,7 +3228,7 @@ app.post('/api/share/create', authMiddleware, (req, res) => { // 更新分享的存储类型 const { db } = require('./database'); db.prepare('UPDATE shares SET storage_type = ? WHERE id = ?') - .run(req.user.current_storage_type || 'sftp', result.id); + .run(req.user.current_storage_type || 'oss', result.id); const shareUrl = `${getSecureBaseUrl(req)}/s/${result.share_code}`; @@ -3187,7 +3365,7 @@ app.get('/api/share/:code/theme', (req, res) => { } }); -// 访问分享链接 - 验证密码(支持本地存储和SFTP) +// 访问分享链接 - 验证密码(支持本地存储和OSS) app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) => { const { code } = req.params; const { password } = req.body; @@ -3251,8 +3429,6 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) = // 增加查看次数 ShareDB.incrementViewCount(code); - const sanitizedShareHttpBase = sanitizeHttpBaseUrl(share.http_download_base_url); - // 构建返回数据 const responseData = { success: true, @@ -3284,7 +3460,7 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) = if (!shareOwner) { throw new Error('分享者不存在'); } - const storageType = shareOwner.current_storage_type || 'sftp'; + const storageType = shareOwner.current_storage_type || 'oss'; console.log(`[缓存未命中] 分享码: ${code},存储类型: ${storageType}`); // 使用统一存储接口 @@ -3307,17 +3483,11 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) = } if (fileInfo) { - const normalizedFilePath = filePath.startsWith('/') ? filePath : `/${filePath}`; - // SFTP存储才提供HTTP下载URL,本地存储使用API下载 - const httpDownloadUrl = (storageType === 'sftp') - ? buildHttpDownloadUrl(sanitizedShareHttpBase, normalizedFilePath) - : null; - const fileData = { name: fileName, type: 'file', isDirectory: false, - httpDownloadUrl: httpDownloadUrl, + httpDownloadUrl: null, // OSS 使用 API 下载 size: fileInfo.size, sizeFormatted: formatFileSize(fileInfo.size), modifiedAt: new Date(fileInfo.modifyTime) @@ -3337,17 +3507,11 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) = throw storageError; } // 存储失败时仍返回基本信息,只是没有大小 - const normalizedFilePath = filePath.startsWith('/') ? filePath : `/${filePath}`; - const storageType = share.storage_type || 'sftp'; - const httpDownloadUrl = (storageType === 'sftp') - ? buildHttpDownloadUrl(sanitizedShareHttpBase, normalizedFilePath) - : null; - responseData.file = { name: fileName, type: 'file', isDirectory: false, - httpDownloadUrl: httpDownloadUrl, + httpDownloadUrl: null, size: 0, sizeFormatted: '-' }; @@ -3367,7 +3531,7 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) = } }); -// 获取分享的文件列表(支持本地存储和SFTP) +// 获取分享的文件列表(支持本地存储和OSS) app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => { const { code } = req.params; const { password, path: subPath } = req.body; @@ -3445,7 +3609,7 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => // 使用统一存储接口,根据分享的storage_type选择存储后端 const { StorageInterface } = require('./storage'); - const storageType = share.storage_type || 'sftp'; + const storageType = share.storage_type || 'oss'; console.log(`[分享列表] 存储类型: ${storageType}, 分享路径: ${share.share_path}`); // 临时构造用户对象以使用存储接口 @@ -3457,7 +3621,6 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => const storageInterface = new StorageInterface(userForStorage); storage = await storageInterface.connect(); - const sanitizedShareHttpBase = sanitizeHttpBaseUrl(share.http_download_base_url); let formattedList = []; // 如果是单文件分享 @@ -3477,14 +3640,6 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => const fileInfo = list.find(item => item.name === fileName); if (fileInfo) { - // 确保文件路径以斜杠开头 - const normalizedFilePath = filePath.startsWith('/') ? filePath : `/${filePath}`; - - // SFTP存储才提供HTTP下载URL,本地存储使用API下载 - const httpDownloadUrl = (storageType === 'sftp') - ? buildHttpDownloadUrl(sanitizedShareHttpBase, normalizedFilePath) - : null; - formattedList = [{ name: fileInfo.name, type: 'file', @@ -3492,7 +3647,7 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => sizeFormatted: formatFileSize(fileInfo.size), modifiedAt: new Date(fileInfo.modifyTime), isDirectory: false, - httpDownloadUrl: httpDownloadUrl + httpDownloadUrl: null // OSS 使用 API 下载 }]; } } @@ -3501,14 +3656,6 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => const list = await storage.list(requestedPath); formattedList = list.map(item => { - // 构建完整的文件路径用于下载 - let httpDownloadUrl = null; - if (storageType === 'sftp' && sanitizedShareHttpBase && item.type !== 'd') { - const normalizedPath = requestedPath.startsWith('/') ? requestedPath : `/${requestedPath}`; - const filePath = normalizedPath === '/' ? `/${item.name}` : `${normalizedPath}/${item.name}`; - httpDownloadUrl = buildHttpDownloadUrl(sanitizedShareHttpBase, filePath); - } - return { name: item.name, type: item.type === 'd' ? 'directory' : 'file', @@ -3516,7 +3663,7 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => sizeFormatted: formatFileSize(item.size), modifiedAt: new Date(item.modifyTime), isDirectory: item.type === 'd', - httpDownloadUrl: httpDownloadUrl + httpDownloadUrl: null // OSS 使用 API 下载 }; }); @@ -3573,7 +3720,103 @@ app.post('/api/share/:code/download', (req, res) => { } }); -// 分享文件下载(支持本地存储和SFTP,公开API,需要分享码和密码验证) +// 生成分享文件下载签名 URL(OSS 直连下载,公开 API) +app.get('/api/share/:code/download-url', async (req, res) => { + const { code } = req.params; + const { path: filePath, password } = req.query; + + if (!filePath) { + return res.status(400).json({ + success: false, + message: '缺少文件路径参数' + }); + } + + try { + const share = ShareDB.findByCode(code); + + if (!share) { + return res.status(404).json({ + success: false, + message: '分享不存在' + }); + } + + // 验证密码(如果需要) + if (share.share_password) { + if (!password || !ShareDB.verifyPassword(password, share.share_password)) { + return res.status(401).json({ + success: false, + message: '密码错误或未提供密码' + }); + } + } + + // 安全验证:检查请求路径是否在分享范围内 + if (!isPathWithinShare(filePath, share)) { + return res.status(403).json({ + success: false, + message: '无权访问该文件' + }); + } + + // 获取分享者的用户信息 + const shareOwner = UserDB.findById(share.user_id); + if (!shareOwner) { + return res.status(404).json({ + success: false, + message: '分享者不存在' + }); + } + + // 检查是否使用 OSS + if (!shareOwner.has_oss_config || share.storage_type !== 'oss') { + // 本地存储模式:返回后端下载 URL + return res.json({ + success: true, + downloadUrl: `${req.protocol}://${req.get('host')}/api/share/${code}/download-file?path=${encodeURIComponent(filePath)}${password ? '&password=' + encodeURIComponent(password) : ''}`, + direct: false + }); + } + + // OSS 模式:生成签名 URL + const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3'); + const { getSignedUrl } = require('@aws-sdk/request-presigner'); + + // 构建 S3 客户端 + const client = new S3Client(buildS3Config(shareOwner)); + + // 构建对象 Key + const objectKey = `user_${shareOwner.id}${filePath}`; + + // 创建 GetObject 命令 + const command = new GetObjectCommand({ + Bucket: shareOwner.oss_bucket, + Key: objectKey, + ResponseContentDisposition: `attachment; filename="${encodeURIComponent(filePath.split('/').pop())}"` + }); + + // 生成签名 URL(1小时有效) + const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 }); + + res.json({ + success: true, + downloadUrl: signedUrl, + direct: true, + expiresIn: 3600 + }); + + } catch (error) { + console.error('[分享签名] 生成下载签名失败:', error); + res.status(500).json({ + success: false, + message: '生成下载签名失败: ' + error.message + }); + } +}); + +// 分享文件下载(支持本地存储,公开API,需要分享码和密码验证) +// 注意:OSS 模式请使用 /api/share/:code/download-url 获取直连下载链接 app.get('/api/share/:code/download-file', shareRateLimitMiddleware, async (req, res) => { const { code } = req.params; const { path: filePath, password } = req.query; @@ -3635,7 +3878,7 @@ app.get('/api/share/:code/download-file', shareRateLimitMiddleware, async (req, // 使用统一存储接口,根据分享的storage_type选择存储后端 const { StorageInterface } = require('./storage'); - const storageType = shareOwner.current_storage_type || 'sftp'; + const storageType = shareOwner.current_storage_type || 'oss'; console.log(`[分享下载] 存储类型: ${storageType} (分享者当前), 文件路径: ${filePath}`); // 临时构造用户对象以使用存储接口 @@ -3664,7 +3907,7 @@ app.get('/api/share/:code/download-file', shareRateLimitMiddleware, async (req, res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"; filename*=UTF-8''${encodeURIComponent(fileName)}`); // 创建文件流并传输(流式下载,服务器不保存临时文件) - const stream = storage.createReadStream(filePath); + const stream = await storage.createReadStream(filePath); stream.on('error', (error) => { console.error('文件流错误:', error); @@ -4081,7 +4324,7 @@ app.get('/api/admin/storage-stats', authMiddleware, adminMiddleware, async (req, users.forEach(user => { // 只统计使用本地存储的用户(local_only 或 user_choice) - const storagePermission = user.storage_permission || 'sftp_only'; + const storagePermission = user.storage_permission || 'oss_only'; if (storagePermission === 'local_only' || storagePermission === 'user_choice') { totalUserQuotas += user.local_storage_quota || 0; totalUserUsed += user.local_storage_used || 0; @@ -4116,11 +4359,11 @@ app.get('/api/admin/users', authMiddleware, adminMiddleware, (req, res) => { is_active: u.is_active, is_verified: u.is_verified, is_banned: u.is_banned, - has_ftp_config: u.has_ftp_config, + has_oss_config: u.has_oss_config, created_at: u.created_at, // 新增:存储相关字段 - storage_permission: u.storage_permission || 'sftp_only', - current_storage_type: u.current_storage_type || 'sftp', + storage_permission: u.storage_permission || 'oss_only', + current_storage_type: u.current_storage_type || 'oss', local_storage_quota: u.local_storage_quota || 1073741824, local_storage_used: u.local_storage_used || 0 })) @@ -4270,7 +4513,7 @@ app.delete('/api/admin/users/:id', authMiddleware, adminMiddleware, async (req, }; // 1. 删除本地存储文件(如果用户使用了本地存储) - const storagePermission = user.storage_permission || 'sftp_only'; + const storagePermission = user.storage_permission || 'oss_only'; if (storagePermission === 'local_only' || storagePermission === 'user_choice') { const storageRoot = process.env.STORAGE_ROOT || path.join(__dirname, 'storage'); const userStorageDir = path.join(storageRoot, `user_${id}`); @@ -4293,10 +4536,10 @@ app.delete('/api/admin/users/:id', authMiddleware, adminMiddleware, async (req, } } - // 2. SFTP存储文件 - 只记录警告,不实际删除(安全考虑) - if (user.has_ftp_config && (storagePermission === 'sftp_only' || storagePermission === 'user_choice')) { + // 2. OSS存储文件 - 只记录警告,不实际删除(安全考虑) + if (user.has_oss_config && (storagePermission === 'oss_only' || storagePermission === 'user_choice')) { deletionLog.warnings.push( - `用户配置了SFTP存储 (${user.ftp_host}:${user.ftp_port}),SFTP文件未自动删除,请手动处理` + `用户配置了OSS存储 (${user.oss_provider}:${user.oss_bucket}),OSS文件未自动删除,请手动处理` ); } @@ -4376,7 +4619,7 @@ app.post('/api/admin/users/:id/storage-permission', authMiddleware, adminMiddleware, [ - body('storage_permission').isIn(['local_only', 'sftp_only', 'user_choice']).withMessage('无效的存储权限') + body('storage_permission').isIn(['local_only', 'oss_only', 'user_choice']).withMessage('无效的存储权限') ], (req, res) => { const errors = validationResult(req); @@ -4409,10 +4652,10 @@ app.post('/api/admin/users/:id/storage-permission', if (storage_permission === 'local_only') { updates.current_storage_type = 'local'; - } else if (storage_permission === 'sftp_only') { - // 只有配置了SFTP才切换到SFTP - if (user.has_ftp_config) { - updates.current_storage_type = 'sftp'; + } else if (storage_permission === 'oss_only') { + // 只有配置了OSS才切换到OSS + if (user.has_oss_config) { + updates.current_storage_type = 'oss'; } } // user_choice 不自动切换,保持用户当前选择 @@ -4439,7 +4682,7 @@ app.post('/api/admin/users/:id/storage-permission', app.get('/api/admin/users/:id/files', authMiddleware, adminMiddleware, async (req, res) => { const { id } = req.params; const dirPath = req.query.path || '/'; - let sftp; + let ossClient; try { const user = UserDB.findById(id); @@ -4451,15 +4694,17 @@ app.get('/api/admin/users/:id/files', authMiddleware, adminMiddleware, async (re }); } - if (!user.has_ftp_config) { + if (!user.has_oss_config) { return res.status(400).json({ success: false, - message: '该用户未配置SFTP服务器' + message: '该用户未配置OSS服务' }); } - sftp = await connectToSFTP(user); - const list = await sftp.list(dirPath); + const { OssStorageClient } = require('./storage'); + ossClient = new OssStorageClient(user); + await ossClient.connect(); + const list = await ossClient.list(dirPath); const formattedList = list.map(item => ({ name: item.name, @@ -4489,7 +4734,7 @@ app.get('/api/admin/users/:id/files', authMiddleware, adminMiddleware, async (re message: '获取文件列表失败: ' + error.message }); } finally { - if (sftp) await sftp.end(); + if (ossClient) await ossClient.end(); } }); diff --git a/backend/storage.js b/backend/storage.js index a9a5a84..5aa3418 100644 --- a/backend/storage.js +++ b/backend/storage.js @@ -1,4 +1,5 @@ -const SftpClient = require('ssh2-sftp-client'); +const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectsCommand, ListObjectsV2Command, HeadObjectCommand } = require('@aws-sdk/client-s3'); +const { Upload } = require('@aws-sdk/lib-storage'); const fs = require('fs'); const path = require('path'); const { UserDB } = require('./database'); @@ -12,7 +13,7 @@ const { UserDB } = require('./database'); class StorageInterface { constructor(user) { this.user = user; - this.type = user.current_storage_type || 'sftp'; + this.type = user.current_storage_type || 'oss'; } /** @@ -24,7 +25,7 @@ class StorageInterface { await client.init(); return client; } else { - const client = new SftpStorageClient(this.user); + const client = new OssStorageClient(this.user); await client.connect(); return client; } @@ -321,96 +322,513 @@ class LocalStorageClient { } } -// ===== SFTP存储客户端 ===== +// ===== OSS存储客户端 ===== -class SftpStorageClient { +/** + * OSS 存储客户端(基于 S3 协议) + * 支持阿里云 OSS、腾讯云 COS、AWS S3 + */ +class OssStorageClient { constructor(user) { this.user = user; - this.sftp = new SftpClient(); + this.s3Client = null; + this.prefix = `user_${user.id}/`; // 用户隔离前缀 } /** - * 连接SFTP服务器 + * 验证 OSS 配置是否完整 + * @throws {Error} 配置不完整时抛出错误 + */ + validateConfig() { + const { oss_provider, oss_access_key_id, oss_access_key_secret, oss_bucket } = this.user; + + if (!oss_provider || !['aliyun', 'tencent', 'aws'].includes(oss_provider)) { + throw new Error('无效的 OSS 服务商,必须是 aliyun、tencent 或 aws'); + } + if (!oss_access_key_id || oss_access_key_id.trim() === '') { + throw new Error('OSS Access Key ID 不能为空'); + } + if (!oss_access_key_secret || oss_access_key_secret.trim() === '') { + throw new Error('OSS Access Key Secret 不能为空'); + } + if (!oss_bucket || oss_bucket.trim() === '') { + throw new Error('OSS 存储桶名称不能为空'); + } + } + + /** + * 根据服务商构建 S3 配置 + */ + buildConfig() { + // 先验证配置 + this.validateConfig(); + + const { oss_provider, oss_region, oss_access_key_id, oss_access_key_secret, oss_endpoint } = this.user; + + // AWS S3 默认配置 + let config = { + region: oss_region || 'us-east-1', + credentials: { + accessKeyId: oss_access_key_id, + secretAccessKey: oss_access_key_secret + }, + // 设置超时时间 + requestHandler: { + requestTimeout: 30000, // 30秒 + httpsAgent: undefined // 可后续添加 keep-alive agent + } + }; + + // 阿里云 OSS + if (oss_provider === 'aliyun') { + config.region = oss_region || 'oss-cn-hangzhou'; + if (!oss_endpoint) { + // 默认 endpoint 格式:https://oss-{region}.aliyuncs.com + config.endpoint = `https://oss-${config.region.replace('oss-', '')}.aliyuncs.com`; + } else { + config.endpoint = oss_endpoint; + } + } + // 腾讯云 COS + else if (oss_provider === 'tencent') { + config.region = oss_region || 'ap-guangzhou'; + if (!oss_endpoint) { + // 默认 endpoint 格式:https://cos.{region}.myqcloud.com + config.endpoint = `https://cos.${config.region}.myqcloud.com`; + } else { + config.endpoint = oss_endpoint; + } + } + // AWS S3 或其他兼容服务 + else { + if (oss_endpoint) { + config.endpoint = oss_endpoint; + } + // AWS 使用默认 endpoint,无需额外配置 + } + + return config; + } + + /** + * 连接 OSS 服务(初始化 S3 客户端) */ async connect() { - await this.sftp.connect({ - host: this.user.ftp_host, - port: this.user.ftp_port || 22, - username: this.user.ftp_user, - password: this.user.ftp_password - }); - return this; + try { + const config = this.buildConfig(); + this.s3Client = new S3Client(config); + console.log(`[OSS存储] 已连接: ${this.user.oss_provider}, bucket: ${this.user.oss_bucket}`); + return this; + } catch (error) { + console.error(`[OSS存储] 连接失败:`, error.message); + throw new Error(`OSS 连接失败: ${error.message}`); + } + } + + /** + * 获取对象的完整 Key(带用户前缀) + */ + getObjectKey(relativePath) { + // 规范化路径 + let normalized = path.normalize(relativePath || '').replace(/^(\.\.[\/\\])+/, ''); + + // 移除开头的斜杠 + if (normalized.startsWith('/') || normalized.startsWith('\\')) { + normalized = normalized.substring(1); + } + + // 空路径表示根目录 + if (normalized === '' || normalized === '.') { + normalized = ''; + } + + // 拼接用户前缀 + return normalized ? this.prefix + normalized : this.prefix; } /** * 列出目录内容 */ async list(dirPath) { - return await this.sftp.list(dirPath); + try { + const prefix = this.getObjectKey(dirPath); + const bucket = this.user.oss_bucket; + + const command = new ListObjectsV2Command({ + Bucket: bucket, + Prefix: prefix, + Delimiter: '/', // 使用分隔符模拟目录结构 + MaxKeys: 1000 + }); + + const response = await this.s3Client.send(command); + const items = []; + + // 处理"子目录"(CommonPrefixes) + if (response.CommonPrefixes) { + for (const prefixObj of response.CommonPrefixes) { + const dirName = prefixObj.Prefix.substring(prefix.length).replace(/\/$/, ''); + if (dirName) { + items.push({ + name: dirName, + type: 'd', + size: 0, + modifyTime: Date.now() + }); + } + } + } + + // 处理文件(Contents) + if (response.Contents) { + for (const obj of response.Contents) { + const key = obj.Key; + // 跳过目录标记本身 + if (key === prefix || key.endsWith('/')) { + continue; + } + const fileName = key.substring(prefix.length); + if (fileName) { + items.push({ + name: fileName, + type: '-', + size: obj.Size || 0, + modifyTime: obj.LastModified ? obj.LastModified.getTime() : Date.now() + }); + } + } + } + + return items; + } catch (error) { + console.error(`[OSS存储] 列出目录失败: ${dirPath}`, error.message); + + // 判断错误类型并给出友好的错误信息 + if (error.name === 'NoSuchBucket') { + throw new Error('OSS 存储桶不存在,请检查配置'); + } else if (error.name === 'AccessDenied') { + throw new Error('OSS 访问被拒绝,请检查权限配置'); + } else if (error.name === 'InvalidAccessKeyId') { + throw new Error('OSS Access Key 无效,请重新配置'); + } + throw new Error(`列出目录失败: ${error.message}`); + } } /** - * 上传文件 + * 上传文件(支持分片上传) */ async put(localPath, remotePath) { - // 使用临时文件+重命名模式(与upload_tool保持一致) - const tempRemotePath = `${remotePath}.uploading_${Date.now()}`; + let fileStream = null; - // 第一步:上传到临时文件 - await this.sftp.put(localPath, tempRemotePath); - - // 第二步:检查目标文件是否存在,如果存在先删除 try { - await this.sftp.stat(remotePath); - await this.sftp.delete(remotePath); - } catch (err) { - // 文件不存在,无需删除 - } + const key = this.getObjectKey(remotePath); + const bucket = this.user.oss_bucket; + const fileSize = fs.statSync(localPath).size; - // 第三步:重命名临时文件为目标文件 - await this.sftp.rename(tempRemotePath, remotePath); + // 创建文件读取流 + fileStream = fs.createReadStream(localPath); + + // 使用 AWS SDK 的 Upload 类处理分片上传 + const upload = new Upload({ + client: this.s3Client, + params: { + Bucket: bucket, + Key: key, + Body: fileStream + }, + queueSize: 3, // 并发分片数 + partSize: 5 * 1024 * 1024 // 5MB 分片 + }); + + // 监听上传进度(可选) + upload.on('httpUploadProgress', (progress) => { + if (progress && progress.loaded && progress.total) { + const percent = Math.round((progress.loaded / progress.total) * 100); + // 只在较大文件时打印进度(避免日志过多) + if (progress.total > 10 * 1024 * 1024 || percent % 20 === 0) { + console.log(`[OSS存储] 上传进度: ${percent}% (${key})`); + } + } + }); + + await upload.done(); + console.log(`[OSS存储] 上传成功: ${key} (${this.formatSize(fileSize)})`); + + // 上传成功后,手动关闭流 + if (fileStream && !fileStream.destroyed) { + fileStream.destroy(); + } + } catch (error) { + // 确保流被关闭,防止泄漏 + if (fileStream && !fileStream.destroyed) { + fileStream.destroy(); + } + + console.error(`[OSS存储] 上传失败: ${remotePath}`, error.message); + + // 判断错误类型并给出友好的错误信息 + if (error.name === 'NoSuchBucket') { + throw new Error('OSS 存储桶不存在,请检查配置'); + } else if (error.name === 'AccessDenied') { + throw new Error('OSS 访问被拒绝,请检查权限配置'); + } else if (error.name === 'EntityTooLarge') { + throw new Error('文件过大,超过了 OSS 允许的最大大小'); + } + throw new Error(`文件上传失败: ${error.message}`); + } } /** - * 删除文件 + * 删除文件或文件夹 */ async delete(filePath) { - return await this.sftp.delete(filePath); + try { + const key = this.getObjectKey(filePath); + const bucket = this.user.oss_bucket; + + // 检查是文件还是目录(忽略不存在的文件) + let statResult; + try { + statResult = await this.stat(filePath); + } catch (statError) { + if (statError.message && statError.message.includes('不存在')) { + console.warn(`[OSS存储] 文件不存在,跳过删除: ${key}`); + return; // 文件不存在,直接返回 + } + throw statError; // 其他错误继续抛出 + } + + if (statResult.isDirectory) { + // 删除目录:列出所有对象并批量删除 + const listCommand = new ListObjectsV2Command({ + Bucket: bucket, + Prefix: key + }); + + const listResponse = await this.s3Client.send(listCommand); + + if (listResponse.Contents && listResponse.Contents.length > 0) { + // 分批删除(AWS S3 单次最多删除 1000 个对象) + const deleteCommand = new DeleteObjectsCommand({ + Bucket: bucket, + Delete: { + Objects: listResponse.Contents.map(obj => ({ Key: obj.Key })), + Quiet: false + } + }); + + const deleteResult = await this.s3Client.send(deleteCommand); + + // 检查删除结果 + if (deleteResult.Errors && deleteResult.Errors.length > 0) { + console.warn(`[OSS存储] 部分对象删除失败:`, deleteResult.Errors); + } + + console.log(`[OSS存储] 删除目录: ${key} (${listResponse.Contents.length} 个对象)`); + } + } else { + // 删除单个文件 + const command = new DeleteObjectsCommand({ + Bucket: bucket, + Delete: { + Objects: [{ Key: key }], + Quiet: false + } + }); + + await this.s3Client.send(command); + console.log(`[OSS存储] 删除文件: ${key}`); + } + } catch (error) { + console.error(`[OSS存储] 删除失败: ${filePath}`, error.message); + + // 判断错误类型并给出友好的错误信息 + if (error.name === 'NoSuchBucket') { + throw new Error('OSS 存储桶不存在,请检查配置'); + } else if (error.name === 'AccessDenied') { + throw new Error('OSS 访问被拒绝,请检查权限配置'); + } + throw new Error(`删除文件失败: ${error.message}`); + } } /** - * 重命名文件 + * 重命名文件(OSS 不支持直接重命名,需要复制后删除) */ async rename(oldPath, newPath) { - return await this.sftp.rename(oldPath, newPath); + try { + const oldKey = this.getObjectKey(oldPath); + const newKey = this.getObjectKey(newPath); + const bucket = this.user.oss_bucket; + + // 先复制 + const copyCommand = new PutObjectCommand({ + Bucket: bucket, + Key: newKey, + CopySource: `${bucket}/${oldKey}` + }); + + await this.s3Client.send(copyCommand); + + // 再删除原文件 + await this.delete(oldPath); + + console.log(`[OSS存储] 重命名: ${oldKey} -> ${newKey}`); + } catch (error) { + console.error(`[OSS存储] 重命名失败: ${oldPath} -> ${newPath}`, error.message); + + // 判断错误类型并给出友好的错误信息 + if (error.name === 'NoSuchBucket') { + throw new Error('OSS 存储桶不存在,请检查配置'); + } else if (error.name === 'AccessDenied') { + throw new Error('OSS 访问被拒绝,请检查权限配置'); + } + throw new Error(`重命名文件失败: ${error.message}`); + } } /** * 获取文件信息 */ async stat(filePath) { - return await this.sftp.stat(filePath); + const key = this.getObjectKey(filePath); + const bucket = this.user.oss_bucket; + + try { + const command = new HeadObjectCommand({ + Bucket: bucket, + Key: key + }); + + const response = await this.s3Client.send(command); + return { + size: response.ContentLength || 0, + modifyTime: response.LastModified ? response.LastModified.getTime() : Date.now(), + isDirectory: false + }; + } catch (error) { + if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) { + // 可能是目录,尝试列出前缀 + const listCommand = new ListObjectsV2Command({ + Bucket: bucket, + Prefix: key.endsWith('/') ? key : key + '/', + MaxKeys: 1 + }); + + try { + const listResponse = await this.s3Client.send(listCommand); + if (listResponse.Contents && listResponse.Contents.length > 0) { + return { isDirectory: true, size: 0, modifyTime: Date.now() }; + } + } catch (listError) { + // 忽略列表错误 + } + } + throw new Error(`对象不存在: ${key}`); + } } /** - * 创建文件读取流 + * 创建文件读取流(异步方法) + * @returns {Promise} 返回可读流 Promise */ - createReadStream(filePath) { - return this.sftp.createReadStream(filePath); + async createReadStream(filePath) { + const key = this.getObjectKey(filePath); + const bucket = this.user.oss_bucket; + + const command = new GetObjectCommand({ + Bucket: bucket, + Key: key + }); + + try { + const response = await this.s3Client.send(command); + // AWS SDK v3 返回的 Body 是一个 IncomingMessage 类型的流 + return response.Body; + } catch (error) { + console.error(`[OSS存储] 创建读取流失败: ${key}`, error.message); + throw error; + } } /** - * 关闭连接 + * 创建文件夹(通过创建空对象模拟) + * OSS 中文件夹实际上是一个以斜杠结尾的空对象 + */ + async mkdir(dirPath) { + try { + const key = this.getObjectKey(dirPath); + const bucket = this.user.oss_bucket; + + // OSS 中文件夹通过以斜杠结尾的空对象模拟 + const folderKey = key.endsWith('/') ? key : `${key}/`; + + const { PutObjectCommand } = require('@aws-sdk/client-s3'); + const command = new PutObjectCommand({ + Bucket: bucket, + Key: folderKey, + Body: '', // 空内容 + ContentType: 'application/x-directory' + }); + + await this.s3Client.send(command); + console.log(`[OSS存储] 创建文件夹: ${folderKey}`); + } catch (error) { + console.error(`[OSS存储] 创建文件夹失败: ${dirPath}`, error.message); + if (error.name === 'AccessDenied') { + throw new Error('OSS 访问被拒绝,请检查权限配置'); + } + throw new Error(`创建文件夹失败: ${error.message}`); + } + } + + /** + * 获取下载 URL(用于分享链接) + */ + getSignedUrl(filePath, expiresIn = 3600) { + const key = this.getObjectKey(filePath); + const bucket = this.user.oss_bucket; + + // 简单的公开 URL 拼接(如果 bucket 是公共读) + const endpoint = this.s3Client.config.endpoint; + const region = this.s3Client.config.region; + + let baseUrl; + if (endpoint) { + baseUrl = endpoint.href || endpoint.toString(); + } else if (this.user.oss_provider === 'aliyun') { + baseUrl = `https://${bucket}.${this.user.oss_region || 'oss-cn-hangzhou'}.aliyuncs.com`; + } else if (this.user.oss_provider === 'tencent') { + baseUrl = `https://${bucket}.cos.${this.user.oss_region || 'ap-guangzhou'}.myqcloud.com`; + } else { + baseUrl = `https://${bucket}.s3.${region}.amazonaws.com`; + } + + return `${baseUrl}/${key}`; + } + + /** + * 关闭连接(S3Client 无需显式关闭) */ async end() { - if (this.sftp) { - await this.sftp.end(); - } + this.s3Client = null; + } + + /** + * 格式化文件大小 + */ + formatSize(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; } } module.exports = { StorageInterface, LocalStorageClient, - SftpStorageClient + OssStorageClient }; diff --git a/frontend/app.html b/frontend/app.html index ba7437f..0889434 100644 --- a/frontend/app.html +++ b/frontend/app.html @@ -1322,7 +1322,7 @@ - +
- -