/* Copyright © 2025 maxtheweb */ package cmd import ( "crypto/hmac" "crypto/sha1" "encoding/base64" "fmt" "math/rand" "net/url" "sort" "strings" "time" ) // OAuthClient handles OAuth 1.0a authentication type OAuthClient struct { ConsumerKey string ConsumerSecret string AccessToken string AccessTokenSecret string } // NewOAuthClient creates a new OAuth client from config func NewOAuthClient(config *Config) *OAuthClient { return &OAuthClient{ ConsumerKey: config.OAuth.ConsumerKey, ConsumerSecret: config.OAuth.ConsumerSecret, AccessToken: config.OAuth.AccessToken, AccessTokenSecret: config.OAuth.AccessTokenSecret, } } // GetAuthorizationHeader generates the OAuth 1.0a Authorization header func (o *OAuthClient) GetAuthorizationHeader(method, requestURL string, params map[string]string) string { // OAuth parameters oauthParams := map[string]string{ "oauth_consumer_key": o.ConsumerKey, "oauth_token": o.AccessToken, "oauth_signature_method": "HMAC-SHA1", "oauth_timestamp": fmt.Sprintf("%d", time.Now().Unix()), "oauth_nonce": generateNonce(), "oauth_version": "1.0", } // Combine OAuth params and request params for signature allParams := make(map[string]string) for k, v := range oauthParams { allParams[k] = v } for k, v := range params { allParams[k] = v } // Generate signature signature := o.generateSignature(method, requestURL, allParams) oauthParams["oauth_signature"] = signature // Build Authorization header var headerParts []string for k, v := range oauthParams { headerParts = append(headerParts, fmt.Sprintf(`%s="%s"`, percentEncode(k), percentEncode(v))) } sort.Strings(headerParts) return "OAuth " + strings.Join(headerParts, ", ") } // generateSignature creates the OAuth signature func (o *OAuthClient) generateSignature(method, requestURL string, params map[string]string) string { // 1. Create parameter string var paramPairs []string for k, v := range params { paramPairs = append(paramPairs, percentEncode(k)+"="+percentEncode(v)) } sort.Strings(paramPairs) paramString := strings.Join(paramPairs, "&") // 2. Create signature base string signatureBase := strings.Join([]string{ method, percentEncode(requestURL), percentEncode(paramString), }, "&") // 3. Create signing key signingKey := percentEncode(o.ConsumerSecret) + "&" + percentEncode(o.AccessTokenSecret) // 4. Calculate HMAC-SHA1 signature mac := hmac.New(sha1.New, []byte(signingKey)) mac.Write([]byte(signatureBase)) signature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) return signature } // percentEncode encodes a string according to RFC 3986 func percentEncode(s string) string { encoded := url.QueryEscape(s) // url.QueryEscape uses + for spaces, but OAuth needs %20 encoded = strings.ReplaceAll(encoded, "+", "%20") return encoded } // generateNonce generates a random nonce for OAuth func generateNonce() string { const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" result := make([]byte, 32) for i := range result { result[i] = chars[rand.Intn(len(chars))] } return string(result) }