github.com/decred/politeia@v1.4.0/util/token.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package util
     6  
     7  import (
     8  	"encoding/hex"
     9  	"fmt"
    10  	"regexp"
    11  
    12  	pdv1 "github.com/decred/politeia/politeiad/api/v1"
    13  	pdv2 "github.com/decred/politeia/politeiad/api/v2"
    14  )
    15  
    16  var (
    17  	// TokenTypeGit represents a token from the politeiad git backend.
    18  	TokenTypeGit = "git"
    19  
    20  	// TokenTypeTstore represents a token from the politeiad tstore
    21  	// backend.
    22  	TokenTypeTstore = "tstore"
    23  
    24  	// tokenRegexp is a regexp that matches short tokens and full
    25  	// length tokens.
    26  	tokenRegexp = regexp.MustCompile(fmt.Sprintf("^[0-9a-f]{%v,%v}$",
    27  		pdv2.ShortTokenLength, pdv2.TokenSize*2))
    28  )
    29  
    30  // ShortTokenSize returns the size, in bytes, of a politeiad short token.
    31  func ShortTokenSize() int {
    32  	// If the short token length is an odd number of characters then
    33  	// padding would have needed to be added to it prior to encoding it
    34  	// to hex to prevent a hex.ErrLenth (odd length hex string) error.
    35  	// This function accounts for this padding in the returned size.
    36  	var size int
    37  	if pdv1.TokenPrefixLength%2 == 1 {
    38  		// Add 1 to the length to account for padding
    39  		size = (pdv2.ShortTokenLength + 1) / 2
    40  	} else {
    41  		// No padding was required
    42  		size = pdv2.ShortTokenLength / 2
    43  	}
    44  	return size
    45  }
    46  
    47  // ShortToken returns the short version of a token.
    48  func ShortToken(token []byte) ([]byte, error) {
    49  	s := ShortTokenSize()
    50  	if len(token) < s {
    51  		return nil, fmt.Errorf("token is not large enough")
    52  	}
    53  	return token[:s], nil
    54  }
    55  
    56  // ShortTokenString takes a hex encoded token and returns the shortened token
    57  // for it.
    58  func ShortTokenString(token string) (string, error) {
    59  	if tokenRegexp.FindString(token) == "" {
    60  		return "", fmt.Errorf("invalid token %v", tokenRegexp.String())
    61  	}
    62  	return token[:pdv2.ShortTokenLength], nil
    63  }
    64  
    65  // ShortTokenEncode returns the hex encoded shortened token.
    66  func ShortTokenEncode(token []byte) (string, error) {
    67  	t, err := ShortToken(token)
    68  	if err != nil {
    69  		return "", err
    70  	}
    71  	return TokenEncode(t), nil
    72  }
    73  
    74  // TokenIsFullLength returns whether a token is a valid, full length politeiad
    75  // censorship token.
    76  func TokenIsFullLength(tokenType string, token []byte) bool {
    77  	switch tokenType {
    78  	case TokenTypeGit:
    79  		return len(token) == pdv1.TokenSize
    80  	case TokenTypeTstore:
    81  		return len(token) == pdv2.TokenSize
    82  	default:
    83  		panic("invalid token type")
    84  	}
    85  }
    86  
    87  // TokenDecode decodes a full length token. An error is returned if the token
    88  // is not a full length, hex token.
    89  func TokenDecode(tokenType, token string) ([]byte, error) {
    90  	// Verify token is valid
    91  	if tokenRegexp.FindString(token) == "" {
    92  		return nil, fmt.Errorf("invalid token %v", tokenRegexp.String())
    93  	}
    94  
    95  	// Decode token
    96  	t, err := hex.DecodeString(token)
    97  	if err != nil {
    98  		return nil, fmt.Errorf("invalid hex")
    99  	}
   100  
   101  	// Verify token is full length
   102  	if !TokenIsFullLength(tokenType, t) {
   103  		return nil, fmt.Errorf("invalid token size")
   104  	}
   105  
   106  	return t, nil
   107  }
   108  
   109  // TokenDecodeAnyLength decodes both short tokens and full length tokens.
   110  func TokenDecodeAnyLength(tokenType, token string) ([]byte, error) {
   111  	// Verify token is valid
   112  	if tokenRegexp.FindString(token) == "" {
   113  		return nil, fmt.Errorf("invalid token %v", tokenRegexp.String())
   114  	}
   115  
   116  	// Decode token. If provided token has odd length, add padding
   117  	// to prevent a hex.ErrLength (odd length hex string) error.
   118  	if len(token)%2 == 1 {
   119  		token = token + "0"
   120  	}
   121  	t, err := hex.DecodeString(token)
   122  	if err != nil {
   123  		return nil, fmt.Errorf("invalid hex")
   124  	}
   125  
   126  	// Verify token byte slice is either a short token or a full length
   127  	// token.
   128  	switch {
   129  	case len(t) == ShortTokenSize():
   130  		// This is a short token. Short tokens are the same size
   131  		// regardless of token type.
   132  	case tokenType == TokenTypeGit && TokenIsFullLength(TokenTypeGit, t):
   133  		// Token is a valid git backend token
   134  	case tokenType == TokenTypeTstore && TokenIsFullLength(TokenTypeTstore, t):
   135  		// Token is a valid tstore backend token
   136  	default:
   137  		return nil, fmt.Errorf("invalid token size")
   138  	}
   139  
   140  	return t, nil
   141  }
   142  
   143  // TokenEncode returns the hex encoded token. Its possible that padding has
   144  // been added to the token when it was originally decode in order to make it
   145  // valid hex. This function checks for padding and removes it before encoding
   146  // the token.
   147  func TokenEncode(token []byte) string {
   148  	t := hex.EncodeToString(token)
   149  	if len(t) == pdv2.ShortTokenLength+1 {
   150  		// This is a short token that has had padding added to it. Remove
   151  		// the padding.
   152  		t = t[:pdv2.ShortTokenLength]
   153  	}
   154  	return t
   155  }
   156  
   157  // TokenRegexp returns the string regexp that is used to match tokens.
   158  func TokenRegexp() string {
   159  	return tokenRegexp.String()
   160  }