@@ -28,6 +28,8 @@ type Client struct {
2828 login string
2929 apiKey string
3030
31+ signer * Signer
32+
3133 baseURL * url.URL
3234 HTTPClient * http.Client
3335}
@@ -38,6 +40,7 @@ func NewClient(login string, apiKey string) *Client {
3840 return & Client {
3941 login : login ,
4042 apiKey : apiKey ,
43+ signer : NewSigner (),
4144 baseURL : baseURL ,
4245 HTTPClient : & http.Client {Timeout : 10 * time .Second },
4346 }
@@ -74,7 +77,7 @@ func (c Client) doRequest(ctx context.Context, endpoint *url.URL, params url.Val
7477 }
7578
7679 req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
77- req .Header .Set (authenticationHeader , c .createSignature (endpoint .Path , payload ))
80+ req .Header .Set (authenticationHeader , c .signer . Sign (endpoint .Path , payload , c . login , c . apiKey ))
7881
7982 resp , err := c .HTTPClient .Do (req )
8083 if err != nil {
@@ -90,33 +93,49 @@ func (c Client) doRequest(ctx context.Context, endpoint *url.URL, params url.Val
9093 return nil
9194}
9295
93- func (c Client ) createSignature (uri string , body string ) string {
94- // This is the only part of this that needs to be serialized.
95- salt := make ([]byte , 16 )
96- for i := 0 ; i < 16 ; i ++ {
97- salt [i ] = saltBytes [rand .Intn (len (saltBytes ))]
96+ func parseError (req * http.Request , resp * http.Response ) error {
97+ raw , _ := io .ReadAll (resp .Body )
98+
99+ errAPI := & APIError {}
100+ err := json .Unmarshal (raw , errAPI )
101+ if err != nil {
102+ return errutils .NewUnexpectedStatusCodeError (req , resp .StatusCode , raw )
98103 }
99104
105+ return errAPI
106+ }
107+
108+ type Signer struct {
109+ saltShaker func () []byte
110+ clock func () time.Time
111+ }
112+
113+ func NewSigner () * Signer {
114+ return & Signer {saltShaker : getRandomSalt , clock : time .Now }
115+ }
116+
117+ func (c Signer ) Sign (uri string , body , login , apiKey string ) string {
100118 // Header is "login;timestamp;salt;hash".
101119 // hash is SHA1("login;timestamp;salt;api-key;request-uri;body-hash")
102120 // and body-hash is SHA1(body).
103121
104122 bodyHash := sha1 .Sum ([]byte (body ))
105- timestamp := strconv .FormatInt (time . Now ().Unix (), 10 )
123+ timestamp := strconv .FormatInt (c . clock ().Unix (), 10 )
106124
107- hashInput := fmt .Sprintf ("%s;%s;%s;%s;%s;%02x" , c .login , timestamp , salt , c .apiKey , uri , bodyHash )
125+ // Workaround for https://golang.org/issue/58605
126+ uri = "/" + strings .TrimLeft (uri , "/" )
108127
109- return fmt .Sprintf ("%s;%s;%s;%02x" , c .login , timestamp , salt , sha1 .Sum ([]byte (hashInput )))
110- }
128+ hashInput := fmt .Sprintf ("%s;%s;%s;%s;%s;%02x" , login , timestamp , c .saltShaker (), apiKey , uri , bodyHash )
111129
112- func parseError ( req * http. Request , resp * http. Response ) error {
113- raw , _ := io . ReadAll ( resp . Body )
130+ return fmt . Sprintf ( "%s;%s;%s;%02x" , login , timestamp , c . saltShaker (), sha1 . Sum ([] byte ( hashInput )))
131+ }
114132
115- errAPI := & APIError {}
116- err := json .Unmarshal (raw , errAPI )
117- if err != nil {
118- return errutils .NewUnexpectedStatusCodeError (req , resp .StatusCode , raw )
133+ func getRandomSalt () []byte {
134+ // This is the only part of this that needs to be serialized.
135+ salt := make ([]byte , 16 )
136+ for i := 0 ; i < 16 ; i ++ {
137+ salt [i ] = saltBytes [rand .Intn (len (saltBytes ))]
119138 }
120139
121- return errAPI
140+ return salt
122141}
0 commit comments