package contact import ( "bytes" "errors" "io" "net/http" "strings" "github.com/gin-gonic/gin" ) const maxChannelRequestBytes = 8 * 1024 const channelBodyKey = "bell.contact.channel-body" const channelBodyErrorKey = "bell.contact.channel-body-error" var redactedChannelBody = []byte(`{"redacted":true}`) // RedactRequestBody must run before GoAdmin's LoggerToFile middleware. The // handler restores the original body from Gin context, while sys_opera_log // only sees a fixed marker and never the contact address. func RedactRequestBody() gin.HandlerFunc { return func(c *gin.Context) { if c.Request.Method != http.MethodPost || !isChannelCreatePath(c.Request.URL.Path) { c.Next() return } body, err := io.ReadAll(io.LimitReader(c.Request.Body, maxChannelRequestBytes+1)) if err != nil { c.Set(channelBodyErrorKey, err) } else if len(body) > maxChannelRequestBytes { c.Set(channelBodyErrorKey, errors.New("request body too large")) } else { c.Set(channelBodyKey, body) } _ = c.Request.Body.Close() c.Request.Body = io.NopCloser(bytes.NewReader(redactedChannelBody)) c.Next() } } func restoreChannelBody(c *gin.Context) error { if value, ok := c.Get(channelBodyErrorKey); ok { return value.(error) } value, ok := c.Get(channelBodyKey) if !ok { return errors.New("channel request body was not captured") } c.Request.Body = io.NopCloser(bytes.NewReader(value.([]byte))) return nil } func isChannelCreatePath(path string) bool { const prefix = "/api/v1/bell/contacts/" const suffix = "/channels" if !strings.HasPrefix(path, prefix) || !strings.HasSuffix(path, suffix) { return false } id := strings.TrimSuffix(strings.TrimPrefix(path, prefix), suffix) return id != "" && !strings.Contains(id, "/") }