Differentiate between validation errors and others
appease linter
Showing 1 of 3 files from the diff.
jwt/validate.go
changed.
Other files ignored by Codecov
jwt/validate_test.go
has changed.
docs/01-jwt.md
has changed.
@@ -22,7 +22,7 @@
Loading
22 | 22 | case ExpirationKey, IssuedAtKey, NotBeforeKey: |
|
23 | 23 | return nil |
|
24 | 24 | } |
|
25 | - | return errors.Errorf(`unsupported time claim %s`, strconv.Quote(c)) |
|
25 | + | return NewValidationError(errors.Errorf(`unsupported time claim %s`, strconv.Quote(c))) |
|
26 | 26 | } |
|
27 | 27 | ||
28 | 28 | func timeClaim(t Token, clock Clock, c string) time.Time { |
@@ -130,18 +130,58 @@
Loading
130 | 130 | if iitr.less { // t1 - t2 <= iitr.dur |
|
131 | 131 | // t1 - t2 < iitr.dur + skew |
|
132 | 132 | if t1.Sub(t2) > iitr.dur+skew { |
|
133 | - | return errors.Errorf(`iitr between %s and %s exceeds %s (skew %s)`, iitr.c1, iitr.c2, iitr.dur, skew) |
|
133 | + | return NewValidationError(errors.Errorf(`iitr between %s and %s exceeds %s (skew %s)`, iitr.c1, iitr.c2, iitr.dur, skew)) |
|
134 | 134 | } |
|
135 | 135 | } else { |
|
136 | 136 | if t1.Sub(t2) < iitr.dur-skew { |
|
137 | - | return errors.Errorf(`iitr between %s and %s is less than %s (skew %s)`, iitr.c1, iitr.c2, iitr.dur, skew) |
|
137 | + | return NewValidationError(errors.Errorf(`iitr between %s and %s is less than %s (skew %s)`, iitr.c1, iitr.c2, iitr.dur, skew)) |
|
138 | 138 | } |
|
139 | 139 | } |
|
140 | 140 | return nil |
|
141 | 141 | } |
|
142 | 142 | ||
143 | + | type ValidationError interface { |
|
144 | + | error |
|
145 | + | isValidationError() |
|
146 | + | } |
|
147 | + | ||
148 | + | func NewValidationError(err error) ValidationError { |
|
149 | + | return &validationError{error: err} |
|
150 | + | } |
|
151 | + | ||
152 | + | // This is a generic validation error. |
|
153 | + | type validationError struct { |
|
154 | + | error |
|
155 | + | } |
|
156 | + | ||
157 | + | func (validationError) isValidationError() {} |
|
158 | + | ||
159 | + | var errTokenExpired = NewValidationError(errors.New(`exp not satisfied`)) |
|
160 | + | var errInvalidIssuedAt = NewValidationError(errors.New(`iat not satisfied`)) |
|
161 | + | var errTokenNotYetValid = NewValidationError(errors.New(`nbf not satisfied`)) |
|
162 | + | ||
163 | + | // ErrTokenExpired returns the immutable error used when `exp` claim |
|
164 | + | // is not satisfied |
|
165 | + | func ErrTokenExpired() error { |
|
166 | + | return errTokenExpired |
|
167 | + | } |
|
168 | + | ||
169 | + | // ErrInvalidIssuedAt returns the immutable error used when `iat` claim |
|
170 | + | // is not satisfied |
|
171 | + | func ErrInvalidIssuedAt() error { |
|
172 | + | return errInvalidIssuedAt |
|
173 | + | } |
|
174 | + | ||
175 | + | func ErrTokenNotYetValid() error { |
|
176 | + | return errTokenNotYetValid |
|
177 | + | } |
|
178 | + | ||
143 | 179 | // Validator describes interface to validate a Token. |
|
144 | 180 | type Validator interface { |
|
181 | + | // Validate should return an error if a required conditions is not met. |
|
182 | + | // This method will be changed in the next major release to return |
|
183 | + | // jwt.ValidationError instead of error to force users to return |
|
184 | + | // a validation error even for user-specified validators |
|
145 | 185 | Validate(context.Context, Token) error |
|
146 | 186 | } |
|
147 | 187 |
@@ -193,7 +233,7 @@
Loading
193 | 233 | ttv := tv.Truncate(time.Second) |
|
194 | 234 | skew := ValidationCtxSkew(ctx) // MUST be populated |
|
195 | 235 | if !now.Before(ttv.Add(skew)) { |
|
196 | - | return errors.New(`exp not satisfied`) |
|
236 | + | return ErrTokenExpired() |
|
197 | 237 | } |
|
198 | 238 | } |
|
199 | 239 | return nil |
@@ -217,7 +257,7 @@
Loading
217 | 257 | ttv := tv.Truncate(time.Second) |
|
218 | 258 | skew := ValidationCtxSkew(ctx) // MUST be populated |
|
219 | 259 | if now.Before(ttv.Add(-1 * skew)) { |
|
220 | - | return errors.New(`iat not satisfied`) |
|
260 | + | return ErrInvalidIssuedAt() |
|
221 | 261 | } |
|
222 | 262 | } |
|
223 | 263 | return nil |
@@ -242,7 +282,7 @@
Loading
242 | 282 | skew := ValidationCtxSkew(ctx) // MUST be populated |
|
243 | 283 | // now cannot be before t, so we check for now > t - skew |
|
244 | 284 | if !now.Equal(ttv) && !now.After(ttv.Add(-1*skew)) { |
|
245 | - | return errors.New(`nbf not satisfied`) |
|
285 | + | return ErrTokenNotYetValid() |
|
246 | 286 | } |
|
247 | 287 | } |
|
248 | 288 | return nil |
@@ -263,15 +303,30 @@
Loading
263 | 303 | } |
|
264 | 304 | } |
|
265 | 305 | ||
306 | + | // IsValidationError returns true if the error is a validation error |
|
307 | + | func IsValidationError(err error) bool { |
|
308 | + | switch err { |
|
309 | + | case errTokenExpired, errTokenNotYetValid, errInvalidIssuedAt: |
|
310 | + | return true |
|
311 | + | default: |
|
312 | + | switch err.(type) { |
|
313 | + | case *validationError: |
|
314 | + | return true |
|
315 | + | default: |
|
316 | + | return false |
|
317 | + | } |
|
318 | + | } |
|
319 | + | } |
|
320 | + | ||
266 | 321 | func (ccs claimContainsString) Validate(_ context.Context, t Token) error { |
|
267 | 322 | v, ok := t.Get(ccs.name) |
|
268 | 323 | if !ok { |
|
269 | - | return errors.Errorf(`claim %q not found`, ccs.name) |
|
324 | + | return NewValidationError(errors.Errorf(`claim %q not found`, ccs.name)) |
|
270 | 325 | } |
|
271 | 326 | ||
272 | 327 | list, ok := v.([]string) |
|
273 | 328 | if !ok { |
|
274 | - | return errors.Errorf(`claim %q must be a []string (got %T)`, ccs.name, v) |
|
329 | + | return NewValidationError(errors.Errorf(`claim %q must be a []string (got %T)`, ccs.name, v)) |
|
275 | 330 | } |
|
276 | 331 | ||
277 | 332 | var found bool |
@@ -282,7 +337,7 @@
Loading
282 | 337 | } |
|
283 | 338 | } |
|
284 | 339 | if !found { |
|
285 | - | return errors.Errorf(`%s not satisfied`, ccs.name) |
|
340 | + | return NewValidationError(errors.Errorf(`%s not satisfied`, ccs.name)) |
|
286 | 341 | } |
|
287 | 342 | return nil |
|
288 | 343 | } |
@@ -303,10 +358,10 @@
Loading
303 | 358 | func (cv *claimValueIs) Validate(_ context.Context, t Token) error { |
|
304 | 359 | v, ok := t.Get(cv.name) |
|
305 | 360 | if !ok { |
|
306 | - | return errors.Errorf(`%q not satisfied: claim %q does not exist`, cv.name, cv.name) |
|
361 | + | return NewValidationError(errors.Errorf(`%q not satisfied: claim %q does not exist`, cv.name, cv.name)) |
|
307 | 362 | } |
|
308 | 363 | if v != cv.value { |
|
309 | - | return errors.Errorf(`%q not satisfied: values do not match`, cv.name) |
|
364 | + | return NewValidationError(errors.Errorf(`%q not satisfied: values do not match`, cv.name)) |
|
310 | 365 | } |
|
311 | 366 | return nil |
|
312 | 367 | } |
@@ -322,7 +377,7 @@
Loading
322 | 377 | func (ir isRequired) Validate(_ context.Context, t Token) error { |
|
323 | 378 | _, ok := t.Get(string(ir)) |
|
324 | 379 | if !ok { |
|
325 | - | return errors.Errorf(`required claim %q was not found`, string(ir)) |
|
380 | + | return NewValidationError(errors.Errorf(`required claim %q was not found`, string(ir))) |
|
326 | 381 | } |
|
327 | 382 | return nil |
|
328 | 383 | } |
Files | Coverage |
---|---|
internal | 80.92% |
jwa | 100.00% |
jwe | 64.22% |
jwk | 72.79% |
jws | 75.51% |
jwt | 75.60% |
cmd/jwx/jwx.go | 100.00% |
format.go | 84.62% |
formatkind_string_gen.go | 100.00% |
options.go | 66.67% |
x25519/x25519.go | 82.35% |
Project Totals (80 files) | 72.67% |
1542458585
1542458585
1542458585
1542458585
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file.
The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files.
The size and color of each slice is representing the number of statements and the coverage, respectively.