-
Notifications
You must be signed in to change notification settings - Fork 0
/
jwt.go
154 lines (130 loc) · 3.65 KB
/
jwt.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
package jwt
import (
"errors"
"fmt"
"time"
"github.com/dgrijalva/jwt-go"
)
// Claims represents the JWT claims covidtrace cares about
type Claims struct {
Hash string `json:"covidtrace:hash,omitempty"`
Identifier string `json:"covidtrace:identifier,omitempty"`
Refreshed int `json:"covidtrace:refreshed"`
Role string `json:"covidtrace:role,omitempty"`
jwt.StandardClaims
}
// Issuer is the core covidtrace/jwt type. It exposes methods to issue and
// verify tokens.
type Issuer struct {
sm jwt.SigningMethod
key []byte
iss string
aud string
dur time.Duration
}
// NewIssuer returns, well, a new `Issuer`
func NewIssuer(key []byte, iss, aud string, dur time.Duration) *Issuer {
return &Issuer{sm: jwt.SigningMethodHS256, key: key, iss: iss, aud: aud, dur: dur}
}
// Copy returns a copy of `Issuer`
func (i *Issuer) Copy() *Issuer {
return &Issuer{
sm: i.sm,
key: i.key,
iss: i.iss,
aud: i.aud,
dur: i.dur,
}
}
// WithAud returns a copy of `Issuer` with `aud` overwritten
func (i *Issuer) WithAud(aud string) *Issuer {
copy := i.Copy()
i.aud = aud
return copy
}
// WithDur returns a copy of `Issuer` with `dur` overwritten
func (i *Issuer) WithDur(dur time.Duration) *Issuer {
copy := i.Copy()
i.dur = dur
return copy
}
// Claims constructs a new Claims object, filling details in from i
func (i *Issuer) Claims(hash string, refresh int, identifier, role string) *Claims {
return &Claims{
Hash: hash,
Identifier: identifier,
Refreshed: refresh,
Role: role,
StandardClaims: jwt.StandardClaims{
Audience: i.aud,
Issuer: i.iss,
ExpiresAt: time.Now().Add(i.dur).Unix(),
},
}
}
// Token handles generating a signed JWT token with the given `hash` and
// `refresh` count
func (i *Issuer) Token(hash string, refresh int, identifier, role string) (string, error) {
t := jwt.NewWithClaims(i.sm, i.Claims(hash, refresh, identifier, role))
return t.SignedString(i.key)
}
func getClaimString(claims jwt.MapClaims, key, def string) string {
result := def
if iface, ok := claims[key]; ok {
if str, ok := iface.(string); ok {
result = str
}
}
return result
}
func getClaimFloat64(claims jwt.MapClaims, key string, def float64) float64 {
result := def
iface, ok := claims[key]
if !ok {
iface = def
}
if flt, ok := iface.(float64); ok {
result = flt
}
return result
}
// Validate handles ensuring `signedString` is a valid JWT issued by this
// issuer. It returns the `hash` and `refreshed` claims, or an `error` if the
// token is invalid
func (i *Issuer) Validate(signedString string) (*Claims, error) {
t, err := jwt.Parse(signedString, func(t *jwt.Token) (interface{}, error) {
if t == nil {
return nil, errors.New("Token is nil")
}
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", t.Header["alg"])
}
return i.key, nil
})
if err != nil {
return nil, err
}
if t == nil || !t.Valid {
return nil, errors.New("Invalid jwt")
}
claims, ok := t.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("Invalid jwt claims")
}
if iss, ok := claims["iss"]; !ok {
return nil, errors.New("Missing iss")
} else if iss, ok = iss.(string); !ok || iss != i.iss {
return nil, fmt.Errorf("Invalid iss: %v", iss)
}
if aud, ok := claims["aud"]; !ok {
return nil, errors.New("Missing aud")
} else if aud, ok = aud.(string); !ok || aud != i.aud {
return nil, fmt.Errorf("Invalid aud: %v", aud)
}
return i.Claims(
getClaimString(claims, "covidtrace:hash", ""),
int(getClaimFloat64(claims, "covidtrace:refresh", 0)),
getClaimString(claims, "covidtrace:identifier", ""),
getClaimString(claims, "covidtrace:role", ""),
), nil
}