Initial QSfera import
This commit is contained in:
+597
@@ -0,0 +1,597 @@
|
||||
//go:generate ../tools/cmd/genjwt.sh
|
||||
//go:generate stringer -type=TokenOption -output=token_options_gen.go
|
||||
|
||||
// Package jwt implements JSON Web Tokens as described in https://tools.ietf.org/html/rfc7519
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/json"
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jws"
|
||||
jwterrs "github.com/lestrrat-go/jwx/v3/jwt/internal/errors"
|
||||
"github.com/lestrrat-go/jwx/v3/jwt/internal/types"
|
||||
)
|
||||
|
||||
var defaultTruncation atomic.Int64
|
||||
|
||||
// Settings controls global settings that are specific to JWTs.
|
||||
func Settings(options ...GlobalOption) {
|
||||
var flattenAudience bool
|
||||
var parsePedantic bool
|
||||
var parsePrecision = types.MaxPrecision + 1 // illegal value, so we can detect nothing was set
|
||||
var formatPrecision = types.MaxPrecision + 1 // illegal value, so we can detect nothing was set
|
||||
truncation := time.Duration(-1)
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identTruncation{}:
|
||||
if err := option.Value(&truncation); err != nil {
|
||||
panic(fmt.Sprintf("jwt.Settings: value for WithTruncation must be time.Duration: %s", err))
|
||||
}
|
||||
case identFlattenAudience{}:
|
||||
if err := option.Value(&flattenAudience); err != nil {
|
||||
panic(fmt.Sprintf("jwt.Settings: value for WithFlattenAudience must be bool: %s", err))
|
||||
}
|
||||
case identNumericDateParsePedantic{}:
|
||||
if err := option.Value(&parsePedantic); err != nil {
|
||||
panic(fmt.Sprintf("jwt.Settings: value for WithNumericDateParsePedantic must be bool: %s", err))
|
||||
}
|
||||
case identNumericDateParsePrecision{}:
|
||||
var v int
|
||||
if err := option.Value(&v); err != nil {
|
||||
panic(fmt.Sprintf("jwt.Settings: value for WithNumericDateParsePrecision must be int: %s", err))
|
||||
}
|
||||
// only accept this value if it's in our desired range
|
||||
if v >= 0 && v <= int(types.MaxPrecision) {
|
||||
parsePrecision = uint32(v)
|
||||
}
|
||||
case identNumericDateFormatPrecision{}:
|
||||
var v int
|
||||
if err := option.Value(&v); err != nil {
|
||||
panic(fmt.Sprintf("jwt.Settings: value for WithNumericDateFormatPrecision must be int: %s", err))
|
||||
}
|
||||
// only accept this value if it's in our desired range
|
||||
if v >= 0 && v <= int(types.MaxPrecision) {
|
||||
formatPrecision = uint32(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if parsePrecision <= types.MaxPrecision { // remember we set default to max + 1
|
||||
v := atomic.LoadUint32(&types.ParsePrecision)
|
||||
if v != parsePrecision {
|
||||
atomic.CompareAndSwapUint32(&types.ParsePrecision, v, parsePrecision)
|
||||
}
|
||||
}
|
||||
|
||||
if formatPrecision <= types.MaxPrecision { // remember we set default to max + 1
|
||||
v := atomic.LoadUint32(&types.FormatPrecision)
|
||||
if v != formatPrecision {
|
||||
atomic.CompareAndSwapUint32(&types.FormatPrecision, v, formatPrecision)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
v := atomic.LoadUint32(&types.Pedantic)
|
||||
if (v == 1) != parsePedantic {
|
||||
var newVal uint32
|
||||
if parsePedantic {
|
||||
newVal = 1
|
||||
}
|
||||
atomic.CompareAndSwapUint32(&types.Pedantic, v, newVal)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
defaultOptionsMu.Lock()
|
||||
if flattenAudience {
|
||||
defaultOptions.Enable(FlattenAudience)
|
||||
} else {
|
||||
defaultOptions.Disable(FlattenAudience)
|
||||
}
|
||||
defaultOptionsMu.Unlock()
|
||||
}
|
||||
|
||||
if truncation >= 0 {
|
||||
defaultTruncation.Store(int64(truncation))
|
||||
}
|
||||
}
|
||||
|
||||
var registry = json.NewRegistry()
|
||||
|
||||
// ParseString calls Parse against a string
|
||||
func ParseString(s string, options ...ParseOption) (Token, error) {
|
||||
tok, err := parseBytes([]byte(s), options...)
|
||||
if err != nil {
|
||||
return nil, jwterrs.ParseErrorf(`jwt.ParseString`, `failed to parse string: %w`, err)
|
||||
}
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// Parse parses the JWT token payload and creates a new `jwt.Token` object.
|
||||
// The token must be encoded in JWS compact format, or a raw JSON form of JWT
|
||||
// without any signatures.
|
||||
//
|
||||
// If you need JWE support on top of JWS, you will need to rollout your
|
||||
// own workaround.
|
||||
//
|
||||
// If the token is signed, and you want to verify the payload matches the signature,
|
||||
// you must pass the jwt.WithKey(alg, key) or jwt.WithKeySet(jwk.Set) option.
|
||||
// If you do not specify these parameters, no verification will be performed.
|
||||
//
|
||||
// During verification, if the JWS headers specify a key ID (`kid`), the
|
||||
// key used for verification must match the specified ID. If you are somehow
|
||||
// using a key without a `kid` (which is highly unlikely if you are working
|
||||
// with a JWT from a well-know provider), you can work around this by modifying
|
||||
// the `jwk.Key` and setting the `kid` header.
|
||||
//
|
||||
// If you also want to assert the validity of the JWT itself (i.e. expiration
|
||||
// and such), use the `Validate()` function on the returned token, or pass the
|
||||
// `WithValidate(true)` option. Validate options can also be passed to
|
||||
// `Parse`
|
||||
//
|
||||
// This function takes both ParseOption and ValidateOption types:
|
||||
// ParseOptions control the parsing behavior, and ValidateOptions are
|
||||
// passed to `Validate()` when `jwt.WithValidate` is specified.
|
||||
func Parse(s []byte, options ...ParseOption) (Token, error) {
|
||||
tok, err := parseBytes(s, options...)
|
||||
if err != nil {
|
||||
return nil, jwterrs.ParseErrorf(`jwt.Parse`, `failed to parse token: %w`, err)
|
||||
}
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// ParseInsecure is exactly the same as Parse(), but it disables
|
||||
// signature verification and token validation.
|
||||
//
|
||||
// You cannot override `jwt.WithVerify()` or `jwt.WithValidate()`
|
||||
// using this function. Providing these options would result in
|
||||
// an error
|
||||
func ParseInsecure(s []byte, options ...ParseOption) (Token, error) {
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identVerify{}, identValidate{}:
|
||||
return nil, jwterrs.ParseErrorf(`jwt.ParseInsecure`, `jwt.WithVerify() and jwt.WithValidate() may not be specified`)
|
||||
}
|
||||
}
|
||||
|
||||
options = append(options, WithVerify(false), WithValidate(false))
|
||||
tok, err := Parse(s, options...)
|
||||
if err != nil {
|
||||
return nil, jwterrs.ParseErrorf(`jwt.ParseInsecure`, `failed to parse token: %w`, err)
|
||||
}
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// ParseReader calls Parse against an io.Reader
|
||||
func ParseReader(src io.Reader, options ...ParseOption) (Token, error) {
|
||||
// We're going to need the raw bytes regardless. Read it.
|
||||
data, err := io.ReadAll(src)
|
||||
if err != nil {
|
||||
return nil, jwterrs.ParseErrorf(`jwt.ParseReader`, `failed to read from token data source: %w`, err)
|
||||
}
|
||||
tok, err := parseBytes(data, options...)
|
||||
if err != nil {
|
||||
return nil, jwterrs.ParseErrorf(`jwt.ParseReader`, `failed to parse token: %w`, err)
|
||||
}
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
type parseCtx struct {
|
||||
token Token
|
||||
validateOpts []ValidateOption
|
||||
verifyOpts []jws.VerifyOption
|
||||
localReg *json.Registry
|
||||
pedantic bool
|
||||
skipVerification bool
|
||||
validate bool
|
||||
withKeyCount int
|
||||
withKey *withKey // this is used to detect if we have a WithKey option
|
||||
}
|
||||
|
||||
func parseBytes(data []byte, options ...ParseOption) (Token, error) {
|
||||
var ctx parseCtx
|
||||
|
||||
// Validation is turned on by default. You need to specify
|
||||
// jwt.WithValidate(false) if you want to disable it
|
||||
ctx.validate = true
|
||||
|
||||
// Verification is required (i.e., it is assumed that the incoming
|
||||
// data is in JWS format) unless the user explicitly asks for
|
||||
// it to be skipped.
|
||||
verification := true
|
||||
|
||||
var verifyOpts []Option
|
||||
for _, o := range options {
|
||||
if v, ok := o.(ValidateOption); ok {
|
||||
ctx.validateOpts = append(ctx.validateOpts, v)
|
||||
// context is used for both verification and validation, so we can't just continue
|
||||
switch o.Ident() {
|
||||
case identContext{}:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch o.Ident() {
|
||||
case identKey{}:
|
||||
// it would be nice to be able to detect if ctx.verifyOpts[0]
|
||||
// is a WithKey option, but unfortunately at that point we have
|
||||
// already converted the options to a jws option, which means
|
||||
// we can no longer compare its Ident() to jwt.identKey{}.
|
||||
// So let's just count this here
|
||||
ctx.withKeyCount++
|
||||
if ctx.withKeyCount == 1 {
|
||||
if err := o.Value(&ctx.withKey); err != nil {
|
||||
return nil, fmt.Errorf("jws.parseBytes: value for WithKey option must be a *jwt.withKey: %w", err)
|
||||
}
|
||||
}
|
||||
verifyOpts = append(verifyOpts, o)
|
||||
case identKeySet{}, identVerifyAuto{}, identKeyProvider{}, identBase64Encoder{}, identContext{}:
|
||||
verifyOpts = append(verifyOpts, o)
|
||||
case identToken{}:
|
||||
var token Token
|
||||
if err := o.Value(&token); err != nil {
|
||||
return nil, fmt.Errorf("jws.parseBytes: value for WithToken option must be a jwt.Token: %w", err)
|
||||
}
|
||||
ctx.token = token
|
||||
case identPedantic{}:
|
||||
if err := o.Value(&ctx.pedantic); err != nil {
|
||||
return nil, fmt.Errorf("jws.parseBytes: value for WithPedantic option must be a bool: %w", err)
|
||||
}
|
||||
case identValidate{}:
|
||||
if err := o.Value(&ctx.validate); err != nil {
|
||||
return nil, fmt.Errorf("jws.parseBytes: value for WithValidate option must be a bool: %w", err)
|
||||
}
|
||||
case identVerify{}:
|
||||
if err := o.Value(&verification); err != nil {
|
||||
return nil, fmt.Errorf("jws.parseBytes: value for WithVerify option must be a bool: %w", err)
|
||||
}
|
||||
case identTypedClaim{}:
|
||||
var pair claimPair
|
||||
if err := o.Value(&pair); err != nil {
|
||||
return nil, fmt.Errorf("jws.parseBytes: value for WithTypedClaim option must be claimPair: %w", err)
|
||||
}
|
||||
if ctx.localReg == nil {
|
||||
ctx.localReg = json.NewRegistry()
|
||||
}
|
||||
ctx.localReg.Register(pair.Name, pair.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if !verification {
|
||||
ctx.skipVerification = true
|
||||
}
|
||||
|
||||
lvo := len(verifyOpts)
|
||||
if lvo == 0 && verification {
|
||||
return nil, fmt.Errorf(`jwt.Parse: no keys for verification are provided (use jwt.WithVerify(false) to explicitly skip)`)
|
||||
}
|
||||
|
||||
if lvo > 0 {
|
||||
converted, err := toVerifyOptions(verifyOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jwt.Parse: failed to convert options into jws.VerifyOption: %w`, err)
|
||||
}
|
||||
ctx.verifyOpts = converted
|
||||
}
|
||||
|
||||
data = bytes.TrimSpace(data)
|
||||
return parse(&ctx, data)
|
||||
}
|
||||
|
||||
const (
|
||||
_JwsVerifyInvalid = iota
|
||||
_JwsVerifyDone
|
||||
_JwsVerifyExpectNested
|
||||
_JwsVerifySkipped
|
||||
)
|
||||
|
||||
var _ = _JwsVerifyInvalid
|
||||
|
||||
func verifyJWS(ctx *parseCtx, payload []byte) ([]byte, int, error) {
|
||||
lvo := len(ctx.verifyOpts)
|
||||
if lvo == 0 {
|
||||
return nil, _JwsVerifySkipped, nil
|
||||
}
|
||||
|
||||
if lvo == 1 && ctx.withKeyCount == 1 {
|
||||
wk := ctx.withKey
|
||||
alg, ok := wk.alg.(jwa.SignatureAlgorithm)
|
||||
if ok && len(wk.options) == 0 {
|
||||
verified, err := jws.VerifyCompactFast(wk.key, payload, alg)
|
||||
if err != nil {
|
||||
return nil, _JwsVerifyDone, err
|
||||
}
|
||||
return verified, _JwsVerifyDone, nil
|
||||
}
|
||||
}
|
||||
|
||||
verifyOpts := append(ctx.verifyOpts, jws.WithCompact())
|
||||
verified, err := jws.Verify(payload, verifyOpts...)
|
||||
return verified, _JwsVerifyDone, err
|
||||
}
|
||||
|
||||
// verify parameter exists to make sure that we don't accidentally skip
|
||||
// over verification just because alg == "" or key == nil or something.
|
||||
func parse(ctx *parseCtx, data []byte) (Token, error) {
|
||||
payload := data
|
||||
const maxDecodeLevels = 2
|
||||
|
||||
// If cty = `JWT`, we expect this to be a nested structure
|
||||
var expectNested bool
|
||||
|
||||
OUTER:
|
||||
for i := range maxDecodeLevels {
|
||||
switch kind := jwx.GuessFormat(payload); kind {
|
||||
case jwx.JWT:
|
||||
if ctx.pedantic {
|
||||
if expectNested {
|
||||
return nil, fmt.Errorf(`expected nested encrypted/signed payload, got raw JWT`)
|
||||
}
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
// We were NOT enveloped in other formats
|
||||
if !ctx.skipVerification {
|
||||
if _, _, err := verifyJWS(ctx, payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break OUTER
|
||||
case jwx.InvalidFormat:
|
||||
return nil, UnknownPayloadTypeError()
|
||||
case jwx.UnknownFormat:
|
||||
// "Unknown" may include invalid JWTs, for example, those who lack "aud"
|
||||
// claim. We could be pedantic and reject these
|
||||
if ctx.pedantic {
|
||||
return nil, fmt.Errorf(`unknown JWT format (pedantic)`)
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
// We were NOT enveloped in other formats
|
||||
if !ctx.skipVerification {
|
||||
if _, _, err := verifyJWS(ctx, payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
break OUTER
|
||||
case jwx.JWS:
|
||||
// Food for thought: This is going to break if you have multiple layers of
|
||||
// JWS enveloping using different keys. It is highly unlikely use case,
|
||||
// but it might happen.
|
||||
|
||||
// skipVerification should only be set to true by us. It's used
|
||||
// when we just want to parse the JWT out of a payload
|
||||
if !ctx.skipVerification {
|
||||
// nested return value means:
|
||||
// false (next envelope _may_ need to be processed)
|
||||
// true (next envelope MUST be processed)
|
||||
v, state, err := verifyJWS(ctx, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if state != _JwsVerifySkipped {
|
||||
payload = v
|
||||
|
||||
// We only check for cty and typ if the pedantic flag is enabled
|
||||
if !ctx.pedantic {
|
||||
continue
|
||||
}
|
||||
|
||||
if state == _JwsVerifyExpectNested {
|
||||
expectNested = true
|
||||
continue OUTER
|
||||
}
|
||||
|
||||
// if we're not nested, we found our target. bail out of this loop
|
||||
break OUTER
|
||||
}
|
||||
}
|
||||
|
||||
// No verification.
|
||||
m, err := jws.Parse(data, jws.WithCompact())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`invalid jws message: %w`, err)
|
||||
}
|
||||
payload = m.Payload()
|
||||
default:
|
||||
return nil, fmt.Errorf(`unsupported format (layer: #%d)`, i+1)
|
||||
}
|
||||
expectNested = false
|
||||
}
|
||||
|
||||
if ctx.token == nil {
|
||||
ctx.token = New()
|
||||
}
|
||||
|
||||
if ctx.localReg != nil {
|
||||
dcToken, ok := ctx.token.(TokenWithDecodeCtx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`typed claim was requested, but the token (%T) does not support DecodeCtx`, ctx.token)
|
||||
}
|
||||
dc := json.NewDecodeCtx(ctx.localReg)
|
||||
dcToken.SetDecodeCtx(dc)
|
||||
defer func() { dcToken.SetDecodeCtx(nil) }()
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(payload, ctx.token); err != nil {
|
||||
return nil, fmt.Errorf(`failed to parse token: %w`, err)
|
||||
}
|
||||
|
||||
if ctx.validate {
|
||||
if err := Validate(ctx.token, ctx.validateOpts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ctx.token, nil
|
||||
}
|
||||
|
||||
// Sign is a convenience function to create a signed JWT token serialized in
|
||||
// compact form.
|
||||
//
|
||||
// It accepts either a raw key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc)
|
||||
// or a jwk.Key, and the name of the algorithm that should be used to sign
|
||||
// the token.
|
||||
//
|
||||
// For well-known algorithms with no special considerations (e.g. detached
|
||||
// payloads, extra protected heders, etc), this function will automatically
|
||||
// take the fast path and bypass the jws.Sign() machinery, which improves
|
||||
// performance significantly.
|
||||
//
|
||||
// If the key is a jwk.Key and the key contains a key ID (`kid` field),
|
||||
// then it is added to the protected header generated by the signature
|
||||
//
|
||||
// The algorithm specified in the `alg` parameter must be able to support
|
||||
// the type of key you provided, otherwise an error is returned.
|
||||
// For convenience `alg` is of type jwa.KeyAlgorithm so you can pass
|
||||
// the return value of `(jwk.Key).Algorithm()` directly, but in practice
|
||||
// it must be an instance of jwa.SignatureAlgorithm, otherwise an error
|
||||
// is returned.
|
||||
//
|
||||
// The protected header will also automatically have the `typ` field set
|
||||
// to the literal value `JWT`, unless you provide a custom value for it
|
||||
// by jws.WithProtectedHeaders option, that can be passed to `jwt.WithKey“.
|
||||
func Sign(t Token, options ...SignOption) ([]byte, error) {
|
||||
// fast path; can only happen if there is exactly one option
|
||||
if len(options) == 1 && (options[0].Ident() == identKey{}) {
|
||||
// The option must be a withKey option.
|
||||
var wk *withKey
|
||||
if err := options[0].Value(&wk); err == nil {
|
||||
alg, ok := wk.alg.(jwa.SignatureAlgorithm)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`jwt.Sign: invalid algorithm type %T. jwa.SignatureAlgorithm is required`, wk.alg)
|
||||
}
|
||||
|
||||
// Check if option contains anything other than alg/key
|
||||
if len(wk.options) == 0 {
|
||||
// yay, we have something we can put in the FAST PATH!
|
||||
return signFast(t, alg, wk.key)
|
||||
}
|
||||
// fallthrough
|
||||
}
|
||||
// fallthrough
|
||||
}
|
||||
|
||||
var soptions []jws.SignOption
|
||||
if l := len(options); l > 0 {
|
||||
// we need to from SignOption to Option because ... reasons
|
||||
// (todo: when go1.18 prevails, use type parameters
|
||||
rawoptions := make([]Option, l)
|
||||
for i, option := range options {
|
||||
rawoptions[i] = option
|
||||
}
|
||||
|
||||
converted, err := toSignOptions(rawoptions...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jwt.Sign: failed to convert options into jws.SignOption: %w`, err)
|
||||
}
|
||||
soptions = converted
|
||||
}
|
||||
return NewSerializer().sign(soptions...).Serialize(t)
|
||||
}
|
||||
|
||||
// Equal compares two JWT tokens. Do not use `reflect.Equal` or the like
|
||||
// to compare tokens as they will also compare extra detail such as
|
||||
// sync.Mutex objects used to control concurrent access.
|
||||
//
|
||||
// The comparison for values is currently done using a simple equality ("=="),
|
||||
// except for time.Time, which uses time.Equal after dropping the monotonic
|
||||
// clock and truncating the values to 1 second accuracy.
|
||||
//
|
||||
// if both t1 and t2 are nil, returns true
|
||||
func Equal(t1, t2 Token) bool {
|
||||
if t1 == nil && t2 == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// we already checked for t1 == t2 == nil, so safe to do this
|
||||
if t1 == nil || t2 == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
j1, err := json.Marshal(t1)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
j2, err := json.Marshal(t2)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(j1, j2)
|
||||
}
|
||||
|
||||
func (t *stdToken) Clone() (Token, error) {
|
||||
dst := New()
|
||||
|
||||
dst.Options().Set(*(t.Options()))
|
||||
for _, k := range t.Keys() {
|
||||
var v any
|
||||
if err := t.Get(k, &v); err != nil {
|
||||
return nil, fmt.Errorf(`jwt.Clone: failed to get %s: %w`, k, err)
|
||||
}
|
||||
if err := dst.Set(k, v); err != nil {
|
||||
return nil, fmt.Errorf(`jwt.Clone failed to set %s: %w`, k, err)
|
||||
}
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
type CustomDecoder = json.CustomDecoder
|
||||
type CustomDecodeFunc = json.CustomDecodeFunc
|
||||
|
||||
// RegisterCustomField allows users to specify that a private field
|
||||
// be decoded as an instance of the specified type. This option has
|
||||
// a global effect.
|
||||
//
|
||||
// For example, suppose you have a custom field `x-birthday`, which
|
||||
// you want to represent as a string formatted in RFC3339 in JSON,
|
||||
// but want it back as `time.Time`.
|
||||
//
|
||||
// In such case you would register a custom field as follows
|
||||
//
|
||||
// jwt.RegisterCustomField(`x-birthday`, time.Time{})
|
||||
//
|
||||
// Then you can use a `time.Time` variable to extract the value
|
||||
// of `x-birthday` field, instead of having to use `any`
|
||||
// and later convert it to `time.Time`
|
||||
//
|
||||
// var bday time.Time
|
||||
// _ = token.Get(`x-birthday`, &bday)
|
||||
//
|
||||
// If you need a more fine-tuned control over the decoding process,
|
||||
// you can register a `CustomDecoder`. For example, below shows
|
||||
// how to register a decoder that can parse RFC822 format string:
|
||||
//
|
||||
// jwt.RegisterCustomField(`x-birthday`, jwt.CustomDecodeFunc(func(data []byte) (any, error) {
|
||||
// return time.Parse(time.RFC822, string(data))
|
||||
// }))
|
||||
//
|
||||
// Please note that use of custom fields can be problematic if you
|
||||
// are using a library that does not implement MarshalJSON/UnmarshalJSON
|
||||
// and you try to roundtrip from an object to JSON, and then back to an object.
|
||||
// For example, in the above example, you can _parse_ time values formatted
|
||||
// in the format specified in RFC822, but when you convert an object into
|
||||
// JSON, it will be formatted in RFC3339, because that's what `time.Time`
|
||||
// likes to do. To avoid this, it's always better to use a custom type
|
||||
// that wraps your desired type (in this case `time.Time`) and implement
|
||||
// MarshalJSON and UnmashalJSON.
|
||||
func RegisterCustomField(name string, object any) {
|
||||
registry.Register(name, object)
|
||||
}
|
||||
|
||||
func getDefaultTruncation() time.Duration {
|
||||
return time.Duration(defaultTruncation.Load())
|
||||
}
|
||||
Reference in New Issue
Block a user