mirror of
https://github.com/ThisTine/Snip.git
synced 2026-08-18 23:18:47 +07:00
75 lines
2.0 KiB
Go
75 lines
2.0 KiB
Go
// Package security provides the password hasher and stateless session manager.
|
|
package security
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
// BcryptHasher implements port.PasswordHasher.
|
|
type BcryptHasher struct{ cost int }
|
|
|
|
func NewBcryptHasher() *BcryptHasher { return &BcryptHasher{cost: bcrypt.DefaultCost} }
|
|
|
|
func (h *BcryptHasher) Hash(plain string) (string, error) {
|
|
b, err := bcrypt.GenerateFromPassword([]byte(plain), h.cost)
|
|
return string(b), err
|
|
}
|
|
|
|
func (h *BcryptHasher) Compare(hash, plain string) bool {
|
|
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(plain)) == nil
|
|
}
|
|
|
|
// HMACSessions implements port.SessionManager with signed, stateless tokens of
|
|
// the form base64(userID|expiryUnix).base64(hmacSHA256). No server-side store.
|
|
type HMACSessions struct{ key []byte }
|
|
|
|
func NewHMACSessions(secret string) *HMACSessions {
|
|
return &HMACSessions{key: []byte(secret)}
|
|
}
|
|
|
|
func (s *HMACSessions) Issue(userID string, ttl time.Duration) (string, error) {
|
|
payload := userID + "|" + strconv.FormatInt(time.Now().Add(ttl).Unix(), 10)
|
|
p := base64.RawURLEncoding.EncodeToString([]byte(payload))
|
|
return p + "." + s.sign(p), nil
|
|
}
|
|
|
|
func (s *HMACSessions) Verify(token string) (string, error) {
|
|
p, sig, ok := strings.Cut(token, ".")
|
|
if !ok {
|
|
return "", errors.New("malformed token")
|
|
}
|
|
if !hmac.Equal([]byte(sig), []byte(s.sign(p))) {
|
|
return "", errors.New("bad signature")
|
|
}
|
|
raw, err := base64.RawURLEncoding.DecodeString(p)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
userID, expStr, ok := strings.Cut(string(raw), "|")
|
|
if !ok {
|
|
return "", errors.New("malformed payload")
|
|
}
|
|
exp, err := strconv.ParseInt(expStr, 10, 64)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if time.Now().Unix() > exp {
|
|
return "", errors.New("session expired")
|
|
}
|
|
return userID, nil
|
|
}
|
|
|
|
func (s *HMACSessions) sign(payload string) string {
|
|
mac := hmac.New(sha256.New, s.key)
|
|
mac.Write([]byte(payload))
|
|
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
|
|
}
|