Commit Graph

12 Commits

Author SHA1 Message Date
wangjia afcd7b325c feat(codes): implement activation-code module (tsk_tFMU7-hKzfOf)
Implements server/internal/codes/ with all required functionality:

## Generator (generator.go)
- Crockford Base32 16-char codes (15 data + 1 Crockford mod-37 check char)
- crypto/rand for unbiased random generation with rejection sampling
- Canonicalize(): I/L→1, O→0 folding, hyphen/space stripping
- Hash(): SHA-256 of canonical plaintext (only value stored in DB)
- Algorithm hard-coded; check char detects all single-char substitution errors

## Store (store.go)
- MySQL-backed via database/sql
- CreateBatch / CreateCode with ErrDuplicate on UNIQUE conflict
- FindCodeByHashForUpdate: SELECT … FOR UPDATE for row-level concurrency control
- MarkRedeemed, ExtendSubscription, CreateSubscription, GetActiveSubscriptions
- WriteAuditLog, CodeExistsByHash
- BeginTx at READ COMMITTED (FOR UPDATE provides row exclusivity)

## Service (service.go)
- Redeem(): 9-step flow with full idempotency and concurrency safety
  - isLocked / recordFail / clearFail via Redis key redeem:fail:{user_id}
  - SELECT … FOR UPDATE → single winner under N-concurrent redemptions
  - Same-plan: extends existing subscription (max(expires_at,now)+days)
  - Cross-plan: creates new subscription (max(now,latest)+days)
  - Idempotent: same user re-submits → 200 without re-applying
  - 5 failures → ACCOUNT_LOCKED for 1 hour (sliding window via Redis)
- CreateBatch(): generates N codes, stores hashes, returns plaintext once
  - Automatic retry on hash collision (birthday probability ≈10⁻⁸)

## Webhook (webhook.go)
- POST /webhook/store/codes — outside /v1, no JWT required
- HMAC-SHA256 with hmac.Equal constant-time comparison
- ±5 min timestamp window
- Redis SetNX nonce deduplication (15-min TTL)
- Idempotent: duplicate nonce → 200; duplicate code_hash → 200

## HTTP Handler (handler.go)
- POST /v1/redeem endpoint wired to Service.Redeem
- Reads userID from context key (set by JWT middleware from auth module)
- Bilingual error responses {code, message_zh, message_en}

## Export (export.go)
- ExportCSV(): streams plaintext codes to io.Writer as CSV
- Plaintext NEVER stored in DB; only SHA-256 hash persists

## CLI (cmd/codegen/main.go)
- codegen -plan -days -count -channel -note -dsn [-out]
- Transition tool until admin panel (#8) is ready
- Outputs CSV to stdout or file; warns operator about plaintext sensitivity

## Infrastructure (skeleton)
- internal/config/config.go: env-var configuration
- internal/db/db.go: MySQL connection pool helper
- internal/redisutil/redis.go: Redis client constructor
- internal/apierr/apierr.go: bilingual error types
- migrations/001_init.sql: DDL for codes module tables
- go.mod with all dependencies

## Tests
- generator_test.go (unit, no deps):
  - Format, uniqueness (10k codes, zero collisions), normalization,
    check-char detection of all single-char errors, hash consistency
- webhook_test.go (unit, no deps):
  - Signature rejection, missing signature, stale/future timestamp,
    missing nonce, constant-time HMAC comparison
- service_test.go (//go:build integration, testcontainers):
  - N=20 concurrent redeemers → exactly 1 winner
  - Idempotent redeem returns success for same user
  - Other-user redemption → CODE_REDEEMED + failure counted
  - 5 failures → ACCOUNT_LOCKED on 6th attempt
  - Same-plan extension: expires_at precision assertion
  - Cross-plan creation: new subscription row
  - Audit log written on every successful redemption
  - CSV export: no plaintext in DB, hash present
  - Webhook nonce replay: idempotent 200
  - Webhook same-hash: idempotent 200, single DB row

Run unit tests: make test
Run integration tests: make test-integration (requires Docker)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 02:16:16 +08:00
wangjia a642bf16a2 feat: 同步 design/ 设计系统(含 iPad tablet kit) + 架构任务拆分(todo/)
design/ 同步自最新设计导出,新增 ui_kits/tablet/ 平板分栏布局;todo/ 录入 18 个并行实施任务。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:57:57 +08:00
wangjia 3ae96ef229 docs: 新增整体架构设计文档(doc/,HTML 七章)
覆盖前端五端/后端/MySQL/弹性节点拓扑/Web安全/安全总纲,遵循 design/ 设计系统视觉呈现。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:57:49 +08:00
wangjia 4bff8593e7 docs: 新增 CLAUDE.md(部署架构与主机约束指引)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 23:30:08 +08:00
wangjia 73e026a4df edge 容器自带证书续期(certbot in nginx 镜像)
deploy-pangolin / deploy (push) Has been cancelled
- edge 改为自定义镜像:官方 nginx + certbot + dns-cloudflare 插件
- pangolin-entrypoint.sh:后台每12h certbot renew,续期后本容器内 nginx -s reload
- 不再需要宿主 cron / docker socket / 独立 certbot 服务做日常续期
- /etc/letsencrypt 改 rw 挂载,挂入 cloudflare.ini
- compose 去掉 build:(宿主 docker-compose 不支持 compose build),改脚本 docker build
- deploy.sh/cutover.sh: docker build -t pangolin-edge:local 后再 up

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 23:10:17 +08:00
wangjia 39e7f2fc11 回滚:rollback.sh + docs/回滚手册.md
deploy-pangolin / deploy (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 17:09:00 +08:00
wangjia 1bb3a26f0c cutover: 失败自动回滚到宿主 nginx
deploy-pangolin / deploy (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 16:52:05 +08:00
wangjia fa3954724c print-clients: 修正提示路径为 ~/pangolin
deploy-pangolin / deploy (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 16:45:15 +08:00
wangjia 1a346c32c1 方案C:分发层容器化(edge nginx)+ Hy2 + DNS-01 续期
deploy-pangolin / deploy (push) Has been cancelled
- edge: 容器化 nginx 接管 80/443,逐字移植现网 vhost(blog/jiu/marzban/pay)+ stream SNI 分流
- singbox: Hysteria2(host 网络,UDP 443)
- certbot: DNS-01(Cloudflare)续期,容器化
- xray: REALITY 仅 profile newnode(本台沿用 Marzban)
- deploy.sh 幂等且切换感知;cutover.sh 一次性停宿主 nginx 切容器(可秒级回滚)
- 弃用方案A 的 host nginx-apply/sudoers/root-setup
- 部署目录改 ~/pangolin(免 root);CI 注入 CF_API_TOKEN

EC2 实测:edge nginx -t 通过(真实证书)、compose 校验通过

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 16:42:30 +08:00
wangjia bc77e85b7d root-setup: 同时创建并授权 /opt/pangolin 部署目录
deploy-pangolin / deploy (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 11:52:32 +08:00
wangjia cf96989e01 Phase 0: 容器化数据面 + CI/CD 自动部署
deploy-pangolin / deploy (push) Has been cancelled
- deploy/: Xray VLESS+REALITY (11443, 经宿主 nginx SNI 分流) + sing-box Hysteria2 (UDP 443) docker-compose
- 幂等 deploy.sh / gen-secrets.sh / print-clients.sh;受限 nginx-apply 特权脚本 + 一次性 root-setup
- .gitea/workflows/deploy.yml: NAS runner 经 SSH 远程部署到 EC2
- docs/ plan/: 设计方案与实施计划

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 11:48:47 +08:00
wangjia 833b8365da Initial commit 2026-05-29 23:53:38 +00:00