79 lines
1.7 KiB
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
|
|
}
|