refactor: refactor sdk3rd client implementations to use a unified signer structure

This commit is contained in:
Fu Diwei 2026-06-17 17:11:56 +08:00 committed by RHQYZ
parent 6826210c0d
commit dcd0ac6916
54 changed files with 1040 additions and 742 deletions

View File

@ -50,7 +50,7 @@ func NewNotifier(config *NotifierConfig) (*Notifier, error) {
client := resty.New().
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if config.Secret != "" {
timestamp := fmt.Sprintf("%d", time.Now().UnixMilli())

View File

@ -1,9 +1,7 @@
package onepanel
import (
"crypto/md5"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
@ -36,22 +34,23 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
restyClient := resty.New().
signer := &signer{
apiKey: opts.ApiKey,
}
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/api/v1").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
timestamp := fmt.Sprintf("%d", time.Now().Unix())
tokenMd5 := md5.Sum([]byte("1panel" + opts.ApiKey + timestamp))
tokenMd5Hex := hex.EncodeToString(tokenMd5[:])
req.Header.Set("1Panel-Timestamp", timestamp)
req.Header.Set("1Panel-Token", tokenMd5Hex)
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
return nil
})
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -0,0 +1,28 @@
package onepanel
import (
"crypto/md5"
"encoding/hex"
"fmt"
"net/http"
"time"
)
type signer struct {
apiKey string
}
func (s *signer) Sign(req *http.Request) error {
// API 签名机制:
// https://1panel.cn/docs/v1/dev_manual/api_manual/
timestamp := fmt.Sprintf("%d", time.Now().Unix())
tokenMd5 := md5.Sum([]byte("1panel" + s.apiKey + timestamp))
tokenMd5Hex := hex.EncodeToString(tokenMd5[:])
req.Header.Set("1Panel-Timestamp", timestamp)
req.Header.Set("1Panel-Token", tokenMd5Hex)
return nil
}

View File

@ -1,9 +1,7 @@
package v2
import (
"crypto/md5"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
@ -36,23 +34,24 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
restyClient := resty.New().
signer := &signer{
apiKey: opts.ApiKey,
}
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/api/v2").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("CurrentNode", opts.CurrentNode).
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
timestamp := fmt.Sprintf("%d", time.Now().Unix())
tokenMd5 := md5.Sum([]byte("1panel" + opts.ApiKey + timestamp))
tokenMd5Hex := hex.EncodeToString(tokenMd5[:])
req.Header.Set("1Panel-Timestamp", timestamp)
req.Header.Set("1Panel-Token", tokenMd5Hex)
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
return nil
})
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -0,0 +1,28 @@
package v2
import (
"crypto/md5"
"encoding/hex"
"fmt"
"net/http"
"time"
)
type signer struct {
apiKey string
}
func (s *signer) Sign(req *http.Request) error {
// API 签名机制:
// https://1panel.cn/docs/v2/dev_manual/api_manual/
timestamp := fmt.Sprintf("%d", time.Now().Unix())
tokenMd5 := md5.Sum([]byte("1panel" + s.apiKey + timestamp))
tokenMd5Hex := hex.EncodeToString(tokenMd5[:])
req.Header.Set("1Panel-Timestamp", timestamp)
req.Header.Set("1Panel-Token", tokenMd5Hex)
return nil
}

View File

@ -1,19 +1,12 @@
package oss
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/xml"
"fmt"
"hash"
"net/http"
"net/url"
"sort"
"strings"
"time"
"github.com/go-resty/resty/v2"
@ -57,127 +50,25 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
}
}
restyClient := resty.New().
signer := &signer{
accessKeyId: opts.AccessKeyId,
accessKeySecret: opts.AccessKeySecret,
region: opts.Region,
}
httper := resty.New().
SetBaseURL(endpoint).
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
// API 签名机制:
// https://help.aliyun.com/zh/oss/developer-reference/recommend-to-use-signature-version-4
// https://www.alibabacloud.com/help/en/oss/developer-reference/recommend-to-use-signature-version-4
method := strings.ToUpper(req.Method)
nowUtc := time.Now().UTC()
headerDateStr := nowUtc.Format(http.TimeFormat)
requestDateStr := nowUtc.Format("20060102T150405Z")
signDateStr := nowUtc.Format("20060102")
requestResStr := req.Header.Get("X-API-Resource")
req.Header.Del("X-API-Resource")
canonicalUrl := escapePath(req.URL.Path)
if canonicalUrl == "" {
canonicalUrl = "/"
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
if canonicalUrl == "/" && requestResStr != "" {
canonicalUrl = escapePath(requestResStr)
}
canonicalQueryStr := ""
if len(req.URL.Query()) > 0 {
query := req.URL.Query()
keys := make([]string, 0, len(query))
for key := range query {
keys = append(keys, key)
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
canonicalQueryStr += "&"
}
value := query.Get(key)
if value == "" {
canonicalQueryStr += escapeQuery(key)
} else {
canonicalQueryStr += escapeQuery(key) + "=" + escapeQuery(value)
}
}
}
canonicalHeaders := ""
additionalHeaders := ""
if len(req.Header) > 0 {
if req.Header.Get("X-OSS-Date") == "" {
req.Header.Set("X-OSS-Date", requestDateStr)
}
if req.Header.Get("X-OSS-Content-SHA256") == "" {
req.Header.Set("X-OSS-Content-SHA256", "UNSIGNED-PAYLOAD")
}
keys := make([]string, 0, len(req.Header))
for key := range req.Header {
key = strings.ToLower(key)
if strings.HasPrefix(key, "x-oss-") {
keys = append(keys, key)
}
if key == "content-type" || key == "content-md5" {
keys = append(keys, key)
}
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
canonicalHeaders += "\n"
}
value := strings.TrimSpace(req.Header.Get(key))
canonicalHeaders += key + ":" + value
}
canonicalHeaders += "\n"
}
hashedPayload := req.Header.Get("X-OSS-Content-SHA256")
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, additionalHeaders, hashedPayload)
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
const signAlgorithmHeader = "OSS4-HMAC-SHA256"
scope := fmt.Sprintf("%s/%s/oss/aliyun_v4_request", signDateStr, opts.Region)
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", signAlgorithmHeader, requestDateStr, scope, canonicalRequestHashHex)
var h hash.Hash
h = hmac.New(sha256.New, []byte("aliyun_v4"+opts.AccessKeySecret))
h.Write([]byte(signDateStr))
kDate := h.Sum(nil)
h = hmac.New(sha256.New, kDate)
h.Write([]byte(opts.Region))
kRegion := h.Sum(nil)
h = hmac.New(sha256.New, kRegion)
h.Write([]byte("oss"))
kService := h.Sum(nil)
h = hmac.New(sha256.New, kService)
h.Write([]byte("aliyun_v4_request"))
kSigning := h.Sum(nil)
h = hmac.New(sha256.New, kSigning)
h.Write([]byte(stringToSign))
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
req.Header.Set("Authorization", fmt.Sprintf("%s Credential=%s/%s, Signature=%s", signAlgorithmHeader, opts.AccessKeyId, scope, signature))
req.Header.Set("Date", headerDateStr)
return nil
})
return &Client{
bucket: opts.Bucket,
rc: restyClient,
rc: httper,
}, nil
}
@ -267,30 +158,3 @@ func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*rest
return resp, nil
}
func escapeQuery(str string) string {
res := url.QueryEscape(str)
res = strings.ReplaceAll(res, "+", "%20")
return res
}
func escapePath(path string) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
noEscape := (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '.' ||
c == '_' ||
c == '~' ||
c == '/'
if noEscape {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}

View File

@ -0,0 +1,165 @@
package oss
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
type signer struct {
accessKeyId string
accessKeySecret string
region string
}
func (s *signer) Sign(req *http.Request) error {
// API 签名机制:
// https://help.aliyun.com/zh/oss/developer-reference/recommend-to-use-signature-version-4
// https://www.alibabacloud.com/help/en/oss/developer-reference/recommend-to-use-signature-version-4
method := strings.ToUpper(req.Method)
nowUtc := time.Now().UTC()
headerDateStr := nowUtc.Format(http.TimeFormat)
requestDateStr := nowUtc.Format("20060102T150405Z")
signDateStr := nowUtc.Format("20060102")
requestResStr := req.Header.Get("X-API-Resource")
req.Header.Del("X-API-Resource")
canonicalUrl := escapePath(req.URL.Path)
if canonicalUrl == "" {
canonicalUrl = "/"
}
if canonicalUrl == "/" && requestResStr != "" {
canonicalUrl = escapePath(requestResStr)
}
canonicalQueryStr := ""
if len(req.URL.Query()) > 0 {
query := req.URL.Query()
keys := make([]string, 0, len(query))
for key := range query {
keys = append(keys, key)
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
canonicalQueryStr += "&"
}
value := query.Get(key)
if value == "" {
canonicalQueryStr += escapeQuery(key)
} else {
canonicalQueryStr += escapeQuery(key) + "=" + escapeQuery(value)
}
}
}
canonicalHeaders := ""
additionalHeaders := ""
if len(req.Header) > 0 {
if req.Header.Get("X-OSS-Date") == "" {
req.Header.Set("X-OSS-Date", requestDateStr)
}
if req.Header.Get("X-OSS-Content-SHA256") == "" {
req.Header.Set("X-OSS-Content-SHA256", "UNSIGNED-PAYLOAD")
}
keys := make([]string, 0, len(req.Header))
for key := range req.Header {
key = strings.ToLower(key)
if strings.HasPrefix(key, "x-oss-") {
keys = append(keys, key)
}
if key == "content-type" || key == "content-md5" {
keys = append(keys, key)
}
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
canonicalHeaders += "\n"
}
value := strings.TrimSpace(req.Header.Get(key))
canonicalHeaders += key + ":" + value
}
canonicalHeaders += "\n"
}
hashedPayload := req.Header.Get("X-OSS-Content-SHA256")
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, additionalHeaders, hashedPayload)
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
const signAlgorithmHeader = "OSS4-HMAC-SHA256"
scope := fmt.Sprintf("%s/%s/oss/aliyun_v4_request", signDateStr, s.region)
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", signAlgorithmHeader, requestDateStr, scope, canonicalRequestHashHex)
var h hash.Hash
h = hmac.New(sha256.New, []byte("aliyun_v4"+s.accessKeySecret))
h.Write([]byte(signDateStr))
kDate := h.Sum(nil)
h = hmac.New(sha256.New, kDate)
h.Write([]byte(s.region))
kRegion := h.Sum(nil)
h = hmac.New(sha256.New, kRegion)
h.Write([]byte("oss"))
kService := h.Sum(nil)
h = hmac.New(sha256.New, kService)
h.Write([]byte("aliyun_v4_request"))
kSigning := h.Sum(nil)
h = hmac.New(sha256.New, kSigning)
h.Write([]byte(stringToSign))
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
authorization := fmt.Sprintf("%s Credential=%s/%s, Signature=%s", signAlgorithmHeader, s.accessKeyId, scope, signature)
req.Header.Set("Authorization", authorization)
req.Header.Set("Date", headerDateStr)
return nil
}
func escapeQuery(str string) string {
res := url.QueryEscape(str)
res = strings.ReplaceAll(res, "+", "%20")
return res
}
func escapePath(path string) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
noEscape := (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '.' ||
c == '_' ||
c == '~' ||
c == '/'
if noEscape {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}

View File

@ -33,14 +33,14 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/apisix/admin").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetHeader("X-Api-Key", opts.ApiKey)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -24,14 +24,14 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://cdn.api.baishan.com").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetQueryParam("token", opts.ApiToken)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -38,7 +38,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")).
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/x-www-form-urlencoded").
@ -46,7 +46,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return &Client{
apiKey: opts.ApiKey,
rc: restyClient,
rc: httper,
}, nil
}
@ -96,9 +96,9 @@ func (c *Client) newRequest(method string, path string, params any) (*resty.Requ
}
}
timestamp := time.Now().Unix()
data["request_time"] = fmt.Sprintf("%d", timestamp)
data["request_token"] = generateSignature(fmt.Sprintf("%d", timestamp), c.apiKey)
timestamp := fmt.Sprintf("%d", time.Now().Unix())
data["request_time"] = timestamp
data["request_token"] = generateSignature(timestamp, c.apiKey)
req := c.rc.R()
req.Method = method

View File

@ -39,7 +39,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")).
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/x-www-form-urlencoded").
@ -47,7 +47,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return &Client{
apiKey: opts.ApiKey,
rc: restyClient,
rc: httper,
}, nil
}
@ -97,9 +97,9 @@ func (c *Client) newRequest(method string, path string, params any, multipart bo
}
}
timestamp := time.Now().Unix()
data["request_time"] = fmt.Sprintf("%d", timestamp)
data["request_token"] = generateSignature(fmt.Sprintf("%d", timestamp), c.apiKey)
timestamp := fmt.Sprintf("%d", time.Now().Unix())
data["request_time"] = timestamp
data["request_token"] = generateSignature(timestamp, c.apiKey)
req := c.rc.R()
req.Method = method

View File

@ -1,9 +1,7 @@
package btwaf
import (
"crypto/md5"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
@ -36,24 +34,23 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
restyClient := resty.New().
signer := &signer{
apiKey: opts.ApiKey,
}
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/api").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
timestamp := fmt.Sprintf("%d", time.Now().Unix())
keyMd5 := md5.Sum([]byte(opts.ApiKey))
keyMd5Hex := strings.ToLower(hex.EncodeToString(keyMd5[:]))
signMd5 := md5.Sum([]byte(timestamp + keyMd5Hex))
signMd5Hex := strings.ToLower(hex.EncodeToString(signMd5[:]))
req.Header.Set("waf_request_time", timestamp)
req.Header.Set("waf_request_token", signMd5Hex)
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
return nil
})
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -0,0 +1,32 @@
package btwaf
import (
"crypto/md5"
"encoding/hex"
"fmt"
"net/http"
"strings"
"time"
)
type signer struct {
apiKey string
}
func (s *signer) Sign(req *http.Request) error {
// API 签名机制:
// https://github.com/aaPanel/aaWAF/blob/main/API.md
timestamp := fmt.Sprintf("%d", time.Now().Unix())
keyMd5 := md5.Sum([]byte(s.apiKey))
keyMd5Hex := strings.ToLower(hex.EncodeToString(keyMd5[:]))
signMd5 := md5.Sum([]byte(timestamp + keyMd5Hex))
signMd5Hex := strings.ToLower(hex.EncodeToString(signMd5[:]))
req.Header.Set("waf_request_time", timestamp)
req.Header.Set("waf_request_token", signMd5Hex)
return nil
}

View File

@ -23,14 +23,14 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://api.bunny.net").
SetHeader("Accept", "application/json").
SetHeader("AccessKey", opts.ApiKey).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -24,14 +24,14 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://api.cachefly.com/api/2.5").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetHeader("X-CF-Authorization", "Bearer "+opts.ApiToken)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -36,7 +36,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiSecret")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/v1").
SetHeader("Accept", "application/json").
SetHeader("API-Key", opts.ApiKey).
@ -44,7 +44,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -24,14 +24,14 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://api.cloudflare.com/client/v4").
SetHeader("Accept", "application/json").
SetHeader("Authorization", "Bearer "+opts.ApiToken).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -54,7 +54,7 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.accessToken != "" {
req.Header.Set("X-Auth-Token", client.accessToken)
}

View File

@ -36,13 +36,13 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/execute").
SetHeader("Accept", "application/json").
SetHeader("Authorization", "cpanel "+opts.Username+":"+opts.ApiToken).
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -1,21 +1,13 @@
package openapi
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
"io"
"net/http"
"net/url"
"time"
"github.com/go-resty/resty/v2"
"github.com/pocketbase/pocketbase/tools/security"
"github.com/certimate-go/certimate/internal/app"
)
@ -43,62 +35,24 @@ func NewClient(baseUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset secretAccessKey")
}
restyClient := resty.New().
signer := &signer{
accessKeyId: opts.AccessKeyId,
secretAccessKey: opts.SecretAccessKey,
}
httper := resty.New().
SetBaseURL(baseUrl).
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
// API 签名机制:
// https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=77&api=%u6784%u9020%u8BF7%u6C42&data=114&vid=107
now := time.Now()
eopDate := now.Format("20060102T150405Z")
eopReqId := security.RandomString(32)
queryStr := ""
if req.URL != nil {
queryStr = req.URL.Query().Encode()
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
payloadStr := ""
if req.Method != http.MethodGet && req.Body != nil {
payloadb, err := io.ReadAll(req.Body)
if err != nil {
return err
}
payloadStr = string(payloadb)
req.Body = io.NopCloser(bytes.NewReader(payloadb))
}
payloadHash := sha256.Sum256([]byte(payloadStr))
payloadHashHex := hex.EncodeToString(payloadHash[:])
var h hash.Hash
h = hmac.New(sha256.New, []byte(opts.SecretAccessKey))
h.Write([]byte(eopDate))
kTime := h.Sum(nil)
h = hmac.New(sha256.New, kTime)
h.Write([]byte(opts.AccessKeyId))
kAk := h.Sum(nil)
h = hmac.New(sha256.New, kAk)
h.Write([]byte(now.Format("20060102")))
kDate := h.Sum(nil)
stringToSign := fmt.Sprintf("ctyun-eop-request-id:%s\neop-date:%s\n\n%s\n%s", eopReqId, eopDate, queryStr, payloadHashHex)
h = hmac.New(sha256.New, kDate)
h.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
req.Header.Set("ctyun-eop-request-id", eopReqId)
req.Header.Set("eop-date", eopDate)
req.Header.Set("eop-authorization", fmt.Sprintf("%s Headers=ctyun-eop-request-id;eop-date Signature=%s", opts.AccessKeyId, signature))
return nil
})
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -0,0 +1,73 @@
package openapi
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"hash"
"io"
"net/http"
"time"
"github.com/pocketbase/pocketbase/tools/security"
)
type signer struct {
accessKeyId string
secretAccessKey string
}
func (s *signer) Sign(req *http.Request) error {
// API 签名机制:
// https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=77&api=%u6784%u9020%u8BF7%u6C42&data=114&vid=107
now := time.Now()
eopDate := now.Format("20060102T150405Z")
eopReqId := security.RandomString(32)
queryStr := ""
if req.URL != nil {
queryStr = req.URL.Query().Encode()
}
payloadStr := ""
if req.Method != http.MethodGet && req.Body != nil {
payloadb, err := io.ReadAll(req.Body)
if err != nil {
return err
}
payloadStr = string(payloadb)
req.Body = io.NopCloser(bytes.NewReader(payloadb))
}
payloadHash := sha256.Sum256([]byte(payloadStr))
payloadHashHex := hex.EncodeToString(payloadHash[:])
var h hash.Hash
h = hmac.New(sha256.New, []byte(s.secretAccessKey))
h.Write([]byte(eopDate))
kTime := h.Sum(nil)
h = hmac.New(sha256.New, kTime)
h.Write([]byte(s.accessKeyId))
kAk := h.Sum(nil)
h = hmac.New(sha256.New, kAk)
h.Write([]byte(now.Format("20060102")))
kDate := h.Sum(nil)
stringToSign := fmt.Sprintf("ctyun-eop-request-id:%s\neop-date:%s\n\n%s\n%s", eopReqId, eopDate, queryStr, payloadHashHex)
h = hmac.New(sha256.New, kDate)
h.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
eopAuthorization := fmt.Sprintf("%s Headers=ctyun-eop-request-id;eop-date Signature=%s", s.accessKeyId, signature)
req.Header.Set("ctyun-eop-request-id", eopReqId)
req.Header.Set("eop-date", eopDate)
req.Header.Set("eop-authorization", eopAuthorization)
return nil
}

View File

@ -72,7 +72,7 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.apiUserToken != "" {
req.Header.Set("Token", client.apiUserToken)
}

View File

@ -24,14 +24,14 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset accessToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://api.digitalocean.com/v2").
SetHeader("Accept", "application/json").
SetHeader("Authorization", "Bearer "+opts.AccessToken).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -1,13 +1,8 @@
package dogecloud
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
@ -33,44 +28,24 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset secretKey")
}
restyClient := resty.New().
signer := &signer{
accessKey: opts.AccessKey,
secretKey: opts.SecretKey,
}
httper := resty.New().
SetBaseURL("https://api.dogecloud.com").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(ctx *resty.Client, req *http.Request) error {
// API 签名机制:
// https://docs.dogecloud.com/cdn/api-access-token
path := req.URL.Path
queryStr := req.URL.Query().Encode()
if queryStr != "" {
path += "?" + queryStr
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
payloadStr := ""
if req.Body != nil {
payloadb, err := io.ReadAll(req.Body)
if err != nil {
return err
}
payloadStr = string(payloadb)
req.Body = io.NopCloser(bytes.NewReader(payloadb))
}
stringToSign := fmt.Sprintf("%s\n%s", path, payloadStr)
h := hmac.New(sha1.New, []byte(opts.SecretKey))
h.Write([]byte(stringToSign))
signature := hex.EncodeToString(h.Sum(nil))
req.Header.Set("Authorization", fmt.Sprintf("TOKEN %s:%s", opts.AccessKey, signature))
return nil
})
return &Client{restyClient}, nil
return &Client{httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -0,0 +1,50 @@
package dogecloud
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"net/http"
)
type signer struct {
accessKey string
secretKey string
}
func (s *signer) Sign(req *http.Request) error {
// API 签名机制:
// https://docs.dogecloud.com/cdn/api-access-token
path := req.URL.Path
queryStr := req.URL.Query().Encode()
if queryStr != "" {
path += "?" + queryStr
}
payloadStr := ""
if req.Body != nil {
payloadb, err := io.ReadAll(req.Body)
if err != nil {
return err
}
payloadStr = string(payloadb)
req.Body = io.NopCloser(bytes.NewReader(payloadb))
}
stringToSign := fmt.Sprintf("%s\n%s", path, payloadStr)
h := hmac.New(sha1.New, []byte(s.secretKey))
h.Write([]byte(stringToSign))
signature := hex.EncodeToString(h.Sum(nil))
authorization := fmt.Sprintf("TOKEN %s:%s", s.accessKey, signature)
req.Header.Set("Authorization", authorization)
return nil
}

View File

@ -33,14 +33,14 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/api").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetHeader("X-Api-Key", opts.ApiKey)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -24,14 +24,14 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset httpToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://dynv6.com/api/v2").
SetHeader("Accept", "application/json").
SetHeader("Authorization", "Bearer "+opts.HttpToken).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -62,7 +62,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.accessToken != "" {
req.Header.Set("X-Cloud-Access-Token", client.accessToken)
}

View File

@ -24,14 +24,14 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://api.machines.dev/v1").
SetHeader("Accept", "application/json").
SetHeader("Authorization", "Bearer "+opts.ApiToken).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -62,7 +62,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.accessToken != "" {
req.Header.Set("X-Edge-Access-Token", client.accessToken)
}

View File

@ -1,17 +1,12 @@
package obs
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"encoding/base64"
"encoding/xml"
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"time"
"github.com/go-resty/resty/v2"
@ -53,82 +48,23 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
}
}
restyClient := resty.New().
signer := &signer{
accessKeyId: opts.AccessKeyId,
secretAccessKey: opts.SecretAccessKey,
bucket: opts.Bucket,
}
httper := resty.New().
SetBaseURL(endpoint).
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
// API 签名机制:
// https://support.huaweicloud.com/api-obs/obs_04_0010.html
canonicalizedHeaders := ""
if len(req.Header) > 0 {
keys := make([]string, 0, len(req.Header))
for key := range req.Header {
key = strings.ToLower(key)
if strings.HasPrefix(key, "x-obs-") {
keys = append(keys, key)
}
}
sort.Strings(keys)
for _, key := range keys {
value := strings.TrimSpace(req.Header.Get(key))
canonicalizedHeaders += key + ":" + escapeQuery(value)
canonicalizedHeaders += "\n"
}
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
bucketName := opts.Bucket
objectName := strings.Trim(req.URL.Path, "/")
canonicalizedResources := escapePath(fmt.Sprintf("/%s/%s", bucketName, objectName))
if bucketName == "" && objectName == "" {
canonicalizedResources = "/"
}
if len(req.URL.Query()) > 0 {
query := req.URL.Query()
keys := make([]string, 0, len(query))
for key := range query {
keys = append(keys, key)
}
sort.Strings(keys)
for i, key := range keys {
if i == 0 {
canonicalizedResources += "?"
} else {
canonicalizedResources += "&"
}
value := query.Get(key)
if value == "" {
canonicalizedResources += escapeQuery(key)
} else {
canonicalizedResources += escapeQuery(key) + "=" + escapeQuery(value)
}
}
}
method := strings.ToUpper(req.Method)
dateStr := time.Now().UTC().Format(http.TimeFormat)
contentMd5 := req.Header.Get("Content-MD5")
contentType := req.Header.Get("Content-Type")
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s\n%s%s", method, contentMd5, contentType, dateStr, canonicalizedHeaders, canonicalizedResources)
h := hmac.New(sha1.New, []byte(opts.SecretAccessKey))
h.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
req.Header.Set("Authorization", fmt.Sprintf("OBS %s:%s", opts.AccessKeyId, signature))
req.Header.Set("Date", dateStr)
return nil
})
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {
@ -216,32 +152,3 @@ func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*rest
return resp, nil
}
func escapeQuery(str string) string {
res := url.QueryEscape(str)
res = strings.ReplaceAll(res, "%7E", "~")
res = strings.ReplaceAll(res, "%2F", "/")
res = strings.ReplaceAll(res, "%20", "+")
return res
}
func escapePath(path string) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
noEscape := (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '.' ||
c == '_' ||
c == '~' ||
c == '/'
if noEscape {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}

View File

@ -0,0 +1,123 @@
package obs
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
type signer struct {
accessKeyId string
secretAccessKey string
bucket string
}
func (s *signer) Sign(req *http.Request) error {
// API 签名机制:
// https://support.huaweicloud.com/api-obs/obs_04_0010.html
canonicalizedHeaders := ""
if len(req.Header) > 0 {
keys := make([]string, 0, len(req.Header))
for key := range req.Header {
key = strings.ToLower(key)
if strings.HasPrefix(key, "x-obs-") {
keys = append(keys, key)
}
}
sort.Strings(keys)
for _, key := range keys {
value := strings.TrimSpace(req.Header.Get(key))
canonicalizedHeaders += key + ":" + escapeQuery(value)
canonicalizedHeaders += "\n"
}
}
bucketName := s.bucket
objectName := strings.Trim(req.URL.Path, "/")
canonicalizedResources := escapePath(fmt.Sprintf("/%s/%s", bucketName, objectName))
if bucketName == "" && objectName == "" {
canonicalizedResources = "/"
}
if len(req.URL.Query()) > 0 {
query := req.URL.Query()
keys := make([]string, 0, len(query))
for key := range query {
keys = append(keys, key)
}
sort.Strings(keys)
for i, key := range keys {
if i == 0 {
canonicalizedResources += "?"
} else {
canonicalizedResources += "&"
}
value := query.Get(key)
if value == "" {
canonicalizedResources += escapeQuery(key)
} else {
canonicalizedResources += escapeQuery(key) + "=" + escapeQuery(value)
}
}
}
method := strings.ToUpper(req.Method)
dateStr := time.Now().UTC().Format(http.TimeFormat)
contentMd5 := req.Header.Get("Content-MD5")
contentType := req.Header.Get("Content-Type")
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s\n%s%s", method, contentMd5, contentType, dateStr, canonicalizedHeaders, canonicalizedResources)
h := hmac.New(sha1.New, []byte(s.secretAccessKey))
h.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
authorization := fmt.Sprintf("OBS %s:%s", s.accessKeyId, signature)
req.Header.Set("Authorization", authorization)
req.Header.Set("Date", dateStr)
return nil
}
func escapeQuery(str string) string {
res := url.QueryEscape(str)
res = strings.ReplaceAll(res, "%7E", "~")
res = strings.ReplaceAll(res, "%2F", "/")
res = strings.ReplaceAll(res, "%20", "+")
return res
}
func escapePath(path string) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
noEscape := (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '.' ||
c == '_' ||
c == '~' ||
c == '/'
if noEscape {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}

View File

@ -38,14 +38,14 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
baseUrl += fmt.Sprintf("/%s", url.PathEscape(opts.Workspace))
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL(baseUrl).
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetHeader("Kong-Admin-Token", opts.ApiToken)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -51,7 +51,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
client.rc = resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/prod-api").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.accessToken != "" {
req.Header.Set("Authorization", "Bearer "+client.accessToken)
}

View File

@ -53,7 +53,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.accessToken != "" {
req.Header.Set("Authorization", "Bearer "+client.accessToken)
}

View File

@ -24,14 +24,14 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset accessToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://api.linode.com/v4").
SetHeader("Accept", "application/json").
SetHeader("Authorization", "Bearer "+opts.AccessToken).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -43,7 +43,7 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.jwtToken != "" {
req.Header.Set("JWT", "Bearer "+client.jwtToken)
}

View File

@ -24,14 +24,14 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://api.netlify.com/api/v1").
SetHeader("Accept", "application/json").
SetHeader("Authorization", "Bearer "+opts.ApiToken).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -51,7 +51,7 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.jwtToken != "" {
req.Header.Set("Authorization", "Bearer "+client.jwtToken)
}

View File

@ -36,14 +36,14 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset tokenSecret")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/api2/json").
SetHeader("Accept", "application/json").
SetHeader("Authorization", fmt.Sprintf("PVEAPIToken=%s=%s", opts.TokenId, opts.TokenSecret)).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -1,13 +1,9 @@
package dns
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"github.com/go-resty/resty/v2"
@ -32,43 +28,24 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset secretAccessKey")
}
restyClient := resty.New().
signer := &signer{
accessKeyId: options.AccessKeyId,
secretAccessKey: options.SecretAccessKey,
}
httper := resty.New().
SetBaseURL("http://api.routewize.com").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
// 签名机制:
// https://docsv4.qingcloud.com/user_guide/development_docs/api/api_list/dns/calling_method/signature/
date := time.Now().UTC().Format(time.RFC1123)
verb := req.Method
canonicalizedResource := "/"
if req.URL != nil {
canonicalizedResource = req.URL.Path
if req.URL.RawQuery != "" {
values, _ := url.ParseQuery(req.URL.RawQuery)
canonicalizedResource += "?" + values.Encode()
}
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
stringToSign := verb + "\n" +
date + "\n" +
canonicalizedResource
h := hmac.New(sha256.New, []byte(options.SecretAccessKey))
h.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
req.Header.Set("Date", date)
req.Header.Set("Authorization", fmt.Sprintf("QC-HMAC-SHA256 %s:%s", options.AccessKeyId, signature))
return nil
})
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -0,0 +1,49 @@
package dns
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
"net/url"
"time"
)
type signer struct {
accessKeyId string
secretAccessKey string
}
func (s *signer) Sign(req *http.Request) error {
// 签名机制:
// https://docsv4.qingcloud.com/user_guide/development_docs/api/api_list/dns/calling_method/signature/
date := time.Now().UTC().Format(time.RFC1123)
verb := req.Method
canonicalizedResource := "/"
if req.URL != nil {
canonicalizedResource = req.URL.Path
if req.URL.RawQuery != "" {
values, _ := url.ParseQuery(req.URL.RawQuery)
canonicalizedResource += "?" + values.Encode()
}
}
stringToSign := verb + "\n" +
date + "\n" +
canonicalizedResource
h := hmac.New(sha256.New, []byte(s.secretAccessKey))
h.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
authorization := fmt.Sprintf("QC-HMAC-SHA256 %s:%s", s.accessKeyId, signature)
req.Header.Set("Date", date)
req.Header.Set("Authorization", authorization)
return nil
}

View File

@ -24,14 +24,14 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://api.v2.rainyun.com").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetHeader("X-API-Key", options.ApiKey)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -1,14 +1,9 @@
package ratpanel
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
@ -42,58 +37,24 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset accessToken")
}
restyClient := resty.New().
signer := &signer{
accessTokenId: options.AccessTokenId,
accessToken: options.AccessToken,
}
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/api").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
// API 签名机制:
// https://ratpanel.github.io/advanced/api#authentication-mechanism
payloadStr := ""
if req.Body != nil {
payloadb, err := io.ReadAll(req.Body)
if err != nil {
return err
}
payloadStr = string(payloadb)
req.Body = io.NopCloser(bytes.NewReader(payloadb))
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
canonicalPath := req.URL.Path
if !strings.HasPrefix(canonicalPath, "/api") {
index := strings.Index(canonicalPath, "/api")
if index != -1 {
canonicalPath = canonicalPath[index:]
}
}
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s",
req.Method,
canonicalPath,
req.URL.Query().Encode(),
sumSha256(payloadStr),
)
timestamp := time.Now().Unix()
stringToSign := fmt.Sprintf("%s\n%d\n%s",
"HMAC-SHA256",
timestamp,
sumSha256(canonicalRequest),
)
signature := sumHmacSha256(stringToSign, options.AccessToken)
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", timestamp))
req.Header.Set("Authorization", fmt.Sprintf("HMAC-SHA256 Credential=%d, Signature=%s", options.AccessTokenId, signature))
return nil
})
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {
@ -163,16 +124,3 @@ func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*rest
return resp, nil
}
func sumSha256(str string) string {
sum := sha256.Sum256([]byte(str))
dst := make([]byte, hex.EncodedLen(len(sum)))
hex.Encode(dst, sum[:])
return string(dst)
}
func sumHmacSha256(data string, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}

View File

@ -0,0 +1,75 @@
package ratpanel
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"strings"
"time"
)
type signer struct {
accessTokenId int64
accessToken string
}
func (s *signer) Sign(req *http.Request) error {
// API 签名机制:
// https://ratpanel.github.io/advanced/api#authentication-mechanism
payloadStr := ""
if req.Body != nil {
payloadb, err := io.ReadAll(req.Body)
if err != nil {
return err
}
payloadStr = string(payloadb)
req.Body = io.NopCloser(bytes.NewReader(payloadb))
}
canonicalPath := req.URL.Path
if !strings.HasPrefix(canonicalPath, "/api") {
index := strings.Index(canonicalPath, "/api")
if index != -1 {
canonicalPath = canonicalPath[index:]
}
}
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s",
req.Method,
canonicalPath,
req.URL.Query().Encode(),
sumSha256(payloadStr),
)
timestamp := fmt.Sprintf("%d", time.Now().Unix())
stringToSign := fmt.Sprintf("%s\n%s\n%s", "HMAC-SHA256", timestamp, sumSha256(canonicalRequest))
signature := sumHmacSha256(stringToSign, s.accessToken)
authorization := fmt.Sprintf("HMAC-SHA256 Credential=%d, Signature=%s", s.accessTokenId, signature)
req.Header.Set("X-Timestamp", timestamp)
req.Header.Set("Authorization", authorization)
return nil
}
func sumSha256(str string) string {
sum := sha256.Sum256([]byte(str))
dst := make([]byte, hex.EncodedLen(len(sum)))
hex.Encode(dst, sum[:])
return string(dst)
}
func sumHmacSha256(data string, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}

View File

@ -33,14 +33,14 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")).
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetHeader("X-SLCE-API-TOKEN", options.ApiToken)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -33,14 +33,14 @@ func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL(strings.TrimRight(serverUrl, "/")+"/api/v1").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetHeader("X-API-Key", options.ApiKey)
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -38,7 +38,7 @@ func NewClient(serverUrl string) (*Client, error) {
client.rc = resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")).
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.synoToken != "" {
req.Header.Set("X-SYNO-TOKEN", client.synoToken)
}

View File

@ -44,7 +44,7 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.loginCookie != "" {
req.Header.Set("Cookie", client.loginCookie)
}

View File

@ -25,13 +25,13 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset apiToken")
}
restyClient := resty.New().
httper := resty.New().
SetBaseURL("https://api.vercel.com/v8").
SetHeader("Accept", "application/json").
SetHeader("Authorization", "Bearer "+options.ApiToken).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if options.TeamId != "" {
qs := req.URL.Query()
qs.Set("teamId", options.TeamId)
@ -41,7 +41,7 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil
})
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -1,19 +1,12 @@
package tos
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
"net/http"
"net/url"
"sort"
"strings"
"time"
"github.com/go-resty/resty/v2"
@ -55,121 +48,23 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
}
}
restyClient := resty.New().
signer := &signer{
accessKeyId: opts.AccessKeyId,
secretAccessKey: opts.SecretAccessKey,
region: opts.Region,
}
httper := resty.New().
SetBaseURL(endpoint).
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
// API 签名机制:
// https://www.volcengine.com/docs/6349/74839
// https://docs.byteplus.com/en/docs/tos/reference-signature-mechanism_1
method := strings.ToUpper(req.Method)
nowUtc := time.Now().UTC()
headerDateStr := nowUtc.Format(http.TimeFormat)
requestDateStr := nowUtc.Format("20060102T150405Z")
credentialDateStr := nowUtc.Format("20060102")
canonicalUrl := escapePath(req.URL.Path)
if canonicalUrl == "" {
canonicalUrl = "/"
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
canonicalQueryStr := ""
if len(req.URL.Query()) > 0 {
query := req.URL.Query()
keys := make([]string, 0, len(query))
for key := range query {
keys = append(keys, key)
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
canonicalQueryStr += "&"
}
value := query.Get(key)
canonicalQueryStr += escapeQuery(key) + "=" + escapeQuery(value)
}
}
canonicalHeaders := ""
signedHeaders := ""
if len(req.Header) > 0 || req.Host != "" {
if req.Header.Get("Host") == "" {
req.Header.Set("Host", req.Host)
}
if req.Header.Get("X-TOS-Date") == "" {
req.Header.Set("X-TOS-Date", requestDateStr)
}
keys := make([]string, 0, len(req.Header))
for key := range req.Header {
key = strings.ToLower(key)
if strings.HasPrefix(key, "x-tos-") || key == "host" {
keys = append(keys, key)
}
if key == "content-type" && req.Header.Get("X-TOS-Content-SHA256") != "" {
keys = append(keys, key)
}
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
canonicalHeaders += "\n"
signedHeaders += ";"
}
value := strings.TrimSpace(req.Header.Get(key))
canonicalHeaders += key + ":" + value
signedHeaders += key
}
canonicalHeaders += "\n"
}
hashedPayload := req.Header.Get("X-TOS-Content-SHA256")
if hashedPayload == "" {
temp := sha256.Sum256([]byte{})
hashedPayload = strings.ToLower(hex.EncodeToString(temp[:]))
}
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, signedHeaders, hashedPayload)
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
const signAlgorithmHeader = "TOS4-HMAC-SHA256"
credentialScope := fmt.Sprintf("%s/%s/tos/request", credentialDateStr, opts.Region)
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", signAlgorithmHeader, requestDateStr, credentialScope, canonicalRequestHashHex)
var h hash.Hash
h = hmac.New(sha256.New, []byte(opts.SecretAccessKey))
h.Write([]byte(credentialDateStr))
kDate := h.Sum(nil)
h = hmac.New(sha256.New, kDate)
h.Write([]byte(opts.Region))
kRegion := h.Sum(nil)
h = hmac.New(sha256.New, kRegion)
h.Write([]byte("tos"))
kService := h.Sum(nil)
h = hmac.New(sha256.New, kService)
h.Write([]byte("request"))
kSigning := h.Sum(nil)
h = hmac.New(sha256.New, kSigning)
h.Write([]byte(stringToSign))
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
req.Header.Set("Authorization", fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", signAlgorithmHeader, opts.AccessKeyId, credentialScope, signedHeaders, signature))
req.Header.Set("Date", headerDateStr)
return nil
})
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {
@ -257,30 +152,3 @@ func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*rest
return resp, nil
}
func escapeQuery(str string) string {
res := url.QueryEscape(str)
res = strings.ReplaceAll(res, "+", "%20")
return res
}
func escapePath(path string) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
noEscape := (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '.' ||
c == '_' ||
c == '~' ||
c == '/'
if noEscape {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}

View File

@ -0,0 +1,161 @@
package tos
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
type signer struct {
accessKeyId string
secretAccessKey string
region string
}
func (s *signer) Sign(req *http.Request) error {
// API 签名机制:
// https://www.volcengine.com/docs/6349/74839
// https://docs.byteplus.com/en/docs/tos/reference-signature-mechanism_1
method := strings.ToUpper(req.Method)
nowUtc := time.Now().UTC()
headerDateStr := nowUtc.Format(http.TimeFormat)
requestDateStr := nowUtc.Format("20060102T150405Z")
credentialDateStr := nowUtc.Format("20060102")
canonicalUrl := escapePath(req.URL.Path)
if canonicalUrl == "" {
canonicalUrl = "/"
}
canonicalQueryStr := ""
if len(req.URL.Query()) > 0 {
query := req.URL.Query()
keys := make([]string, 0, len(query))
for key := range query {
keys = append(keys, key)
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
canonicalQueryStr += "&"
}
value := query.Get(key)
canonicalQueryStr += escapeQuery(key) + "=" + escapeQuery(value)
}
}
canonicalHeaders := ""
signedHeaders := ""
if len(req.Header) > 0 || req.Host != "" {
if req.Header.Get("Host") == "" {
req.Header.Set("Host", req.Host)
}
if req.Header.Get("X-TOS-Date") == "" {
req.Header.Set("X-TOS-Date", requestDateStr)
}
keys := make([]string, 0, len(req.Header))
for key := range req.Header {
key = strings.ToLower(key)
if strings.HasPrefix(key, "x-tos-") || key == "host" {
keys = append(keys, key)
}
if key == "content-type" && req.Header.Get("X-TOS-Content-SHA256") != "" {
keys = append(keys, key)
}
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
canonicalHeaders += "\n"
signedHeaders += ";"
}
value := strings.TrimSpace(req.Header.Get(key))
canonicalHeaders += key + ":" + value
signedHeaders += key
}
canonicalHeaders += "\n"
}
hashedPayload := req.Header.Get("X-TOS-Content-SHA256")
if hashedPayload == "" {
temp := sha256.Sum256([]byte{})
hashedPayload = strings.ToLower(hex.EncodeToString(temp[:]))
}
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, signedHeaders, hashedPayload)
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
const signAlgorithmHeader = "TOS4-HMAC-SHA256"
credentialScope := fmt.Sprintf("%s/%s/tos/request", credentialDateStr, s.region)
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", signAlgorithmHeader, requestDateStr, credentialScope, canonicalRequestHashHex)
var h hash.Hash
h = hmac.New(sha256.New, []byte(s.secretAccessKey))
h.Write([]byte(credentialDateStr))
kDate := h.Sum(nil)
h = hmac.New(sha256.New, kDate)
h.Write([]byte(s.region))
kRegion := h.Sum(nil)
h = hmac.New(sha256.New, kRegion)
h.Write([]byte("tos"))
kService := h.Sum(nil)
h = hmac.New(sha256.New, kService)
h.Write([]byte("request"))
kSigning := h.Sum(nil)
h = hmac.New(sha256.New, kSigning)
h.Write([]byte(stringToSign))
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
authorization := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", signAlgorithmHeader, s.accessKeyId, credentialScope, signedHeaders, signature)
req.Header.Set("Authorization", authorization)
req.Header.Set("Date", headerDateStr)
return nil
}
func escapeQuery(str string) string {
res := url.QueryEscape(str)
res = strings.ReplaceAll(res, "+", "%20")
return res
}
func escapePath(path string) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
noEscape := (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '.' ||
c == '_' ||
c == '~' ||
c == '/'
if noEscape {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}

View File

@ -1,17 +1,9 @@
package openapi
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/go-resty/resty/v2"
@ -36,85 +28,24 @@ func NewClient(optFns ...OptionsFunc) (*Client, error) {
return nil, fmt.Errorf("sdkerr: unset secretKey")
}
restyClient := resty.New().
signer := &signer{
accessKey: options.AccessKey,
secretKey: options.SecretKey,
}
httper := resty.New().
SetBaseURL("https://open.chinanetcenter.com").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
// API 签名机制:
// https://www.wangsu.com/document/openapi/api-authentication
method := strings.ToUpper(req.Method)
path := "/"
if req.URL != nil {
path = req.URL.Path
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if err := signer.Sign(req); err != nil {
return fmt.Errorf("sdkerr: sign error: %w", err)
}
queryStr := ""
if method != http.MethodPost && req.URL != nil {
queryStr = req.URL.RawQuery
s, err := url.QueryUnescape(queryStr)
if err != nil {
return err
}
queryStr = s
}
canonicalHeaders := "" +
"content-type:" + strings.TrimSpace(strings.ToLower(req.Header.Get("Content-Type"))) + "\n" +
"host:" + strings.TrimSpace(strings.ToLower(req.Host)) + "\n"
signedHeaders := "content-type;host"
payloadStr := ""
if method != http.MethodGet && req.Body != nil {
payloadb, err := io.ReadAll(req.Body)
if err != nil {
return err
}
payloadStr = string(payloadb)
req.Body = io.NopCloser(bytes.NewReader(payloadb))
}
payloadHash := sha256.Sum256([]byte(payloadStr))
payloadHashHex := strings.ToLower(hex.EncodeToString(payloadHash[:]))
nowUtc := time.Now().UTC()
timestampStr := req.Header.Get("X-CNC-Timestamp")
if timestampStr == "" {
timestampStr = fmt.Sprintf("%d", nowUtc.Unix())
} else {
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil {
return err
}
nowUtc = time.Unix(timestamp, 0).UTC()
}
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, path, queryStr, canonicalHeaders, signedHeaders, payloadHashHex)
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
const signAlgorithmHeader = "CNC-HMAC-SHA256"
stringToSign := fmt.Sprintf("%s\n%s\n%s", signAlgorithmHeader, timestampStr, canonicalRequestHashHex)
h := hmac.New(sha256.New, []byte(options.SecretKey))
h.Write([]byte(stringToSign))
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
req.Header.Set("Authorization", fmt.Sprintf("%s Credential=%s, SignedHeaders=%s, Signature=%s", signAlgorithmHeader, options.AccessKey, signedHeaders, signature))
req.Header.Set("Date", nowUtc.Format(http.TimeFormat))
req.Header.Set("X-CNC-Auth-Method", "AKSK")
req.Header.Set("X-CNC-AccessKey", options.AccessKey)
req.Header.Set("X-CNC-Timestamp", timestampStr)
return nil
})
return &Client{rc: restyClient}, nil
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {

View File

@ -0,0 +1,95 @@
package openapi
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
type signer struct {
accessKey string
secretKey string
}
func (s *signer) Sign(req *http.Request) error {
// API 签名机制:
// https://www.wangsu.com/document/openapi/api-authentication
method := strings.ToUpper(req.Method)
path := "/"
if req.URL != nil {
path = req.URL.Path
}
queryStr := ""
if method != http.MethodPost && req.URL != nil {
queryStr = req.URL.RawQuery
s, err := url.QueryUnescape(queryStr)
if err != nil {
return err
}
queryStr = s
}
canonicalHeaders := "" +
"content-type:" + strings.TrimSpace(strings.ToLower(req.Header.Get("Content-Type"))) + "\n" +
"host:" + strings.TrimSpace(strings.ToLower(req.Host)) + "\n"
signedHeaders := "content-type;host"
payloadStr := ""
if method != http.MethodGet && req.Body != nil {
payloadb, err := io.ReadAll(req.Body)
if err != nil {
return err
}
payloadStr = string(payloadb)
req.Body = io.NopCloser(bytes.NewReader(payloadb))
}
payloadHash := sha256.Sum256([]byte(payloadStr))
payloadHashHex := strings.ToLower(hex.EncodeToString(payloadHash[:]))
nowUtc := time.Now().UTC()
timestampStr := req.Header.Get("X-CNC-Timestamp")
if timestampStr == "" {
timestampStr = fmt.Sprintf("%d", nowUtc.Unix())
} else {
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil {
return err
}
nowUtc = time.Unix(timestamp, 0).UTC()
}
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, path, queryStr, canonicalHeaders, signedHeaders, payloadHashHex)
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
const signAlgorithmHeader = "CNC-HMAC-SHA256"
stringToSign := fmt.Sprintf("%s\n%s\n%s", signAlgorithmHeader, timestampStr, canonicalRequestHashHex)
h := hmac.New(sha256.New, []byte(s.secretKey))
h.Write([]byte(stringToSign))
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
authorization := fmt.Sprintf("%s Credential=%s, SignedHeaders=%s, Signature=%s", signAlgorithmHeader, s.accessKey, signedHeaders, signature)
req.Header.Set("Authorization", authorization)
req.Header.Set("Date", nowUtc.Format(http.TimeFormat))
req.Header.Set("X-CNC-Auth-Method", "AKSK")
req.Header.Set("X-CNC-AccessKey", s.accessKey)
req.Header.Set("X-CNC-Timestamp", timestampStr)
return nil
}