diff --git a/cmd/auth/login_test.go b/cmd/auth/login_test.go index 50b7f3fc27..dfa42dd606 100644 --- a/cmd/auth/login_test.go +++ b/cmd/auth/login_test.go @@ -740,7 +740,7 @@ func TestAuthLoginRun_MissingRequestedScopeAlignsWithLoginSuccess(t *testing.T) }) reg.Register(&httpmock.Stub{ Method: "POST", - URL: larkauth.PathOAuthTokenV2, + URL: core.OAuthTokenV3Path, Body: map[string]interface{}{ "access_token": "user-access-token", "refresh_token": "refresh-token", @@ -856,7 +856,7 @@ func TestAuthLoginRun_DeviceCodeUsesCachedRequestedScopes(t *testing.T) { }) reg.Register(&httpmock.Stub{ Method: "POST", - URL: larkauth.PathOAuthTokenV2, + URL: core.OAuthTokenV3Path, Body: map[string]interface{}{ "access_token": "user-access-token", "refresh_token": "refresh-token", diff --git a/internal/auth/device_flow.go b/internal/auth/device_flow.go index 7acdad859c..b26353a007 100644 --- a/internal/auth/device_flow.go +++ b/internal/auth/device_flow.go @@ -57,7 +57,7 @@ func ResolveOAuthEndpoints(brand core.LarkBrand) OAuthEndpoints { return OAuthEndpoints{ DeviceAuthorization: ep.Accounts + PathDeviceAuthorization, Revoke: ep.Accounts + PathOAuthRevoke, - Token: ep.Open + PathOAuthTokenV2, + Token: ep.Accounts + core.OAuthTokenV3Path, } } diff --git a/internal/auth/device_flow_test.go b/internal/auth/device_flow_test.go index 397098cc18..69f6484dda 100644 --- a/internal/auth/device_flow_test.go +++ b/internal/auth/device_flow_test.go @@ -34,7 +34,7 @@ func TestResolveOAuthEndpoints_Feishu(t *testing.T) { if ep.Revoke != "https://accounts.feishu.cn/oauth/v1/revoke" { t.Errorf("Revoke = %q", ep.Revoke) } - if ep.Token != "https://open.feishu.cn/open-apis/authen/v2/oauth/token" { + if ep.Token != "https://accounts.feishu.cn/oauth/v3/token" { t.Errorf("Token = %q", ep.Token) } } @@ -48,7 +48,7 @@ func TestResolveOAuthEndpoints_Lark(t *testing.T) { if ep.Revoke != "https://accounts.larksuite.com/oauth/v1/revoke" { t.Errorf("Revoke = %q", ep.Revoke) } - if ep.Token != "https://open.larksuite.com/open-apis/authen/v2/oauth/token" { + if ep.Token != "https://accounts.larksuite.com/oauth/v3/token" { t.Errorf("Token = %q", ep.Token) } } diff --git a/internal/auth/paths.go b/internal/auth/paths.go index 453ebe1466..822e93dc5b 100644 --- a/internal/auth/paths.go +++ b/internal/auth/paths.go @@ -11,8 +11,6 @@ const ( PathOAuthRevoke = "/oauth/v1/revoke" // PathAppRegistration is the endpoint for application registration. PathAppRegistration = "/oauth/v1/app/registration" - // PathOAuthTokenV2 is the endpoint for requesting an OAuth token (v2). - PathOAuthTokenV2 = "/open-apis/authen/v2/oauth/token" // PathUserInfoV1 is the endpoint for fetching user information. PathUserInfoV1 = "/open-apis/authen/v1/user_info" // PathApplicationInfoV6Prefix is the prefix endpoint for fetching application info. diff --git a/internal/auth/uat_client.go b/internal/auth/uat_client.go index 85aa4b85cf..1dc63b78e8 100644 --- a/internal/auth/uat_client.go +++ b/internal/auth/uat_client.go @@ -4,7 +4,6 @@ package auth import ( - "bytes" "context" "encoding/json" "errors" @@ -12,7 +11,9 @@ import ( "io" "net/http" "net/http/httptrace" + "net/url" "os" + "strings" "sync/atomic" "time" @@ -138,13 +139,6 @@ func refreshWithLock(httpClient *http.Client, opts UATCallOptions) (*StoredUATok const refreshMaxAttempts = 2 -type refreshRequest struct { - GrantType string `json:"grant_type"` - RefreshToken string `json:"refresh_token"` - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` -} - // refreshResponse contains the OAuth token fields consumed by the refresh // flow. Pointers distinguish an omitted numeric field from a real zero value. type refreshResponse struct { @@ -275,19 +269,11 @@ func doRefreshToken(httpClient *http.Client, opts UATCallOptions, stored *Stored } func refreshOnce(httpClient *http.Client, endpoint string, opts UATCallOptions, stored *StoredUAToken) refreshResult { - payload, err := json.Marshal(refreshRequest{ - GrantType: "refresh_token", - RefreshToken: stored.RefreshToken, - ClientID: opts.AppId, - ClientSecret: opts.AppSecret, - }) - if err != nil { - return refreshResult{ - action: refreshStopAndPreserve, - err: errs.NewInternalError(errs.SubtypeSDKError, - "failed to encode token refresh request: %v", err). - WithCause(err), - } + form := url.Values{ + "grant_type": {"refresh_token"}, + "refresh_token": {stored.RefreshToken}, + "client_id": {opts.AppId}, + "client_secret": {opts.AppSecret}, } var wroteRequest atomic.Bool @@ -297,7 +283,7 @@ func refreshOnce(httpClient *http.Client, endpoint string, opts UATCallOptions, }, } ctx := httptrace.WithClientTrace(context.Background(), trace) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(payload)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(form.Encode())) if err != nil { return refreshResult{ action: refreshStopAndPreserve, @@ -306,7 +292,7 @@ func refreshOnce(httpClient *http.Client, endpoint string, opts UATCallOptions, WithCause(err), } } - req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := httpClient.Do(req) if err != nil { diff --git a/internal/auth/uat_client_refresh_test.go b/internal/auth/uat_client_refresh_test.go index d16ba58acd..2d43423a51 100644 --- a/internal/auth/uat_client_refresh_test.go +++ b/internal/auth/uat_client_refresh_test.go @@ -4,12 +4,12 @@ package auth import ( - "encoding/json" "errors" "io" "io/fs" "net/http" "net/http/httptrace" + "net/url" "path/filepath" "runtime" "strings" @@ -117,21 +117,26 @@ func TestGetValidAccessTokenRetriesAndStoresSuccessfulRefresh(t *testing.T) { if req.Method != http.MethodPost || req.URL.String() != ResolveOAuthEndpoints(opts.Domain).Token { t.Fatalf("refresh request = %s %s, want documented token endpoint", req.Method, req.URL) } - if req.Header.Get("Content-Type") != "application/json; charset=utf-8" { - t.Fatalf("Content-Type = %q, want JSON", req.Header.Get("Content-Type")) + if req.Header.Get("Content-Type") != "application/x-www-form-urlencoded" { + t.Fatalf("Content-Type = %q, want form data", req.Header.Get("Content-Type")) } - var payload refreshRequest - if err := json.NewDecoder(req.Body).Decode(&payload); err != nil { - t.Fatalf("decode refresh request: %v", err) + body, err := io.ReadAll(req.Body) + if err != nil { + t.Fatalf("read refresh request: %v", err) } - want := refreshRequest{ - GrantType: "refresh_token", - RefreshToken: stored.RefreshToken, - ClientID: opts.AppId, - ClientSecret: opts.AppSecret, + form, err := url.ParseQuery(string(body)) + if err != nil { + t.Fatalf("parse refresh request: %v", err) } - if payload != want { - t.Fatalf("refresh payload = %#v, want %#v", payload, want) + for key, want := range map[string]string{ + "grant_type": "refresh_token", + "refresh_token": stored.RefreshToken, + "client_id": opts.AppId, + "client_secret": opts.AppSecret, + } { + if got := form.Get(key); got != want { + t.Fatalf("refresh form %s = %q, want %q", key, got, want) + } } if call == 1 { return refreshHTTPResponse(req, `{"code":20050,"error_description":"retry"}`), nil