openreplay/backend/pkg/token/tokenizer.go

79 lines
1.7 KiB
Go

package token
import (
"crypto/hmac"
"crypto/sha256"
"errors"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcutil/base58"
)
var (
EXPIRED = errors.New("token expired")
JUST_EXPIRED = errors.New("token just expired")
)
type Tokenizer struct {
secret []byte
}
func NewTokenizer(secret string) *Tokenizer {
return &Tokenizer{[]byte(secret)}
}
type TokenData struct {
ID uint64
Delay int64
ExpTime int64
}
func (tokenizer *Tokenizer) sign(body string) []byte {
mac := hmac.New(sha256.New, tokenizer.secret)
mac.Write([]byte(body))
return mac.Sum(nil)
}
func (tokenizer *Tokenizer) Compose(d TokenData) string {
body := strconv.FormatUint(d.ID, 36) +
"." + strconv.FormatInt(d.Delay, 36) +
"." + strconv.FormatInt(d.ExpTime, 36)
sign := base58.Encode(tokenizer.sign(body))
return body + "." + sign
}
func (tokenizer *Tokenizer) Parse(token string) (*TokenData, error) {
data := strings.Split(token, ".")
if len(data) != 4 {
return nil, errors.New("wrong token format")
}
if !hmac.Equal(
base58.Decode(data[len(data)-1]),
tokenizer.sign(strings.Join(data[:len(data)-1], ".")),
) {
return nil, errors.New("wrong token sign")
}
id, err := strconv.ParseUint(data[0], 36, 64)
if err != nil {
return nil, err
}
delay, err := strconv.ParseInt(data[1], 36, 64)
if err != nil {
return nil, err
}
expTime, err := strconv.ParseInt(data[2], 36, 64)
if err != nil {
return nil, err
}
res := &TokenData{id, delay, expTime}
if expTime <= time.Now().UnixMilli() {
// If token is expired less than 30 seconds ago, we still consider it semi-valid
if expTime+30000 > time.Now().UnixMilli() {
return res, JUST_EXPIRED
}
return res, EXPIRED
}
return res, nil
}