...

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

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

     1  package jwt
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math"
     7  	"strconv"
     8  	"time"
     9  )
    10  
    11  // TimePrecision sets the precision of times and dates within this library. This
    12  // has an influence on the precision of times when comparing expiry or other
    13  // related time fields. Furthermore, it is also the precision of times when
    14  // serializing.
    15  //
    16  // For backwards compatibility the default precision is set to seconds, so that
    17  // no fractional timestamps are generated.
    18  var TimePrecision = time.Second
    19  
    20  // MarshalSingleStringAsArray modifies the behavior of the ClaimStrings type,
    21  // especially its MarshalJSON function.
    22  //
    23  // If it is set to true (the default), it will always serialize the type as an
    24  // array of strings, even if it just contains one element, defaulting to the
    25  // behavior of the underlying []string. If it is set to false, it will serialize
    26  // to a single string, if it contains one element. Otherwise, it will serialize
    27  // 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  	//
    62  	// 1. Take the normal unix timestamp to form the whole number part of the
    63  	//    output,
    64  	// 2. Take the result of the Nanosecond function, which returns the offset
    65  	//    within the second of the particular unix time instance, to form the
    66  	//    decimal part of the output
    67  	// 3. Concatenate them to produce the final result
    68  	seconds := strconv.FormatInt(truncatedDate.Unix(), 10)
    69  	nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64)
    70  
    71  	output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...)
    72  
    73  	return output, nil
    74  }
    75  
    76  // UnmarshalJSON is an implementation of the json.RawMessage interface and
    77  // deserializes a [NumericDate] from a JSON representation, i.e. a
    78  // [json.Number]. This number represents an UNIX epoch with either integer or
    79  // non-integer seconds.
    80  func (date *NumericDate) UnmarshalJSON(b []byte) (err error) {
    81  	var (
    82  		number json.Number
    83  		f      float64
    84  	)
    85  
    86  	if err = json.Unmarshal(b, &number); err != nil {
    87  		return fmt.Errorf("could not parse NumericData: %w", err)
    88  	}
    89  
    90  	if f, err = number.Float64(); err != nil {
    91  		return fmt.Errorf("could not convert json number value to float: %w", err)
    92  	}
    93  
    94  	n := newNumericDateFromSeconds(f)
    95  	*date = *n
    96  
    97  	return nil
    98  }
    99  
   100  // ClaimStrings is basically just a slice of strings, but it can be either
   101  // serialized from a string array or just a string. This type is necessary,
   102  // since the "aud" claim can either be a single string or an array.
   103  type ClaimStrings []string
   104  
   105  func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) {
   106  	var value interface{}
   107  
   108  	if err = json.Unmarshal(data, &value); err != nil {
   109  		return err
   110  	}
   111  
   112  	var aud []string
   113  
   114  	switch v := value.(type) {
   115  	case string:
   116  		aud = append(aud, v)
   117  	case []string:
   118  		aud = ClaimStrings(v)
   119  	case []interface{}:
   120  		for _, vv := range v {
   121  			vs, ok := vv.(string)
   122  			if !ok {
   123  				return ErrInvalidType
   124  			}
   125  			aud = append(aud, vs)
   126  		}
   127  	case nil:
   128  		return nil
   129  	default:
   130  		return ErrInvalidType
   131  	}
   132  
   133  	*s = aud
   134  
   135  	return
   136  }
   137  
   138  func (s ClaimStrings) MarshalJSON() (b []byte, err error) {
   139  	// This handles a special case in the JWT RFC. If the string array, e.g.
   140  	// used by the "aud" field, only contains one element, it MAY be serialized
   141  	// as a single string. This may or may not be desired based on the ecosystem
   142  	// of other JWT library used, so we make it configurable by the variable
   143  	// MarshalSingleStringAsArray.
   144  	if len(s) == 1 && !MarshalSingleStringAsArray {
   145  		return json.Marshal(s[0])
   146  	}
   147  
   148  	return json.Marshal([]string(s))
   149  }
   150  

View as plain text