OKX: Fix authenticated websocket login (#2051)

* Okx:remove

* Okx:replace

* Okx:ping gws.PingMessage

* Okx:ping gws.PingMessage

* Okx: add authenticateConnection

* Okx: fix pingHandler

* Update exchanges/okx/okx_business_websocket.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/okx/okx_business_websocket.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/okx/okx_websocket.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/okx/okx_business_websocket.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Okx:UseMultiConnectionManagement

* Okx:rm UseMultiConnectionManagement

* Okx:roll back

* Okx:apply diff

* Okx:make lint fix

* Okx:make lint fix

* Okx:make lint fix

* Okx:fix

* Okx:fix name

* Okx:fix NilGuard depends on #2076

* Okx:remove comment

---------

Co-authored-by: Ryan O'Hara-Reid <oharareid.ryan@gmail.com>
Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>
This commit is contained in:
dazi005
2025-10-10 07:59:08 +08:00
committed by GitHub
parent 3ae0197e68
commit cce28f9d2b
6 changed files with 124 additions and 84 deletions

View File

@@ -2,16 +2,12 @@ package okx
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"strconv"
"strings"
"time"
gws "github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/encoding/json"
"github.com/thrasher-corp/gocryptotrader/exchange/websocket"
@@ -55,15 +51,13 @@ func (e *Exchange) WsConnectBusiness(ctx context.Context) error {
dialer.WriteBufferSize = 8192
e.Websocket.Conn.SetURL(okxBusinessWebsocketURL)
err := e.Websocket.Conn.Dial(ctx, &dialer, http.Header{})
if err != nil {
if err := e.Websocket.Conn.Dial(ctx, &dialer, http.Header{}); err != nil {
return err
}
e.Websocket.Wg.Add(1)
go e.wsReadData(ctx, e.Websocket.Conn)
e.Websocket.Wg.Go(func() { e.wsReadData(ctx, e.Websocket.Conn) })
if e.Verbose {
log.Debugf(log.ExchangeSys, "Successful connection to %v\n",
e.Websocket.GetWebsocketURL())
log.Debugf(log.ExchangeSys, "Successful connection to %v", e.Websocket.GetWebsocketURL())
}
e.Websocket.Conn.SetupPingHandler(request.UnAuth, websocket.PingHandler{
MessageType: gws.TextMessage,
@@ -71,45 +65,14 @@ func (e *Exchange) WsConnectBusiness(ctx context.Context) error {
Delay: time.Second * 20,
})
if e.Websocket.CanUseAuthenticatedEndpoints() {
err = e.WsSpreadAuth(ctx)
if err != nil {
log.Errorf(log.ExchangeSys, "Error connecting auth socket: %s\n", err.Error())
if err := e.authenticateConnection(ctx, e.Websocket.Conn); err != nil { // business WS uses same conn for public and private
log.Errorf(log.ExchangeSys, "Error authenticating business websocket: %s", err)
e.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
}
return nil
}
// WsSpreadAuth will connect to Okx's Private websocket connection and Authenticate with a login payload.
func (e *Exchange) WsSpreadAuth(ctx context.Context) error {
if !e.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", e.Name)
}
creds, err := e.GetCredentials(ctx)
if err != nil {
return err
}
e.Websocket.SetCanUseAuthenticatedEndpoints(true)
ts := time.Now().Unix()
signPath := "/users/self/verify"
hmac, err := crypto.GetHMAC(crypto.HashSHA256,
[]byte(strconv.FormatInt(ts, 10)+http.MethodGet+signPath),
[]byte(creds.Secret),
)
if err != nil {
return err
}
args := []WebsocketLoginData{
{
APIKey: creds.Key,
Passphrase: creds.ClientID,
Timestamp: ts,
Sign: base64.StdEncoding.EncodeToString(hmac),
},
}
return e.SendAuthenticatedWebsocketRequest(ctx, request.Unset, "login-response", operationLogin, args, nil)
}
// GenerateDefaultBusinessSubscriptions returns a list of default subscriptions to business websocket.
func (e *Exchange) GenerateDefaultBusinessSubscriptions() ([]subscription.Subscription, error) {
var subs []string

View File

@@ -3171,6 +3171,12 @@ type WebsocketLoginData struct {
Sign string `json:"sign"`
}
// WebsocketAuthLogin holds the operation and arguments
type WebsocketAuthLogin struct {
Operation string `json:"op"`
Arguments []WebsocketLoginData `json:"args"`
}
// SubscriptionInfo holds the channel and instrument IDs
type SubscriptionInfo struct {
Channel string `json:"channel,omitempty"`

View File

@@ -41,7 +41,7 @@ var (
// See: https://www.okx.com/docs-v5/en/#error-code-websocket-public
authConnErrorCodes = []string{
"60007", "60022", "60023", "60024", "60026", "63999", "60032", "60011", "60009",
"60005", "60021", "60031", "50110",
"60005", "60021", "60031", "50110", "60033",
}
)
@@ -245,15 +245,12 @@ func (e *Exchange) WsConnect() error {
dialer.ReadBufferSize = 8192
dialer.WriteBufferSize = 8192
err := e.Websocket.Conn.Dial(ctx, &dialer, http.Header{})
if err != nil {
if err := e.Websocket.Conn.Dial(ctx, &dialer, http.Header{}); err != nil {
return err
}
e.Websocket.Wg.Add(1)
go e.wsReadData(ctx, e.Websocket.Conn)
e.Websocket.Wg.Go(func() { e.wsReadData(ctx, e.Websocket.Conn) })
if e.Verbose {
log.Debugf(log.ExchangeSys, "Successful connection to %v\n",
e.Websocket.GetWebsocketURL())
log.Debugf(log.ExchangeSys, "Successful connection to %v", e.Websocket.GetWebsocketURL())
}
e.Websocket.Conn.SetupPingHandler(request.Unset, websocket.PingHandler{
MessageType: gws.TextMessage,
@@ -261,8 +258,7 @@ func (e *Exchange) WsConnect() error {
Delay: time.Second * 20,
})
if e.Websocket.CanUseAuthenticatedEndpoints() {
err = e.WsAuth(ctx)
if err != nil {
if err := e.WsAuth(ctx); err != nil {
log.Errorf(log.ExchangeSys, "Error connecting auth socket: %s\n", err.Error())
e.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
@@ -275,24 +271,24 @@ func (e *Exchange) WsAuth(ctx context.Context) error {
if !e.AreCredentialsValid(ctx) || !e.Websocket.CanUseAuthenticatedEndpoints() {
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", e.Name)
}
creds, err := e.GetCredentials(ctx)
if err != nil {
return err
}
var dialer gws.Dialer
err = e.Websocket.AuthConn.Dial(ctx, &dialer, http.Header{})
if err != nil {
if err := e.Websocket.AuthConn.Dial(ctx, &dialer, http.Header{}); err != nil {
return err
}
e.Websocket.Wg.Add(1)
go e.wsReadData(ctx, e.Websocket.AuthConn)
e.Websocket.Wg.Go(func() { e.wsReadData(ctx, e.Websocket.AuthConn) })
e.Websocket.AuthConn.SetupPingHandler(request.Unset, websocket.PingHandler{
MessageType: gws.TextMessage,
Message: pingMsg,
Delay: time.Second * 20,
})
return e.authenticateConnection(ctx, e.Websocket.AuthConn)
}
e.Websocket.SetCanUseAuthenticatedEndpoints(true)
func (e *Exchange) authenticateConnection(ctx context.Context, conn websocket.Connection) error {
creds, err := e.GetCredentials(ctx)
if err != nil {
return err
}
ts := time.Now().Unix()
signPath := "/users/self/verify"
hmac, err := crypto.GetHMAC(crypto.HashSHA256,
@@ -303,21 +299,37 @@ func (e *Exchange) WsAuth(ctx context.Context) error {
return err
}
args := []WebsocketLoginData{
{
APIKey: creds.Key,
Passphrase: creds.ClientID,
Timestamp: ts,
Sign: base64.StdEncoding.EncodeToString(hmac),
op := WebsocketAuthLogin{
Operation: operationLogin,
Arguments: []WebsocketLoginData{
{
APIKey: creds.Key,
Passphrase: creds.ClientID,
Timestamp: ts,
Sign: base64.StdEncoding.EncodeToString(hmac),
},
},
}
resp, err := conn.SendMessageReturnResponse(ctx, request.Unset, "login-response", op)
if err != nil {
return err
}
var intermediary struct {
Code int64 `json:"code,string"`
Message string `json:"msg"`
}
if err := json.Unmarshal(resp, &intermediary); err != nil {
return err
}
return e.SendAuthenticatedWebsocketRequest(ctx, request.Unset, "login-response", operationLogin, args, nil)
if intermediary.Code != 0 {
return getStatusError(intermediary.Code, intermediary.Message)
}
return nil
}
// wsReadData sends msgs from public and auth websockets to data handler
func (e *Exchange) wsReadData(ctx context.Context, ws websocket.Connection) {
defer e.Websocket.Wg.Done()
for {
resp := ws.ReadMessage()
if resp.Raw == nil {