Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/auth/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion internal/auth/device_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
4 changes: 2 additions & 2 deletions internal/auth/device_flow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand All @@ -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)
}
}
Expand Down
2 changes: 0 additions & 2 deletions internal/auth/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
32 changes: 9 additions & 23 deletions internal/auth/uat_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
package auth

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptrace"
"net/url"
"os"
"strings"
"sync/atomic"
"time"

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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 {
Expand Down
31 changes: 18 additions & 13 deletions internal/auth/uat_client_refresh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
package auth

import (
"encoding/json"
"errors"
"io"
"io/fs"
"net/http"
"net/http/httptrace"
"net/url"
"path/filepath"
"runtime"
"strings"
Expand Down Expand Up @@ -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
Expand Down
Loading