Files
mailgo/internal/auth/oauth2_email_test.go
T
kevin b1c86d436d fix(security): 写信配额泄漏统一补偿,OAuth2 仅接受已验证邮箱
安全审计 #21/#22(security_todo.md P2):

- #21 DoSend 配额补偿收敛为 defer + persistedBytes 统一对账:
  预扣总量与实际落库量的差额在任何退出路径退还,覆盖外发入队
  失败/本地投递失败/Sent 副本保存失败三条提前 return 的泄漏路径;
  移除文件读取与附件保存失败的分散回退
- #22 OAuth2 仅接受已验证邮箱:GitHub /user/emails 过滤
  verified==true(主邮箱优先,其次任一已验证);Google
  email_verified 为 false 或缺失时拒绝(fail closed);解析逻辑
  抽为 parseGitHubEmail/parseGoogleEmail 纯函数,不依赖 IdP
  当前只返回已验证邮箱的实现细节
- 新增 8 项单测(DoSend 回退/不误退、GitHub/Google 邮箱解析矩阵)
- security_todo.md 勾选 #21/#22
2026-08-28 14:15:09 +08:00

73 lines
2.3 KiB
Go

package auth
// OAuth2 邮箱解析回归测试(#22):仅接受已验证邮箱——未验证邮箱可能
// 不属于该账号所有者,放行会允许冒充既有用户登录(账号接管)。
import (
"strings"
"testing"
)
func TestParseGitHubEmailPrefersVerifiedPrimary(t *testing.T) {
body := []byte(`[{"email":"p@example.com","primary":true,"verified":true}]`)
email, err := parseGitHubEmail(body)
if err != nil {
t.Fatalf("parseGitHubEmail: %v", err)
}
if email != "p@example.com" {
t.Fatalf("email = %q, want p@example.com", email)
}
}
func TestParseGitHubEmailSkipsUnverifiedPrimary(t *testing.T) {
// 主邮箱未验证:不能用它登录,回退到任一已验证邮箱
body := []byte(`[
{"email":"unverified@example.com","primary":true,"verified":false},
{"email":"verified@example.com","primary":false,"verified":true}
]`)
email, err := parseGitHubEmail(body)
if err != nil {
t.Fatalf("parseGitHubEmail: %v", err)
}
if email != "verified@example.com" {
t.Fatalf("email = %q, want verified@example.com", email)
}
}
func TestParseGitHubEmailRejectsAllUnverified(t *testing.T) {
body := []byte(`[
{"email":"a@example.com","primary":true,"verified":false},
{"email":"b@example.com","primary":false,"verified":false}
]`)
_, err := parseGitHubEmail(body)
if err == nil || !strings.Contains(err.Error(), "已验证") {
t.Fatalf("expected unverified-email rejection, got %v", err)
}
}
func TestParseGitHubEmailMalformedBody(t *testing.T) {
if _, err := parseGitHubEmail([]byte("not json")); err == nil {
t.Fatal("expected error for malformed body")
}
}
func TestParseGoogleEmailAcceptsVerified(t *testing.T) {
email, err := parseGoogleEmail([]byte(`{"email":"a@example.com","verified_email":true}`))
if err != nil {
t.Fatalf("parseGoogleEmail: %v", err)
}
if email != "a@example.com" {
t.Fatalf("email = %q, want a@example.com", email)
}
}
func TestParseGoogleEmailRejectsUnverified(t *testing.T) {
if _, err := parseGoogleEmail([]byte(`{"email":"a@example.com","verified_email":false}`)); err == nil {
t.Fatal("expected rejection for verified_email=false")
}
// verified_email 缺失按未验证处理(fail closed)
if _, err := parseGoogleEmail([]byte(`{"email":"a@example.com"}`)); err == nil {
t.Fatal("expected rejection when verified_email is missing")
}
}