@@ -100,6 +100,13 @@ func LicensesEntitlements(
100
100
// 'Entitlements' group as a whole.
101
101
for _ , license := range licenses {
102
102
claims , err := ParseClaims (license .JWT , keys )
103
+ var vErr * jwt.ValidationError
104
+ if xerrors .As (err , & vErr ) && vErr .Is (jwt .ErrTokenNotValidYet ) {
105
+ // The license isn't valid yet. We don't consider any entitlements contained in it, but
106
+ // it's also not an error. Just skip it silently. This can happen if an administrator
107
+ // uploads a license for a new term that hasn't started yet.
108
+ continue
109
+ }
103
110
if err != nil {
104
111
entitlements .Errors = append (entitlements .Errors ,
105
112
fmt .Sprintf ("Invalid license (%s) parsing claims: %s" , license .UUID .String (), err .Error ()))
@@ -287,6 +294,8 @@ var (
287
294
ErrInvalidVersion = xerrors .New ("license must be version 3" )
288
295
ErrMissingKeyID = xerrors .Errorf ("JOSE header must contain %s" , HeaderKeyID )
289
296
ErrMissingLicenseExpires = xerrors .New ("license missing license_expires" )
297
+ ErrMissingExp = xerrors .New ("exp claim missing or not parsable" )
298
+ ErrMultipleIssues = xerrors .New ("license has multiple issues; contact support" )
290
299
)
291
300
292
301
type Features map [codersdk.FeatureName ]int64
@@ -336,7 +345,7 @@ func ParseRaw(l string, keys map[string]ed25519.PublicKey) (jwt.MapClaims, error
336
345
return nil , xerrors .New ("unable to parse Claims" )
337
346
}
338
347
339
- // ParseClaims validates a database.License record , and if valid, returns the claims. If
348
+ // ParseClaims validates a raw JWT , and if valid, returns the claims. If
340
349
// unparsable or invalid, it returns an error
341
350
func ParseClaims (rawJWT string , keys map [string ]ed25519.PublicKey ) (* Claims , error ) {
342
351
tok , err := jwt .ParseWithClaims (
@@ -348,18 +357,53 @@ func ParseClaims(rawJWT string, keys map[string]ed25519.PublicKey) (*Claims, err
348
357
if err != nil {
349
358
return nil , err
350
359
}
351
- if claims , ok := tok .Claims .(* Claims ); ok && tok .Valid {
360
+ return validateClaims (tok )
361
+ }
362
+
363
+ func validateClaims (tok * jwt.Token ) (* Claims , error ) {
364
+ if claims , ok := tok .Claims .(* Claims ); ok {
352
365
if claims .Version != uint64 (CurrentVersion ) {
353
366
return nil , ErrInvalidVersion
354
367
}
355
368
if claims .LicenseExpires == nil {
356
369
return nil , ErrMissingLicenseExpires
357
370
}
371
+ if claims .ExpiresAt == nil {
372
+ return nil , ErrMissingExp
373
+ }
358
374
return claims , nil
359
375
}
360
376
return nil , xerrors .New ("unable to parse Claims" )
361
377
}
362
378
379
+ // ParseClaimsIgnoreNbf validates a raw JWT, but ignores `nbf` claim. If otherwise valid, it returns
380
+ // the claims. If unparsable or invalid, it returns an error. Ignoring the `nbf` (not before) is
381
+ // useful to determine if a JWT _will_ become valid at any point now or in the future.
382
+ func ParseClaimsIgnoreNbf (rawJWT string , keys map [string ]ed25519.PublicKey ) (* Claims , error ) {
383
+ tok , err := jwt .ParseWithClaims (
384
+ rawJWT ,
385
+ & Claims {},
386
+ keyFunc (keys ),
387
+ jwt .WithValidMethods (ValidMethods ),
388
+ )
389
+ var vErr * jwt.ValidationError
390
+ if xerrors .As (err , & vErr ) {
391
+ // zero out the NotValidYet error to check if there were other problems
392
+ vErr .Errors = vErr .Errors & (^ jwt .ValidationErrorNotValidYet )
393
+ if vErr .Errors != 0 {
394
+ // There are other errors besides not being valid yet. We _could_ go
395
+ // through all the jwt.ValidationError bits and try to work out the
396
+ // correct error, but if we get here something very strange is
397
+ // going on so let's just return a generic error that says to get in
398
+ // touch with our support team.
399
+ return nil , ErrMultipleIssues
400
+ }
401
+ } else if err != nil {
402
+ return nil , err
403
+ }
404
+ return validateClaims (tok )
405
+ }
406
+
363
407
func keyFunc (keys map [string ]ed25519.PublicKey ) func (* jwt.Token ) (interface {}, error ) {
364
408
return func (j * jwt.Token ) (interface {}, error ) {
365
409
keyID , ok := j .Header [HeaderKeyID ].(string )
0 commit comments