node build fixed
This commit is contained in:
176
seanime-2.9.10/internal/util/hmac_auth.go
Normal file
176
seanime-2.9.10/internal/util/hmac_auth.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TokenClaims struct {
|
||||
Endpoint string `json:"endpoint"` // The endpoint this token is valid for
|
||||
IssuedAt int64 `json:"iat"`
|
||||
ExpiresAt int64 `json:"exp"`
|
||||
}
|
||||
|
||||
type HMACAuth struct {
|
||||
secret []byte
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
// base64URLEncode encodes to base64url without padding (to match frontend)
|
||||
func base64URLEncode(data []byte) string {
|
||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(data), "=")
|
||||
}
|
||||
|
||||
// base64URLDecode decodes from base64url with or without padding
|
||||
func base64URLDecode(data string) ([]byte, error) {
|
||||
// Add padding if needed
|
||||
if m := len(data) % 4; m != 0 {
|
||||
data += strings.Repeat("=", 4-m)
|
||||
}
|
||||
return base64.URLEncoding.DecodeString(data)
|
||||
}
|
||||
|
||||
// NewHMACAuth creates a new HMAC authentication instance
|
||||
func NewHMACAuth(secret string, ttl time.Duration) *HMACAuth {
|
||||
return &HMACAuth{
|
||||
secret: []byte(secret),
|
||||
ttl: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateToken generates an HMAC-signed token for the given endpoint
|
||||
func (h *HMACAuth) GenerateToken(endpoint string) (string, error) {
|
||||
now := time.Now().Unix()
|
||||
claims := TokenClaims{
|
||||
Endpoint: endpoint,
|
||||
IssuedAt: now,
|
||||
ExpiresAt: now + int64(h.ttl.Seconds()),
|
||||
}
|
||||
|
||||
// Serialize claims to JSON
|
||||
claimsJSON, err := json.Marshal(claims)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal claims: %w", err)
|
||||
}
|
||||
|
||||
// Encode claims as base64url without padding
|
||||
claimsB64 := base64URLEncode(claimsJSON)
|
||||
|
||||
// Generate HMAC signature
|
||||
mac := hmac.New(sha256.New, h.secret)
|
||||
mac.Write([]byte(claimsB64))
|
||||
signature := base64URLEncode(mac.Sum(nil))
|
||||
|
||||
// Return token in format: claims.signature
|
||||
return claimsB64 + "." + signature, nil
|
||||
}
|
||||
|
||||
// ValidateToken validates an HMAC token and returns the claims if valid
|
||||
func (h *HMACAuth) ValidateToken(token string, endpoint string) (*TokenClaims, error) {
|
||||
// Split token into claims and signature
|
||||
parts := splitToken(token)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid token format - expected 2 parts, got %d", len(parts))
|
||||
}
|
||||
|
||||
claimsB64, signature := parts[0], parts[1]
|
||||
|
||||
// Verify signature
|
||||
mac := hmac.New(sha256.New, h.secret)
|
||||
mac.Write([]byte(claimsB64))
|
||||
expectedSignature := base64URLEncode(mac.Sum(nil))
|
||||
|
||||
if !hmac.Equal([]byte(signature), []byte(expectedSignature)) {
|
||||
return nil, fmt.Errorf("invalid token signature, the password hashes may not match")
|
||||
}
|
||||
|
||||
// Decode claims
|
||||
claimsJSON, err := base64URLDecode(claimsB64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode claims: %w", err)
|
||||
}
|
||||
|
||||
var claims TokenClaims
|
||||
if err := json.Unmarshal(claimsJSON, &claims); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal claims: %w", err)
|
||||
}
|
||||
|
||||
// Validate expiration
|
||||
now := time.Now().Unix()
|
||||
if claims.ExpiresAt < now {
|
||||
return nil, fmt.Errorf("token expired - expires at %d, current time %d", claims.ExpiresAt, now)
|
||||
}
|
||||
|
||||
// Validate endpoint (optional, can be wildcard *)
|
||||
if endpoint != "" && claims.Endpoint != "*" && claims.Endpoint != endpoint {
|
||||
return nil, fmt.Errorf("token not valid for endpoint %s - claim endpoint: %s", endpoint, claims.Endpoint)
|
||||
}
|
||||
|
||||
return &claims, nil
|
||||
}
|
||||
|
||||
// GenerateQueryParam generates a query parameter string with the HMAC token
|
||||
func (h *HMACAuth) GenerateQueryParam(endpoint string, symbol string) (string, error) {
|
||||
token, err := h.GenerateToken(endpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if symbol == "" {
|
||||
symbol = "?"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%stoken=%s", symbol, token), nil
|
||||
}
|
||||
|
||||
// ValidateQueryParam extracts and validates token from query parameter
|
||||
func (h *HMACAuth) ValidateQueryParam(tokenParam string, endpoint string) (*TokenClaims, error) {
|
||||
if tokenParam == "" {
|
||||
return nil, fmt.Errorf("no token provided")
|
||||
}
|
||||
|
||||
return h.ValidateToken(tokenParam, endpoint)
|
||||
}
|
||||
|
||||
// splitToken splits a token string by the last dot separator
|
||||
func splitToken(token string) []string {
|
||||
// Find the last dot to split claims from signature
|
||||
for i := len(token) - 1; i >= 0; i-- {
|
||||
if token[i] == '.' {
|
||||
return []string{token[:i], token[i+1:]}
|
||||
}
|
||||
}
|
||||
return []string{token}
|
||||
}
|
||||
|
||||
func (h *HMACAuth) GetTokenExpiry(token string) (time.Time, error) {
|
||||
parts := splitToken(token)
|
||||
if len(parts) != 2 {
|
||||
return time.Time{}, fmt.Errorf("invalid token format")
|
||||
}
|
||||
|
||||
claimsJSON, err := base64URLDecode(parts[0])
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("failed to decode claims: %w", err)
|
||||
}
|
||||
|
||||
var claims TokenClaims
|
||||
if err := json.Unmarshal(claimsJSON, &claims); err != nil {
|
||||
return time.Time{}, fmt.Errorf("failed to unmarshal claims: %w", err)
|
||||
}
|
||||
|
||||
return time.Unix(claims.ExpiresAt, 0), nil
|
||||
}
|
||||
|
||||
func (h *HMACAuth) IsTokenExpired(token string) bool {
|
||||
expiry, err := h.GetTokenExpiry(token)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return time.Now().After(expiry)
|
||||
}
|
||||
Reference in New Issue
Block a user