2021-08-15 11:03:25 -06:00
|
|
|
package token
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/binary"
|
2021-12-05 16:16:52 -07:00
|
|
|
"log"
|
2021-08-15 11:03:25 -06:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type T struct {
|
|
|
|
expiration time.Time
|
|
|
|
mac []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t T) computeMac(secret []byte) []byte {
|
|
|
|
mac := hmac.New(sha256.New, secret)
|
|
|
|
binary.Write(mac, binary.BigEndian, t.expiration)
|
|
|
|
return mac.Sum([]byte{})
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns the string encoding of the token
|
|
|
|
func (t T) String() string {
|
|
|
|
f := new(bytes.Buffer)
|
2021-12-05 16:16:52 -07:00
|
|
|
if err := binary.Write(f, binary.BigEndian, t.expiration.Unix()); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2021-08-15 11:03:25 -06:00
|
|
|
f.Write(t.mac)
|
|
|
|
return base64.StdEncoding.EncodeToString(f.Bytes())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Valid returns true iff the token is valid for the given secret and current time
|
|
|
|
func (t T) Valid(secret []byte) bool {
|
|
|
|
if time.Now().After(t.expiration) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !hmac.Equal(t.mac, t.computeMac(secret)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a new token
|
|
|
|
func New(secret []byte, expiration time.Time) T {
|
|
|
|
t := T{
|
|
|
|
expiration: expiration,
|
|
|
|
}
|
|
|
|
t.mac = t.computeMac(secret)
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse returns a new token from the given bytes
|
|
|
|
func Parse(b []byte) (T, error) {
|
|
|
|
t := T{
|
|
|
|
mac: make([]byte, sha256.Size),
|
|
|
|
}
|
|
|
|
f := bytes.NewReader(b)
|
2021-12-05 16:16:52 -07:00
|
|
|
{
|
|
|
|
var sec int64
|
|
|
|
if err := binary.Read(f, binary.BigEndian, &sec); err != nil {
|
|
|
|
return t, err
|
|
|
|
}
|
|
|
|
t.expiration = time.Unix(sec, 0)
|
2021-08-15 11:03:25 -06:00
|
|
|
}
|
|
|
|
if n, err := f.Read(t.mac); err != nil {
|
|
|
|
return t, err
|
|
|
|
} else {
|
|
|
|
t.mac = t.mac[:n]
|
|
|
|
}
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseString parses a base64-encoded string, as created by T.String()
|
|
|
|
func ParseString(s string) (T, error) {
|
|
|
|
b, err := base64.StdEncoding.DecodeString(s)
|
|
|
|
if err != nil {
|
|
|
|
return T{}, nil
|
|
|
|
}
|
|
|
|
return Parse(b)
|
|
|
|
}
|