Bump lico to latest release

This commit is contained in:
Ralf Haferkamp
2024-05-08 17:57:35 +02:00
parent 3be286a2a3
commit 62dbf737c7
18 changed files with 92 additions and 101 deletions

6
go.mod
View File

@@ -59,7 +59,7 @@ require (
github.com/kovidgoyal/imaging v1.6.3
github.com/leonelquinteros/gotext v1.6.0
github.com/libregraph/idm v0.5.0
github.com/libregraph/lico v0.61.3-0.20240322112242-72cf9221d3a7
github.com/libregraph/lico v0.62.0
github.com/mitchellh/mapstructure v1.5.0
github.com/mna/pigeon v1.2.1
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
@@ -258,7 +258,7 @@ require (
github.com/klauspost/compress v1.17.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/libregraph/oidc-go v1.0.0 // indirect
github.com/libregraph/oidc-go v1.1.0 // indirect
github.com/longsleep/go-metrics v1.0.0 // indirect
github.com/longsleep/rndm v1.2.0 // indirect
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
@@ -293,7 +293,7 @@ require (
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/pquerna/cachecontrol v0.2.0 // indirect
github.com/prometheus/alertmanager v0.26.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.48.0 // indirect

12
go.sum
View File

@@ -1639,10 +1639,10 @@ github.com/leonelquinteros/gotext v1.6.0 h1:IYL2+dKsaYYvqGAOafaC7mpAGBhMrD/vKjHU
github.com/leonelquinteros/gotext v1.6.0/go.mod h1:qQRISjoonXYFdRGrTG1LARQ38Gpibad0IPeB4hpvyyM=
github.com/libregraph/idm v0.5.0 h1:tDMwKbAOZzdeDYMxVlY5PbSqRKO7dbAW9KT42A51WSk=
github.com/libregraph/idm v0.5.0/go.mod h1:BGMwIQ/6orJSPVzJ1x6kgG2JyG9GY05YFmbsnaD80k0=
github.com/libregraph/lico v0.61.3-0.20240322112242-72cf9221d3a7 h1:fcPgiBu7DGyGeokE0Qk+S+GW/3n+QWu1dIjw0TqadhI=
github.com/libregraph/lico v0.61.3-0.20240322112242-72cf9221d3a7/go.mod h1:TgZGBAYzVRQSRdBC8PgGQKjYhtXuTr6UCM3ZZyGTleQ=
github.com/libregraph/oidc-go v1.0.0 h1:l2tE/EwLyLXVy0B5BuVKgIFX9pNpz/5J3x5IBw0KEhc=
github.com/libregraph/oidc-go v1.0.0/go.mod h1:7TRHrY/H1Vg6JqFjV0oAe1+kN+mGFBqXYvclSyvhRyc=
github.com/libregraph/lico v0.62.0 h1:4aa0hp8kLCj/oy/aGuGKQCzdQRLHY/cvDs25bdkdcXU=
github.com/libregraph/lico v0.62.0/go.mod h1:Byuq0dALiJD9hpx2gvX5UTVZ6eWZs3sO0SeoXQQeFcE=
github.com/libregraph/oidc-go v1.1.0 h1:RyudjL3UyQblqeBQI06W53PniWobqODeeyAy6v/HumA=
github.com/libregraph/oidc-go v1.1.0/go.mod h1:qW9ubcXvZrfbbWZBaLMuk7bt5qAUMYyt9/NtXQt07Cw=
github.com/linode/linodego v0.25.3/go.mod h1:GSBKPpjoQfxEfryoCRcgkuUOCuVtGHWhzI8OMdycNTE=
github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
@@ -1840,8 +1840,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/alertmanager v0.26.0 h1:uOMJWfIwJguc3NaM3appWNbbrh6G/OjvaHMk22aBBYc=
github.com/prometheus/alertmanager v0.26.0/go.mod h1:rVcnARltVjavgVaNnmevxK7kOn7IZavyf0KNgHkbEpU=

View File

@@ -21,9 +21,9 @@ import (
"bytes"
"reflect"
"gopkg.in/square/go-jose.v2/json"
"github.com/go-jose/go-jose/v3/json"
"gopkg.in/square/go-jose.v2"
"github.com/go-jose/go-jose/v3"
)
// Builder is a utility for making JSON Web Tokens. Calls can be chained, and
@@ -36,7 +36,7 @@ type Builder interface {
Claims(i interface{}) Builder
// Token builds a JSONWebToken from provided data.
Token() (*JSONWebToken, error)
// FullSerialize serializes a token using the full serialization format.
// FullSerialize serializes a token using the JWS/JWE JSON Serialization format.
FullSerialize() (string, error)
// CompactSerialize serializes a token using the compact serialization format.
CompactSerialize() (string, error)
@@ -53,7 +53,7 @@ type NestedBuilder interface {
Claims(i interface{}) NestedBuilder
// Token builds a NestedJSONWebToken from provided data.
Token() (*NestedJSONWebToken, error)
// FullSerialize serializes a token using the full serialization format.
// FullSerialize serializes a token using the JSON Serialization format.
FullSerialize() (string, error)
// CompactSerialize serializes a token using the compact serialization format.
CompactSerialize() (string, error)

View File

@@ -21,7 +21,7 @@ import (
"strconv"
"time"
"gopkg.in/square/go-jose.v2/json"
"github.com/go-jose/go-jose/v3/json"
)
// Claims represents public claim values (as specified in RFC 7519).
@@ -111,6 +111,15 @@ func (s *Audience) UnmarshalJSON(b []byte) error {
return nil
}
// MarshalJSON converts audience to json representation.
func (s Audience) MarshalJSON() ([]byte, error) {
if len(s) == 1 {
return json.Marshal(s[0])
}
return json.Marshal([]string(s))
}
// Contains checks whether a given string is included in the Audience
func (s Audience) Contains(v string) bool {
for _, a := range s {
if a == v {

View File

@@ -15,8 +15,6 @@
*/
/*
Package jwt provides an implementation of the JSON Web Token standard.
*/
package jwt

View File

@@ -20,34 +20,34 @@ package jwt
import "errors"
// ErrUnmarshalAudience indicates that aud claim could not be unmarshalled.
var ErrUnmarshalAudience = errors.New("square/go-jose/jwt: expected string or array value to unmarshal to Audience")
var ErrUnmarshalAudience = errors.New("go-jose/go-jose/jwt: expected string or array value to unmarshal to Audience")
// ErrUnmarshalNumericDate indicates that JWT NumericDate could not be unmarshalled.
var ErrUnmarshalNumericDate = errors.New("square/go-jose/jwt: expected number value to unmarshal NumericDate")
var ErrUnmarshalNumericDate = errors.New("go-jose/go-jose/jwt: expected number value to unmarshal NumericDate")
// ErrInvalidClaims indicates that given claims have invalid type.
var ErrInvalidClaims = errors.New("square/go-jose/jwt: expected claims to be value convertible into JSON object")
var ErrInvalidClaims = errors.New("go-jose/go-jose/jwt: expected claims to be value convertible into JSON object")
// ErrInvalidIssuer indicates invalid iss claim.
var ErrInvalidIssuer = errors.New("square/go-jose/jwt: validation failed, invalid issuer claim (iss)")
var ErrInvalidIssuer = errors.New("go-jose/go-jose/jwt: validation failed, invalid issuer claim (iss)")
// ErrInvalidSubject indicates invalid sub claim.
var ErrInvalidSubject = errors.New("square/go-jose/jwt: validation failed, invalid subject claim (sub)")
var ErrInvalidSubject = errors.New("go-jose/go-jose/jwt: validation failed, invalid subject claim (sub)")
// ErrInvalidAudience indicated invalid aud claim.
var ErrInvalidAudience = errors.New("square/go-jose/jwt: validation failed, invalid audience claim (aud)")
var ErrInvalidAudience = errors.New("go-jose/go-jose/jwt: validation failed, invalid audience claim (aud)")
// ErrInvalidID indicates invalid jti claim.
var ErrInvalidID = errors.New("square/go-jose/jwt: validation failed, invalid ID claim (jti)")
var ErrInvalidID = errors.New("go-jose/go-jose/jwt: validation failed, invalid ID claim (jti)")
// ErrNotValidYet indicates that token is used before time indicated in nbf claim.
var ErrNotValidYet = errors.New("square/go-jose/jwt: validation failed, token not valid yet (nbf)")
var ErrNotValidYet = errors.New("go-jose/go-jose/jwt: validation failed, token not valid yet (nbf)")
// ErrExpired indicates that token is used after expiry time indicated in exp claim.
var ErrExpired = errors.New("square/go-jose/jwt: validation failed, token is expired (exp)")
var ErrExpired = errors.New("go-jose/go-jose/jwt: validation failed, token is expired (exp)")
// ErrIssuedInTheFuture indicates that the iat field is in the future.
var ErrIssuedInTheFuture = errors.New("square/go-jose/jwt: validation field, token issued in the future (iat)")
var ErrIssuedInTheFuture = errors.New("go-jose/go-jose/jwt: validation field, token issued in the future (iat)")
// ErrInvalidContentType indicates that token requires JWT cty header.
var ErrInvalidContentType = errors.New("square/go-jose/jwt: expected content type to be JWT (cty header)")
var ErrInvalidContentType = errors.New("go-jose/go-jose/jwt: expected content type to be JWT (cty header)")

View File

@@ -21,8 +21,8 @@ import (
"fmt"
"strings"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/json"
jose "github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/json"
)
// JSONWebToken represents a JSON Web Token (as specified in RFC7519).
@@ -39,9 +39,7 @@ type NestedJSONWebToken struct {
// Claims deserializes a JSONWebToken into dest using the provided key.
func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
payloadKey := tryJWKS(t.Headers, key)
b, err := t.payload(payloadKey)
b, err := t.payload(key)
if err != nil {
return err
}
@@ -60,7 +58,7 @@ func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
// verified. This function won't work for encrypted JWTs.
func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{}) error {
if t.unverifiedPayload == nil {
return fmt.Errorf("square/go-jose: Cannot get unverified claims")
return fmt.Errorf("go-jose/go-jose: Cannot get unverified claims")
}
claims := t.unverifiedPayload()
for _, d := range dest {
@@ -72,9 +70,7 @@ func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{}) erro
}
func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken, error) {
key := tryJWKS(t.Headers, decryptionKey)
b, err := t.enc.Decrypt(key)
b, err := t.enc.Decrypt(decryptionKey)
if err != nil {
return nil, err
}
@@ -135,35 +131,3 @@ func ParseSignedAndEncrypted(s string) (*NestedJSONWebToken, error) {
Headers: []jose.Header{enc.Header},
}, nil
}
func tryJWKS(headers []jose.Header, key interface{}) interface{} {
var jwks jose.JSONWebKeySet
switch jwksType := key.(type) {
case *jose.JSONWebKeySet:
jwks = *jwksType
case jose.JSONWebKeySet:
jwks = jwksType
default:
return key
}
var kid string
for _, header := range headers {
if header.KeyID != "" {
kid = header.KeyID
break
}
}
if kid == "" {
return key
}
keys := jwks.Key(kid)
if len(keys) == 0 {
return key
}
return keys[0].Key
}

View File

@@ -25,7 +25,9 @@ const (
)
// Expected defines values used for protected claims validation.
// If field has zero value then validation is skipped.
// If field has zero value then validation is skipped, with the exception of
// Time, where the zero value means "now." To skip validating them, set the
// corresponding field in the Claims struct to nil.
type Expected struct {
// Issuer matches the "iss" claim exactly.
Issuer string
@@ -61,7 +63,7 @@ func (c Claims) Validate(e Expected) error {
// ValidateWithLeeway checks claims in a token against expected values. A
// custom leeway may be specified for comparing time values. You may pass a
// zero value to check time values with no leeway, but you should not that
// zero value to check time values with no leeway, but you should note that
// numeric date values are rounded to the nearest second and sub-second
// precision is not supported.
//
@@ -94,20 +96,24 @@ func (c Claims) ValidateWithLeeway(e Expected, leeway time.Duration) error {
}
}
if !e.Time.IsZero() {
if c.NotBefore != nil && e.Time.Add(leeway).Before(c.NotBefore.Time()) {
return ErrNotValidYet
}
// validate using the e.Time, or time.Now if not provided
validationTime := e.Time
if validationTime.IsZero() {
validationTime = time.Now()
}
if c.Expiry != nil && e.Time.Add(-leeway).After(c.Expiry.Time()) {
return ErrExpired
}
if c.NotBefore != nil && validationTime.Add(leeway).Before(c.NotBefore.Time()) {
return ErrNotValidYet
}
// IssuedAt is optional but cannot be in the future. This is not required by the RFC, but
// something is misconfigured if this happens and we should not trust it.
if c.IssuedAt != nil && e.Time.Add(leeway).Before(c.IssuedAt.Time()) {
return ErrIssuedInTheFuture
}
if c.Expiry != nil && validationTime.Add(-leeway).After(c.Expiry.Time()) {
return ErrExpired
}
// IssuedAt is optional but cannot be in the future. This is not required by the RFC, but
// something is misconfigured if this happens and we should not trust it.
if c.IssuedAt != nil && validationTime.Add(leeway).Before(c.IssuedAt.Time()) {
return ErrIssuedInTheFuture
}
return nil

View File

@@ -4,6 +4,15 @@
## v0.62.0 (2024-05-08)
- Update golangci-lint config
- Bump go-jose to latest backwards compatible release
- Bump golang.org/x/net from 0.17.0 to 0.24.0
- enhancement: enhance Security by Allowing Same-Site Cookie Value Modification
- Bump ip from 2.0.0 to 2.0.1 in /identifier
## v0.61.2 (2024-02-19)
- Limit oidc check session iframe postMessage hook scope

View File

@@ -15,9 +15,9 @@ import (
"path/filepath"
"strings"
"github.com/go-jose/go-jose/v3"
"github.com/golang-jwt/jwt/v4"
"github.com/sirupsen/logrus"
"gopkg.in/square/go-jose.v2"
"github.com/libregraph/lico/signing"
)

View File

@@ -28,12 +28,12 @@ import (
"strings"
"time"
"github.com/deckarep/golang-set"
mapset "github.com/deckarep/golang-set"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/gorilla/mux"
"github.com/longsleep/rndm"
"github.com/sirupsen/logrus"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
"github.com/libregraph/oidc-go"

View File

@@ -22,7 +22,7 @@ import (
"net/http"
"net/url"
"gopkg.in/square/go-jose.v2"
"github.com/go-jose/go-jose/v3"
)
// Supported Authority kind string values.

View File

@@ -27,10 +27,10 @@ import (
"strings"
"sync"
"github.com/go-jose/go-jose/v3"
"github.com/golang-jwt/jwt/v4"
"github.com/libregraph/oidc-go"
"github.com/sirupsen/logrus"
"gopkg.in/square/go-jose.v2"
konnectoidc "github.com/libregraph/lico/oidc"
"github.com/libregraph/lico/oidc/payload"

View File

@@ -23,11 +23,11 @@ import (
"net/http"
"strings"
"github.com/go-jose/go-jose/v3"
"github.com/golang-jwt/jwt/v4"
"github.com/libregraph/oidc-go"
"github.com/longsleep/rndm"
"github.com/sirupsen/logrus"
"gopkg.in/square/go-jose.v2"
konnect "github.com/libregraph/lico"
"github.com/libregraph/lico/identity"

View File

@@ -26,7 +26,7 @@ import (
"sync"
"github.com/desertbit/timer"
"gopkg.in/square/go-jose.v2"
"github.com/go-jose/go-jose/v3"
)
// Provider represents an OpenID Connect server's configuration.

View File

@@ -1,8 +1,6 @@
# cachecontrol: HTTP Caching Parser and Interpretation
[![PkgGoDev](https://pkg.go.dev/badge/github.com/pquerna/cachecontrol?tab=doc)](https://pkg.go.dev/github.com/pquerna/cachecontrol?tab=doc)[![Build Status](https://travis-ci.org/pquerna/cachecontrol.svg?branch=master)](https://travis-ci.org/pquerna/cachecontrol)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/pquerna/cachecontrol?tab=doc)](https://pkg.go.dev/github.com/pquerna/cachecontrol?tab=doc)[![Build Status](https://travis-ci.org/pquerna/cachecontrol.svg?branch=main)](https://travis-ci.org/pquerna/cachecontrol)
`cachecontrol` implements [RFC 7234](http://tools.ietf.org/html/rfc7234) __Hypertext Transfer Protocol (HTTP/1.1): Caching__. It does this by parsing the `Cache-Control` and other headers, providing information about requests and responses -- but `cachecontrol` does not implement an actual cache backend, just the control plane to make decisions about if a particular response is cachable.

View File

@@ -132,7 +132,6 @@ func parse(value string, cd cacheDirective) error {
// time in seconds: http://tools.ietf.org/html/rfc7234#section-1.2.1
//
// When set to -1, this means unset.
//
type DeltaSeconds int32
// Parser for delta-seconds, a uint31, more or less:
@@ -167,7 +166,6 @@ type cacheDirective interface {
// LOW LEVEL API: Representation of possible request directives in a `Cache-Control` header: http://tools.ietf.org/html/rfc7234#section-5.2.1
//
// Note: Many fields will be `nil` in practice.
//
type RequestCacheDirectives struct {
// max-age(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.1
@@ -188,7 +186,7 @@ type RequestCacheDirectives struct {
// by no more than the specified number of seconds. If no value is
// assigned to max-stale, then the client is willing to accept a stale
// response of any age.
MaxStale DeltaSeconds
MaxStale DeltaSeconds
MaxStaleSet bool
// min-fresh(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.3
@@ -227,6 +225,9 @@ type RequestCacheDirectives struct {
// wishes to obtain a stored response.
OnlyIfCached bool
// stale-if-error(delta seconds): https://datatracker.ietf.org/doc/html/rfc5861#section-4
StaleIfError DeltaSeconds
// Extensions: http://tools.ietf.org/html/rfc7234#section-5.2.3
//
// The Cache-Control header field can be extended through the use of one
@@ -253,6 +254,8 @@ func (cd *RequestCacheDirectives) addToken(token string) error {
cd.NoTransform = true
case "only-if-cached":
cd.OnlyIfCached = true
case "stale-if-error":
err = ErrMaxAgeDeltaSeconds
default:
cd.Extensions = append(cd.Extensions, token)
}
@@ -286,6 +289,11 @@ func (cd *RequestCacheDirectives) addPair(token string, v string) error {
err = ErrNoTransformNoArgs
case "only-if-cached":
err = ErrOnlyIfCachedNoArgs
case "stale-if-error":
cd.StaleIfError, err = parseDeltaSeconds(v)
if err != nil {
err = ErrStaleIfErrorDeltaSeconds
}
default:
// TODO(pquerna): this sucks, making user re-parse
cd.Extensions = append(cd.Extensions, token+"="+v)
@@ -312,7 +320,6 @@ func ParseRequestCacheControl(value string) (*RequestCacheDirectives, error) {
// LOW LEVEL API: Repersentation of possible response directives in a `Cache-Control` header: http://tools.ietf.org/html/rfc7234#section-5.2.2
//
// Note: Many fields will be `nil` in practice.
//
type ResponseCacheDirectives struct {
// must-revalidate(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.1

8
vendor/modules.txt vendored
View File

@@ -897,6 +897,7 @@ github.com/go-ini/ini
github.com/go-jose/go-jose/v3
github.com/go-jose/go-jose/v3/cipher
github.com/go-jose/go-jose/v3/json
github.com/go-jose/go-jose/v3/jwt
# github.com/go-jose/go-jose/v4 v4.0.1
## explicit; go 1.21
github.com/go-jose/go-jose/v4
@@ -1280,7 +1281,7 @@ github.com/libregraph/idm/server
github.com/libregraph/idm/server/handler
github.com/libregraph/idm/server/handler/boltdb
github.com/libregraph/idm/server/handler/ldif
# github.com/libregraph/lico v0.61.3-0.20240322112242-72cf9221d3a7
# github.com/libregraph/lico v0.62.0
## explicit; go 1.18
github.com/libregraph/lico
github.com/libregraph/lico/bootstrap
@@ -1310,7 +1311,7 @@ github.com/libregraph/lico/server
github.com/libregraph/lico/signing
github.com/libregraph/lico/utils
github.com/libregraph/lico/version
# github.com/libregraph/oidc-go v1.0.0
# github.com/libregraph/oidc-go v1.1.0
## explicit; go 1.13
github.com/libregraph/oidc-go
# github.com/longsleep/go-metrics v1.0.0
@@ -1635,7 +1636,7 @@ github.com/pkg/xattr
# github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
## explicit
github.com/pmezard/go-difflib/difflib
# github.com/pquerna/cachecontrol v0.1.0
# github.com/pquerna/cachecontrol v0.2.0
## explicit; go 1.16
github.com/pquerna/cachecontrol
github.com/pquerna/cachecontrol/cacheobject
@@ -2325,7 +2326,6 @@ gopkg.in/ini.v1
gopkg.in/square/go-jose.v2
gopkg.in/square/go-jose.v2/cipher
gopkg.in/square/go-jose.v2/json
gopkg.in/square/go-jose.v2/jwt
# gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
## explicit
gopkg.in/tomb.v1