-
Notifications
You must be signed in to change notification settings - Fork 2
/
secret.go
97 lines (82 loc) · 2.76 KB
/
secret.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package secret
// Text provides a way to safely store your secret string until you actually need it. Operations
// like printing and serializing see a proxy/redact string there by avoiding leaking the secret.
// Once created, the instance is readonly except for the [Text.UnmarshalText] operation, but that
// too only modifies the local copy. Hence the type is concurrent safe.
type Text struct {
secret *string
redact *string
}
// New returns [Text] for the secret with [FiveStar] as the default redact string. Provide options
// like [RedactAs] to modify default behavior.
func New(secret string, options ...func(*Text)) Text {
tx := Text{
secret: new(string),
redact: new(string),
}
*tx.secret = secret
*tx.redact = FiveStar
for _, o := range options {
o(&tx)
}
return tx
}
// RedactAs is a functional option to set r as the redact string for [Text]. You can use one of
// the common redact strings provided with this package like [FiveX] or provide your own.
func RedactAs(r string) func(*Text) {
return func(t *Text) {
*t.redact = r
}
}
// Some common redact strings.
const (
FiveX string = "XXXXX"
FiveStar string = "*****"
Redacted string = "[REDACTED]"
)
// String implements the [fmt.Stringer] interface and returns only the redact string. This prevents
// the actual secret string from being sent to std*, logs etc.
func (tx Text) String() string {
if tx.redact != nil {
return *tx.redact
}
return FiveStar
}
// Secret returns the actual secret string stored inside [Text].
func (tx Text) Secret() string {
if tx.secret != nil {
return *tx.secret
}
return ""
}
// MarshalText implements [encoding.TextMarshaler]. It marshals redact string into bytes rather than
// the actual secret value.
func (tx Text) MarshalText() ([]byte, error) {
return []byte(tx.String()), nil
}
// UnmarshalText implements [encoding.TextUnmarshaler]. It unmarshals b into receiver's new secret
// value. If redact string is present then it is reused.
func (tx *Text) UnmarshalText(b []byte) error {
s := string(b)
// If the original redact is not nil then use it otherwise fallback to default.
if tx.redact != nil {
*tx = New(s, RedactAs(*tx.redact))
} else {
*tx = New(s)
}
return nil
}
// Equal returns true if both arguments have the same secret regardless of the redact strings.
func Equal(tx1, tx2 Text) bool {
// If both pointers are equal then it means either both are nil or point to same value.
if tx1.secret == tx2.secret {
return true
}
// If we are here then it means the two pointers have different values hence return false
// if any one of them is nil.
if tx1.secret == nil || tx2.secret == nil {
return false
}
// If we are here then it means both pointers are not nil hence compare the values pointed by them.
return *tx1.secret == *tx2.secret
}