afiqandico13/tokokita

GitHub: afiqandico13/tokokita

TokoKita 是一个以安全加固为核心特色的 Node.js + Express 电商示例应用,演示了如何在完整的购物车与后台管理业务中落地多种 Web 安全防护措施。

Stars: 0 | Forks: 0

# TokoKita — 仿 Indomaret 风格的电商网站 🧺 ![Node](https://img.shields.io/badge/node-%3E%3D18-green) ![Express](https://img.shields.io/badge/express-5-blue) ![License](https://img.shields.io/badge/license-MIT-blue) ![Security](https://img.shields.io/badge/security-hardened-brightgreen) ## 功能 **顾客:** - 按分类浏览商品 - 添加到购物车,更新数量,移除商品 - 填写姓名 / 电话 / 地址进行结账 - 结账成功后自动扣减库存 **管理员**(`/admin/login`,默认账号 `admin` / `admin123`): - 基于 session 的登录 - 带有商品/订单/收入统计的数据看板 - 商品 CRUD(支持图片上传) - 分类 CRUD - 查看所有订单 + 更新状态(`pending → diproses → dikirim → selesai → cancelled`) **视觉打磨:** - 带有交互式 3D 旋转购物篮的 Hero 区域 - 鼠标悬停时带有 3D 倾斜效果的商品卡片 - 橄榄绿配色方案,使用 Fraunces + Inter 字体排版 ## 快速开始 ``` # 1. 安装依赖 npm install # 2. 启动 server npm start # 3. 打开 browser # Customer shop: http://localhost:3000 # Admin login: http://localhost:3000/admin/login ``` 默认管理员:`admin` / `admin123` — **在部署到公共服务器之前,请务必修改。** ## 技术栈 | 层级 | 选型 | 原因 | |---|---|---| | 运行时 | Node.js ≥ 18 | 现代语法、fetch、原生测试运行器 | | 框架 | Express 5 | 极简、知名度高 | | 模板 | EJS | 服务端渲染,默认自动转义 | | 数据库 | sql.js (基于 WebAssembly 的 SQLite) | 零原生编译 — 随处运行 | | Session | express-session | 服务端,签名 cookie | | 密码 | bcryptjs | 纯 JS 实现 bcrypt(无需原生构建) | | 上传 | multer | 事实上的 multipart 解析器 | ## 🔒 安全加固 — 我做了哪些强化 这原本只是一个基础的 CRUD 示例。随后我进行了手动安全审计,并应用了以下修复: ### 1. 安全响应头 (`helmet`) - 限制 script/style 来源的 Content-Security-Policy(允许 Three.js CDN) - X-Frame-Options: SAMEORIGIN(防点击劫持) - X-Content-Type-Options: nosniff - Strict-Transport-Security(仅限生产环境) - Referrer-Policy: no-referrer ### 2. CSRF 防护(双重提交 Cookie 模式) - 存储在 `req.session.csrfToken` 中的每个 session 独有的 token - 通过 `req.body._csrf` 对每个非 multipart 的 POST 请求进行验证 - 辅助局部视图 `views/partials/csrf.ejs` 会自动包含在所有 11 个表单中 - 登录时轮换 token(`req.session.regenerate`)以防 session 固定攻击 ### 3. Session 加固 - 密钥来自 `process.env.SESSION_SECRET`(开发环境下回退为随机的 48 字节十六进制字符串) - Cookie:`httpOnly`、`secure`(生产环境)、`sameSite: 'lax'`、4 小时过期 - 自定义 Cookie 名称(`toko.sid`,而非默认的 `connect.sid`) - 登录/登出时重新生成 session ID ### 4. 速率限制 (`express-rate-limit`) - `/admin/login`:每个 IP 每 15 分钟 5 次尝试 - 返回 `Retry-After` + `RateLimit-*` 标准响应头 ### 5. 文件上传校验 (`multer`) - MIME 类型白名单:`image/jpeg`、`image/png`、`image/webp`、`image/gif` - 扩展名白名单(纵深防御 — 仅靠 MIME 类型是不够的) - 文件名使用 `crypto.randomUUID()`(无用户控制的路径) - 大小限制为 2 MB - 这会阻止经典的**通过上传 `.html`/`.svg` payload 进行的存储型 XSS** ### 6. 开放重定向修复 `/cart/add` 最初会重定向到 `req.get('referer')` — 这允许攻击者构造来自 `evil.com` 的链接,在受害者加入购物车后对其进行重定向。现在会验证 referer 的主机是否与请求的主机匹配。 ### 7. 计时安全的密码比较 始终调用 `bcrypt.compareSync`(如果未找到用户,则使用一个虚拟哈希),这样响应时间就不会暴露哪些用户名是存在的。 ### 8. 原子化库存扣减 竞态条件修复:`UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?` 如果在结账过程中库存耗尽,将返回 `rowsModified = 0`。结合 transaction wrapper,可以防止超卖。 ### 9. 输入长度校验 所有文本字段都在服务端进行截断(姓名:200 字符,电话:30,地址:500,分类:100,顾客字段类似)。防止通过 10 MB 的提交导致数据库膨胀。 ### 10. 订单状态白名单 `POST /admin/orders/:id/status` 在更新之前,会针对一个枚举值验证新状态 — 防止任意状态字符串注入。 ### 11. 全局错误处理器 生产环境的响应会隐藏堆栈跟踪;开发模式则会显示它们。404 会返回一个样式化的错误页面,而不是泄露内部信息。 ## 已知的权衡 **Multipart CSRF:** CSRF 检查对 `multipart/form-data` 是跳过的,因为 multer 会消耗 body 流,并与基于 body-parser 的 token 验证相冲突。补偿性控制措施: - `SameSite=Lax` cookie(浏览器会阻止跨站 POST) - 每个管理员上传路由上都带有 `requireAdmin` 中间件 - 文件类型校验作为主要防御手段 对于高安全性的部署,请通过自定义 HTTP header(`X-CSRF-Token`)而不是 body 字段来发送 CSRF token,这可以通过表单的 JS 来设置。 ## 项目结构 ``` toko-app/ ├── server.js # Express app + all routes + security middleware ├── package.json ├── db/ │ ├── database.js # sql.js wrapper with transaction() + atomic decrement │ └── toko.sqlite # Created on first run (auto-seeded with sample data) ├── public/ │ ├── css/style.css # Olive-green palette, Fraunces + Inter fonts │ ├── js/ │ │ ├── hero-3d.js # Three.js rotating shopping basket │ │ └── tilt.js # 3D tilt effect on product cards │ └── uploads/ # Product images (gitignored) └── views/ ├── index.ejs, cart.ejs, checkout.ejs, order-success.ejs ├── error.ejs ├── partials/ │ ├── header.ejs, footer.ejs │ └── csrf.ejs # CSRF hidden input — include in every form └── admin/ ├── login.ejs, dashboard.ejs, products.ejs, orders.ejs, order-detail.ejs └── _layout_top.ejs, _layout_bottom.ejs ``` ## 生产环境检查清单 在将其部署到公共服务器之前,请执行以下操作: 1. **设置 `SESSION_SECRET`** 为一个稳定的随机值: export SESSION_SECRET=*** -e "console.log(require('crypto').randomBytes(48).toString('hex'))") 2. **设置 `NODE_ENV=production`** 以启用 `secure` cookie + HSTS 3. **修改默认管理员密码** — 编辑 `db/database.js` 的 `seedAdmin()` 或添加一个密码修改 UI 4. **放置在 HTTPS 反向代理之后**(nginx / Caddy / Cloudflare)并设置 `TRUST_PROXY=1`,以便 `req.ip` 能反映真实的客户端 IP 5. **考虑迁移**:从 `sql.js`(内存 + 持久化)迁移到 `better-sqlite3`(原生,适当的文件锁定)以应对生产环境的流量 6. **添加支付集成**(Midtrans、Xendit、Stripe)以实现真实的结账 7. **添加顾客账户**,以便可以按顾客查询订单历史 ## 如果有更多时间,我会改进的地方 - 面向顾客的订单历史 + 状态跟踪 - 新订单的电子邮件/短信通知 - 商品搜索 + 按价格筛选 - 图片变体(缩略图,响应式 srcset) - 自动化测试(`node:test` + supertest) - GitHub Actions CI,在 push 时进行 lint + 冒烟测试 - Docker Compose 实现一键部署 ## 作者 **Afiq Andico Pangimpian** — IT 专家 & 极客,印度尼西亚巴厘岛。 - GitHub: [@afiqandico13](https://github.com/afiqandico13) - 联系方式: afiqandico13@gmail.com 作为一个作品集项目而构建,旨在通过一个具体、可运行的应用程序来演示全栈 Web 安全知识,而不是抽象的例子。 ## 许可证 MIT — 查看 [LICENSE](LICENSE)。
标签:Express, GitHub Advanced Security, GNU通用公共许可证, MITM代理, Node.js, SQLite, Three.js, 安全加固, 电子商务, 自定义脚本