发布 0.1.0:通知渠道、告警规则、令牌版本与安全加固
This commit is contained in:
@@ -2,6 +2,8 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -183,3 +185,103 @@ func TestSystemLogsEndpoint(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoginFieldLimits 超长登录字段被绑定校验拒绝(S-03 高基数防护第一道)。
|
||||
func TestLoginFieldLimits(t *testing.T) {
|
||||
r, _, _ := newTestRouter(t)
|
||||
longName := strings.Repeat("a", 65)
|
||||
longPass := strings.Repeat("b", 129)
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
}{
|
||||
{name: "用户名超长", body: `{"username":"` + longName + `","password":"x"}`},
|
||||
{name: "密码超长", body: `{"username":"admin","password":"` + longPass + `"}`},
|
||||
{name: "验证码超长", body: `{"username":"admin","password":"x","totpCode":"123456789"}`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := doRequest(t, r, http.MethodPost, "/api/v1/auth/login", "", tt.body)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("status = %d, want 400", w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBodyLimitRejectsHuge 面板 API 请求体超 1MB 被拒(S-03)。
|
||||
func TestBodyLimitRejectsHuge(t *testing.T) {
|
||||
r, _, _ := newTestRouter(t)
|
||||
huge := `{"username":"admin","password":"` + strings.Repeat("x", 2<<20) + `"}`
|
||||
w := doRequest(t, r, http.MethodPost, "/api/v1/auth/login", "", huge)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("status = %d, want 400(body too large)", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRevokeSessionsEndpoint 撤销全部会话:直接调用即生效,
|
||||
// 响应带新 token,旧 token 随即失效。
|
||||
func TestRevokeSessionsEndpoint(t *testing.T) {
|
||||
r, _, _ := newTestRouter(t)
|
||||
login := doRequest(t, r, http.MethodPost, "/api/v1/auth/login", "", `{"username":"admin","password":"pass123"}`)
|
||||
var sess struct{ Token string }
|
||||
if err := json.Unmarshal(login.Body.Bytes(), &sess); err != nil || sess.Token == "" {
|
||||
t.Fatalf("login: %s", login.Body.String())
|
||||
}
|
||||
w := doRequest(t, r, http.MethodPost, "/api/v1/auth/revoke-sessions", sess.Token, "")
|
||||
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "token") {
|
||||
t.Fatalf("revoke = %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
w = doRequest(t, r, http.MethodGet, "/api/v1/auth/credentials", sess.Token, "")
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Errorf("撤销后旧 token 访问 = %d, want 401", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRespondErrorSanitizesInternal 内部错误边界(S-08):wrap 链细节不透传,
|
||||
// 响应为固定文案 + requestId;记录缺失映射 404 固定文案。
|
||||
func TestRespondErrorSanitizesInternal(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
cases := []struct {
|
||||
name string
|
||||
err error
|
||||
wantStatus int
|
||||
wantBody string // 必须出现
|
||||
leaks []string // 不得出现
|
||||
}{
|
||||
{
|
||||
name: "内部错误脱敏",
|
||||
err: fmt.Errorf("query users: dial tcp 10.0.0.5:3306: dsn user:secretpw"),
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
wantBody: "requestId",
|
||||
leaks: []string{"secretpw", "10.0.0.5", "dsn", "dial tcp"},
|
||||
},
|
||||
{
|
||||
name: "记录缺失固定文案",
|
||||
err: fmt.Errorf("find oci config 5: %w", gorm.ErrRecordNotFound),
|
||||
wantStatus: http.StatusNotFound,
|
||||
wantBody: "资源不存在",
|
||||
leaks: []string{"find oci config"},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/x", nil)
|
||||
respondError(c, tc.err)
|
||||
if rec.Code != tc.wantStatus {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, tc.wantStatus)
|
||||
}
|
||||
body := rec.Body.String()
|
||||
if !strings.Contains(body, tc.wantBody) {
|
||||
t.Errorf("body %q 应包含 %q", body, tc.wantBody)
|
||||
}
|
||||
for _, leak := range tc.leaks {
|
||||
if strings.Contains(body, leak) {
|
||||
t.Errorf("body %q 泄露内部细节 %q", body, leak)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user