增加短信渠道
This commit is contained in:
@@ -97,7 +97,7 @@ func TestGinStartupLogsEveryRegisteredRoute(t *testing.T) {
|
||||
if got, want := strings.Count(text, `"msg":"router registered"`), len(engine.Routes()); got != want {
|
||||
t.Fatalf("registered route log count = %d, want %d", got, want)
|
||||
}
|
||||
if !strings.Contains(text, `"msg":"router register success"`) || !strings.Contains(text, `"route_count":196`) {
|
||||
if !strings.Contains(text, `"msg":"router register success"`) || !strings.Contains(text, `"route_count":197`) {
|
||||
t.Fatalf("startup route summary is missing: %s", text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,6 @@ var ProviderSet = wire.NewSet(
|
||||
NewExport, NewVersion, NewDictionary, NewParameter, NewAPIToken,
|
||||
NewSystemConfig, NewPublic, NewUser, NewNavigation, NewSession,
|
||||
NewIntegrationConfig,
|
||||
NewSMS,
|
||||
wire.Struct(new(Set), "*"),
|
||||
)
|
||||
|
||||
@@ -138,6 +138,8 @@ func (h *Public) InitializeDatabase(engine *gin.Engine) gin.HandlerFunc {
|
||||
Write(c, CodeSuccess, gin.H{}, "自动创建数据库成功")
|
||||
return
|
||||
}
|
||||
// Preserve the underlying error for ErrorAudit without exposing it in the response.
|
||||
_ = c.Error(err).SetType(gin.ErrorTypePrivate)
|
||||
Fail(c, "自动创建数据库失败,请查看后台日志,检查后在进行初始化")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
type Set struct {
|
||||
SMS *SMS
|
||||
Authority *Authority
|
||||
Menu *Menu
|
||||
API *API
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"kra/internal/service/dto"
|
||||
service "kra/internal/service/integration"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type SMS struct{ service *service.SMSService }
|
||||
|
||||
func NewSMS(s *service.SMSService) *SMS { return &SMS{service: s} }
|
||||
|
||||
func (h *SMS) Send(c *gin.Context) {
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 16<<10)
|
||||
var req dto.SMSSendRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
Fail(c, "短信参数无效")
|
||||
return
|
||||
}
|
||||
result, err := h.service.Send(c.Request.Context(), c.Param("provider"), &req)
|
||||
if err != nil {
|
||||
Fail(c, err.Error())
|
||||
return
|
||||
}
|
||||
OKWithData(c, result)
|
||||
}
|
||||
@@ -87,7 +87,9 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
||||
paymentCallback := bodyPolicy == routecatalog.BodyPolicyPaymentCallback
|
||||
paymentConfigWrite := bodyPolicy == routecatalog.BodyPolicyPaymentConfig
|
||||
requestText := ""
|
||||
if paymentCallback {
|
||||
if bodyPolicy == routecatalog.BodyPolicySMS {
|
||||
requestText = "[短信发送参数已隐藏]"
|
||||
} else if paymentCallback {
|
||||
requestText = paymentCallbackSummary(requestBody, c.GetHeader("Content-Type"))
|
||||
} else if paymentConfigWrite {
|
||||
requestText = paymentConfigSummary(requestBody)
|
||||
|
||||
@@ -101,6 +101,9 @@ func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder
|
||||
if !capturedBody && routecatalog.BodyPolicyFor(c.Request.Method, path) == routecatalog.BodyPolicyPaymentConfig {
|
||||
operationBody = paymentConfigSummary(requestBody)
|
||||
}
|
||||
if routecatalog.BodyPolicyFor(c.Request.Method, path) == routecatalog.BodyPolicySMS {
|
||||
operationBody = "[短信发送参数已隐藏]"
|
||||
}
|
||||
if err := recorder.RecordOperationRequest(c.Request.Context(), &dto.OperationRecordRequest{IP: c.ClientIP(), Method: c.Request.Method, Path: path, Status: status, LatencyMS: time.Since(started).Milliseconds(), Agent: c.Request.UserAgent(), ErrorMessage: errorMessage, Body: operationBody, Response: responseBody, UserID: userID, RequestID: stringValue(requestID), TraceID: stringValueFromContext(c, "trace_id"), DeviceID: c.GetHeader("X-Device-Id")}); err != nil {
|
||||
// Preserve the business response, but expose audit persistence failures
|
||||
// to the global access/error logging pipeline.
|
||||
|
||||
@@ -20,6 +20,7 @@ var sensitivePayloadKeys = map[string]struct{}{
|
||||
"passwd": {}, "pwd": {}, "token": {}, "accesstoken": {}, "refreshtoken": {},
|
||||
"secret": {}, "clientsecret": {}, "apikey": {}, "privatekey": {}, "idcard": {},
|
||||
"key": {}, "signingkey": {}, "secretkey": {},
|
||||
"accesskeysecret": {}, "secretid": {},
|
||||
"appkey": {}, "mchkey": {}, "apiv3key": {}, "clientcert": {}, "clientkey": {},
|
||||
"platformcert": {}, "platformserialno": {}, "publiccert": {}, "credentialcode": {}, "certfile": {},
|
||||
"keyfile": {}, "publickey": {}, "rootcert": {}, "appcert": {}, "webhookid": {},
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gin-gonic/gin"
|
||||
"kra/internal/config"
|
||||
"kra/internal/routecatalog"
|
||||
"log/slog"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSMSAuditHidesPhoneAndTemplateParameters(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
var output bytes.Buffer
|
||||
engine := gin.New()
|
||||
engine.Use(AccessLog(config.NewStore(&config.Config{Admin: &config.Admin{}}), slog.New(slog.NewJSONHandler(&output, nil)), "test"))
|
||||
engine.POST("/api/integration/sms/:provider/send", func(c *gin.Context) {
|
||||
if body, _ := c.Get(ctxReqBodyKey); body != "[短信发送参数已隐藏]" {
|
||||
t.Fatalf("unsafe audit body: %v", body)
|
||||
}
|
||||
c.JSON(200, gin.H{"code": 0})
|
||||
})
|
||||
path := "/api/integration/sms/aliyun/send"
|
||||
request := httptest.NewRequest("POST", path, strings.NewReader(`{"phone":"+8613800138000","params":{"code":"987654"}}`))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
engine.ServeHTTP(httptest.NewRecorder(), request)
|
||||
for _, value := range []string{"13800138000", "987654"} {
|
||||
if strings.Contains(output.String(), value) {
|
||||
t.Fatal("SMS content leaked into access log")
|
||||
}
|
||||
}
|
||||
if !routecatalog.ShouldAudit("POST", path) {
|
||||
t.Fatal("send route is not audited")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSMSConfigSecretsAreRedacted(t *testing.T) {
|
||||
text := redactJSON([]byte(`{"config":{"access_key_secret":"ali-value","secret_id":"tc-id-value","secret_key":"tc-value"}}`), "application/json", 4096)
|
||||
for _, secret := range []string{"ali-value", "tc-id-value", "tc-value"} {
|
||||
if strings.Contains(text, secret) {
|
||||
t.Fatalf("secret leaked: %s", text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ func (r *Routes) RegisterRoutes(public, private *gin.RouterGroup, engine *gin.En
|
||||
func() { RegisterAnnouncement(private, public, r.handlers.Announcement) },
|
||||
func() { RegisterEmail(private, r.handlers.Email) },
|
||||
func() { RegisterIntegrationConfig(private, r.handlers.IntegrationConfig) },
|
||||
func() { private.POST("/integration/sms/:provider/send", r.handlers.SMS.Send) },
|
||||
func() { RegisterPayment(private, public, r.handlers.Payment) },
|
||||
}
|
||||
for _, register := range registrations {
|
||||
|
||||
Reference in New Issue
Block a user