forked from baron/baron-sso
- Added support for fixed UUIDs during bulk registration (Search-first + ExternalID mapping) - Implemented idempotency and visibility restoration for soft-deleted users - Enhanced bulk upload UI to show 'New/Updated/Unchanged' status and modified fields - Added logic to reclaim identifiers (login_id) from colliding records - Added frontend E2E and backend unit tests for UUID integrity and conflict handling - Fixed i18n, formatting, and mock tests to satisfy code-check - Applied 'go fix' for 'omitzero' tags and general Go standards
148 lines
4.0 KiB
Go
148 lines
4.0 KiB
Go
package logger
|
|
|
|
import (
|
|
"log/slog"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var sensitiveClientLogKeys = map[string]struct{}{
|
|
"password": {},
|
|
"currentpassword": {},
|
|
"newpassword": {},
|
|
"oldpassword": {},
|
|
"token": {},
|
|
"accesstoken": {},
|
|
"refreshtoken": {},
|
|
"secret": {},
|
|
"clientsecret": {},
|
|
"authorization": {},
|
|
"cookie": {},
|
|
"setcookie": {},
|
|
"verificationcode": {},
|
|
"code": {},
|
|
"loginchallenge": {},
|
|
"loginverifier": {},
|
|
"sessionjwt": {},
|
|
"accessjwt": {},
|
|
"refreshjwt": {},
|
|
}
|
|
|
|
var (
|
|
logJSONSensitivePattern = regexp.MustCompile(`(?i)"(password|currentpassword|newpassword|oldpassword|token|accesstoken|refreshtoken|secret|clientsecret|authorization|cookie|setcookie|verificationcode|code|loginchallenge|loginverifier|sessionjwt|accessjwt|refreshjwt)"\s*:\s*"[^"]*"`)
|
|
logKVPattern = regexp.MustCompile(`(?i)\b(password|current_password|currentpassword|new_password|newpassword|old_password|oldpassword|token|access_token|accesstoken|refresh_token|refreshtoken|authorization|cookie|session_jwt|sessionjwt|access_jwt|accessjwt|refresh_jwt|refreshjwt)\b\s*[:=]\s*([^\s,;]+)`)
|
|
)
|
|
|
|
func IsProductionEnv(appEnv string) bool {
|
|
return IsProductionLikeEnv(appEnv)
|
|
}
|
|
|
|
func parseOptionalBoolFlag(raw string) (bool, bool) {
|
|
switch strings.ToLower(strings.TrimSpace(raw)) {
|
|
case "1", "true", "yes", "y", "on":
|
|
return true, true
|
|
case "0", "false", "no", "n", "off":
|
|
return false, true
|
|
default:
|
|
return false, false
|
|
}
|
|
}
|
|
|
|
func ClientDebugEnabled(appEnv, debugFlag string) bool {
|
|
if enabled, ok := parseOptionalBoolFlag(debugFlag); ok {
|
|
return enabled
|
|
}
|
|
if !IsProductionEnv(appEnv) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func NormalizeClientLogLevel(level string) slog.Level {
|
|
switch strings.ToUpper(strings.TrimSpace(level)) {
|
|
case "SEVERE", "ERROR":
|
|
return slog.LevelError
|
|
case "WARNING", "WARN":
|
|
return slog.LevelWarn
|
|
case "DEBUG", "FINE", "TRACE":
|
|
return slog.LevelDebug
|
|
default:
|
|
return slog.LevelInfo
|
|
}
|
|
}
|
|
|
|
func ShouldAcceptClientLog(appEnv, productionDebugFlag, level string) bool {
|
|
if ClientDebugEnabled(appEnv, productionDebugFlag) {
|
|
return true
|
|
}
|
|
return NormalizeClientLogLevel(level) >= slog.LevelWarn
|
|
}
|
|
|
|
func ShouldFilterNoisyClientInfo(appEnv, productionDebugFlag, message string) bool {
|
|
if ClientDebugEnabled(appEnv, productionDebugFlag) {
|
|
return false
|
|
}
|
|
msg := strings.ToLower(message)
|
|
return strings.Contains(msg, "navigating to") ||
|
|
strings.Contains(msg, "going to") ||
|
|
strings.Contains(msg, "redirecting to") ||
|
|
strings.Contains(msg, "full paths for routes")
|
|
}
|
|
|
|
func SanitizeClientLogMessage(message string) string {
|
|
if strings.TrimSpace(message) == "" {
|
|
return message
|
|
}
|
|
sanitized := logJSONSensitivePattern.ReplaceAllStringFunc(message, func(segment string) string {
|
|
parts := strings.SplitN(segment, ":", 2)
|
|
if len(parts) != 2 {
|
|
return segment
|
|
}
|
|
return parts[0] + `:"*****"`
|
|
})
|
|
sanitized = logKVPattern.ReplaceAllString(sanitized, `$1=*****`)
|
|
return sanitized
|
|
}
|
|
|
|
func SanitizeClientLogData(data map[string]any) map[string]any {
|
|
if len(data) == 0 {
|
|
return data
|
|
}
|
|
out := make(map[string]any, len(data))
|
|
for k, v := range data {
|
|
if isSensitiveClientLogKey(k) {
|
|
out[k] = "*****"
|
|
continue
|
|
}
|
|
out[k] = sanitizeClientLogValue(v)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func sanitizeClientLogValue(v any) any {
|
|
switch val := v.(type) {
|
|
case map[string]any:
|
|
return SanitizeClientLogData(val)
|
|
case []any:
|
|
next := make([]any, len(val))
|
|
for i := range val {
|
|
next[i] = sanitizeClientLogValue(val[i])
|
|
}
|
|
return next
|
|
case string:
|
|
return SanitizeClientLogMessage(val)
|
|
default:
|
|
return val
|
|
}
|
|
}
|
|
|
|
func isSensitiveClientLogKey(key string) bool {
|
|
normalized := strings.ToLower(strings.TrimSpace(key))
|
|
normalized = strings.ReplaceAll(normalized, "-", "")
|
|
normalized = strings.ReplaceAll(normalized, "_", "")
|
|
normalized = strings.ReplaceAll(normalized, ".", "")
|
|
normalized = strings.ReplaceAll(normalized, " ", "")
|
|
_, ok := sensitiveClientLogKeys[normalized]
|
|
return ok
|
|
}
|