cart and checkout
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package customerauth
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"encoding/base64"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Purpose-scoped tokens (email verification, password reset) reuse the session
|
||||
// Signer's HMAC secret but bind a purpose + subject, so a token minted for one
|
||||
// flow cannot be replayed in another. Format:
|
||||
//
|
||||
// base64url(<purpose> NUL <subject> NUL <expUnix>) "." base64url(hmac)
|
||||
//
|
||||
// subject is the normalized email. Like session tokens these are signed, not
|
||||
// encrypted: they carry no secret, only a purpose, an email and an expiry, and
|
||||
// the HMAC makes them tamper-evident.
|
||||
const (
|
||||
purposeVerifyEmail = "verify"
|
||||
purposePasswordReset = "reset"
|
||||
)
|
||||
|
||||
// IssuePurpose returns a signed, purpose-bound token for subject that expires
|
||||
// after ttl.
|
||||
func (s *Signer) IssuePurpose(purpose, subject string, ttl time.Duration) string {
|
||||
exp := time.Now().Add(ttl).Unix()
|
||||
payload := purpose + "\x00" + subject + "\x00" + strconv.FormatInt(exp, 10)
|
||||
b := base64.RawURLEncoding.EncodeToString([]byte(payload))
|
||||
return b + "." + s.sign(b)
|
||||
}
|
||||
|
||||
// ParsePurpose verifies a purpose token and returns its subject. It requires the
|
||||
// embedded purpose to match want and the token to be unexpired, returning
|
||||
// ErrInvalidToken for a bad signature/format/purpose and ErrExpiredToken when
|
||||
// the token has expired.
|
||||
func (s *Signer) ParsePurpose(want, token string) (string, error) {
|
||||
b, sig, ok := strings.Cut(token, ".")
|
||||
if !ok || b == "" || sig == "" {
|
||||
return "", ErrInvalidToken
|
||||
}
|
||||
if !hmac.Equal([]byte(sig), []byte(s.sign(b))) {
|
||||
return "", ErrInvalidToken
|
||||
}
|
||||
raw, err := base64.RawURLEncoding.DecodeString(b)
|
||||
if err != nil {
|
||||
return "", ErrInvalidToken
|
||||
}
|
||||
parts := strings.Split(string(raw), "\x00")
|
||||
if len(parts) != 3 || parts[0] != want {
|
||||
return "", ErrInvalidToken
|
||||
}
|
||||
exp, err := strconv.ParseInt(parts[2], 10, 64)
|
||||
if err != nil {
|
||||
return "", ErrInvalidToken
|
||||
}
|
||||
if time.Now().Unix() >= exp {
|
||||
return "", ErrExpiredToken
|
||||
}
|
||||
return parts[1], nil
|
||||
}
|
||||
Reference in New Issue
Block a user