Files
2026-06-22 22:39:08 +03:00

90 lines
2.2 KiB
Go

package service
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"os"
"strings"
"time"
"golang.org/x/crypto/bcrypt"
)
const defaultTokenTTL = 24 * time.Hour
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(bytes), nil
}
func checkPassword(hash, password string) error {
if strings.TrimSpace(hash) == "" {
return errors.New("password hash is empty")
}
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
}
func tokenSecret() string {
secret := strings.TrimSpace(os.Getenv("TOKEN_SECRET"))
if secret == "" {
return "local-dev-secret-change-me"
}
return secret
}
func tokenTTL() time.Duration {
raw := strings.TrimSpace(os.Getenv("TOKEN_TTL"))
if raw == "" {
return defaultTokenTTL
}
ttl, err := time.ParseDuration(raw)
if err != nil || ttl <= 0 {
return defaultTokenTTL
}
return ttl
}
func signToken(payload tokenPayload) (string, error) {
bytes, err := json.Marshal(payload)
if err != nil {
return "", err
}
body := base64.RawURLEncoding.EncodeToString(bytes)
signature := computeTokenSignature(body)
return "fable-token." + body + "." + signature, nil
}
func parseToken(token string) (tokenPayload, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 || parts[0] != "fable-token" {
return tokenPayload{}, errors.New("invalid token format")
}
if !hmac.Equal([]byte(parts[2]), []byte(computeTokenSignature(parts[1]))) {
return tokenPayload{}, errors.New("invalid token signature")
}
bytes, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return tokenPayload{}, err
}
var payload tokenPayload
if err := json.Unmarshal(bytes, &payload); err != nil {
return tokenPayload{}, err
}
if payload.ExpiresAt > 0 && time.Now().UTC().Unix() > payload.ExpiresAt {
return tokenPayload{}, errors.New("token expired")
}
return payload, nil
}
func computeTokenSignature(body string) string {
mac := hmac.New(sha256.New, []byte(tokenSecret()))
_, _ = mac.Write([]byte(body))
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}