Commit Graph
14 Commits
Author SHA1 Message Date
kevin dc164fdf66 feat(mailbox): 文件夹数据驱动,Web 通过 IMAP 共享服务层操作邮箱
- 新增 mailboxes 表与 MailboxStore(系统文件夹幂等创建,自定义文件夹 CRUD)
- 提取 MailboxService:IMAP 会话与 Web handler 共用,LIST 返回什么 Web 就显示什么
- IMAP 支持 CREATE/DELETE/RENAME/SUBSCRIBE(系统文件夹禁删改、非空禁删)
- Web 删除改为 IMAP 语义:移入 Trash,新增恢复/彻底删除/清空
- 新增通用 /folder/:name 页面与动态侧边栏,/inbox /sent /drafts 保留兼容重定向
2026-08-20 01:25:40 +08:00
kevin 962c5f454c fix(imap): 升级 go-imap v2 + \Deleted 落库,修复删除邮件只复制到垃圾桶
删除流程(Apple Mail/iOS 等):UID COPY→Trash + UID STORE \Deleted +
UID EXPUNGE。此前 go-imap v1.2.1 不支持 UID EXPUNGE(回
Command unsupported with UID),且 \Deleted 只存内存会话 map(重选/
重连即丢失),导致 EXPUNGE 不删原邮件——垃圾桶只有副本、INBOX 原封
不动。

- 依赖:go-imap v1.2.1 → go-imap/v2 v2.0.0-beta.8(原生支持 UID
  EXPUNGE,且库为 race-clean,移除集成测试 !race 标签)
- Message 新增 IsDeleted 字段持久化 \Deleted 标记(AutoMigrate 自动
  加列,SQLite/MySQL 通用;migrate 工具随模型字段自动复制)
- IMAP 层重写为 v2 imapserver.Session 架构(session.go 替代
  backend.go):SELECT/STATUS/LIST/FETCH/SEARCH/STORE/COPY/MOVE/
  APPEND/EXPUNGE/Poll/Idle;EXPUNGE 按数据库 IsDeleted 删除,UID
  EXPUNGE 只删集合内已标记消息
- server.go:显式能力集(IMAP4rev1+UIDPLUS+MOVE+LITERAL++CHILDREN+
  SPECIAL-USE);自实现 mailboxHub 跨会话推送(含来源排除,避免 v2
  库 MailboxTracker 对 EXPUNGE/EXISTS 的回声重复响应);Pusher 接口
  签名不变,Web/SMTP/POP3 调用方零改动;DisconnectByAddr 改用会话
  注册表 + conn.Bye()
- 测试:集成测试改用 v2 imapclient,新增删除流程/跨会话持久化/MOVE
  回归用例;notify_test 适配 hub 推送模型并覆盖来源排除与
  allowExpunge 语义
2026-08-20 00:44:37 +08:00
kevin f533fc8feb Merge pull request 'feat(db): 支持 MySQL 迁移(修复模型外键冲突 + 大文本类型 + 迁移工具)' (#13) from dsh/mailgo:fix/mysql-migration into main
Reviewed-on: kevin/mailgo#13
2026-08-19 11:49:06 -04:00
dsh 07e81fc328 feat(db): 支持 MySQL 迁移(修复模型外键冲突 + 大文本类型 + 迁移工具)
- Attachment 模型移除 Message 关联:其 foreignKey 名 MessageID 与
  Message.MessageID 字符串字段冲突,GORM AutoMigrate 会生成错误外键
  (messages.message_id → attachments.id 且强转 bigint),MySQL 下建表
  直接失败(SQLite 因动态类型侥幸可用)
- Message.TextBody/HtmlBody 改 mediumtext:MySQL TEXT 仅 64KB,
  大 HTML 邮件会写入失败
- 新增 cmd/migrate:SQLite → MySQL 一次性迁移工具(GORM 模型读源、
  批量写目标、时间统一 UTC、ban_entries 零值时间转 NULL、逐表校验)
2026-08-19 11:26:32 -04:00
dsh 6d5a7a8ed4 fix(imap): mailbox_states 表加入 AutoMigrate(此前漏迁移导致 UIDVALIDITY 回退 1) 2026-08-19 09:45:39 -04:00
dsh 7ede29a18a fix(imap): UIDVALIDITY 改为持久化随机值,修复数据库重建后客户端缓存永不失效
根因:Status() 硬编码 UidValidity=1。数据库重建后消息 ID 空间
完全变化(uid 2-166 对应完全不同的邮件),但 UIDVALIDITY 不变,
Thunderbird 认为本地缓存(旧 uid 1-166)仍然有效,只下载"新增"
(uid 167:*)与"缺失"(2,4,8,9)的少数邮件——列表只剩 1-3 封,
每次刷新数量随机。

修复(RFC 3501 UIDVALIDITY 语义):新增 mailbox_states 表,
每(用户,文件夹)首次访问随机生成并持久化 UIDVALIDITY;数据库
重建后新值触发客户端丢弃缓存全量重同步。0 值修正,不同用户/
文件夹互相独立。

新增 TestMailboxStateUidValidity 单元测试。
2026-08-19 09:36:55 -04:00
kevin 07ba01a6f6 fix(imap): 修复 Thunderbird 未读状态丢失(SQLite 并发写 + 客户端 seq 错位)
A. SQLite 并发写失败被吞(高概率根因):
- DSN 追加 _busy_timeout=5000&_journal_mode=WAL&_synchronous=NORMAL,
  WAL 下读不阻塞写,消除瞬时 SQLITE_BUSY
- UpdateMessagesFlags 不再吞 MarkReadState/MarkFlagged 错误:
  出错记日志并返回 error(客户端收到 NO 会重试)
- Web MarkRead/Delete 同款静默丢错补日志

B. 客户端 seq 视图错位(集成测试复现后修复):
- 真实服务器+脚本客户端测试复现:客户端按日期倒序自编号发
  seq 式 STORE 时,旧排序(id ASC)把最旧邮件标为已读
- 规范排序改为 date DESC, id DESC(最新在前),与主流客户端
  默认视图一致;buildNewMessageUpdate 改用 seqOf 取真实序号
- 新增 UID STORE / 服务器下发 seq STORE / 自编号 seq STORE 集成测试

注:go-imap v1.2.1 存在库内 *conn.silent() 数据竞争(启用推送后
必然触发),集成测试以 //go:build !race 排除,-race 下由单元测试
覆盖推送逻辑
2026-08-19 20:30:06 +08:00
kevin f2493da03e feat(security): IP 阶段性封禁,前3次触发不封禁、第4次起按档位递增
- 封禁规则:达到失败阈值记为一次触发,前 3 次只计数不封禁;
  第 4 次起封禁并按档位递增:30分钟(ban_duration_min)→ 3小时
  → 3个月 → 半年(上限);封禁过期后保留记录作为升档依据,
  成功登录或管理员解封清零
- BanEntry 新增 BanCount(累计触发次数),每 IP 一条记录 upsert,
  不再重复建行;RecordAuthFailure 统一 Web/LDAP/SMTP/IMAP/POP3
  五处封禁逻辑,原因带档位(如"第1次封禁:登录失败次数过多
  (第4次触发,失败5次)")
- 黑名单页修复:列表仅显示已封禁或曾封禁记录(原因/到期时间必填),
  新增封禁次数列与封禁中/已过期状态徽章,移除清理过期按钮
- 用户封禁页显示第 N 次封禁档位;新增档位升级与列表过滤单测
2026-08-19 19:07:25 +08:00
kevin 353bfa88f2 feat: 新增 SMTP/IMAP/POP3 协议调用日志(含攻击分析筛选)
- 每个连接记录一条日志:协议、端口、来源 IP、用户名、成功/失败、
  失败原因(密码错误/IP封禁/中继被拒/发件人伪造/未认证发信等)、
  操作摘要、消息数与会话时长
- 管理后台新增「协议日志」页:按协议/状态/IP/用户名/时间筛选,
  今日与历史成功/失败统计卡片,分页查看,可手动清理
- 后台每 6 小时自动清理超出 protocol_log_keep_days(默认30天)
  的日志;新增 [web] protocol_log_keep_days 配置项
- 修复 POP3 认证既有 bug:handleUSER 丢弃邮箱域名导致 PASS 永远失败
- 新增 store 单测、SMTP/POP3 端到端测试与模板渲染测试
2026-08-19 18:49:29 +08:00
kevin 3f28ec20f4 fix(security): 修复 P2 中危项(cookie/协议限速/路径遍历/默认口令/中继TLS/安全头/信息泄露)
- 会话 cookie 增加 Secure 标志;新增 [web].cookie_secure 配置
  (默认 true,仅本地 HTTP 调试关闭;缺失字段按安全默认处理)
- SMTP/IMAP/POP3 认证接入封禁体系(store.RecordAuthFailure 与 Web
  共用 ban_entries):失败计数达 ban.max_fail_attempts 即封禁 IP,
  已封禁 IP 拒绝认证,堵住协议层暴力破解
- 附件存储路径遍历防护重写:FullPath 白名单校验(UUID 文件名格式)
  + baseDir 前缀兜底,非法路径返回错误;Save 扩展名白名单化
- 初始管理员不再使用 admin/admin:密码取 MAILGO_ADMIN_PASSWORD 或
  随机生成并打印一次;新增 MustChangePassword 首登强制改密
  (管理员重置密码同样触发)
- 外发中继默认验证 TLS 证书(保护 AUTH 凭据,防 MITM),直投 MX
  保持机会式 TLS;新增 outbound.relay_tls_insecure 开关(默认 false)
- 新增安全响应头中间件:HSTS、X-Frame-Options DENY、nosniff、
  Referrer-Policy、基础 CSP(frame-ancestors 'none' 防点击劫持,
  connect-src/form-action 'self' 防数据外泄)
- LDAP/OAuth 登录错误统一为通用文案,原始错误只写日志,
  不再回显邮箱/内部细节(防用户枚举与信息泄露)
- 新增 25 个回归测试:cookie 标志、封禁阈值、路径遍历用例、
  中继 TLS 验证(自签证书 STARTTLS 集成)、安全头、OAuth 文案

部署注意:升级后所有会话失效需重新登录;若直接以 HTTP 提供
服务需显式配置 cookie_secure = false。
2026-08-19 16:45:21 +08:00
dsh 5eb9bc2c71 feat: 实现对外邮件投递(外发队列 + MX 直投 + DKIM + 退信 + 管理后台)
- 新增 internal/outbound 模块:MX 查询、SMTP 出站客户端(EHLO/STARTTLS/
  MAIL/RCPT/DATA/QUIT)、4xx 临时失败与 5xx 永久失败分类、8BITMIME 支持
- 新增 outbound_messages 队列表与 OutboundStore,后台 worker 指数退避重试
- 永久失败/超限退信到发件人收件箱,包含原因与目标收件人
- 外发邮件使用域名 DKIM 私钥签名(go-msgauth)
- SMTP 提交集成:认证用户可发外部收件人,MAIL FROM 必须等于登录用户邮箱,
  未认证外部投递明确拒绝(防开放中继)
- Web 发信集成:外部收件人自动入队,附件以 multipart/mixed + base64 编码
  加入邮件正文
- 每用户每分钟/每日发送限速(max_per_day=0 可禁用外部投递)
- 管理后台新增外发队列页面:状态统计、失败原因、手动重试/取消
- 新增 [outbound] 配置段并更新 README / todo.md
2026-08-15 17:18:54 -04:00
kevin 3dcc3f9a35 up 2026-06-01 20:43:01 +08:00
kevin 4e233c82b4 二阶段差不多 2026-06-01 19:46:51 +08:00
kevin 9e50d05e71 一阶段ok 2026-06-01 18:59:55 +08:00