...

Source file src/github.com/golang-jwt/jwt/v4/types.go

Documentation: github.com/golang-jwt/jwt/v4

     1  package jwt
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math"
     7  	"reflect"
     8  	"strconv"
     9  	"time"
    10  )
    11  
    12  // TimePrecision sets the precision of times and dates within this library.
    13  // This has an influence on the precision of times when comparing expiry or
    14  // other related time fields. Furthermore, it is also the precision of times
    15  // when serializing.
    16  //
    17  // For backwards compatibility the default precision is set to seconds, so that
    18  // no fractional timestamps are generated.
    19  var TimePrecision = time.Second
    20  
    21  // MarshalSingleStringAsArray modifies the behaviour of the ClaimStrings type, especially
    22  // its MarshalJSON function.
    23  //
    24  // If it is set to true (the default), it will always serialize the type as an
    25  // array of strings, even if it just contains one element, defaulting to the behaviour
    26  // of the underlying []string. If it is set to false, it will serialize to a single
    27  // string, if it contains one element. Otherwise, it will serialize to an array of strings.
    28  var MarshalSingleStringAsArray = true
    29  
    30  // NumericDate represents a JSON numeric date value, as referenced at
    31  // https://datatracker.ietf.org/doc/html/rfc7519#section-2.
    32  type NumericDate struct {
    33  	time.Time
    34  }
    35  
    36  // NewNumericDate constructs a new *NumericDate from a standard library time.Time struct.
    37  // It will truncate the timestamp according to the precision specified in TimePrecision.
    38  func NewNumericDate(t time.Time) *NumericDate {
    39  	return &NumericDate{t.Truncate(TimePrecision)}
    40  }
    41  
    42  // newNumericDateFromSeconds creates a new *NumericDate out of a float64 representing a
    43  // UNIX epoch with the float fraction representing non-integer seconds.
    44  func newNumericDateFromSeconds(f float64) *NumericDate {
    45  	round, frac := math.Modf(f)
    46  	return NewNumericDate(time.Unix(int64(round), int64(frac*1e9)))
    47  }
    48  
    49  // MarshalJSON is an implementation of the json.RawMessage interface and serializes the UNIX epoch
    50  // represented in NumericDate to a byte array, using the precision specified in TimePrecision.
    51  func (date NumericDate) MarshalJSON() (b []byte, err error) {
    52  	var prec int
    53  	if TimePrecision < time.Second {
    54  		prec = int(math.Log10(float64(time.Second) / float64(TimePrecision)))
    55  	}
    56  	truncatedDate := date.Truncate(TimePrecision)
    57  
    58  	// For very large timestamps, UnixNano would overflow an int64, but this
    59  	// function requires nanosecond level precision, so we have to use the
    60  	// following technique to get round the issue:
    61  	// 1. Take the normal unix timestamp to form the whole number part of the
    62  	//    output,
    63  	// 2. Take the result of the Nanosecond function, which retuns the offset
    64  	//    within the second of the particular unix time instance, to form the
    65  	//    decimal part of the output
    66  	// 3. Concatenate them to produce the final result
    67  	seconds := strconv.FormatInt(truncatedDate.Unix(), 10)
    68  	nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64)
    69  
    70  	output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...)
    71  
    72  	return output, nil
    73  }
    74  
    75  // UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a
    76  // NumericDate from a JSON representation, i.e. a json.Number. This number represents an UNIX epoch
    77  // with either integer or non-integer seconds.
    78  func (date *NumericDate) UnmarshalJSON(b []byte) (err error) {
    79  	var (
    80  		number json.Number
    81  		f      float64
    82  	)
    83  
    84  	if err = json.Unmarshal(b, &number); err != nil {
    85  		return fmt.Errorf("could not parse NumericData: %w", err)
    86  	}
    87  
    88  	if f, err = number.Float64(); err != nil {
    89  		return fmt.Errorf("could not convert json number value to float: %w", err)
    90  	}
    91  
    92  	n := newNumericDateFromSeconds(f)
    93  	*date = *n
    94  
    95  	return nil
    96  }
    97  
    98  // ClaimStrings is basically just a slice of strings, but it can be either serialized from a string array or just a string.
    99  // This type is necessary, since the "aud" claim can either be a single string or an array.
   100  type ClaimStrings []string
   101  
   102  func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) {
   103  	var value interface{}
   104  
   105  	if err = json.Unmarshal(data, &value); err != nil {
   106  		return err
   107  	}
   108  
   109  	var aud []string
   110  
   111  	switch v := value.(type) {
   112  	case string:
   113  		aud = append(aud, v)
   114  	case []string:
   115  		aud = ClaimStrings(v)
   116  	case []interface{}:
   117  		for _, vv := range v {
   118  			vs, ok := vv.(string)
   119  			if !ok {
   120  				return &json.UnsupportedTypeError{Type: reflect.TypeOf(vv)}
   121  			}
   122  			aud = append(aud, vs)
   123  		}
   124  	case nil:
   125  		return nil
   126  	default:
   127  		return &json.UnsupportedTypeError{Type: reflect.TypeOf(v)}
   128  	}
   129  
   130  	*s = aud
   131  
   132  	return
   133  }
   134  
   135  func (s ClaimStrings) MarshalJSON() (b []byte, err error) {
   136  	// This handles a special case in the JWT RFC. If the string array, e.g. used by the "aud" field,
   137  	// only contains one element, it MAY be serialized as a single string. This may or may not be
   138  	// desired based on the ecosystem of other JWT library used, so we make it configurable by the
   139  	// variable MarshalSingleStringAsArray.
   140  	if len(s) == 1 && !MarshalSingleStringAsArray {
   141  		return json.Marshal(s[0])
   142  	}
   143  
   144  	return json.Marshal([]string(s))
   145  }
   146  

View as plain text