35 Commits

Author SHA1 Message Date
xfy
373498870a style: apply cargo fmt to workspace
Some checks failed
CI / check (push) Failing after 5m35s
CI / build (push) Has been skipped
2026-06-29 13:47:40 +08:00
xfy
1c6974ca68 fix(auth): run dummy Argon2 verify for non-existent users to prevent timing enumeration
用户不存在时执行一次固定合法哈希的 verify(必然失败),抹平与「密码错误」
路径的时序差,防止通过响应时间枚举账号(L2)。dummy 哈希走 spawn_blocking,
与真实校验路径耗时一致;响应消息保持一致。
2026-06-18 13:29:55 +08:00
xfy
05c01ccebe fix(session): serialize session-limit enforcement with row lock
login 的 COUNT→DELETE→INSERT 改为事务内执行,并对 users 行加 FOR UPDATE,
串行化同一用户的并发登录,消除超出 MAX_SESSIONS_PER_USER 的竞态(M1)。

锁定 users 行而非 sessions 表,粒度最小;commit 后无残留 DB 操作。
2026-06-18 13:27:49 +08:00
xfy
53bfb1b7c0 feat(session): invalidate all sessions on role/status change via generation
users 表加 session_generation 列。get_user_by_token 缓存命中后回查 DB
generation,不匹配则逐出缓存并视为未登录,消除降级/封禁用户的权限残留
窗口(H2)。新增 invalidate_user_sessions 用于 bump generation,当前仓库
无运行时角色变更入口,作为未来用户管理功能的基础设施预留。

- 迁移 012:ADD COLUMN IF NOT EXISTS session_generation INT DEFAULT 0
- User/SessionUser 同步加字段,From<User> 如实传递
- 缓存校验走主键查询,亚毫秒级
2026-06-18 13:26:09 +08:00
xfy
9986d1ce4e perf(auth): compile email regex once via LazyLock
auth.rs 与 comments/helpers.rs 原先每次校验都 Regex::new 重新编译。
改为 LazyLock 全局静态,正则只编译一次。
2026-06-18 11:25:15 +08:00
xfy
62600a6687 perf(auth): offload Argon2 hash/verify to spawn_blocking
Argon2 是 memory-hard 计算,登录/注册时同步执行会阻塞 Tokio worker
数百毫秒。改为在阻塞线程池执行,JoinError 兜底任务 panic。
2026-06-18 11:16:13 +08:00
xfy
518b4e5d64 refactor(auth): store SessionUser instead of full User in session cache 2026-06-18 09:55:00 +08:00
xfy
1d216faa2f feat(auth): add in-memory session cache 2026-06-17 17:20:47 +08:00
xfy
449a545886 security: fix critical issues from repository review
Some checks failed
CI / build (push) Has been cancelled
CI / check (push) Has been cancelled
P0 blockers:
- Fix migration numbering conflict and duplicate indexes
- Change comments.post_id FK to ON DELETE CASCADE
- Restrict public post detail endpoint to published posts only
- Fix rate-limiting IP extraction and fallback to ConnectInfo
- Harden HTML sanitizer: deny unknown URL schemes, restrict data URIs
- Remove session token from login response body
- Enforce image pixel/dimension limits on upload and serving

P1 high-risk:
- Validate uploads by magic bytes and decode GIF/WebP
- Add pagination/rate-limiting to search, tag posts, and comments
- Make first-admin registration and slug uniqueness check atomic
- HTML-escape comment author fields
- Improve HTML minify cache key and skip admin/error responses
- Add mobile navigation menu

P2 accessibility/quality:
- Associate form labels with inputs
- Key PostDetail article by slug to re-init scripts on navigation
- Improve image viewer keyboard accessibility
- Make theme toggle SSR-friendly and add aria-label
- Invalidate slug 404 cache on create and pending count on new comment
- Deduplicate tags case-insensitively

P3 cleanup:
- Remove unused tower-http dependency, expand make clean
- Configure DB pool timeouts and verified recycling
- Run background cleanup tasks immediately on startup
- Use SHA-256 for stable disk cache keys
- Log DB errors with Display instead of Debug
- Update README migration instructions

All tests pass (321), clippy clean, dx check clean.
2026-06-17 10:34:14 +08:00
xfy
cafbddb861 refactor: replace server-only dead_code allows with cfg(feature = "server")
Replace #[allow(dead_code)] on server-only helpers with #[cfg(feature = "server")]
to make the server/WASM split explicit and avoid compiling unused server logic
into the WASM frontend.

- Gate password/session/auth/comment helpers and model parsers with server feature
- Gate related imports and tests accordingly
- Remove genuinely unused CreatePostRequest and CreateCommentRequest
- Keep #[allow(dead_code)] for true dead code (stub methods, unused public APIs)
- Use #[cfg(any(target_arch = "wasm32", test))] for THEME_KEY
- Update AGENTS.md note
2026-06-16 16:45:08 +08:00
xfy
c60e4e08d7 fix(build): 用 cfg gate 消除 WASM 构建的 27 个假阳性 warning
Some checks failed
CI / check (push) Failing after 5m17s
CI / build (push) Has been skipped
Dioxus fullstack 在 WASM 构建中会剥离 #[server] 函数体,导致仅在
函数体内使用的导入/函数/常量被误报为 unused / dead_code。

- 仅在服务端使用的导入加 #[cfg(feature="server")]:get_conn、
  PostStatus/PostStats/Tag、password/session、User/UserRole、
  slug.rs 的 dioxus::prelude(保留 ServerFnError 给 server-gated
  的 ensure_unique_slug)
- 仅在服务端使用的项加 cfg gate:clamp_pagination 及其常量、
  rebuild 常量、format_relative_time、compute_content_hash、
  CommentStatus::from_str
- CommentCountResponse/PendingCommentsResponse 出现在 #[server]
  签名中(WASM 保留),无法 gate,改用 #[allow(dead_code)],
  与现有 CreateCommentRequest 处理一致

make build 零 warning,cargo test 299 passed。
2026-06-16 11:29:18 +08:00
xfy
26b012c40c docs(api, auth): 补充中文注释 2026-06-12 18:27:24 +08:00
xfy
942ac853fe refactor: tighten module-level allow attributes 2026-06-12 17:26:46 +08:00
xfy
294d60afab style: format rust code
Some checks failed
CI / build (push) Has been cancelled
CI / check (push) Has been cancelled
2026-06-12 17:14:31 +08:00
xfy
4ae7b38131 feat(auth): add Secure flag to session cookie via COOKIE_SECURE env 2026-06-12 17:02:22 +08:00
xfy
04737300e6 feat(comments): add complete comment system with guest commenting, moderation, and admin UI
Implements a fully self-built comment system for the blog:

Data layer:
- comments table with BIGSERIAL PK, parent_id self-reference (ON DELETE SET NULL),
  depth tracking (max 20), status workflow (pending/approved/spam/trash),
  content hashing for dedup, GDPR consent tracking, IP/UA storage with auto-purge
- 5 partial indexes optimized for read patterns
- updated_at auto-trigger

API (9 Dioxus server functions):
- Public: get_comments, get_comment_count, create_comment
- Admin: get_pending_comments, get_pending_count, get_all_comments,
  approve_comment (with ancestor auto-approval), spam_comment, trash_comment,
  batch_update_comment_status

Security:
- Function-level rate limiting (1/sec, burst 5) via FullstackContext IP extraction
- Input validation (name, email, URL scheme, content length, consent)
- Parent chain validation (must be approved, same post)
- Strict comment Markdown renderer (headings→strong, no img/id/data URIs, nofollow links)
- Honeypot anti-spam field
- 5-minute dedup window via SHA-256 content hash

Frontend:
- CommentSection with SuspenseBoundary isolation
- Flat-list rendering with depth-based CSS indentation (responsive)
- Gravatar via cravatar.cn (server-computed, email never exposed)
- Inline reply forms (one-at-a-time via Signal)
- Admin action buttons (approve/spam/delete) visible per-comment
- CommentForm with privacy consent, Markdown hint, loading states

Admin:
- /admin/comments page with status tabs, batch operations, pagination
- Pending count badge on admin dashboard

Infrastructure:
- Shared get_current_admin_user moved from posts/helpers to auth module
- COMMENT_LIMITER rate limiter tier
- Moka caches (60s TTL for comments, 10s for pending count)
- IP/UA purge background task (daily, 90-day retention)
2026-06-11 12:34:26 +08:00
xfy
ccd564312b feat: hash session tokens and enforce configurable session limit per user 2026-06-11 09:45:56 +08:00
xfy
bf401e4916 fix(api): correct AppError variant semantics for transaction/query errors
- client.transaction() failures: db_conn -> tx (logs correct category)
- auth session INSERT: db_conn -> query
- delete_post UPDATE: tx -> query (no transaction context)
2026-06-10 13:19:57 +08:00
xfy
a3fa602df2 refactor(api): unify error handling with AppError enum
Replace scattered ServerFnError::new("...") inline calls and utils.rs
helper functions with a single AppError enum that provides:
- Automatic tracing::error logging for system errors
- Safe generic messages to frontend (no SQL/connection detail leaks)
- Typed error variants (Unauthorized, Forbidden, NotFound, etc.)
- 5 unit tests for message passthrough and detail hiding

Removes src/api/utils.rs entirely; all error creation now goes through
src/api/error.rs.
2026-06-10 13:16:06 +08:00
xfy
4f368e6fb8 test: add 122 unit tests across 12 modules
Cover utils/text, api/slug, auth/password, auth/session,
models/post, models/user, api/auth validation, api/markdown,
api/image, highlight, api/rate_limit. Add make test target.
2026-06-09 09:25:44 +08:00
xfy
e74b9f3c39 feat: apply rate limiting to Register, Login, and upload endpoints
- Remove unused tags.rs and related re-exports
- Convert strict_limit/upload_limit from Layer to manual check functions
- Add IP-based rate limiting checks to Register, Login, and upload_image
- Keep general_limit as global middleware for all other routes
2026-06-08 17:30:26 +08:00
xfy
e5d37aa034 refactor(api): extract error helper functions, DRY up db/query/tx error handling 2026-06-08 16:17:03 +08:00
xfy
675349a5b4 fix: add #[cfg(feature = server)] to server-only imports for WASM compatibility 2026-06-04 16:40:42 +08:00
xfy
28707d3c3a refactor: reuse session validation in get_current_admin_user via get_user_by_token 2026-06-04 16:02:47 +08:00
xfy
d3be04bcec refactor: extract session parsing to auth/session.rs and db error helpers to api/utils.rs 2026-06-04 15:16:40 +08:00
xfy
593666135c feat: add DB connection retry logic with get_conn() helper 2026-06-04 10:03:56 +08:00
xfy
f5413e00cc fix(auth): prevent password_hash from reaching the frontend
Introduce PublicUser struct without password_hash field. The
get_current_user server function now returns PublicUser via
CurrentUserResponse, so Argon2 hashes are never serialized to WASM.

Internal server-side functions (get_current_admin_user) continue
using the full User struct.
2026-06-03 10:32:15 +08:00
xfy
9c5b09a278 chore: code cleanup - formatting, EOF newlines, model helper, and UI tweaks 2026-06-02 17:33:28 +08:00
xfy
32e8407ed7 fix: use Debug format {:?} for database errors
tokio-postgres Display only shows 'db error', use {:?} for full error chain
2026-06-02 17:33:28 +08:00
xfy
5695b1c62b feat: add explicit error logging and startup validation
- Add DATABASE_URL validation on server startup (exit early with clear error)
- Add tracing::error! to all server functions in api/auth.rs
- Bulk add tracing::error! to all database error handlers in api/posts.rs
- Server 500 errors now log detailed context to terminal
2026-06-02 17:33:28 +08:00
xfy
0be0719fdb 将 session cookie 改为后端 HttpOnly 设置
- login 通过 Set-Cookie 响应头设置 HttpOnly cookie
- get_current_user 从请求 Cookie header 读取 token,匹配具体 session
- logout 通过 Set-Cookie 清除 cookie,并删除对应 session
- 移除前端 document.cookie 操作代码

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 23:54:58 +08:00
xfy
91d9c04a3d 支持使用邮箱登录
- 登录查询同时匹配 username 或 email
- 更新登录页标签和 placeholder

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 16:16:40 +08:00
xfy
c4dfd1f445 Fix additional clippy warnings and update progress
- Add #[allow(dead_code)] to temporarily unused functions
- Remove unused is_expired() and UserRole::as_str()
- Fix unused variable warnings (token, theme)
- Update progress.txt: mark all stories complete

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:02:14 +08:00
xfy
14b92c3a89 US-005: 后台页面与路由整合
- src/router.rs: Dioxus 路由定义 (/login, /register, /admin, /)
- src/main.rs: 整合所有模块 + server block 启动 dotenvy + session 清理任务
- src/tasks/session_cleanup.rs: 每小时清理过期 session
- 修复编译错误: chrono serde, tokio-postgres chrono, argon2 OsRng
- 移除 server_context 依赖,简化 get_current_user 查询
- Tailwind CSS CDN + darkMode class 配置

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:29:50 +08:00
xfy
b2a1e13c79 US-003: 认证 API (Server Functions)
- src/api/auth.rs: register, login, logout, get_current_user
- register(): 输入验证 + 首个用户 admin + 后续关闭注册
- login(): argon2 验证 + session 创建 + token 返回
- logout(): session 清理
- get_current_user(): 从 cookie 读取 token,返回 Option<User>
- 使用 server_context().request_parts() 读取请求 cookie

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:21:11 +08:00