marcomg-byte/offser

GitHub: marcomg-byte/offser

这是一个专为红队行动设计的基于 TypeScript 的 Express 服务器,集成了 SMTP 邮件发送、凭据收集存储及 Matrix 风格监控面板。

Stars: 0 | Forks: 0

# Offser 一个基于 TypeScript 的 Express.js 服务器,用于通过 SMTP 发送邮件、渲染模板、使用 MySQL 管理数据库记录、提供强大的验证、错误处理、模块化架构和高级日志记录。 ## 功能 - 带有模块化路由的 Express.js API - TypeScript 提供类型安全 - 对所有请求 payload 进行 Zod schema 验证 - 集成 Nodemailer 进行 SMTP 邮件投递 - 带有生产环境缓存的 Handlebars 模板渲染 - **MySQL 数据库集成与连接池** - **支持 SSL/TLS 以确保数据库和 SMTP 连接安全** - 集中式错误处理中间件 - 带有验证的环境变量配置 - 针对邮件和渲染端点的速率限制 - 用于字符串格式化和错误提取的工具函数 - **使用 [Pino](https://github.com/pinojs/pino) 和 multistream 的高级日志记录:** - 带有颜色高亮的漂亮终端输出 - 按级别(error, info, warn, debug)输出的结构化 JSON 日志 - 在 `logs/` 目录中自动创建日志文件 - 自定义时间戳格式 (MM-DD-YYYY HH:mm:ss) - 全面的错误日志记录和调试 - **支持带有 SSL/TLS 证书的 HTTPS 服务器** - **带有信号拦截的优雅关机处理** - **Matrix 主题的 UI 组件(仪表板和 404 页面)** ## 法律免责声明 本工具**专门用于经授权的渗透测试、红队操作和安全研究**,仅限于您拥有或已获得明确书面许可的系统。 未经事先同意,针对系统使用本工具是非法的,并可能违反计算机犯罪法律,包括但不限于《计算机欺诈和滥用法》(CFAA)、《英国计算机滥用法》以及您所在司法管辖区的相关法律。 作者对于因滥用本工具而造成的任何滥用、损害或非法行为**不承担任何责任**。使用本软件即表示您同意,您有责任独自遵守所有适用的地方法、州法、国家和国际法律。 **请负责任且合乎道德地使用。** ## 项目结构 ``` . ├── assets/ │ ├── facebook.ico │ └── matrix.ico ├── backup/ │ └── logs/ # (if present at runtime) ├── certs/ │ ├── db/ │ │ ├── ca-cert.pem │ │ ├── client-cert.pem │ │ └── client-key.pem │ ├── server/ │ │ ├── cert.pem │ │ └── key.pem │ └── README.md ├── db/ │ ├── offser_passwords.sql │ ├── offser_routines.sql │ └── README.md ├── src/ │ ├── __mocks__/ │ │ ├── index.ts │ │ ├── nodemailer.ts │ │ └── server.ts │ ├── config/ │ │ ├── __mocks__/ │ │ │ └── env.ts │ │ ├── env.spec.ts │ │ └── env.ts │ ├── controllers/ │ │ ├── app.controller.ts │ │ ├── db.controller.ts │ │ ├── index.ts │ │ ├── mail.controller.spec.ts │ │ ├── mail.controller.ts │ │ ├── template.controller.spec.ts │ │ └── template.controller.ts │ ├── errors/ │ │ ├── db.error.ts │ │ ├── index.ts │ │ ├── mail.error.ts │ │ ├── server.error.ts │ │ ├── template.error.ts │ │ └── types/ │ │ ├── error.ts │ │ └── index.ts │ ├── index.ts │ ├── middleware/ │ │ ├── error.middleware.spec.ts │ │ ├── error.middleware.ts │ │ ├── index.ts │ │ ├── not-found.middleware.spec.ts │ │ └── not-found.middleware.ts │ ├── routes/ │ │ ├── app.routes.ts │ │ ├── db.routes.ts │ │ ├── health.routes.spec.ts │ │ ├── health.routes.ts │ │ ├── index.ts │ │ ├── mail.routes.ts │ │ └── template.routes.ts │ ├── schemas/ │ │ ├── __mocks__/ │ │ │ └── index.ts │ │ ├── app.schema.ts │ │ ├── db.schema.ts │ │ ├── index.ts │ │ ├── mail.schema.spec.ts │ │ ├── mail.schema.ts │ │ ├── template.schema.spec.ts │ │ └── template.schema.ts │ ├── services/ │ │ ├── __mocks__/ │ │ │ └── index.ts │ │ ├── db.service.spec.ts │ │ ├── db.service.ts │ │ ├── index.ts │ │ ├── mail.service.spec.ts │ │ ├── mail.service.ts │ │ ├── template.service.spec.ts │ │ └── template.service.ts │ ├── templates/ │ │ ├── dashboard.hbs │ │ ├── facebook-login.hbs │ │ ├── not-found.hbs │ │ ├── offers.hbs │ │ ├── sales.hbs │ │ └── shipment.hbs │ └── utils/ │ ├── __mocks__/ │ │ └── (if present) │ ├── error.util.spec.ts │ ├── error.util.ts │ ├── format.util.spec.ts │ ├── format.util.ts │ ├── index.ts │ ├── logger.util.spec.ts │ ├── logger.util.ts │ ├── shutdown.util.spec.ts │ └── shutdown.util.ts ├── .github/ │ └── workflows/ │ ├── ci.yml │ ├── container.yml │ ├── publish.yml │ └── release.yml ├── .dockerignore ├── .env ├── .env.container ├── .env.example ├── .gitignore ├── .prettierrc ├── .nvmrc ├── Dockerfile ├── LICENSE ├── README.md ├── esbuild.cli.mjs ├── esbuild.container.mjs ├── eslint.config.js ├── package.json ├── package-lock.json ├── tsconfig.json ├── tsconfig.test.json ├── vitest.config.ts ``` ## 入门指南 ### 前置条件 - [Node.js](https://nodejs.org/) (推荐 v18+) - [npm](https://www.npmjs.com/) - SMTP 服务器凭据(例如,使用应用专用密码的 Gmail、SendGrid 等) - **MySQL 数据库** (推荐 v8.0+) - **(可选)用于 HTTPS 服务器和安全 MySQL 连接的 SSL/TLS 证书** ## Docker 使用 您可以在 Docker 容器中构建和运行 Offser 以便轻松部署。 ### 构建 Docker 镜像 在项目根目录下,运行: ``` docker build -t offser . ``` ### 运行容器 ``` docker run --env-file .env -p 8080:8080 -v $(pwd)/certs:/app/certs offser ``` - `--env-file .env` 加载环境变量(所需值请参见 `.env.example`) - `-p 8080:8080` 将容器端口映射到您的主机 - `-v $(pwd)/certs:/app/certs` 挂载您本地的 `certs` 目录以用于 SSL/DB 证书 #### Docker 的数据库主机 如果您的 MySQL 数据库运行在您的主机(而非 Docker)上,请在您的 `.env` 中设置: ``` DB_HOST=host.docker.internal ``` 这允许容器连接到您主机的 MySQL 实例。如果您的数据库在另一个容器中,请使用 Docker 网络服务名称。 #### .dockerignore 构建使用多阶段 Dockerfile 和 `.dockerignore` 文件来排除不必要的文件(例如 `node_modules`、`logs`、`dist`、本地 `.env`)。 有关更多详细信息,请参阅项目根目录中的 Dockerfile 和 `.dockerignore`。 ### 安装 1. **克隆仓库** git clone https://github.com/marcomg-byte/offser.git cd offser 2. **安装依赖** npm install 3. **配置环境变量** 根据 `.env.example` 创建 `.env` 文件: # Server Configuration NODE_ENV=development PORT=8080 # SMTP Configuration SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password MAIL_FROM=your-email@gmail.com # Rate Limiting MAIL_SERVICE_RATE_LIMIT=10 MAIL_SERVICE_RATE_WINDOW=15 RENDER_SERVICE_RATE_LIMIT=20 RENDER_SERVICE_RATE_WINDOW=10 # Database Configuration DB_HOST=localhost DB_PORT=3306 DB_USER=your_db_user DB_PASS=your_db_password DB_NAME=your_database_name DB_CONNECTION_LIMIT=10 DB_QUEUE_LIMIT=0 DB_WAIT_FOR_CONNECTIONS=true DB_KEEP_ALIVE=true DB_KEEP_ALIVE_INITIAL_DELAY=0 # Database SSL (Optional) DB_SSL_ENABLED=false # DB_SSL_CA=certs/db/ca.pem # DB_SSL_CERT=certs/db/client-cert.pem # DB_SSL_KEY=certs/db/client-key.pem # DB_SSL_REJECT_UNAUTHORIZED=true # HTTPS Configuration (Optional) HTTPS_ENABLED=false # HTTPS_KEY_PATH=certs/server/key.pem # HTTPS_CERT_PATH=certs/server/cert.pem 4. **设置数据库** 从 `db/` 目录运行 SQL 脚本: # Create the passwords table mysql -u your_db_user -p your_database_name < db/offser_passwords.sql # Create stored procedures mysql -u your_db_user -p your_database_name < db/offser_routines.sql 或者在您的 MySQL 客户端中手动执行脚本。有关详细设置说明,请参阅 [db/README.md](db/README.md)。 5. **构建项目** npm run build 6. **启动服务器** npm start 服务器将在 `.env` 中指定的端口上运行(默认:3000)。 ### 开发 对于具有自动重载和模板监视的开发: ``` npm run dev ``` 这使用 `concurrently` 同时运行 TypeScript 编译器和模板监视器。 ### 可用脚本 - `npm run build` - 将 TypeScript 编译为 JavaScript 并将模板复制到 dist - `npm run build:templates` - 将 Handlebars 模板复制到 dist 目录 - `npm run dev` - 启动具有热重载的开发服务器 (Nodemon + tsx) - `npm start` - 运行生产构建 - `npm run lint` - 使用 ESLint 检查代码 - `npm run lint:fix` - 自动修复 ESLint 问题 - `npm test` - 使用 Vitest 运行单元和集成测试(监视模式) - `npm run test:run` - 运行一次测试 - `npm run test:coverage` - 生成测试覆盖率报告 - `npm run test:ui` - 打开 Vitest UI 进行交互式测试 - `npm run clean` - 删除 dist 文件夹 - `npm run clean:logs` - 删除 logs 文件夹 - `npm run backup:logs` - 将日志文件归档到 backup/logs 目录 - `npm run watch:templates` - 监视模板目录的更改 - `npm run prepare` - 清理并构建(在发布之前运行) ## 日志记录 本项目使用带有 multistream 的 [Pino](https://github.com/pinojs/pino) 进行高级日志记录: - **终端输出:** 使用 [pino-pretty](https://github.com/pinojs/pino-pretty) 进行开发时的漂亮打印、彩色日志。 - **日志文件:** `logs/` 目录中按级别分类的结构化 JSON 日志: - `error.log` - 错误级别消息 - `info.log` - 信息级别消息 - `warn.log` - 警告级别消息 - `debug.log` - 调试级别消息 - **自动创建日志目录:** 无需手动设置。 - **自定义时间戳格式:** `MM-DD-YYYY HH:mm:ss` 以便于阅读。 - **大写日志级别:** 所有日志输出格式一致。 - **日志归档:** 在清理之前使用 `npm run backup:logs` 将日志归档到 `backup/logs/`。 所有日志文件都会在 `logs/` 目录中自动创建。记录器在 [`logger.util.ts`](src/utils/logger.util.ts) 中配置。 ## 架构 ### 模块化结构 - **Controllers(控制器)** - 处理 HTTP 请求和响应,协调服务之间的交互 - **Services(服务)** - 业务逻辑(邮件发送、模板渲染、数据库操作) - **Routes(路由)** - 定义 API 端点,应用速率限制和中间件 - **Schemas(模式)** - 基于请求和响应的 Zod 数据验证规则 - **Middleware(中间件)** - 横切关注点(错误处理、404 页面、请求日志记录) - **Utils(工具)** - 可重用的工具函数(日志记录、格式化、错误提取、关机) - **Config(配置)** - 应用程序配置和环境变量验证 - **Templates(模板)** - 用于动态 HTML 邮件和页面的 Handlebars 模板 - **Errors(错误)** - 针对不同失败场景且带有上下文的自定义错误类 ### 错误处理 [`error.middleware.ts`](src/middleware/error.middleware.ts) 中的集中式错误处理中间件捕获并处理: - **Zod 验证错误** → 400 Bad Request,附带美化的验证详细信息 - **数据库错误**(连接、查询、SSL 配置、传输器创建)→ 500 Internal Server Error,附带上下文 - **邮件/模板错误** → 500 Internal Server Error,附带详细上下文 - **SSL/证书错误**(文件丢失、路径无效)→ 500 Internal Server Error,附带文件信息 - **未知错误** → 500 Internal Server Error,附带回退消息 所有错误都使用 Pino 记录器记录,包含: - 完整的堆栈跟踪 - 错误上下文(查询参数、请求正文摘录) - 自定义错误属性(原因、传输器信息、模板数据) - 时间戳和日志级别 有关错误规范化逻辑,请参阅 [`extractErrorInfo`](src/utils/error.util.ts)。 ### 数据库架构 数据库层使用 MySQL 2 和连接池: - **连接池**:可配置的 MySQL 连接池,用于高效利用资源 - **SSL/TLS 支持**:带有证书验证的可选加密数据库连接 - **存储过程**:所有数据库操作都使用 MySQL 存储过程: - `INSERT_PASSWORD(mail, password)` - 插入新的密码记录 - `READ_PASSWORDS(upperLimit, lowerLimit)` - 读取带有范围的密码记录 - `DELETE_ENTRY(lowerLimit, upperLimit)` - 按 ID 范围删除密码记录 - **事务支持**:自动事务处理,出错时执行 BEGIN、COMMIT 和 ROLLBACK - **连接验证**:启动验证确保在接受请求之前数据库连接正常 - **错误处理**:数据库错误被包装在带有查询上下文的自定义错误类中 配置在 [`db.service.ts`](src/services/db.service.ts) 中管理。有关设置详细信息,请参阅 [db/README.md](db/README.md) 和 [certs/README.md](certs/README.md)。 ### 模板系统 带有生产环境缓存的 Handlebars 模板: - **开发模式**:每次请求都会重新加载模板以获得即时反馈 - **生产模式**:模板预加载并缓存在内存中以提升性能 - **自定义助手**:`toCSV` 助手将密码记录转换为 CSV 格式以便导出 - **模板编译**:Handlebars 编译模板并注入数据 - **错误处理**:模板编译错误包含模板名称和数据上下文 有关实现,请参阅 [`template.service.ts`](src/services/template.service.ts)。 ## API 端点 ### 健康检查 - **端点**:`GET /health` - **描述**:检查服务器运行状态、运行时间和当前时间戳 **成功响应 (200):** ``` { "status": "ok", "timestamp": "2024-02-05T09:07:03.000Z", "localDate": "02-05-2024 09:07:03", "uptime": 120.47 } ``` ### 数据库操作 #### 插入数据 - **端点**:`POST /records/insert` - **描述**:向数据库插入新的密码记录 - **速率限制**:无速率限制 **请求正文:** ``` { "mail": "user@example.com", "password": "securePassword123" } ``` **验证:** - `mail`:必须是有效的电子邮件地址 - `password`:必须是非空字符串 **成功响应 (200):** ``` { "title": "Data Inserted Successfully!", "data": { "mail": "user@example.com", "password": "securePassword123" } } ``` #### 读取数据 - **端点**:`GET /records/read` - **描述**:检索指定 ID 范围内的密码记录 - **速率限制**:无速率限制 **请求正文:** ``` { "lowerLimit": 1, "upperLimit": 10 } ``` **验证:** - `lowerLimit`:可选的正整数 >= 1 - `upperLimit`:必需的正整数 >= 1 **成功响应 (200):** ``` { "title": "Data Read Successfully!", "data": [ { "ID": 1, "MAIL": "user@example.com", "PASSWORD": "securePassword123" } ] } ``` #### 删除数据 - **端点**:`DELETE /records/delete` - **描述**:按 ID 或 ID 范围删除密码记录 - **速率限制**:无速率限制 **请求正文:** ``` { "lowerLimit": 5, "upperLimit": 10 } ``` **验证:** - `lowerLimit`:必需的正整数 >= 1 - `upperLimit`:可选的正整数 >=1 **成功响应 (200):** ``` { "title": "Data Deleted Successfully!", "lowerLimit": 5, "upperLimit": 10 } ``` #### 数据库健康检查 - **端点**:`GET /records/health` - **描述**:验证数据库连接并返回连接状态 - **速率限制**:无速率限制 **成功响应 (200):** ``` { "status": "OK", "timestamp": "2024-02-05T09:07:03.000Z", "localDate": "02-05-2024 09:07:03", "uptime": 120.47 } ``` **错误响应 (503):** ``` { "status": "ERROR", "timestamp": "2024-02-05T09:07:03.000Z", "localDate": "02-05-2024 09:07:03", "uptime": 120.47 } ``` ### 应用程序路由 #### 渲染仪表板 - **端点**:`GET /app/dashboard?lowerLimit=1&upperLimit=100` - **描述**:渲染一个 Matrix 主题的仪表板,显示密码记录并具有 CSV 导出功能 - **速率限制**:通过 `RENDER_SERVICE_RATE_LIMIT` 和 `RENDER_SERVICE_RATE_WINDOW` 配置 **查询参数:** - `lowerLimit`(可选,数字)- 记录的起始 ID - `upperLimit`(必需,数字)- 记录的结束 ID **成功响应 (200):** - 返回渲染的 HTML 页面,包含: - 黑底绿字的 Matrix 风格主题,带有下落代码动画 - 显示每条记录的 ID、电子邮件和密码的数据网格 - CSV 导出按钮(仅当存在数据时显示) - 适用于移动设备的响应式设计 - 无数据可用时的空状态消息 **功能:** - 使用 `toCSV` Handlebars 助手进行客户端 CSV 导出 - 自动下载 `password_records.csv` 文件 - 带有发光效果和动画的 Matrix 主题 UI ### 发送邮件 - **端点**:`POST /mail/send` - **描述**:通过配置的 SMTP 服务器发送电子邮件 - **速率限制**:通过 `MAIL_SERVICE_RATE_LIMIT` 和 `MAIL_SERVICE_RATE_WINDOW` 配置 **请求正文:** ``` { "to": "recipient@example.com", "subject": "Email Subject", "text": "Plain text content", "html": "

HTML content

", "templateName": "offers", "templateData": { "customerName": "John", "offers": [ { "title": "Discount", "description": "10% off" } ], "ctaUrl": "https://example.com/offer" } } ``` **请求字段:** - `to`(必需,字符串)- 收件人电子邮件地址(有效的电子邮件格式) - `subject`(必需,字符串)- 电子邮件主题行(至少 1 个字符) - `text`(可选,字符串)- 纯文本电子邮件正文 - `html`(可选,字符串)- HTML 电子邮件正文 - `templateName`(可选,字符串)- 要使用的 Handlebars 模板名称(不带 .hbs 扩展名) - `templateData`(可选,对象)- 用于模板变量的数据对象 **成功响应 (200):** ``` { "title": "Email Sent Successfully!", "data": { "to": "recipient@example.com", "subject": "Email Subject", "templateName": "offers" }, "mailResponse": { "messageId": "", "accepted": ["recipient@example.com"], "response": "250 Message accepted" } } ``` **验证错误响应 (400):** ``` { "title": "Request Validation Error", "error": "[Expected string at 'to', Expected string at 'subject']" } ``` **服务器错误响应 (500):** ``` { "title": "Mail Service Error", "name": "MailDeliveryError", "message": "Failed to send email to recipient@example.com", "stack": "...", "cause": { "code": "EAUTH", "message": "Invalid login credentials" } } ``` ### 渲染模板 - **端点**:`GET /render/:templateName` - **描述**:使用提供的数据渲染 Handlebars 模板 - **速率限制**:通过 `RENDER_SERVICE_RATE_LIMIT` 和 `RENDER_SERVICE_RATE_WINDOW` 配置 **请求示例:** ``` GET /render/offers Content-Type: application/json { "customerName": "John", "offers": [ { "title": "10% Discount", "description": "On all items" } ], "ctaUrl": "https://example.com/offer" } ``` **可用模板:** - `sales` - 带有产品优惠的促销邮件(需要:offers[], ctaUrl) - `offers` - 促销优惠邮件(需要:customerName, offers[], ctaUrl) - `shipment` - 订单发货确认(需要:customerName, orderNumber, shippingAddress, estimatedDelivery, items[], trackingUrl) - `dashboard` - 带有 Matrix 主题的密码记录仪表板(需要:data[]) - `not-found` - 带有 Matrix 主题的 404 错误页面(不需要数据) **成功响应 (200):** - 返回渲染的 HTML 字符串 **验证错误响应 (400):** ``` { "title": "Request Validation Error", "error": "[Expected string at 'customerName']" } ``` **模板未找到 (500):** ``` { "title": "Template Compilation Error", "name": "TemplateCompileError", "message": "Template 'invalid-template' not found", "stack": "..." } ``` ## 关键工具 - [`capitalizeWord(word)`](src/utils/format.util.ts) - 将单词的首字母大写,其余小写 - [`capitalizeString(value)`](src/utils/format.util.ts) - 将字符串中每个单词的首字母大写 - [`formatDate(date)`](src/utils/format.util.ts) - 将 Date 对象格式化为 `MM-DD-YYYY HH:mm:ss` - [`logger`](src/utils/logger.util.ts) - 带有 multistream(终端 + 文件)的 Pino 记录器实例 - [`extractErrorInfo(error)`](src/utils/error.util.ts) - 规范化错误对象以进行一致的日志记录 - [`gracefulShutdown(server, signal)`](src/utils/shutdown.util.ts) - 处理 SIGTERM/SIGINT 时的优雅服务器关机 **错误提取:** `extractErrorInfo` 工具处理各种错误类型: - `ZodError` - 美化带有字段路径的验证错误 - 自定义错误(Mail, Template, Database)- 提取名称、消息、堆栈和自定义属性 - 原生错误 - 标准错误信息 - 未知类型 - 转换为字符串表示形式 ## SSL/TLS 配置 ### HTTPS 服务器 要为 Express 服务器启用 HTTPS: 1. **生成或获取 SSL 证书:** # For development (self-signed): openssl req -x509 -newkey rsa:4096 -keyout certs/server/key.pem -out certs/server/cert.pem -days 365 -nodes 2. **将证书放入 `certs/server/` 目录** 3. **更新 `.env` 文件:** HTTPS_ENABLED=true HTTPS_KEY_PATH=certs/server/key.pem HTTPS_CERT_PATH=certs/server/cert.pem 4. **应用程序将:** - 在配置的端口上启动 HTTPS 服务器 - 记录证书加载状态 - 如果文件丢失则抛出 `CertificateNotFoundError` - 自动验证证书有效性 有关详细的证书管理说明,请参阅 [certs/README.md](certs/README.md)。 ### MySQL SSL 连接 要为加密的数据库连接启用 SSL/TLS: 1. **从您的数据库提供商获取 SSL 证书**(例如,AWS RDS、Google Cloud SQL) 2. **将证书放入 `certs/db/` 目录:** - `ca.pem` - 证书颁发机构证书 - `client-cert.pem` - 客户端证书 - `client-key.pem` - 客户端私钥 3. **更新 `.env` 文件:** DB_SSL_ENABLED=true DB_SSL_CA=certs/db/ca.pem DB_SSL_CERT=certs/db/client-cert.pem DB_SSL_KEY=certs/db/client-key.pem DB_SSL_REJECT_UNAUTHORIZED=true 4. **配置选项:** - `DB_SSL_ENABLED=true` - 为数据库连接启用 SSL - `DB_SSL_CA` - CA 证书的路径(SSL 所需) - `DB_SSL_CERT` - 客户端证书的路径(可选,用于双向 TLS) - `DB_SSL_KEY` - 客户端密钥的路径(可选,用于双向 TLS) - `DB_SSL_REJECT_UNAUTHORIZED=true` - 拒绝无效证书(推荐用于生产环境) 有关更多详细信息,请参阅 [certs/README.md](certs/README.md) 和 [db/README.md](db/README.md)。 ## SMTP 安全配置 ### `SMTP_SECURE` 和 SSL/TLS `SMTP_SECURE` 环境变量控制 SMTP 连接的 SSL/TLS 加密: **安全连接 (SSL/TLS):** ``` SMTP_SECURE=true SMTP_PORT=465 ``` - 从一开始就进行完整的 SSL/TLS 加密 - 使用端口 465(SMTPS 的标准端口) - 推荐用于生产环境 - 当 `SMTP_SECURE=true` 时,应用程序强制使用端口 465 **STARTTLS 连接:** ``` SMTP_SECURE=false SMTP_PORT=587 ``` - 连接开始时未加密,然后升级到 TLS - 使用端口 587(带有 STARTTLS 的 SMTP 的标准端口) - 大多数电子邮件提供商都支持 - 当 `SMTP_SECURE=false` 时,应用程序强制使用端口 587 **端口强制执行:** 应用程序在启动时验证 SMTP 配置,如果出现以下情况将抛出错误: - `SMTP_SECURE=true` 但 `SMTP_PORT` 不是 465 - `SMTP_SECURE=false` 但 `SMTP_PORT` 不是 587 这可以防止配置错误并确保使用正确的加密。 **Gmail 配置示例:** ``` SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password # Use App Password, not account password MAIL_FROM=your-email@gmail.com ``` 有关实现详细信息,请参阅 [`mail.service.ts`](src/services/mail.service.ts)。 ## 环境配置 所有环境变量都在启动时使用 [`env.ts`](src/config/env.ts) 中的 Zod schema 进行验证。 ### 必需变量 **SMTP 配置:** - `SMTP_HOST` - SMTP 服务器主机名(例如,`smtp.gmail.com`) - `SMTP_USER` - SMTP 认证用户名/电子邮件 - `SMTP_PASS` - SMTP 认证密码 - `MAIL_FROM` - 默认发件人电子邮件地址 **速率限制:** - `MAIL_SERVICE_RATE_LIMIT` - 每个时间窗口的最大邮件请求数(整数) - `MAIL_SERVICE_RATE_WINDOW` - 速率限制时间窗口(分钟)(整数) - `RENDER_SERVICE_RATE_LIMIT` - 每个时间窗口的最大渲染请求数(整数) - `RENDER_SERVICE_RATE_WINDOW` - 速率限制时间窗口(分钟)(整数) **应用程序环境:** - `NODE_ENV` - 环境模式(`development`、`production`、`test`) **数据库配置:** - `DB_HOST` - MySQL 服务器主机名 - `DB_PORT` - MySQL 服务器端口(通常为 3306) - `DB_USER` - 数据库用户名 - `DB_PASS` - 数据库密码 - `DB_NAME` - 数据库名称 ### 可选变量 **服务器:** - `PORT` - 服务器监听端口(默认:`3000`) **SMTP:** - `SMTP_PORT` - SMTP 服务器端口(默认:`587`) - `SMTP_SECURE` - 使用 SSL/TLS(默认:`false`) **数据库连接池:** - `DB_CONNECTION_LIMIT` - 最大池连接数(默认:`10`) - `DB_QUEUE_LIMIT` - 最大排队连接请求数(默认:`0` = 无限制) - `DB_WAIT_FOR_CONNECTIONS` - 等待可用连接(默认:`true`) - `DB_KEEP_ALIVE` - 启用 TCP keep-alive(默认:`true`) - `DB_KEEP_ALIVE_INITIAL_DELAY` - Keep-alive 初始延迟(毫秒)(默认:`0`) **数据库 SSL:** - `DB_SSL_ENABLED` - 为数据库启用 SSL(默认:`false`) - `DB_SSL_CA` - CA 证书的路径(可选) - `DB_SSL_CERT` - 客户端证书的路径(可选) - `DB_SSL_KEY` - 客户端密钥的路径(可选) - `DB_SSL_REJECT_UNAUTHORIZED` - 拒绝无效证书(默认:`true`) **HTTPS 服务器:** - `HTTPS_ENABLED` - 启用 HTTPS 服务器(默认:`false`) - `HTTPS_KEY_PATH` - HTTPS 私钥的路径(默认:`./key.pem`) - `HTTPS_CERT_PATH` - HTTPS 证书的路径(默认:`./cert.pem`) **验证:** 缺少必需的变量将导致应用程序在启动期间失败,并显示一条明确的错误消息,指出缺少哪个变量。 有关详细文档和验证逻辑,请参阅 [`env.ts`](src/config/env.ts)。 ## 代码质量 ### Linting 和格式化 - **ESLint** - 具有推荐规则的 TypeScript 感知 linting - 配置:[`eslint.config.js`](eslint.config.js) - 使用 `@typescript-eslint` 进行 TypeScript 支持 - 与 Prettier 集成以进行一致的格式化 - 最大行长度:120 个字符 - **Prettier** - 主观的代码格式化 - 配置:[`.prettierrc`](.prettierrc) - 保存时自动格式化(如果编辑器已配置) - 通过 ESLint 插件强制执行 **命令:** ``` npm run lint # Check for linting issues npm run lint:fix # Auto-fix linting and formatting issues ``` ### 类型安全 - **TypeScript** - 启用严格类型检查 - 主配置:[`tsconfig.json`](tsconfig.json) - 测试配置:[`tsconfig.test.json`](tsconfig.test.json) - 启用严格模式以实现最大的类型安全 - 不允许隐式 `any` 类型 ## 测试 本项目使用 [Vitest](https://vitest.dev/) 进行单元和集成测试,并提供全面的覆盖率。 ### 测试结构 - **配置**:[`vitest.config.ts`](vitest.config.ts) - **测试文件**:与源文件并列存放(`.spec.ts` 扩展名) - **模拟**:位于 `__mocks__` 目录中用于模块模拟 - **覆盖率提供者**:V8 用于准确的覆盖率报告 ### 运行测试 ``` npm test # Run tests in watch mode (interactive) npm run test:run # Run all tests once (CI mode) npm run test:coverage # Generate coverage report npm run test:ui # Open Vitest UI (visual test runner) npm run test:clean # Remove coverage directory ``` ### 测试覆盖率 覆盖率报告在 `coverage/` 目录中生成,包含: - **文本报告** - 控制台输出 - **JSON 报告** - 机器可读格式 - **HTML 报告** - 交互式浏览器视图(打开 `coverage/index.html`) **覆盖率排除项:** - `node_modules/` - 第三方依赖 - `dist/` - 编译输出 - `errors/` - 误类定义 - `config/` - 配置文件 - `*.config.*` - 所有配置文件 - `*.d.ts` - TypeScript 定义文件 - `**/index.ts` - 重新导出文件 - `**/types/**` - 类型定义目录 ### 测试示例 **服务测试:** - [`mail.service.spec.ts`](src/services/mail.service.spec.ts) - 邮件服务和传输器 - [`template.service.spec.ts`](src/services/template.service.spec.ts) - 模板编译和缓存 **控制器测试:** - [`mail.controller.spec.ts`](src/controllers/mail.controller.spec.ts) - 邮件发送端点 **路由测试:** - [`health.routes.spec.ts`](src/routes/health.routes.spec.ts) - 健康检查端点 **工具测试:** - [`format.util.spec.ts`](src/utils/format.util.spec.ts) - 字符串格式化工具 - [`error.util.spec.ts`](src/utils/error.util.spec.ts) - 错误提取逻辑 **配置测试:** - [`env.spec.ts`](src/config/env.spec.ts) - 环境变量验证 ### 模拟策略 - **Nodemailer** - 为邮件测试模拟传输器 - **文件系统** - 为模板测试模拟 `fs` 模块 - **环境** - 为不同的 NODE_ENV 场景模拟配置 - **数据库** - 为控制器测试模拟连接池 ## 开发工作流 1. **创建功能分支:** git checkout -b feature/my-feature 2. **对代码库进行更改** 3. **运行 linting 并修复问题:** npm run lint:fix 4. **运行测试并验证覆盖率:** npm test npm run test:coverage 5. **构建项目:** npm run build 6. **在本地测试您的更改:** npm run dev 7. **提交您的更改:** git add . git commit -m "Add new feature: description" 8. **推送到分支:** git push origin feature/my-feature 9. **在 GitHub 上打开拉取请求** ### 开发技巧 - **热重载**:`npm run dev` 监视 TypeScript 文件和模板 - **模板开发**:模板在开发模式下自动重新加载 - **日志监视**:检查 `logs/` 目录以获取详细的应用程序日志 - **数据库测试**:使用事务安全地测试数据库操作 - **环境切换**:创建 `.env.development` 和 `.env.production` 用于不同的配置 ## CLI 使用(全局安装) 全局安装 Offser 以用作命令行工具: ``` npm install -g offser ``` 这使得 `offser` 命令可在系统范围内使用。 ### 使用方法 从任何地方启动服务器: ``` offser ``` CLI 将: - 在当前目录中查找 `.env` 文件 - 使用相同的配置启动服务器 - 如果未找到 `.env`,则使用 shell 中的环境变量 **要求:** - 项目必须在发布或全局安装之前构建(`npm run build`) - `dist/index.js` 必须存在且可执行 - `.env` 文件应位于当前工作目录中 ### 发布 要发布到 npm: ``` npm run prepare # Clean and build npm publish # Publish to npm registry ``` `prepare` 脚本在发布之前自动运行,以确保构建是最新的。 ## 故障排除 ### SMTP 连接问题 **症状:** - 邮件发送失败并出现身份验证错误 - 连接超时错误 **解决方案:** - 验证 `.env` 文件中的 SMTP 凭据 - 检查防火墙设置(允许端口 587 和 465) - 对于 Gmail: - 启用双重验证 - 生成并使用[应用专用密码](https://support.google.com/accounts/answer/185833) - 不要使用您的 Google 帐户密码 - 验证 `SMTP_SECURE` 和 `SMTP_PORT` 匹配(false 为 587,true 为 465) - 使用邮件客户端(例如 Thunderbird)测试您的凭据 ### 数据库连接问题 **症状:** - "Connection refused" 错误 - 身份验证失败 - SSL 握手错误 **解决方案:** - 验证 `.env` 文件中的数据库凭据 - 确保 MySQL 服务器正在运行: # Check MySQL status sudo systemctl status mysql # Linux brew services list # macOS with Homebrew - 检查防火墙设置(允许端口 3306) - 验证数据库存在且用户具有适当的权限: SHOW DATABASES; SHOW GRANTS FOR 'your_user'@'localhost'; - 如果使用 SSL: - 确保 `.env` 中的证书路径正确 - 验证证书文件存在且可读 - 检查证书过期日期 - 使用 MySQL 客户端测试 SSL 连接 - 检查 MySQL 错误日志以获取详细的错误消息 - 验证连接池配置(限制、超时) ### TypeScript 编译问题 **症状:** - 构建失败并出现类型错误 - IDE 显示红色波浪线 **解决方案:** - 确保 `tsconfig.json` 配置正确 - 运行 `npm run build` 查看所有类型错误 - 检查缺少的类型定义: npm install --save-dev @types/node @types/express - 使用 `npm run lint:fix` 修复格式问题 - 清除 TypeScript 缓存: rm -rf node_modules/.cache - 验证 TypeScript 版本是否符合项目要求 ### 端口已被占用 **症状:** - 启动时出现 "EADDRINUSE" 错误 - 服务器无法绑定到端口 **解决方案:** - 将 `.env` 中的 `PORT` 更改为可用端口 - 终止使用该端口的进程: # macOS/Linux lsof -ti:8080 | xargs kill -9 # Windows netstat -ano | findstr :8080 taskkill /PID /F - 使用不同的端口范围进行开发(8000-9000) ### 日志问题 **症状:** - 未创建日志文件 - 缺少日志条目 - 权限错误 **解决方案:** - 日志文件在 `logs/` 目录中自动创建 - 检查目录权限: ls -la logs/ chmod 755 logs/ # If needed - 确保应用程序进程具有写入权限 - 验证磁盘空间充足 - 在清理之前使用 `npm run backup:logs` 归档日志 - 检查 [`logger.util.ts`](src/utils/logger.util.ts) 中的记录器配置 ### 证书问题 **症状:** - "Certificate not found" 错误 - SSL 握手失败 - 浏览器安全警告 **解决方案:** - 确保证书文件存在于 `.env` 中指定的路径: ls -la certs/server/ ls -la certs/db/ - 检查文件权限(必须可被应用程序读取): chmod 644 certs/server/*.pem - 对于开发,自签名证书是可以接受的 - 对于生产,使用来自受信任 CA 的证书(Let's Encrypt 等) - 验证证书有效性: openssl x509 -in certs/server/cert.pem -text -noout - 检查证书过期日期 - 有关详细的故障排除,请参阅 [certs/README.md](certs/README.md) ### 模板问题 **症状:** - 未找到模板错误 - Handlebars 编译错误 - 缺少模板变量 **解决方案:** - 模板在生产模式下被缓存(`NODE_ENV=production`) - 在开发模式下,每次请求都会重新加载模板 - 确保模板文件存在于 `src/templates/` 目录中: ls src/templates/ - 检查模板语法是否存在 Handlebars 错误 - 验证模板数据与 [`template.schema.ts`](src/schemas/template.schema.ts) 中的 schema 匹配 - 通过重启服务器清除模板缓存 - 使用 `npm run build:templates` 确保模板被复制到 `dist/` ### 速率限制问题 **症状:** - "Too many requests" 错误 - 429 状态码 **解决方案:** - 在 `.env` 中调整速率限制: MAIL_SERVICE_RATE_LIMIT=100 MAIL_SERVICE_RATE_WINDOW=15 RENDER_SERVICE_RATE_LIMIT=50 RENDER_SERVICE_RATE_WINDOW=10 - 速率限制是按 IP 地址计算的 - 等待速率限制时间窗口过期 - 使用不同的 IP 地址进行测试(例如,VPN、移动网络) ## 贡献 欢迎贡献!请遵循以下准则: 1. **Fork 仓库**并创建功能分支 2. **遵循代码风格** - 使用 ESLint 和 Prettier 3. **为新功能编写测试** 4. **更新文档** - 添加 JSDoc 注释并更新 README 5. **提交消息** - 使用清晰、描述性的提交消息 6. **拉取请求** - 提供清晰的更改描述 **代码标准:** - 遵循现有的文件结构和命名约定 - 为所有函数参数和返回值使用 TypeScript 类型 - 为公共 API 添加 JSDoc 注释 - 为工具和服务编写单元测试 - 为控制器和路由编写集成测试 - 将测试覆盖率保持在 80% 以上 ## 链接 - **仓库**:[marcomg-byte/offser](https://github.com/marcomg-byte/offser) - **问题**:[GitHub Issues](https://github.com/marcomg-byte/offser/issues) - **拉取请求**:[GitHub PRs](https://github.com/marcomg-byte/offser/pulls) ## 致谢 - [Express.js](https://expressjs.com/) - Web 框架 - [TypeScript](https://www.typescriptlang.org/) - 类型安全的 JavaScript - [Zod](https://zod.dev/) - Schema 验证 - [Nodemailer](https://nodemailer.com/) - 邮件发送 - [Handlebars](https://handlebarsjs.com/) - 模板引擎 - [Pino](https://github.com/pinojs/pino) - 快速记录器 - [Vitest](https://vitest.dev/) - 测试框架 - [MySQL](https://www.mysql.com/) - 数据库系统 ## 发布与 npm 包 本项目使用自动化的 GitHub Actions 工作流进行版本发布和 npm 发布: - **发布工作流**:当带有 `patch`、`minor` 或 `major` 标签的拉取请求合并到 `main` 时,版本会自动升级,创建 git 标签,并发布 GitHub Release。合并的拉取请求也会收到 `released` 标签。 - **发布工作流**:当推送新版本标签(例如 `v1.2.3`)时,包将被构建并使用受信任的发布者连接发布到 [npm 注册表](https://www.npmjs.com/package/offser)。 ### 如何触发发布 1. 打开一个针对 `main` 的拉取请求,包含您的更改。 2. 添加以下标签之一:`patch`、`minor` 或 `major` 到 PR(根据[语义化版本控制](https://semver.org/))。 3. 合并 PR。 4. 工作流将: - 升级 `package.json` 中的版本并创建 git 标签。 - 发布 GitHub Release。 - 构建并将新版本发布到 npm。 - 将 `released` 标签添加到 PR。 ### npm 包 - **当前版本:** 1.1.6 - **注册表:** [https://www.npmjs.com/package/offser](https://www.npmjs.com/package/offser) - **安装:** npm install -g offser - **使用:** offser --help ## GitHub 工作流 本项目使用 GitHub Actions 进行自动化 CI、容器验证、发布管理和 npm 发布。工作流定义在 `.github/workflows/` 中: ### 1. CI 工作流 (`ci.yml`) - **触发器:** 每次针对 `main` 分支的拉取请求 - **步骤:** - 检出代码 - 使用 `.nvmrc` 中的版本设置 Node.js - 使用 `npm ci` 安装依赖 - 运行所有测试(`npm run test:run`) - 执行 `npm publish` 的空运行以验证可发布性 ### 2. CD 工作流 (`cd.yml`) - **触发器:** 每次针对 `main` 分支的拉取请求 - **步骤:** - 检出代码 - 使用 `.nvmrc` 设置 Node.js - 安装依赖(忽略脚本) - 打包容器(`npm run bundle:container`) - 构建容器镜像(`npm run build:container`) ### 3. 发布工作流 (`release.yml`) - **触发器:** 针对到 `main` 的拉取请求事件(opened, synchronized, reopened, closed) - **步骤:** - 检出带有完整历史的代码 - 根据 PR 标签(`major`、`minor`、`patch`)确定版本升级类型 - (进一步的步骤可能包括版本升级、变更日志生成、标记和发布发布) ### 4. 发布工作流 (`publish.yml`) - **触发器:** - 推送匹配模式 `v*.*.*` 的标签时 - 针对到 `main` 的拉请求(空运行) - **步骤:** - 检出代码 - 使用 `.nvmrc` 设置 Node.js - 使用 `npm ci` 安装依赖 - 对于 PR:执行 `npm publish` 的空运行 - 对于标签:使用 `NODE_AUTH_TOKEN` 密钥将包发布到 npm 并提供来源证明 有关更多详细信息,请参阅 `.github/workflows/` 中的工作流文件。
标签:API开发, ETW劫持, Express.js, GNU通用公共许可证, Handlebars, HTTPS, MITM代理, Node.js, Nodemailer, Pino, SMTP, SSL/TLS, TypeScript, UI组件, Web服务器, Zod, 优雅停机, 后端开发, 安全插件, 安全通信, 数据库集成, 数据验证, 日志记录, 模块化架构, 模板渲染, 网络安全, 请求拦截, 连接池, 邮件发送, 邮件安全, 错误处理, 限流, 隐私保护, 黑客帝国风格