- 越权面(全部 before/after 实测可复现,10 条):cabinets/filter 的 project_id/organization_id
参数分支越权(客户可拿对方向柜全字段)、organization-tree 返回全量组织树、
cabin-boards/compartments/cabinets 三个 options 无归属校验、batteries 无归属(改为经
charge_orders→cabinets→projects 间接归属)、feedback 跨组织读且能改他人状态、
GET /downloads/{id} 漏 user_id 过滤(跨用户元数据泄露)、
POST /downloads/cleanup 全局删除所有组织导出(改客户侧只清自己)、
/api/nodes/* 仅需登录(客户可读内网 IP 并注销节点)→ 收紧 role_level>=2;
/api/devices/stats 同样收紧;新增共享闸门 auth::require_super_admin
- TCP 登录门控:下行投递改为"登录前白名单"(PRE_LOGIN_ALLOWED_ACTS),与上行同一规则
- 60 秒超时对齐协议:挂起项 TTL 120→60 + 值内嵌 deadline 精确判窗 + 超时 WARN(兼容老格式)
- 下发前在线判据改连接级认证标记 device:authed:{imei}(消除"管理端显示成功、设备收不到")
⚠️ device-server 与 api-server 必须同批发布
- 协议对齐:auth_str 登记校验提到限流之前、build_spec Topology 分支 clamp(15/8)、
行号引用改小节引用、修 3 处错引小节
- 状态语义:compartments.status 只表占用,充满/故障由通道状态派生;柜级 status1/status2
与开门按钮(柜级独立动作不联动);列注释对齐(cabinets.status 补 2=禁用、
charge_orders.status 标注 2 保留未使用)
- 假绿单测修复:超时 SQL 抽常量 + 纯函数孪生体双向断言(方向写反必红)
- RSSI 两端统一为同源纯函数 5 档(小程序显示随之变化)+ test/rssi_signal.test.mjs
- nginx:扫码入口 abstract 限流(zone=scan_abstract_zone 30r/m burst=20)+ 主配置仓库镜像
- 文档:AGENTS.md 新增「踩过的坑」7 条教训 + 部署注意事项 11 条 + 文档口径优先级;
docs/API.md 接口总表 80 条与 mod.rs 逐条一致、Redis 契约按代码重写、9 处事实错误修正;
权威协议文档 v1.1.1(协议行为未变,仅修订文档自身的错误/矛盾/缺失)
104 lines
4.3 KiB
Plaintext
104 lines
4.3 KiB
Plaintext
server {
|
||
listen 80;
|
||
server_name app.anzhizhichong.com;
|
||
return 301 https://$server_name$request_uri;
|
||
}
|
||
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name app.anzhizhichong.com;
|
||
|
||
ssl_certificate /etc/nginx/ssl/app.anzhizhichong.com.pem;
|
||
ssl_certificate_key /etc/nginx/ssl/app.anzhizhichong.com.key;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||
|
||
# ===== 安全响应头(2026-09 安全加固)=====
|
||
# HSTS:本域强制 HTTPS(暂不加 includeSubDomains,避免影响未知子域)
|
||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||
# 禁止 MIME 嗅探:/uploads 免认证同源直出,防上传内容被当 HTML/JS 执行
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
# CSP 先 Report-Only 观察(只上报不拦截),确认 SPA 无违规后再考虑收紧为强制
|
||
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always;
|
||
|
||
# 产品介绍页
|
||
root /data/landing;
|
||
index index.html;
|
||
|
||
location /doc/ {
|
||
proxy_pass http://127.0.0.1:3080/;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
client_max_body_size 500m;
|
||
}
|
||
|
||
# 登录接口限流(防暴力破解第二道;第一道在 api-server 内按手机号+IP 计数锁定)
|
||
location = /api/pms/auth/login {
|
||
limit_req zone=auth_login_zone burst=10 nodelay;
|
||
proxy_pass http://127.0.0.1:9000/api/auth/login;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
|
||
# 验证码接口限流(免认证且逐像素渲染,是 CPU 放大攻击面)
|
||
location = /api/pms/auth/challenge {
|
||
limit_req zone=auth_challenge_zone burst=10 nodelay;
|
||
proxy_pass http://127.0.0.1:9000/api/auth/challenge;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
|
||
# 扫码入口(免认证,协议要求:扫二维码 → abstract_id 查柜子)
|
||
# 限流依据:abstract_id 形如 `10-00000001`,可被脚本枚举(能判断柜子是否存在、
|
||
# 柜名与仓体结构)。正常扫码是"一次扫码 1~2 个请求"的高频正常行为,
|
||
# 故阈值比登录/验证码宽松得多:30r/m 持续 + burst=20 瞬时
|
||
# (同一 IP 连扫 20 次不排队),429 只可能来自脚本枚举/刷取。
|
||
location /api/pms/cabinets/abstract/ {
|
||
limit_req zone=scan_abstract_zone burst=20 nodelay;
|
||
proxy_pass http://127.0.0.1:9000/api/cabinets/abstract/;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
|
||
location /api/pms/ {
|
||
proxy_pass http://127.0.0.1:9000/api/;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
client_max_body_size 10m;
|
||
}
|
||
|
||
# 头像上传文件(免认证同源直出)——限流;nosniff 由 server 级继承
|
||
location /uploads/ {
|
||
limit_req zone=uploads_zone burst=60 nodelay;
|
||
proxy_pass http://127.0.0.1:9000/uploads/;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
expires 30d;
|
||
}
|
||
|
||
# PMS管理前端 - 不带斜杠时重定向
|
||
location = /pms {
|
||
return 301 /pms/;
|
||
}
|
||
|
||
# PMS管理前端
|
||
location /pms/ {
|
||
alias /data/pms/web/;
|
||
try_files $uri $uri/ /pms/index.html;
|
||
}
|
||
}
|