-
Notifications
You must be signed in to change notification settings - Fork 36
/
json_token.go
210 lines (195 loc) · 6.34 KB
/
json_token.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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package paseto
import (
"encoding/base64"
"encoding/json"
"reflect"
"time"
"github.com/mitchellh/mapstructure"
errors "golang.org/x/xerrors"
)
var (
// ErrTypeCast type cast error
ErrTypeCast = errors.New("type cast error")
// ErrClaimNotFound claim not found error
ErrClaimNotFound = errors.New("claim not found")
)
// JSONToken defines standard token payload claims and allows for additional
// claims to be added. All of the standard claims are optional.
type JSONToken struct {
// Audience identifies the intended recipients of the token.
// It should be a string or a URI and is case sensitive.
Audience string
// Issuer identifies the entity which issued the token.
// It should be a string or a URI and is case sensitive.
Issuer string
// JTI is a globally unique identifier for the token. It must be created in
// such a way as to ensure that there is negligible probability that the same
// value will be used in another token.
Jti string
// Subject identifies the principal entity that is the subject of the token.
// For example, for an authentication token, the subject might be the user ID
// of a person.
Subject string
// Expiration is a time on or after which the token must not be accepted for processing.
Expiration time.Time
// IssuedAt is the time at which the token was issued.
IssuedAt time.Time
// NotBefore is a time on or before which the token must not be accepted for
// processing.
NotBefore time.Time
claims map[string]interface{}
}
// MapUnmarshaler is the interface used in mapstructure decoder hook.
type MapUnmarshaler interface {
// UnmarshalMap receives `v` from DecodeHookFunc.
// Returned value is used by mapstructure for further processing.
UnmarshalMap(interface{}) (interface{}, error)
}
func decodeHook(from, to reflect.Type, v interface{}) (interface{}, error) {
unmarshalerType := reflect.TypeOf((*MapUnmarshaler)(nil)).Elem()
if to.Implements(unmarshalerType) {
// invoke UnmarshalMap by name
if method, ok := to.MethodByName("UnmarshalMap"); ok {
in := []reflect.Value{reflect.New(to).Elem(), reflect.ValueOf(v)}
r := method.Func.Call(in)
if !r[1].IsNil() {
return nil, r[1].Interface().(error)
}
// get first return parameter and cast reflect.Value
v = r[0].Interface().(interface{})
}
}
return v, nil
}
// Get the value of the claim and uses reflection to store it in the value pointed to by v.
// If the claim doesn't exist an ErrClaimNotFound error is returned
func (t *JSONToken) Get(key string, v interface{}) error {
val, ok := t.claims[key]
if !ok {
return ErrClaimNotFound
}
switch f := v.(type) {
case *string:
s, ok := val.(string)
if !ok {
return errors.Errorf(`failed to cast value to string: %w`, ErrTypeCast)
}
*f = s
case *time.Time:
s, ok := val.(string)
if !ok {
return errors.Errorf(`failed to cast value to time.Time: %w`, ErrTypeCast)
}
date, err := time.Parse(time.RFC3339, s)
if err != nil {
return errors.Errorf(`failed to parse time value: %v: %w`, err, ErrTypeCast)
}
*f = date
case *[]byte:
if val == nil {
return nil
}
s, ok := val.(string)
if !ok {
return errors.Errorf(`failed to cast value to []byte: %w`, ErrTypeCast)
}
bytes, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return errors.Errorf(`failed to decode []byte: %w`, ErrTypeCast)
}
*f = bytes
default:
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: decodeHook,
Result: v,
TagName: "json",
})
if err != nil {
return errors.Errorf("failed to create map decoder: %w", err)
}
if err := decoder.Decode(val); err != nil {
return errors.Errorf(`failed to cast value to %s: %v: %w`, reflect.TypeOf(v).String(), err, ErrTypeCast)
}
}
return nil
}
// Set sets the value of a custom claim
func (t *JSONToken) Set(key string, value interface{}) {
if t.claims == nil {
t.claims = make(map[string]interface{})
}
t.claims[key] = value
}
// MarshalJSON implements json.Marshaler interface
// nolint:gocritic
func (t JSONToken) MarshalJSON() ([]byte, error) {
if t.claims == nil {
t.claims = make(map[string]interface{})
}
if t.Audience != "" {
t.claims["aud"] = t.Audience
}
if t.Issuer != "" {
t.claims["iss"] = t.Issuer
}
if t.Jti != "" {
t.claims["jti"] = t.Jti
}
if t.Subject != "" {
t.claims["sub"] = t.Subject
}
if !t.Expiration.IsZero() {
t.claims["exp"] = t.Expiration.Format(time.RFC3339)
}
if !t.IssuedAt.IsZero() {
t.claims["iat"] = t.IssuedAt.Format(time.RFC3339)
}
if !t.NotBefore.IsZero() {
t.claims["nbf"] = t.NotBefore.Format(time.RFC3339)
}
return json.Marshal(t.claims)
}
// UnmarshalJSON implements json.Unmarshaler interface
//nolint:gocyclo
func (t *JSONToken) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &t.claims); err != nil {
return err
}
if err := t.Get("aud", &t.Audience); err != nil && !errors.Is(err, ErrClaimNotFound) {
return errors.Errorf("failed to parse audience claim: %w", err)
}
if err := t.Get("iss", &t.Issuer); err != nil && !errors.Is(err, ErrClaimNotFound) {
return errors.Errorf("failed to parse issuer claim: %w", err)
}
if err := t.Get("jti", &t.Jti); err != nil && !errors.Is(err, ErrClaimNotFound) {
return errors.Errorf("failed to parse jti claim: %w", err)
}
if err := t.Get("sub", &t.Subject); err != nil && !errors.Is(err, ErrClaimNotFound) {
return errors.Errorf("failed to parse subject claim: %w", err)
}
if err := t.Get("exp", &t.Expiration); err != nil && !errors.Is(err, ErrClaimNotFound) {
return errors.Errorf("failed to parse expiration claim: %w", err)
}
if err := t.Get("iat", &t.IssuedAt); err != nil && !errors.Is(err, ErrClaimNotFound) {
return errors.Errorf("failed to parse issued at claim: %w", err)
}
if err := t.Get("nbf", &t.NotBefore); err != nil && !errors.Is(err, ErrClaimNotFound) {
return errors.Errorf("failed to parse not before claim: %w", err)
}
return nil
}
// Validate validates a token with the given validators. If no validators are
// specified, then by default it validates the token with ValidAt(time.Now()),
// which checks IssuedAt, NotBefore and Expiration fields against the current
// time.
func (t *JSONToken) Validate(validators ...Validator) error {
if len(validators) == 0 {
validators = append(validators, ValidAt(time.Now()))
}
for _, validator := range validators {
if err := validator(t); err != nil {
return err
}
}
return nil
}