Files
status-go/pkg/security/sensitive_string.go
Igor Sirotin 82f7bfbaa8 chore_: move security, version and sentry packages to pkg (#6595)
* chore_: move security, version and sentry packages to pkg

* fix_: more imports
2025-05-22 10:19:29 +01:00

115 lines
3.0 KiB
Go

package security
import (
"database/sql/driver"
"encoding/json"
"fmt"
"strings"
)
const RedactionPlaceholder = "***"
// SensitiveString is a type for handling sensitive information securely.
// This helps to achieve the following goals:
// 1. Prevent accidental logging of sensitive information.
// 2. Provide controlled visibility (e.g., redacted output for String() or MarshalJSON()).
// 3. Enable controlled access to the sensitive value when needed.
type SensitiveString struct {
value string
}
// NewSensitiveString creates a new SensitiveString
func NewSensitiveString(value string) SensitiveString {
return SensitiveString{value: value}
}
func NewSensitiveStringPrintf(format string, args ...interface{}) SensitiveString {
str := fmt.Sprintf(format, args...)
return NewSensitiveString(str)
}
// String provides a redacted version of the sensitive string
func (s SensitiveString) String() string {
if s.value == "" {
return ""
}
return RedactionPlaceholder
}
// MarshalJSON ensures that sensitive strings are redacted when marshaled to JSON
// NOTE: It's important to define this method on the value receiver,
// otherwise `json.Marshal` will not call this method.
func (s SensitiveString) MarshalJSON() ([]byte, error) {
return json.Marshal(s.String())
}
// UnmarshalJSON implements unmarshalling a sensitive string from JSON
// NOTE: It's important to define this method on the pointer receiver,
// otherwise `json.Marshal` will not call this method.
func (s *SensitiveString) UnmarshalJSON(data []byte) error {
var value string
if err := json.Unmarshal(data, &value); err != nil {
return err
}
s.value = value
return nil
}
// Reveal exposes the sensitive value (use with caution)
func (s SensitiveString) Reveal() string {
return s.value
}
// Empty checks if the value is empty
func (s SensitiveString) Empty() bool {
return s.value == ""
}
func (s SensitiveString) TrimRight(cutset any) SensitiveString {
return NewSensitiveString(strings.TrimRight(s.value, getValue(cutset)))
}
func (s SensitiveString) Contains(substr any) bool {
return strings.Contains(s.value, getValue(substr))
}
func (s SensitiveString) Append(others ...any) SensitiveString {
result := s.value
for _, other := range others {
result += getValue(other)
}
return NewSensitiveString(result)
}
// getValue extracts the string value from various types
func getValue(v any) string {
switch x := v.(type) {
case string:
return x
case SensitiveString:
return x.value
default:
panic(fmt.Sprintf("unsupported type: %T", v))
}
}
// Value implements the driver.Valuer interface for SensitiveString
func (s SensitiveString) Value() (driver.Value, error) {
return s.value, nil
}
// Scan implements the sql.Scanner interface for SensitiveString
func (s *SensitiveString) Scan(value interface{}) error {
switch v := value.(type) {
case nil:
s.value = ""
case string:
s.value = v
case []byte:
s.value = string(v)
default:
return fmt.Errorf("cannot scan type %T into SensitiveString", value)
}
return nil
}