lestrrat-go / jwx
Showing 1 of 3 files from the diff.
Other files ignored by Codecov

@@ -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%
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.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading