forked from sensu/sensu-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jwt.go
219 lines (184 loc) · 5.37 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
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
211
212
213
214
215
216
217
218
219
package jwt
import (
"context"
"encoding/hex"
"fmt"
"net/http"
"strings"
jwt "github.com/dgrijalva/jwt-go"
time "github.com/echlebek/timeproxy"
"github.com/sensu/sensu-go/api/core/v2"
"github.com/sensu/sensu-go/backend/store"
"github.com/sensu/sensu-go/types"
utilbytes "github.com/sensu/sensu-go/util/bytes"
)
var (
defaultExpiration = time.Minute * 15
secret []byte
)
// AccessToken creates a new access token and returns it in both JWT and
// signed format, along with any error
func AccessToken(claims *v2.Claims) (*jwt.Token, string, error) {
// Create a unique identifier for the token
jti, err := GenJTI()
if err != nil {
return nil, "", err
}
claims.Id = jti
// Add an expiration to the token
claims.ExpiresAt = time.Now().Add(defaultExpiration).Unix()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(secret)
if err != nil {
return nil, "", err
}
return token, tokenString, nil
}
// NewClaims creates new claim based on username
func NewClaims(user *v2.User) (*v2.Claims, error) {
// Create a unique identifier for the token
jti, err := GenJTI()
if err != nil {
return nil, err
}
claims := &v2.Claims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(defaultExpiration).Unix(),
Id: jti,
Subject: user.Username,
},
Groups: user.Groups,
}
return claims, nil
}
// GenJTI generates a new random JTI
func GenJTI() (string, error) {
jti, err := utilbytes.Random(16)
if err != nil {
return "", err
}
return hex.EncodeToString(jti), err
}
// GetClaims returns the claims from a token
func GetClaims(token *jwt.Token) (*v2.Claims, error) {
if claims, ok := token.Claims.(*v2.Claims); ok {
return claims, nil
}
return nil, fmt.Errorf("could not parse the token claims")
}
// GetClaimsFromContext retrieves the JWT claims from the request context
func GetClaimsFromContext(ctx context.Context) *v2.Claims {
if value := ctx.Value(v2.ClaimsKey); value != nil {
claims, ok := value.(*v2.Claims)
if !ok {
return nil
}
return claims
}
return nil
}
// ExtractBearerToken retrieves the bearer token from a request and returns the
// JWT
func ExtractBearerToken(r *http.Request) string {
// Does a bearer token was provided in the Authorization header?
var tokenString string
tokens, ok := r.Header["Authorization"]
if ok && len(tokens) >= 1 {
tokenString = tokens[0]
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
}
return tokenString
}
// InitSecret initializes and retrieves the secret for our signing tokens
func InitSecret(store store.Store) error {
var s []byte
var err error
// Retrieve the secret
if secret == nil {
s, err = store.GetJWTSecret()
if err != nil {
// The secret does not exist, we need to create one
s, err = utilbytes.Random(32)
if err != nil {
return err
}
// Add the secret to the store
err = store.CreateJWTSecret(s)
if err != nil {
return err
}
}
// Set the secret so it's available accross the package
secret = s
}
return nil
}
// parseToken takes a signed token and parse it to verify its integrity
func parseToken(tokenString string) (*jwt.Token, error) {
return jwt.ParseWithClaims(tokenString, &types.Claims{}, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
// secret is a []byte containing the secret
return secret, nil
})
}
// RefreshToken returns a refresh token for a specific user
func RefreshToken(claims *v2.Claims) (*jwt.Token, string, error) {
// Create a unique identifier for the token
jti, err := GenJTI()
if err != nil {
return nil, "", err
}
claims.Id = jti
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Sign the token as a string using the secret
tokenString, err := token.SignedString(secret)
if err != nil {
return nil, "", err
}
return token, tokenString, nil
}
// SetClaimsIntoContext adds the token claims into the request context for
// easier consumption later
func SetClaimsIntoContext(r *http.Request, claims *v2.Claims) context.Context {
return context.WithValue(r.Context(), v2.ClaimsKey, claims)
}
// ValidateExpiredToken verifies that the provided token is valid, even if
// it's expired
func ValidateExpiredToken(tokenString string) (*jwt.Token, error) {
token, err := parseToken(tokenString)
if token == nil {
return nil, err
}
if _, ok := token.Claims.(*v2.Claims); ok {
if token.Valid {
return token, nil
}
// Inspect the error to determine the cause
if validationError, ok := err.(*jwt.ValidationError); ok {
if validationError.Errors&jwt.ValidationErrorExpired != 0 {
// We already know that the token is expired and we don't care at that
// point, we simply want to know if there's any other error
validationError.Errors ^= jwt.ValidationErrorExpired
}
// Return the token if we have no other validation error
if validationError.Errors == 0 {
return token, nil
}
}
}
return nil, err
}
// ValidateToken verifies that the provided token is valid
func ValidateToken(tokenString string) (*jwt.Token, error) {
token, err := parseToken(tokenString)
if token == nil {
return nil, err
}
if _, ok := token.Claims.(*types.Claims); ok && token.Valid {
return token, nil
}
return nil, err
}