quotation service fixed

This commit is contained in:
Stephan D
2026-02-24 16:14:09 +01:00
parent 6444813f38
commit 2fe90347a8
76 changed files with 769 additions and 230 deletions

View File

@@ -34,40 +34,108 @@ type envConfig struct {
}
const defaultConsumerBufferSize = 1024
const redactedNATSPassword = "xxxxx"
func sanitizeNATSURL(rawURL string) string {
func buildSafePublishableNATSURL(rawURL string) string {
if rawURL == "" {
return rawURL
}
parts := strings.Split(rawURL, ",")
sanitized := make([]string, 0, len(parts))
safe := make([]string, 0, len(parts))
for _, part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed == "" {
continue
}
if !strings.Contains(trimmed, "://") {
sanitized = append(sanitized, trimmed)
built, ok := buildSafePublishableNATSEntry(trimmed)
if !ok {
safe = append(safe, trimmed)
continue
}
parsed, err := url.Parse(trimmed)
if err != nil {
sanitized = append(sanitized, trimmed)
continue
}
if parsed.User == nil {
sanitized = append(sanitized, trimmed)
continue
}
sanitized = append(sanitized, parsed.Redacted())
safe = append(safe, built)
}
if len(sanitized) == 0 {
if len(safe) == 0 {
return strings.TrimSpace(rawURL)
}
return strings.Join(sanitized, ",")
return strings.Join(safe, ",")
}
func buildSafePublishableNATSEntry(raw string) (string, bool) {
parsed, err := url.Parse(raw)
if err == nil && parsed.Host != "" {
safe := &url.URL{
Scheme: parsed.Scheme,
Host: parsed.Host,
Path: parsed.Path,
RawPath: parsed.RawPath,
RawQuery: parsed.RawQuery,
Fragment: parsed.Fragment,
}
if safe.Scheme == "" {
safe.Scheme = "nats"
}
if parsed.User != nil {
username := parsed.User.Username()
if username == "" {
username = redactedNATSPassword
}
safe.User = url.UserPassword(username, redactedNATSPassword)
}
return safe.String(), true
}
return buildSafePublishableFromAuthority(raw)
}
func buildSafePublishableFromAuthority(raw string) (string, bool) {
scheme := "nats"
authorityAndSuffix := raw
if schemeIndex := strings.Index(raw, "://"); schemeIndex >= 0 {
if candidate := strings.TrimSpace(raw[:schemeIndex]); candidate != "" {
scheme = candidate
}
authorityAndSuffix = raw[schemeIndex+3:]
}
authorityEnd := strings.IndexAny(authorityAndSuffix, "/?#")
if authorityEnd < 0 {
authorityEnd = len(authorityAndSuffix)
}
authority := authorityAndSuffix[:authorityEnd]
suffix := authorityAndSuffix[authorityEnd:]
atIndex := strings.LastIndex(authority, "@")
hostPort := authority
username := ""
if atIndex >= 0 {
userInfo := authority[:atIndex]
hostPort = authority[atIndex+1:]
if hostPort == "" {
return "", false
}
username = userInfo
if colonIndex := strings.Index(userInfo, ":"); colonIndex >= 0 {
username = userInfo[:colonIndex]
}
if username == "" {
username = redactedNATSPassword
}
}
if hostPort == "" || strings.ContainsAny(hostPort, " \t\r\n") {
return "", false
}
safe := &url.URL{
Scheme: scheme,
Host: hostPort,
}
if username != "" {
safe.User = url.UserPassword(username, redactedNATSPassword)
}
return safe.String() + suffix, true
}
// loadEnv gathers and validates connection details from environment variables
@@ -144,7 +212,7 @@ func NewNatsBroker(logger mlogger.Logger, settings *nc.Settings) (*NatsBroker, e
}
natsURL = u.String()
}
sanitizedNATSURL := sanitizeNATSURL(natsURL)
publishableNATSURL := buildSafePublishableNATSURL(natsURL)
opts := []nats.Option{
nats.Name(settings.NATSName),
@@ -156,7 +224,7 @@ func NewNatsBroker(logger mlogger.Logger, settings *nc.Settings) (*NatsBroker, e
zap.String("broker", settings.NATSName),
}
if conn != nil {
fields = append(fields, zap.String("connected_url", sanitizeNATSURL(conn.ConnectedUrl())))
fields = append(fields, zap.String("connected_url", buildSafePublishableNATSURL(conn.ConnectedUrl())))
}
if err != nil {
fields = append(fields, zap.Error(err))
@@ -168,7 +236,7 @@ func NewNatsBroker(logger mlogger.Logger, settings *nc.Settings) (*NatsBroker, e
zap.String("broker", settings.NATSName),
}
if conn != nil {
fields = append(fields, zap.String("connected_url", sanitizeNATSURL(conn.ConnectedUrl())))
fields = append(fields, zap.String("connected_url", buildSafePublishableNATSURL(conn.ConnectedUrl())))
}
l.Info("Reconnected to NATS", fields...)
}),
@@ -178,7 +246,7 @@ func NewNatsBroker(logger mlogger.Logger, settings *nc.Settings) (*NatsBroker, e
}
if conn != nil {
if url := conn.ConnectedUrl(); url != "" {
fields = append(fields, zap.String("connected_url", sanitizeNATSURL(url)))
fields = append(fields, zap.String("connected_url", buildSafePublishableNATSURL(url)))
}
if err := conn.LastError(); err != nil {
fields = append(fields, zap.Error(err))
@@ -208,7 +276,7 @@ func NewNatsBroker(logger mlogger.Logger, settings *nc.Settings) (*NatsBroker, e
}
if res.nc, err = nats.Connect(natsURL, opts...); err != nil {
l.Error("Failed to connect to NATS", zap.String("url", sanitizedNATSURL), zap.Error(err))
l.Error("Failed to connect to NATS", zap.String("url", publishableNATSURL), zap.Error(err))
return nil, err
}
if res.js, err = res.nc.JetStream(); err != nil {
@@ -216,7 +284,7 @@ func NewNatsBroker(logger mlogger.Logger, settings *nc.Settings) (*NatsBroker, e
}
logger.Info("Connected to NATS", zap.String("broker", settings.NATSName),
zap.String("url", sanitizedNATSURL))
zap.String("url", publishableNATSURL))
return res, nil
}

View File

@@ -5,14 +5,14 @@ import (
"testing"
)
func TestSanitizeNATSURL(t *testing.T) {
func TestBuildSafePublishableNATSURL(t *testing.T) {
t.Parallel()
t.Run("redacts single URL credentials", func(t *testing.T) {
t.Parallel()
raw := "nats://alice:supersecret@localhost:4222"
sanitized := sanitizeNATSURL(raw)
sanitized := buildSafePublishableNATSURL(raw)
if strings.Contains(sanitized, "supersecret") {
t.Fatalf("expected password to be redacted, got %q", sanitized)
@@ -22,11 +22,25 @@ func TestSanitizeNATSURL(t *testing.T) {
}
})
t.Run("redacts credentials in gateway URL format", func(t *testing.T) {
t.Parallel()
raw := "nats://dev_nats:nats_password_123@dev-nats:4222"
sanitized := buildSafePublishableNATSURL(raw)
if strings.Contains(sanitized, "nats_password_123") {
t.Fatalf("expected password to be redacted, got %q", sanitized)
}
if !strings.Contains(sanitized, "dev_nats:xxxxx@dev-nats:4222") {
t.Fatalf("expected sanitized URL with redacted password, got %q", sanitized)
}
})
t.Run("keeps URL without credentials unchanged", func(t *testing.T) {
t.Parallel()
raw := "nats://localhost:4222"
sanitized := sanitizeNATSURL(raw)
sanitized := buildSafePublishableNATSURL(raw)
if sanitized != raw {
t.Fatalf("expected URL without credentials to remain unchanged, got %q", sanitized)
}
@@ -36,7 +50,7 @@ func TestSanitizeNATSURL(t *testing.T) {
t.Parallel()
raw := " nats://alice:one@localhost:4222, nats://bob:two@localhost:4223 "
sanitized := sanitizeNATSURL(raw)
sanitized := buildSafePublishableNATSURL(raw)
if strings.Contains(sanitized, "one") || strings.Contains(sanitized, "two") {
t.Fatalf("expected passwords to be redacted, got %q", sanitized)
@@ -50,9 +64,37 @@ func TestSanitizeNATSURL(t *testing.T) {
t.Parallel()
raw := "not a url"
sanitized := sanitizeNATSURL(raw)
sanitized := buildSafePublishableNATSURL(raw)
if sanitized != raw {
t.Fatalf("expected invalid URL to remain unchanged, got %q", sanitized)
}
})
t.Run("redacts malformed URL credentials via fallback", func(t *testing.T) {
t.Parallel()
raw := "nats://alice:pa%ss@localhost:4222"
sanitized := buildSafePublishableNATSURL(raw)
if strings.Contains(sanitized, "pa%ss") {
t.Fatalf("expected malformed password to be redacted, got %q", sanitized)
}
if !strings.Contains(sanitized, "alice:xxxxx@localhost:4222") {
t.Fatalf("expected fallback redaction to preserve host and username, got %q", sanitized)
}
})
t.Run("redacts URL without scheme when user info is present", func(t *testing.T) {
t.Parallel()
raw := "alice:topsecret@localhost:4222"
sanitized := buildSafePublishableNATSURL(raw)
if strings.Contains(sanitized, "topsecret") {
t.Fatalf("expected password to be redacted, got %q", sanitized)
}
if !strings.Contains(sanitized, "alice:xxxxx@localhost:4222") {
t.Fatalf("expected sanitized authority with redacted password, got %q", sanitized)
}
})
}