@@ -129,7 +129,7 @@ func WithRegion(region string) ClientOption {
129129 var base string
130130 switch region {
131131 case "us" :
132- base = "https://api.us .terramate.io"
132+ base = "https://us.api .terramate.io"
133133 case "eu" :
134134 base = "https://api.terramate.io"
135135 default :
@@ -190,7 +190,9 @@ func (c *Client) newRequest(ctx context.Context, method, path string, body io.Re
190190 return req , nil
191191}
192192
193- // do executes an HTTP request and handles the response
193+ // do executes an HTTP request and handles the response.
194+ // If the request fails with 401 Unauthorized and the client uses JWT authentication,
195+ // it attempts to refresh the token and retry the request once.
194196func (c * Client ) do (req * http.Request , v interface {}) (* Response , error ) {
195197 const maxBodyBytes = 10 << 20 // 10 MiB
196198 resp , err := c .executeRequestWithRetries (req , 3 )
@@ -206,6 +208,25 @@ func (c *Client) do(req *http.Request, v interface{}) (*Response, error) {
206208
207209 response := & Response {HTTPResponse : resp , Body : body }
208210
211+ // Handle 401 Unauthorized - attempt token refresh if using JWT
212+ if resp .StatusCode == http .StatusUnauthorized {
213+ if jwtCred , ok := c .credential .(* JWTCredential ); ok {
214+ // Try to refresh the token
215+ if refreshErr := jwtCred .Refresh (req .Context ()); refreshErr == nil {
216+ // Token refreshed successfully - retry the request
217+ // Clone the request to avoid reusing the body
218+ retryReq , cloneErr := cloneRequest (req )
219+ if cloneErr == nil {
220+ // Apply the new credentials
221+ if applyErr := c .credential .ApplyCredentials (retryReq ); applyErr == nil {
222+ // Recursively call do() for the retry (will not recurse again due to refreshing flag)
223+ return c .do (retryReq , v )
224+ }
225+ }
226+ }
227+ }
228+ }
229+
209230 if resp .StatusCode >= 400 {
210231 return response , parseAPIError (resp , body )
211232 }
@@ -223,6 +244,23 @@ func (c *Client) do(req *http.Request, v interface{}) (*Response, error) {
223244 return response , nil
224245}
225246
247+ // cloneRequest creates a clone of an HTTP request for retry purposes.
248+ // This is necessary because http.Request.Body can only be read once.
249+ func cloneRequest (req * http.Request ) (* http.Request , error ) {
250+ clonedReq := req .Clone (req .Context ())
251+
252+ // If the request had a body, we need to handle it specially
253+ if req .Body != nil && req .GetBody != nil {
254+ body , err := req .GetBody ()
255+ if err != nil {
256+ return nil , fmt .Errorf ("failed to get request body: %w" , err )
257+ }
258+ clonedReq .Body = body
259+ }
260+
261+ return clonedReq , nil
262+ }
263+
226264func (c * Client ) executeRequestWithRetries (req * http.Request , maxRetries int ) (* http.Response , error ) {
227265 isIdempotent := req .Method == http .MethodGet || req .Method == http .MethodHead || req .Method == http .MethodOptions
228266 for attempt := 0 ; attempt <= maxRetries ; attempt ++ {
0 commit comments