Files
Snip/backend/internal/adapter/security/security.go
T
2026-06-15 21:25:57 +07:00

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))
}